-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tools: implement version info parser based around
rpmspec
RPM spec files contain only a subset of the supported version information. nontheless, this could provide useful for packaging. the parser relies on `rpmspec` to expand any macros in the spec file in order to receive "clean" information.
- Loading branch information
Showing
13 changed files
with
508 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package rpmspec | ||
|
||
import ( | ||
"fmt" | ||
"io/fs" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/UiP9AV6Y/buildinfo" | ||
"github.com/UiP9AV6Y/buildinfo/tools/util" | ||
) | ||
|
||
const ( | ||
// spec field to render/extract for the version | ||
versionMacro = "%{version}" | ||
// spec field to render/extract for the revision | ||
revisionMacro = "%{release}" | ||
systemCommand = "rpmspec" | ||
) | ||
|
||
var ( | ||
// Error when no RPM spec file or `rpmspec` command was found | ||
ErrNoSpec = fs.ErrNotExist | ||
) | ||
|
||
// parser.VersionParser implementation rendering and parsing a RPM spec file | ||
type RPMSpec struct { | ||
cmd string | ||
file string | ||
} | ||
|
||
// TrySystemParse calls TryParse using the rpmspec command found in the PATH | ||
func TrySystemParse(path string) (*RPMSpec, error) { | ||
return TryParse(systemCommand, path) | ||
} | ||
|
||
// TryParse attempts to parse the given directory for a RPM spec file. | ||
// If no file was found or the given command was not found | ||
// ErrNoRepository is returned. All other errors are a result of file | ||
// access problems. | ||
func TryParse(cmd, path string) (*RPMSpec, error) { | ||
realCmd, err := exec.LookPath(cmd) | ||
if err != nil { | ||
// unable to parse spec file without `rpmspec` | ||
return nil, ErrNoSpec | ||
} | ||
|
||
pattern := filepath.Join(path, "*.spec") | ||
haystack, err := filepath.Glob(pattern) | ||
if err != nil { | ||
return nil, err | ||
} else if haystack == nil || len(haystack) == 0 { | ||
return nil, ErrNoSpec | ||
} | ||
|
||
return New(realCmd, haystack[0]), nil | ||
} | ||
|
||
// NewSystem creates a new parser.Parser instance using the provided | ||
// RPM spec file. the rpmspec executable is invoked as-is, | ||
// relying on its presence in one of the PATH directories. | ||
func NewSystem(file string) *RPMSpec { | ||
return New(systemCommand, file) | ||
} | ||
|
||
// New creates a new parser.Parser instance using the provided | ||
// RPM spec file. the rpmspec executable is invoked using | ||
// the provided path. | ||
func New(cmd, file string) *RPMSpec { | ||
result := &RPMSpec{ | ||
cmd: cmd, | ||
file: file, | ||
} | ||
|
||
return result | ||
} | ||
|
||
// String implements the fmt.Stringer interface | ||
func (s *RPMSpec) String() string { | ||
return fmt.Sprintf("(cmd=%s, spec=%s)", s.cmd, s.file) | ||
} | ||
|
||
// Equal compares the fields of this instance to the given one | ||
func (s *RPMSpec) Equal(o *RPMSpec) bool { | ||
if o == nil { | ||
return s == nil | ||
} | ||
|
||
return s.cmd == o.cmd && s.file == o.file | ||
} | ||
|
||
// ParseVersionInfo implements the parser.VersionParser interface | ||
func (s *RPMSpec) ParseVersionInfo() (*buildinfo.VersionInfo, error) { | ||
result := buildinfo.NewVersionInfo() | ||
|
||
if version, err := s.rpmspecQuery(versionMacro); err != nil { | ||
return nil, err | ||
} else if version != "" { | ||
result.Version = version | ||
} | ||
|
||
if revision, err := s.rpmspecQuery(revisionMacro); err != nil { | ||
return nil, err | ||
} else if revision != "" { | ||
result.Revision = revision | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func (s *RPMSpec) rpmspecQuery(query string) (string, error) { | ||
return util.RunCmd(s.cmd, []string{"--query", "--queryformat", query, s.file}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package rpmspec | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"gotest.tools/v3/assert" | ||
|
||
"github.com/UiP9AV6Y/buildinfo" | ||
) | ||
|
||
func mockRPMSPECBin() (string, error) { | ||
wd, err := os.Getwd() | ||
if err != nil { | ||
return "", err | ||
} | ||
mockPath := wd + "/testdata" | ||
|
||
os.Setenv("PATH", mockPath) | ||
|
||
return mockPath + "/rpmspec-mock.sh", nil | ||
} | ||
|
||
func TestTryParse(t *testing.T) { | ||
type testCase struct { | ||
haveCmd, havePath string | ||
wantError bool | ||
want *RPMSpec | ||
} | ||
|
||
rpmspecBin, err := mockRPMSPECBin() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
testCases := map[string]testCase{ | ||
"not in PATH": { | ||
haveCmd: "rpmspec-notexists", | ||
wantError: true, | ||
}, | ||
"no spec file": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/nospec", | ||
wantError: true, | ||
}, | ||
"broken spec file": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/broken", | ||
want: New(rpmspecBin, "testdata/broken/broken.spec"), | ||
}, | ||
"macro spec file": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/macro", | ||
want: New(rpmspecBin, "testdata/macro/macro.spec"), | ||
}, | ||
"minimal spec file": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/minimal", | ||
want: New(rpmspecBin, "testdata/minimal/minimal.spec"), | ||
}, | ||
"multiple spec files": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/multiple", | ||
want: New(rpmspecBin, "testdata/multiple/multiple.spec"), | ||
}, | ||
"relative bin": { | ||
haveCmd: "rpmspec-mock.sh", | ||
havePath: "testdata/minimal", | ||
want: New(rpmspecBin, "testdata/minimal/minimal.spec"), | ||
}, | ||
"absolute bin": { | ||
haveCmd: rpmspecBin, | ||
havePath: "testdata/minimal", | ||
want: New(rpmspecBin, "testdata/minimal/minimal.spec"), | ||
}, | ||
} | ||
|
||
for ctx, tc := range testCases { | ||
t.Run(ctx, func(t *testing.T) { | ||
got, err := TryParse(tc.haveCmd, tc.havePath) | ||
|
||
if tc.wantError { | ||
assert.Assert(t, err != nil) | ||
} else { | ||
assert.Assert(t, err) | ||
assert.Assert(t, tc.want.Equal(got), "want=%s; got=%s", tc.want, got) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestParseVersionInfo(t *testing.T) { | ||
type testCase struct { | ||
have *RPMSpec | ||
wantError bool | ||
want *buildinfo.VersionInfo | ||
} | ||
|
||
rpmspecBin, err := mockRPMSPECBin() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
testCases := map[string]testCase{ | ||
"broken": { | ||
have: New(rpmspecBin, "testdata/broken/broken.spec"), | ||
wantError: true, | ||
}, | ||
"nospec": { | ||
have: New(rpmspecBin, "testdata/nospec/nospec.spec"), | ||
wantError: true, | ||
}, | ||
"macro": { | ||
have: New(rpmspecBin, "testdata/macro/macro.spec"), | ||
want: &buildinfo.VersionInfo{ | ||
Version: "1.2.3~19701230gitd5a3191", | ||
Revision: "1.rhel", | ||
Branch: "trunk", | ||
}, | ||
}, | ||
"minimal": { | ||
have: New(rpmspecBin, "testdata/minimal/minimal.spec"), | ||
want: &buildinfo.VersionInfo{ | ||
Version: "1.0", | ||
Revision: "1", | ||
Branch: "trunk", | ||
}, | ||
}, | ||
} | ||
|
||
for ctx, tc := range testCases { | ||
t.Run(ctx, func(t *testing.T) { | ||
got, err := tc.have.ParseVersionInfo() | ||
|
||
if tc.wantError { | ||
assert.Assert(t, err != nil) | ||
} else { | ||
assert.Assert(t, err) | ||
assert.Assert(t, tc.want.Equal(got), "want=%s; got=%s", tc.want, got) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.