diff --git a/toolkit/tools/internal/rpm/rpm.go b/toolkit/tools/internal/rpm/rpm.go index 96f817b8557..7ace5f19f82 100644 --- a/toolkit/tools/internal/rpm/rpm.go +++ b/toolkit/tools/internal/rpm/rpm.go @@ -64,6 +64,51 @@ const ( MaxCPUDefine = "_smp_ncpus_max" ) +var ( + // RpmSpecBuiltRPMRegex extracts the package name, optional epoch, version, release, distribution, + // and architecture from values returned by 'rpmspec --builtrpms' (also produced by + // QuerySPECForBuiltRPMs), which follow the `%{nevra}` format. + // + // Examples: + // + // kernel-6.6.134.1-2.azl3.x86_64 -> Name: kernel, Epoch: "", Version: 6.6.134.1, Release: 2, Distribution: azl3, Architecture: x86_64 + // python3-perf-5.15.63.1-1.azl3.x86_64 -> Name: python3-perf, Epoch: "", Version: 5.15.63.1, Release: 1, Distribution: azl3, Architecture: x86_64 + // ca-certificates-1:3.0.0-14.azl3.noarch -> Name: ca-certificates, Epoch: 1, Version: 3.0.0, Release: 14, Distribution: azl3, Architecture: noarch + // + // NOTE: regular expression based on the following assumptions: + // - Package version and release values are not allowed to contain a hyphen character. + // - Our tooling prevents the 'Release' tag from having any other form than '[[:digit:]]+%{?dist}'. + // - The distribution tag is not allowed to contain a period or a hyphen. + // - The architecture is not allowed to contain a period or a hyphen. + // - When the package has a non-zero epoch, it appears immediately before the version as `epoch:`; + // a missing epoch (the default `0`) is omitted entirely from the input. + // + // Regex breakdown: + // + // ^(.*) <-- [index 1] package name (may contain hyphens) + // - <-- third-to-last hyphen separating the package name from its version + // (?:(\d+):)? <-- [index 2] optional epoch (digits) followed by a colon + // ([^-]+) <-- [index 3] package version + // - <-- second-to-last hyphen separating the version from the release + // ([^-]+) <-- [index 4] package release + // \. <-- second-to-last period separating the release from the distribution tag + // ([^.]+) <-- [index 5] the distribution tag + // \. <-- last period separating the distribution tag from the architecture string + // ([^.]+)$ <-- [index 6] the architecture string + RpmSpecBuiltRPMRegex = regexp.MustCompile(`^(.*)-(?:(\d+):)?([^-]+)-([^-]+)\.([^.]+)\.([^.]+)$`) +) + +// Index constants for capture groups of RpmSpecBuiltRPMRegex. +const ( + RpmSpecBuiltRPMRegexNameIndex = iota + 1 + RpmSpecBuiltRPMRegexEpochIndex + RpmSpecBuiltRPMRegexVersionIndex + RpmSpecBuiltRPMRegexReleaseIndex + RpmSpecBuiltRPMRegexDistributionIndex + RpmSpecBuiltRPMRegexArchitectureIndex + RpmSpecBuiltRPMRegexMatchesCount +) + const ( packageFQNRegexMatchSubString = iota packageFQNRegexNameIndex = iota diff --git a/toolkit/tools/internal/rpm/rpm_test.go b/toolkit/tools/internal/rpm/rpm_test.go index 1731a5bcb69..600c9bca3fd 100644 --- a/toolkit/tools/internal/rpm/rpm_test.go +++ b/toolkit/tools/internal/rpm/rpm_test.go @@ -725,3 +725,48 @@ func TestStripEpochFromPackageFullQualifiedNameWithInvalidInput(t *testing.T) { }) } } + +func TestRpmSpecBuiltRPMRegexBasic(t *testing.T) { + input := "pkg-1.2.3-1.azl3.x86_64" + expectedResults := []string{"pkg", "", "1.2.3", "1", "azl3", "x86_64"} + + matches := RpmSpecBuiltRPMRegex.FindStringSubmatch(input) + assert.Equal(t, RpmSpecBuiltRPMRegexMatchesCount, len(matches)) + + assert.Equal(t, expectedResults[0], matches[RpmSpecBuiltRPMRegexNameIndex]) + assert.Equal(t, expectedResults[1], matches[RpmSpecBuiltRPMRegexEpochIndex]) + assert.Equal(t, expectedResults[2], matches[RpmSpecBuiltRPMRegexVersionIndex]) + assert.Equal(t, expectedResults[3], matches[RpmSpecBuiltRPMRegexReleaseIndex]) + assert.Equal(t, expectedResults[4], matches[RpmSpecBuiltRPMRegexDistributionIndex]) + assert.Equal(t, expectedResults[5], matches[RpmSpecBuiltRPMRegexArchitectureIndex]) +} + +func TestRpmSpecBuiltRPMRegexUnderscore(t *testing.T) { + input := "pkg-1.2.3-1_2.3.azl3.x86_64" + expectedResults := []string{"pkg", "", "1.2.3", "1_2.3", "azl3", "x86_64"} + + matches := RpmSpecBuiltRPMRegex.FindStringSubmatch(input) + assert.Equal(t, RpmSpecBuiltRPMRegexMatchesCount, len(matches)) + + assert.Equal(t, expectedResults[0], matches[RpmSpecBuiltRPMRegexNameIndex]) + assert.Equal(t, expectedResults[1], matches[RpmSpecBuiltRPMRegexEpochIndex]) + assert.Equal(t, expectedResults[2], matches[RpmSpecBuiltRPMRegexVersionIndex]) + assert.Equal(t, expectedResults[3], matches[RpmSpecBuiltRPMRegexReleaseIndex]) + assert.Equal(t, expectedResults[4], matches[RpmSpecBuiltRPMRegexDistributionIndex]) + assert.Equal(t, expectedResults[5], matches[RpmSpecBuiltRPMRegexArchitectureIndex]) +} + +func TestRpmSpecBuiltRPMRegexEpoch(t *testing.T) { + input := "ca-certificates-1:3.0.0-14.azl3.noarch" + expectedResults := []string{"ca-certificates", "1", "3.0.0", "14", "azl3", "noarch"} + + matches := RpmSpecBuiltRPMRegex.FindStringSubmatch(input) + assert.Equal(t, RpmSpecBuiltRPMRegexMatchesCount, len(matches)) + + assert.Equal(t, expectedResults[0], matches[RpmSpecBuiltRPMRegexNameIndex]) + assert.Equal(t, expectedResults[1], matches[RpmSpecBuiltRPMRegexEpochIndex]) + assert.Equal(t, expectedResults[2], matches[RpmSpecBuiltRPMRegexVersionIndex]) + assert.Equal(t, expectedResults[3], matches[RpmSpecBuiltRPMRegexReleaseIndex]) + assert.Equal(t, expectedResults[4], matches[RpmSpecBuiltRPMRegexDistributionIndex]) + assert.Equal(t, expectedResults[5], matches[RpmSpecBuiltRPMRegexArchitectureIndex]) +} diff --git a/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot.go b/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot.go index 3f000fee67a..50522b0ae30 100644 --- a/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot.go +++ b/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot.go @@ -8,7 +8,6 @@ package rpmssnapshot import ( "fmt" "path/filepath" - "regexp" "runtime" "github.com/microsoft/azurelinux/toolkit/tools/internal/file" @@ -23,37 +22,6 @@ const ( chrootOutputFilePath = "/snapshot.json" ) -// Regular expression to extract package name, version, distribution, and architecture from values returned by 'rpmspec --builtrpms'. -// Examples: -// -// kernel-5.15.63.1-1.azl3.x86_64 -> Name: kernel, Version: 5.15.63.1-1, Distribution: azl3, Architecture: x86_64 -// python3-perf-5.15.63.1-1.azl3.x86_64 -> Name: python3-perf, Version: 5.15.63.1-1, Distribution: azl3, Architecture: x86_64 -// -// NOTE: regular expression based on following assumptions: -// - Package version and release values are not allowed to contain a hyphen character. -// - Our tooling prevents the 'Release' tag from having any other form than '[[:digit:]]+%{?dist}' -// - The distribution tag is not allowed to contain a period or a hyphen. -// - The architecture is not allowed to contain a period or a hyphen. -// -// Regex breakdown: -// -// ^(.*) <-- [index 1] package name -// - <-- second-to-last hyphen separating the package name from its version -// ([^-]+-[^-]+) <-- [index 2] package version and package release number connected by the last hyphen -// \. <-- second-to-last period separating the package release number from the distribution tag -// ([^.]+) <-- [index 3] the distribution tag -// \. <-- last period separating the distribution tag from the architecture string -// ([^.]+)$ <-- [index 4] the architecture string -var rpmSpecBuiltRPMRegex = regexp.MustCompile(`^(.*)-([^-]+-[^-]+)\.([^.]+)\.([^.]+)$`) - -const ( - rpmSpecBuiltRPMRegexNameIndex = iota + 1 - rpmSpecBuiltRPMRegexVersionIndex - rpmSpecBuiltRPMRegexDistributionIndex - rpmSpecBuiltRPMRegexArchitectureIndex - rpmSpecBuiltRPMRegexMatchesCount -) - type SnapshotGenerator struct { simpleToolChroot simpletoolchroot.SimpleToolChroot } @@ -96,16 +64,24 @@ func (s *SnapshotGenerator) convertResultsToRepoContents(allBuiltRPMs []string) } for _, builtRPM := range allBuiltRPMs { - matches := rpmSpecBuiltRPMRegex.FindStringSubmatch(builtRPM) - if len(matches) != rpmSpecBuiltRPMRegexMatchesCount { - return repoContents, fmt.Errorf("RPM package name (%s) doesn't match the regular expression (%s)", builtRPM, rpmSpecBuiltRPMRegex.String()) + matches := rpm.RpmSpecBuiltRPMRegex.FindStringSubmatch(builtRPM) + if len(matches) != rpm.RpmSpecBuiltRPMRegexMatchesCount { + return repoContents, fmt.Errorf("RPM package name (%s) doesn't match the regular expression (%s)", builtRPM, rpm.RpmSpecBuiltRPMRegex.String()) + } + + // Reattach a non-zero epoch to the version (`epoch:version-release`) so the resulting + // 'Version' field keeps the same shape it had before epoch was split out into its own + // capture group. + version := fmt.Sprintf("%s-%s", matches[rpm.RpmSpecBuiltRPMRegexVersionIndex], matches[rpm.RpmSpecBuiltRPMRegexReleaseIndex]) + if epoch := matches[rpm.RpmSpecBuiltRPMRegexEpochIndex]; epoch != "" { + version = fmt.Sprintf("%s:%s", epoch, version) } repoContents.Repo = append(repoContents.Repo, &repocloner.RepoPackage{ - Name: matches[rpmSpecBuiltRPMRegexNameIndex], - Version: matches[rpmSpecBuiltRPMRegexVersionIndex], - Distribution: matches[rpmSpecBuiltRPMRegexDistributionIndex], - Architecture: matches[rpmSpecBuiltRPMRegexArchitectureIndex], + Name: matches[rpm.RpmSpecBuiltRPMRegexNameIndex], + Version: version, + Distribution: matches[rpm.RpmSpecBuiltRPMRegexDistributionIndex], + Architecture: matches[rpm.RpmSpecBuiltRPMRegexArchitectureIndex], }) } diff --git a/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot_test.go b/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot_test.go index fdef9e7de18..bc3366e6ab4 100644 --- a/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot_test.go +++ b/toolkit/tools/pkg/rpmssnapshot/rpmssnapshot_test.go @@ -9,41 +9,15 @@ import ( "testing" "github.com/microsoft/azurelinux/toolkit/tools/internal/packagerepo/repocloner" + "github.com/microsoft/azurelinux/toolkit/tools/internal/rpm" "github.com/stretchr/testify/assert" ) -func TestRegexBasic(t *testing.T) { - input := "pkg-1.2.3-1.azl3.x86_64" - expectedResults := []string{"pkg", "1.2.3-1", "azl3", "x86_64"} - expectedMatchNumber := 5 - - matches := rpmSpecBuiltRPMRegex.FindStringSubmatch(input) - assert.Equal(t, expectedMatchNumber, len(matches)) - - assert.Equal(t, expectedResults[0], matches[rpmSpecBuiltRPMRegexNameIndex]) - assert.Equal(t, expectedResults[1], matches[rpmSpecBuiltRPMRegexVersionIndex]) - assert.Equal(t, expectedResults[2], matches[rpmSpecBuiltRPMRegexDistributionIndex]) - assert.Equal(t, expectedResults[3], matches[rpmSpecBuiltRPMRegexArchitectureIndex]) -} - -func TestRegexUnderscore(t *testing.T) { - input := "pkg-1.2.3-1_2.3.azl3.x86_64" - expectedResults := []string{"pkg", "1.2.3-1_2.3", "azl3", "x86_64"} - expectedMatchNumber := 5 - - matches := rpmSpecBuiltRPMRegex.FindStringSubmatch(input) - assert.Equal(t, expectedMatchNumber, len(matches)) - - assert.Equal(t, expectedResults[0], matches[rpmSpecBuiltRPMRegexNameIndex]) - assert.Equal(t, expectedResults[1], matches[rpmSpecBuiltRPMRegexVersionIndex]) - assert.Equal(t, expectedResults[2], matches[rpmSpecBuiltRPMRegexDistributionIndex]) - assert.Equal(t, expectedResults[3], matches[rpmSpecBuiltRPMRegexArchitectureIndex]) -} - func TestGenerateResults(t *testing.T) { input := []string{ "pkg-1.2.3-1_2.3.azl3.x86_64", "other-pkg-1.2.3-1_2.3.azl3.x86_64", + "ca-certificates-1:3.0.0-14.azl3.noarch", } expectedResults := repocloner.RepoContents{ Repo: []*repocloner.RepoPackage{ @@ -59,6 +33,12 @@ func TestGenerateResults(t *testing.T) { Distribution: "azl3", Architecture: "x86_64", }, + { + Name: "ca-certificates", + Version: "1:3.0.0-14", + Distribution: "azl3", + Architecture: "noarch", + }, }, } emptySnapshotGenerator := SnapshotGenerator{} @@ -73,5 +53,5 @@ func TestGenerateInvalidInput(t *testing.T) { } emptySnapshotGenerator := SnapshotGenerator{} _, err := emptySnapshotGenerator.convertResultsToRepoContents(input) - assert.EqualError(t, err, "RPM package name ("+input[0]+") doesn't match the regular expression ("+rpmSpecBuiltRPMRegex.String()+")") + assert.EqualError(t, err, "RPM package name ("+input[0]+") doesn't match the regular expression ("+rpm.RpmSpecBuiltRPMRegex.String()+")") } diff --git a/toolkit/tools/versionsprocessor/versionsprocessor.go b/toolkit/tools/versionsprocessor/versionsprocessor.go index ba0d35abdb0..fe7d5abe76c 100644 --- a/toolkit/tools/versionsprocessor/versionsprocessor.go +++ b/toolkit/tools/versionsprocessor/versionsprocessor.go @@ -12,8 +12,8 @@ import ( "fmt" "os" "path/filepath" - "regexp" "runtime" + "sort" "strings" "github.com/microsoft/azurelinux/toolkit/tools/internal/exe" @@ -28,8 +28,6 @@ import ( "gopkg.in/alecthomas/kingpin.v2" ) -var packageVersionRegexp = regexp.MustCompile(`^[^:]+: (?:(.+):)?(.+)-(.+)$`) - var ( app = kingpin.New("versionsprocessor", "A tool to generate a macro file of all specs version and release") specsDir = exe.InputDirFlag(app, "Directory to scan for SPECS") @@ -140,25 +138,21 @@ func main() { } func processSpecFile(specFile string, buildArch string, distTag string, macrosOutput []string) (newMacrosOutput []string, err error) { - // Get spec file version-release - specFileName := filepath.Base(specFile) sourceDir := filepath.Dir(specFile) defines := rpm.DefaultDistroDefines(false, distTag) - packages, err := rpm.QuerySPEC(specFile, sourceDir, `%{NAME}: %{evr}\n`, buildArch, defines, rpm.QueryHeaderArgument) - + packages, err := rpm.QuerySPECForBuiltRPMs(specFile, sourceDir, buildArch, defines) if err != nil { logger.Log.Errorf("Failed to query spec file (%s). Error: %s", specFileName, err) return nil, err } - for _, packageVersionString := range packages { - - macros, err := processPackageVersionString(packageVersionString, specFileName, distTag) - if err != nil { - logger.Log.Errorf("Error processing package version string: %s", err) + for _, packageNEVRA := range packages { + macros, processErr := processPackageVersionString(packageNEVRA) + if processErr != nil { + logger.Log.Errorf("Failed to process package (%s) from spec file (%s): %s", packageNEVRA, specFileName, processErr) continue } @@ -168,44 +162,35 @@ func processSpecFile(specFile string, buildArch string, distTag string, macrosOu return macrosOutput, nil } -func processPackageVersionString(packageVersionString string, specFileName string, distTag string) (macros []string, err error) { - const ( - prefix = "azl" - ) - // the output of the above query is in the format of "packagename: version-release", - // so split by ": " to get the version-release portion we want the second part - releaseVerSplit := packageVersionRegexp.FindStringSubmatch(packageVersionString)[1:] - - if len(releaseVerSplit) <= 2 { - errorString := fmt.Sprintf("Empty version-release format retrieved from spec file (%s)", specFileName) - err = fmt.Errorf(errorString) - logger.Log.Errorf(errorString) +func processPackageVersionString(packageNEVRA string) (macros []string, err error) { + const prefix = "azl" - return []string{""}, err + matches := rpm.RpmSpecBuiltRPMRegex.FindStringSubmatch(packageNEVRA) + if len(matches) != rpm.RpmSpecBuiltRPMRegexMatchesCount { + return nil, fmt.Errorf("invalid package NEVRA format: %q", packageNEVRA) } - epoch := releaseVerSplit[0] - version := releaseVerSplit[1] - release := releaseVerSplit[2] - releaseClean := strings.Replace(release, distTag, "", 1) + name := matches[rpm.RpmSpecBuiltRPMRegexNameIndex] + epoch := matches[rpm.RpmSpecBuiltRPMRegexEpochIndex] + version := matches[rpm.RpmSpecBuiltRPMRegexVersionIndex] + release := matches[rpm.RpmSpecBuiltRPMRegexReleaseIndex] - // strip out the .spec suffix and replace '-' with '_' as RPM macros cannot have '-' - packageFileNameMacroFormat := strings.Replace(specFileName, ".spec", "", 1) - packageFileNameMacroFormat = strings.ReplaceAll(packageFileNameMacroFormat, "-", "_") + // Replace '-' with '_' as RPM macros cannot have '-'. + nameMacroFormat := strings.ReplaceAll(name, "-", "_") - epochReleaseString := prefix + "_" + packageFileNameMacroFormat + "_epoch" - versionMacroString := prefix + "_" + packageFileNameMacroFormat + "_version" - releaseMacroString := prefix + "_" + packageFileNameMacroFormat + "_release" + epochMacroString := prefix + "_" + nameMacroFormat + "_epoch" + versionMacroString := prefix + "_" + nameMacroFormat + "_version" + releaseMacroString := prefix + "_" + nameMacroFormat + "_release" // Generate RPM macro definitions instead of modifying spec files directly. macros = []string{ fmt.Sprintf("%%%s %s", versionMacroString, version), - fmt.Sprintf("%%%s %s", releaseMacroString, releaseClean), + fmt.Sprintf("%%%s %s", releaseMacroString, release), } // Only append (to the front of the list) if we have an epoch if epoch != "" { - macros = append([]string{fmt.Sprintf("%%%s %s", epochReleaseString, epoch)}, macros...) + macros = append([]string{fmt.Sprintf("%%%s %s", epochMacroString, epoch)}, macros...) } return macros, nil @@ -218,16 +203,18 @@ func writeExtraFilesToOutput(extraFiles []string, macrosOutput []string, output continue } - contents, readErr := file.Read(extraPath) + contents, readErr := file.ReadLines(extraPath) if readErr != nil { logger.Log.Errorf("Failed to read extra macros file (%s): %s", extraPath, readErr) continue } - macrosOutput = append(macrosOutput, contents) + macrosOutput = append(macrosOutput, contents...) logger.Log.Infof("Appended contents of provided extra macros file (%s) to %s", extraPath, output) } + sort.Strings(macrosOutput) + err = file.WriteLines(macrosOutput, output) if err != nil { logger.Log.Errorf("Failed to write file (%s)", output) diff --git a/toolkit/tools/versionsprocessor/versionsprocessor_test.go b/toolkit/tools/versionsprocessor/versionsprocessor_test.go index a7ba832750b..032c5f50070 100644 --- a/toolkit/tools/versionsprocessor/versionsprocessor_test.go +++ b/toolkit/tools/versionsprocessor/versionsprocessor_test.go @@ -27,111 +27,70 @@ func TestMain(m *testing.M) { // --------------------------------------------------------------------------- func TestProcessPackageVersionString_ValidInput(t *testing.T) { - distTag := ".azl3" - - macros, err := processPackageVersionString("mypackage: 1.2.3-4.azl3", "mypackage.spec", distTag) + macros, err := processPackageVersionString("mypackage-1.2.3-4.azl3.x86_64") require.NoError(t, err) assert.Contains(t, macros, "%azl_mypackage_version 1.2.3") assert.Contains(t, macros, "%azl_mypackage_release 4") } -func TestProcessPackageVersionString_DashesInSpecName(t *testing.T) { - distTag := ".azl3" - - macros, err := processPackageVersionString("my-cool-pkg: 2.0.0-1.azl3", "my-cool-pkg.spec", distTag) +func TestProcessPackageVersionString_DashesInName(t *testing.T) { + macros, err := processPackageVersionString("my-cool-pkg-2.0.0-1.azl3.x86_64") require.NoError(t, err) // Dashes should be replaced with underscores in macro names. assert.Contains(t, macros, "%azl_my_cool_pkg_version 2.0.0") assert.Contains(t, macros, "%azl_my_cool_pkg_release 1") } -func TestProcessPackageVersionString_ReleaseWithoutDistTag(t *testing.T) { - distTag := ".azl3" - - macros, err := processPackageVersionString("pkg: 1.0-5", "pkg.spec", distTag) - require.NoError(t, err) - // When the dist tag is not present in the release, it should remain unchanged. - assert.Contains(t, macros, "%azl_pkg_version 1.0") - assert.Contains(t, macros, "%azl_pkg_release 5") -} - func TestProcessPackageVersionString_EpochInVersion(t *testing.T) { - distTag := ".azl3" - - macros, err := processPackageVersionString("pkg: 2:3.4.5-6.azl3", "pkg.spec", distTag) + macros, err := processPackageVersionString("pkg-2:3.4.5-6.azl3.x86_64") require.NoError(t, err) assert.Contains(t, macros, "%azl_pkg_epoch 2") assert.Contains(t, macros, "%azl_pkg_version 3.4.5") assert.Contains(t, macros, "%azl_pkg_release 6") } -func TestProcessPackageVersionString_PanicsOnBadFormat(t *testing.T) { - distTag := ".azl3" - - // Input without the expected "name: version-release" format will cause a panic - // from the regex submatch slice indexing. - assert.Panics(t, func() { - processPackageVersionString("totally-invalid-input", "bad.spec", distTag) - }) -} - -func TestProcessPackageVersionString_EmptyDistTag(t *testing.T) { - distTag := "" - - macros, err := processPackageVersionString("pkg: 1.0-2.azl3", "pkg.spec", distTag) - require.NoError(t, err) - // With an empty dist tag, the release should not be modified. - assert.Contains(t, macros, "%azl_pkg_release 2.azl3") +func TestProcessPackageVersionString_ErrorsOnBadFormat(t *testing.T) { + macros, err := processPackageVersionString("totally-invalid-input") + assert.Error(t, err) + assert.Nil(t, macros) } func TestProcessPackageVersionString_TableDriven(t *testing.T) { - distTag := ".azl3" - tests := []struct { name string input string - specFile string - prefix string expectedVersion string expectedRelease string }{ { name: "simple package", - input: "bash: 5.1.8-1.azl3", - specFile: "bash.spec", - prefix: "azl", + input: "bash-5.1.8-1.azl3.x86_64", expectedVersion: "%azl_bash_version 5.1.8", expectedRelease: "%azl_bash_release 1", }, { name: "package with underscores already", - input: "python_dateutil: 2.8.2-3.azl3", - specFile: "python_dateutil.spec", - prefix: "azl", + input: "python_dateutil-2.8.2-3.azl3.x86_64", expectedVersion: "%azl_python_dateutil_version 2.8.2", expectedRelease: "%azl_python_dateutil_release 3", }, { name: "multi digit release", - input: "kernel: 6.6.51.1-9.azl3", - specFile: "kernel.spec", - prefix: "azl", + input: "kernel-6.6.51.1-9.azl3.x86_64", expectedVersion: "%azl_kernel_version 6.6.51.1", expectedRelease: "%azl_kernel_release 9", }, { - name: "release with extra suffixes after dist tag", - input: "openssl: 3.3.0-1.azl3.1", - specFile: "openssl.spec", - prefix: "azl", - expectedVersion: "%azl_openssl_version 3.3.0", - expectedRelease: "%azl_openssl_release 1.1", + name: "noarch package", + input: "ca-certificates-base-3.0.0-14.azl3.noarch", + expectedVersion: "%azl_ca_certificates_base_version 3.0.0", + expectedRelease: "%azl_ca_certificates_base_release 14", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - macros, err := processPackageVersionString(tt.input, tt.specFile, distTag) + macros, err := processPackageVersionString(tt.input) require.NoError(t, err) assert.Contains(t, macros, tt.expectedVersion) assert.Contains(t, macros, tt.expectedRelease) @@ -155,6 +114,9 @@ Distribution: Azure Linux %%description Test spec. +%%files +%%defattr(-,root,root) + %%changelog * Mon Oct 11 2021 Test User %s-%s - Test entry. @@ -275,6 +237,15 @@ Summary: Libraries %description libs Libs. +%files +%defattr(-,root,root) + +%files devel +%defattr(-,root,root) + +%files libs +%defattr(-,root,root) + %changelog * Mon Oct 11 2021 Test User 4.0.0-3 - Test entry. @@ -286,12 +257,21 @@ Libs. arch := testBuildArch(t) macros, err := processSpecFile(specPath, arch, distTag, nil) require.NoError(t, err) - // With --srpm, rpmspec returns the source RPM entry (1 result). - require.Len(t, macros, 2) + // With --builtrpms, rpmspec returns one entry per built binary RPM: + // the default package plus the 'devel' and 'libs' subpackages = 3 entries, + // each producing a version and a release macro = 6 macros total. + // Macro names are derived from each subpackage's own name, so they all + // get distinct '%azl__*' macros. + require.Len(t, macros, 6) - assert.NotContains(t, macros[0], "%azl_multipkg_epoch ") - assert.Contains(t, macros[0], "%azl_multipkg_version 4.0.0") - assert.Contains(t, macros[1], "%azl_multipkg_release 3") + combined := strings.Join(macros, "\n") + assert.NotContains(t, combined, "_epoch ") + assert.Contains(t, combined, "%azl_multipkg_version 4.0.0") + assert.Contains(t, combined, "%azl_multipkg_release 3") + assert.Contains(t, combined, "%azl_multipkg_devel_version 4.0.0") + assert.Contains(t, combined, "%azl_multipkg_devel_release 3") + assert.Contains(t, combined, "%azl_multipkg_libs_version 4.0.0") + assert.Contains(t, combined, "%azl_multipkg_libs_release 3") } func TestProcessSpecFile_NonexistentFile(t *testing.T) { @@ -321,23 +301,6 @@ func TestProcessSpecFile_InvalidSpec(t *testing.T) { assert.Nil(t, macros) } -func TestProcessSpecFile_ReleaseWithoutDistMacro(t *testing.T) { - distTag := ".azl3" - - tmpDir := t.TempDir() - // Create spec with a release that has no %{?dist} macro. - specPath := createSpecFile(t, tmpDir, "nodist", "1.0", "5") - arch := testBuildArch(t) - - macros, err := processSpecFile(specPath, arch, distTag, nil) - require.NoError(t, err) - require.Len(t, macros, 2) - // Release should remain as-is since there's no dist tag to strip. - assert.NotContains(t, macros[0], "%azl_nodist_epoch ") - assert.Contains(t, macros[0], "%azl_nodist_version 1.0") - assert.Contains(t, macros[1], "%azl_nodist_release 5") -} - func TestProcessSpecFile_MultiDigitVersion(t *testing.T) { distTag := ".azl3"