Skip to content

Commit

Permalink
Working (#2)
Browse files Browse the repository at this point in the history
* finalize initial functionality
* rpmspec can return an error, so generally ignore it as it'll be still valid. Attempt to clean the specfile comment out %__ to stop a complete error
* documation: improve overall
  • Loading branch information
mcoops authored Jul 28, 2021
1 parent e702a7a commit b846e42
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 52 deletions.
2 changes: 1 addition & 1 deletion cmd/rpmtools/rpmtools.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func main() {
r, err := rpmtools.RpmGetSrcRpm(*urlPtr, "/tmp/")

if err != nil {
fmt.Printf(err.Error())
fmt.Printf("%s", err.Error())
}

source0, _ := r.RpmGetSource0()
Expand Down
17 changes: 11 additions & 6 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,31 @@ func createDir(path string) error {
if _, err := os.Stat(path); !os.IsNotExist(err) {
os.RemoveAll(path)
}
if err := os.Mkdir(path, 0700); err != nil {
if err := os.MkdirAll(path, 0700); err != nil {
return errors.New("CreateRpmBuildStructure: failed to create dir " + path + " - " + err.Error())
}
return nil
}

func CreateRpmBuildStructure(output string) (string, string, error) {
func CreateRpmBuildStructure(output string) (string, string, string, error) {
if output == "" {
return "", "", errors.New("CreateRpmBuildStructure: no file specified")
return "", "", "", errors.New("CreateRpmBuildStructure: no file specified")
}

sourceRPM := filepath.Join(output, "SOURCES")
if err := createDir(sourceRPM); err != nil {
return "", "", err
return "", "", "", err
}

sRPM := filepath.Join(output, "SRPMS")
if err := createDir(sRPM); err != nil {
return sourceRPM, "", err
return sourceRPM, "", "", err
}

return sourceRPM, sRPM, nil
bRPM := filepath.Join(output, "BUILD")
if err := createDir(bRPM); err != nil {
return sourceRPM, sRPM, "", err
}

return sourceRPM, sRPM, bRPM, nil
}
140 changes: 95 additions & 45 deletions rpmtools.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,28 @@ import (
util "github.com/mcoops/rpmtools/internal"
)

// RpmSpec is a reference to metadata about a src rpm, including info like
// the specfile found, the rows in the file and most importantly a map of
// SpecTag structs so help easily reference values, for example
// RpmSpec.Tags["url"].
type RpmSpec struct {
specLocation string
srpmLocation string
sourcesLocation string
outLocation string
SpecLocation string
SrpmLocation string
SourcesLocation string
OutLocation string
BuildLocation string
Tags map[string][]SpecTag
}

// Represents a row/field of key name + key value within a specfile
type SpecTag struct {
TagName string
// Name of value in specfile, i.e. sourec0, url, summary
TagName string
// The actual value of the specfile definition
TagValue string
}

var SpecfileLabelsRegex map[string]*regexp.Regexp
var specfileLabelsRegex map[string]*regexp.Regexp

func init() {
if _, err := exec.LookPath("rpmbuild"); err != nil {
Expand All @@ -42,27 +50,61 @@ func init() {
}

// https://github.com/bkircher/python-rpm-spec/blob/master/pyrpm/spec.py
SpecfileLabelsRegex = make(map[string]*regexp.Regexp)
SpecfileLabelsRegex["name"] = regexp.MustCompile("^Name\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["version"] = regexp.MustCompile("^Version\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["epoch"] = regexp.MustCompile("^Epoch\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["release"] = regexp.MustCompile("^Release\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["summary"] = regexp.MustCompile("^Summary\\s*:\\s*(.+)")
SpecfileLabelsRegex["license"] = regexp.MustCompile("^License\\s*:\\s*(.+)")
SpecfileLabelsRegex["url"] = regexp.MustCompile("^URL\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["buildroot"] = regexp.MustCompile("^BuildRoot\\s*:\\s*(\\S+)")
SpecfileLabelsRegex["buildarch"] = regexp.MustCompile("^BuildArch\\s*:\\s*(\\S+)")

SpecfileLabelsRegex["sources"] = regexp.MustCompile("^(Source\\d*\\s*):\\s*(.+)")
SpecfileLabelsRegex["patches"] = regexp.MustCompile("^(Patch\\d*\\s*):\\s*(\\S+)")
SpecfileLabelsRegex["requires"] = regexp.MustCompile("^Requires\\s*:\\s*(.+)")
SpecfileLabelsRegex["conflicts"] = regexp.MustCompile("^Conflicts\\s*:\\s*(.+)")
SpecfileLabelsRegex["obsoletes"] = regexp.MustCompile("^Obsoletes\\s*:\\s*(.+)")
SpecfileLabelsRegex["provides"] = regexp.MustCompile("^Provides\\s*:\\s*(.+)")
SpecfileLabelsRegex["packages"] = regexp.MustCompile("^%package\\s+(\\S+)")
specfileLabelsRegex = make(map[string]*regexp.Regexp)
specfileLabelsRegex["name"] = regexp.MustCompile(`^Name\s*:\s*(\S+)`)
specfileLabelsRegex["version"] = regexp.MustCompile(`^Version\s*:\s*(\S+)`)
specfileLabelsRegex["epoch"] = regexp.MustCompile(`^Epoch\s*:\s*(\S+)`)
specfileLabelsRegex["release"] = regexp.MustCompile(`^Release\s*:\s*(\S+)`)
specfileLabelsRegex["summary"] = regexp.MustCompile(`^Summary\s*:\s*(.+)`)
specfileLabelsRegex["license"] = regexp.MustCompile(`^License\s*:\s*(.+)`)
specfileLabelsRegex["url"] = regexp.MustCompile(`^URL\s*:\s*(\S+)`)
specfileLabelsRegex["buildroot"] = regexp.MustCompile(`^BuildRoot\s*:\s*(\S+)`)
specfileLabelsRegex["buildarch"] = regexp.MustCompile(`^BuildArch\s*:\s*(\S+)`)
specfileLabelsRegex["buildRequires"] = regexp.MustCompile(`^BuildRequires\s*:\s*(.+)`)

specfileLabelsRegex["sources"] = regexp.MustCompile(`^(Source\d*\s*):\s*(.+)`)
specfileLabelsRegex["patches"] = regexp.MustCompile(`^(Patch\d*\s*):\s*(\S+)`)
specfileLabelsRegex["requires"] = regexp.MustCompile(`^Requires\s*:\s*(.+)`)
specfileLabelsRegex["conflicts"] = regexp.MustCompile(`^Conflicts\s*:\s*(.+)`)
specfileLabelsRegex["obsoletes"] = regexp.MustCompile(`^Obsoletes\s*:\s*(.+)`)
specfileLabelsRegex["provides"] = regexp.MustCompile(`^Provides\s*:\s*(.+)`)
specfileLabelsRegex["packages"] = regexp.MustCompile(`^%package\s+(\S+)`)
}

// Find the first file ending with .spec
// Some specfiles do weird things. If it tries to do weird things, attempt to
// clean some of it so that rpmspec will parse correctly.
func rpmCleanSpecFile(name string) error {
hasChanges := false
f, err := ioutil.ReadFile(name)

if err != nil {
return errors.New("rpmCleanSpecFile: failed to open file " + name)
}
lines := strings.Split(string(f), "\n")

for i, line := range lines {
if strings.HasPrefix(line, "Name:") {
break
}
if strings.HasPrefix(line, "%__") {
lines[i] = "#" + line
hasChanges = true
}
}

if !hasChanges {
return nil
}
output := strings.Join(lines, "\n")
err = ioutil.WriteFile(name, []byte(output), 0644)
if err != nil {
return errors.New("rpmCleanSpecFile: failed to writeback file")
}
return nil
}

// Given a directory to scan, find the first file ending with .spec

func RpmFindSpec(dir string) (string, error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
Expand Down Expand Up @@ -91,24 +133,28 @@ func RpmFindAndParseSpec(dir string) (RpmSpec, error) {

// Given a specfile parse and return fields from the file
func RpmParseSpec(name string) (RpmSpec, error) {
if util.Exists(name) == false {
if !util.Exists(name) {
return RpmSpec{}, errors.New("File: " + name + " not found")
}

rpmCleanSpecFile(name)

rpm := RpmSpec{
Tags: make(map[string][]SpecTag),
}
rpm.specLocation = name
rpm.SpecLocation = name
// run rpmspec first to normalize the data
out, err := exec.Command("rpmspec", "-P", name).Output()
if err != nil {
return RpmSpec{}, err
// rpmspec will occasionally return errors, so ignore them
log.Printf("RpmParseSpec: ignoring error %s", err.Error())
// return RpmSpec{}, err
}

sc := bufio.NewScanner(bytes.NewReader(out))

for sc.Scan() {
for k, i := range SpecfileLabelsRegex {
for k, i := range specfileLabelsRegex {
if match := i.FindStringSubmatch(sc.Text()); match != nil {
if len(match) == 2 {
rpm.Tags[k] = append(rpm.Tags[k], SpecTag{TagName: k, TagValue: match[1]})
Expand All @@ -121,10 +167,10 @@ func RpmParseSpec(name string) (RpmSpec, error) {
return rpm, nil
}

// Using a rpmspec obj return source0
// Using a rpmspec obj return source0. Could be called Source0, or Source
func (rpm RpmSpec) RpmGetSource0() (string, error) {
if rpm.Tags["sources"] == nil {
return "", errors.New("No sources")
return "", errors.New("no sources")
}

for _, source := range rpm.Tags["sources"] {
Expand All @@ -143,10 +189,10 @@ func (rpm RpmSpec) RpmGetSource0() (string, error) {
// Using an rpmspec obj (rpm.spec location) and an output location, extract
// the source rpm and apply patches
func (rpm RpmSpec) RpmApplyPatches() error {
if !strings.HasSuffix(rpm.sourcesLocation, "SOURCES") {
return errors.New("RpmApplyPatches: expected SOURCES path is incorrect: " + rpm.sourcesLocation)
if !strings.HasSuffix(rpm.SourcesLocation, "SOURCES") {
return errors.New("RpmApplyPatches: expected SOURCES path is incorrect: " + rpm.SourcesLocation)
}
cmd := exec.Command("bash", "-c", "rpmbuild -bp --nodeps --define \"_topdir "+rpm.outLocation+" \" "+rpm.specLocation)
cmd := exec.Command("bash", "-c", "rpmbuild -bp --nodeps --define \"_topdir "+rpm.OutLocation+" \" "+rpm.SpecLocation)

if err := cmd.Run(); err != nil {
return errors.New("RpmApplyPatches: failed to run rpmbuild: " + err.Error())
Expand All @@ -155,27 +201,30 @@ func (rpm RpmSpec) RpmApplyPatches() error {
return nil
}

// Best effort. Should check each error code, but prob not worth
// Best effort removal of src rpm files.
func (rpm RpmSpec) RpmCleanup() {
os.RemoveAll(rpm.sourcesLocation)
os.RemoveAll(rpm.srpmLocation)
os.RemoveAll(filepath.Join(rpm.outLocation, "BUILD"))
os.RemoveAll(filepath.Join(rpm.outLocation, "BUILDROOT"))
os.RemoveAll(filepath.Join(rpm.outLocation, "RPMS"))
os.RemoveAll(rpm.SourcesLocation)
os.RemoveAll(rpm.SrpmLocation)
os.RemoveAll(rpm.BuildLocation)
os.RemoveAll(filepath.Join(rpm.OutLocation, "BUILDROOT"))
os.RemoveAll(filepath.Join(rpm.OutLocation, "RPMS"))
}

// Given a `url` download the rpm to the `outputPath` to `SRPM` folder. Then
// using rpm2cpio attempt to unpack to `SOURCES`. If that all completes, find
// and parse the specfile.
func RpmGetSrcRpm(url string, outputPath string) (RpmSpec, error) {
func RpmGetSrpm(url string, outputPath string) (RpmSpec, error) {
resp, err := http.Get(url)

if err != nil {
return RpmSpec{}, errors.New("RpmGetSrcRpm: failed to fetch url: " + url)
}
defer resp.Body.Close()

sourceRPM, sRPM, err := util.CreateRpmBuildStructure(outputPath)
sourceRPM, sRPM, bRPM, err := util.CreateRpmBuildStructure(outputPath)
if err != nil {
return RpmSpec{}, errors.New("RpmGetSrcRpm: failed to create rpmbuild structure")
}

outputRpmPath := filepath.Join(sRPM, filepath.Base(url))
out, err := os.Create(outputRpmPath)
Expand All @@ -201,9 +250,10 @@ func RpmGetSrcRpm(url string, outputPath string) (RpmSpec, error) {
return RpmSpec{}, errors.New("RpmGetSrcRpm: failed to parse specfile: " + err.Error())
}

rpmSpec.srpmLocation = sRPM
rpmSpec.sourcesLocation = sourceRPM
rpmSpec.outLocation = outputPath
rpmSpec.SrpmLocation = sRPM
rpmSpec.SourcesLocation = sourceRPM
rpmSpec.OutLocation = outputPath
rpmSpec.BuildLocation = bRPM

/// move specfile to SPECS
return rpmSpec, nil
Expand Down

0 comments on commit b846e42

Please sign in to comment.