Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions internal/app/azldev/core/sources/overlays.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ func ApplyOverlayToSources(
dryRunnable opctx.DryRunnable,
fs opctx.FS,
overlay projectconfig.ComponentOverlay,
sourcesDirPath, specPath string,
sourcesDirPath, specPath string, options ...spec.OpenOption,
) error {
// Apply the spec component, if any.
if overlay.ModifiesSpec() {
err := ApplySpecOverlayToFileInPlace(fs, overlay, specPath)
err := ApplySpecOverlayToFileInPlace(fs, overlay, specPath, options...)
if err != nil {
return err
}
Expand Down Expand Up @@ -78,13 +78,15 @@ func ApplyOverlayToSources(

// ApplySpecOverlayToFileInPlace applies the given overlay to the specified spec file.
// Changes are made in-place.
func ApplySpecOverlayToFileInPlace(fs opctx.FS, overlay projectconfig.ComponentOverlay, specPath string) error {
func ApplySpecOverlayToFileInPlace(
fs opctx.FS, overlay projectconfig.ComponentOverlay, specPath string, options ...spec.OpenOption,
) error {
specFile, err := fs.Open(specPath)
if err != nil {
return fmt.Errorf("failed to open spec %#q for reading:\n%w", specPath, err)
}

openedSpec, err := spec.OpenSpec(specFile)
openedSpec, err := spec.OpenSpec(specFile, options...)
specFile.Close()

if err != nil {
Expand Down
46 changes: 46 additions & 0 deletions internal/app/azldev/core/sources/overlays_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,52 @@ newname package
}
}

func TestApplySpecOverlay_ShimConditionalRepairAcrossOverlays(t *testing.T) {
openedSpec, err := spec.OpenSpec(strings.NewReader(`Name: shim-unsigned-%{efiarch}

%build
%if 0%{?dbxfile}
echo dbx
%endif
cd build-%{efiarch}
cd build-%{efialtarch}
%install
`), spec.WithEditor(spec.EditorStructural))
require.NoError(t, err)

require.NoError(t, sources.ApplySpecOverlay(projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlaySearchAndReplaceInSpec,
SectionName: "%build",
Regex: `^cd build-%\{efialtarch\}$`,
Replacement: "%if 0\ncd build-%{efialtarch}",
}, openedSpec))

var intermediate bytes.Buffer
require.NoError(t, openedSpec.Serialize(&intermediate))
assert.Contains(t, intermediate.String(), "%if 0\ncd build-%{efialtarch}\n")

require.NoError(t, sources.ApplySpecOverlay(projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAppendSpecLines,
SectionName: "%build",
Lines: []string{"%endif"},
}, openedSpec))

var result bytes.Buffer
require.NoError(t, openedSpec.Serialize(&result))
assert.Equal(t, `Name: shim-unsigned-%{efiarch}

%build
%if 0%{?dbxfile}
echo dbx
%endif
cd build-%{efiarch}
%if 0
cd build-%{efialtarch}
%endif
%install
`, result.String())
}

func TestApplyNonSpecOverlay(t *testing.T) {
testCases := []struct {
name string
Expand Down
25 changes: 6 additions & 19 deletions internal/app/azldev/core/sources/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"log/slog"
"regexp"
"strconv"
"strings"

"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components"
"github.com/microsoft/azure-linux-dev-tools/internal/global/opctx"
Expand All @@ -34,33 +33,21 @@ var staticReleasePattern = regexp.MustCompile(`^(\d+)(%\{\??dist\})?$`)
// GetReleaseTagValue reads the Release tag value from the spec file at specPath.
// It returns the raw value string as written in the spec (e.g. "1%{?dist}" or "%autorelease").
// Returns [spec.ErrNoSuchTag] if no Release tag is found.
func GetReleaseTagValue(fs opctx.FS, specPath string) (string, error) {
func GetReleaseTagValue(fs opctx.FS, specPath string, options ...spec.OpenOption) (string, error) {
specFile, err := fs.Open(specPath)
if err != nil {
return "", fmt.Errorf("failed to open spec %#q:\n%w", specPath, err)
}
defer specFile.Close()

openedSpec, err := spec.OpenSpec(specFile)
openedSpec, err := spec.OpenSpec(specFile, options...)
if err != nil {
return "", fmt.Errorf("failed to parse spec %#q:\n%w", specPath, err)
}

var releaseValue string

err = openedSpec.VisitTagsPackage("", func(tagLine *spec.TagLine, _ *spec.Context) error {
if strings.EqualFold(tagLine.Tag, "Release") {
releaseValue = tagLine.Value
}

return nil
})
releaseValue, err := openedSpec.GetLastTag("", "Release")
if err != nil {
return "", fmt.Errorf("failed to visit tags in spec %#q:\n%w", specPath, err)
}

if releaseValue == "" {
return "", fmt.Errorf("release tag not found in spec %#q:\n%w", specPath, spec.ErrNoSuchTag)
return "", fmt.Errorf("failed to get Release tag from spec %#q:\n%w", specPath, err)
}

return releaseValue, nil
Expand Down Expand Up @@ -146,7 +133,7 @@ func (p *sourcePreparerImpl) readAndBumpRelease(
return err
}

releaseValue, err := GetReleaseTagValue(p.fs, specPath)
releaseValue, err := GetReleaseTagValue(p.fs, specPath, spec.WithEditor(p.specEditor))
if err != nil {
return fmt.Errorf("failed to read Release tag for component %#q:\n%w",
component.GetName(), err)
Expand Down Expand Up @@ -187,7 +174,7 @@ func (p *sourcePreparerImpl) readAndBumpRelease(
Value: newRelease,
}

if err := ApplySpecOverlayToFileInPlace(p.fs, overlay, specPath); err != nil {
if err := ApplySpecOverlayToFileInPlace(p.fs, overlay, specPath, spec.WithEditor(p.specEditor)); err != nil {
return fmt.Errorf("failed to apply release bump overlay for component %#q:\n%w",
component.GetName(), err)
}
Expand Down
38 changes: 38 additions & 0 deletions internal/app/azldev/core/sources/release_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,44 @@ func TestTryBumpStaticRelease_StaticBumps(t *testing.T) {
assert.Contains(t, string(content), "Release: 4%{?dist}")
}

func TestTryBumpStaticRelease_BumpsLastConditionalReleaseAndRereadsIt(t *testing.T) {
ctrl := gomock.NewController(t)
memFS := afero.NewMemMapFs()
preparer := newTestPreparer(memFS)
specDir := filepath.Join(testSourcesDir, "test-pkg")
require.NoError(t, fileutils.MkdirAll(memFS, specDir))
specPath := filepath.Join(specDir, "test-pkg.spec")
require.NoError(t, fileutils.WriteFile(memFS, specPath, []byte(`Name: test-pkg
Version: 1.0.0
%if 0
Release: 1%{?dist}
%else
Release: 2%{?dist}
%endif
`), fileperms.PublicFile))

comp := mockComponent(ctrl, "test-pkg", &projectconfig.ComponentConfig{
Release: projectconfig.ReleaseConfig{Calculation: projectconfig.ReleaseCalculationAuto},
})

require.NoError(t, preparer.tryBumpStaticRelease(comp, specDir, 3))

release, err := GetReleaseTagValue(memFS, specPath)
require.NoError(t, err)
assert.Equal(t, "5%{?dist}", release)

content, err := fileutils.ReadFile(memFS, specPath)
require.NoError(t, err)
assert.Equal(t, `Name: test-pkg
Version: 1.0.0
%if 0
Release: 5%{?dist}
%else
Release: 5%{?dist}
%endif
`, string(content))
}

func TestTryBumpStaticRelease_StaticBumpsNonConditionalDist(t *testing.T) {
ctrl := gomock.NewController(t)
memFS := afero.NewMemMapFs()
Expand Down
6 changes: 6 additions & 0 deletions internal/app/azldev/core/sources/release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ func TestGetReleaseTagValue(t *testing.T) {
{"static with dist", makeSpec("1%{?dist}"), "1%{?dist}", false},
{"autorelease", makeSpec("%autorelease"), "%autorelease", false},
{"braced autorelease", makeSpec("%{autorelease}"), "%{autorelease}", false},
{
"last repeated conditional release",
"Name: test-package\nVersion: 1.0.0\n%if 0\nRelease: 1\n%else\nRelease: 2\n%endif\n",
"2",
false,
},
{"no release tag", "Name: test-package\nVersion: 1.0.0\nSummary: Test\n", "", true},
} {
t.Run(testCase.name, func(t *testing.T) {
Expand Down
9 changes: 8 additions & 1 deletion internal/app/azldev/core/sources/sourceprep.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/microsoft/azure-linux-dev-tools/internal/projectconfig"
"github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders"
"github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders/fedorasource"
"github.com/microsoft/azure-linux-dev-tools/internal/rpm/spec"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/dirdiff"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileperms"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileutils"
Expand Down Expand Up @@ -105,6 +106,10 @@ func WithDirtyDetection() PreparerOption {
// Git-tracked files (spec, patches, scripts, configs) are still fetched from
// the upstream clone. This is useful for rendering, where only the spec and
// sidecar files are needed and downloading large source tarballs is unnecessary.
func WithSpecEditor(mode spec.EditorMode) PreparerOption {
return func(p *sourcePreparerImpl) { p.specEditor = mode }
}

func WithSkipLookaside() PreparerOption {
return func(p *sourcePreparerImpl) {
p.skipLookaside = true
Expand Down Expand Up @@ -156,6 +161,7 @@ func WithAllowNoHashes() PreparerOption {
// Standard implementation of the [SourcePreparer] interface.
type sourcePreparerImpl struct {
sourceManager sourceproviders.SourceManager
specEditor spec.EditorMode
fs opctx.FS
eventListener opctx.EventListener
dryRunnable opctx.DryRunnable
Expand Down Expand Up @@ -228,6 +234,7 @@ func NewPreparer(

impl := &sourcePreparerImpl{
sourceManager: sourceManager,
specEditor: spec.EditorLegacy,
fs: fs,
eventListener: eventListener,
dryRunnable: dryRunnable,
Expand Down Expand Up @@ -1390,7 +1397,7 @@ func (p *sourcePreparerImpl) applyOverlayList(
}

if err := ApplyOverlayToSources(
p.dryRunnable, p.fs, overlay, sourcesDirPath, absSpecPath,
p.dryRunnable, p.fs, overlay, sourcesDirPath, absSpecPath, spec.WithEditor(p.specEditor),
); err != nil {
return fmt.Errorf("failed to apply %#q overlay:\n%w", overlay.Type, err)
}
Expand Down
8 changes: 5 additions & 3 deletions internal/app/azldev/core/sources/upstream_provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (p *sourcePreparerImpl) addUpstreamProvenanceMacros(
return
}

version, release, err := parseSpecVersionRelease(p.fs, specPath)
version, release, err := parseSpecVersionRelease(p.fs, specPath, spec.WithEditor(p.specEditor))
if err != nil {
slog.Warn("Skipping upstream provenance macros; failed to parse spec",
"component", component.GetName(), "error", err)
Expand Down Expand Up @@ -245,13 +245,15 @@ func setMacroIfAbsent(macros map[string]string, name, value string) {
// package of the spec at specPath. Values are captured verbatim (no macro
// expansion beyond the caller's later %{?dist} substitution). Missing tags
// yield empty strings; it is not an error for a tag to be absent.
func parseSpecVersionRelease(fs opctx.FS, specPath string) (version, release string, err error) {
func parseSpecVersionRelease(
fs opctx.FS, specPath string, options ...spec.OpenOption,
) (version, release string, err error) {
data, err := fileutils.ReadFile(fs, specPath)
if err != nil {
return "", "", fmt.Errorf("failed to read spec %#q:\n%w", specPath, err)
}

parsed, err := spec.OpenSpec(bytes.NewReader(data))
parsed, err := spec.OpenSpec(bytes.NewReader(data), options...)
if err != nil {
return "", "", fmt.Errorf("failed to parse spec %#q:\n%w", specPath, err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/microsoft/azure-linux-dev-tools/internal/projectconfig"
"github.com/microsoft/azure-linux-dev-tools/internal/rpm/spec"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileperms"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileutils"
"github.com/spf13/afero"
Expand Down Expand Up @@ -79,6 +80,48 @@ func TestParseSpecVersionRelease(t *testing.T) {
assert.Equal(t, "5%{?dist}", release, "release is captured verbatim, dist is expanded later")
}

func TestParseSpecVersionReleaseReadsFirstRepeatedConditionalRelease(t *testing.T) {
memFS := afero.NewMemMapFs()
require.NoError(t, fileutils.MkdirAll(memFS, provenanceWorkDir))
require.NoError(t, fileutils.WriteFile(memFS, filepath.Join(provenanceWorkDir, "grub2.spec"), []byte(`Name: grub2
Version: 2.12
%if 0
Release: 5%{?dist}
%else
Release: 6%{?dist}
%endif
`), fileperms.PublicFile))

version, release, err := parseSpecVersionRelease(memFS, filepath.Join(provenanceWorkDir, "grub2.spec"))
require.NoError(t, err)
assert.Equal(t, "2.12", version)
assert.Equal(t, "5%{?dist}", release)
}

func TestParseSpecVersionReleaseSkipsEmptyRepeatedConditionalTags(t *testing.T) {
for _, editor := range []spec.EditorMode{spec.EditorLegacy, spec.EditorStructural} {
t.Run(string(editor), func(t *testing.T) {
memFS := afero.NewMemMapFs()
require.NoError(t, fileutils.MkdirAll(memFS, provenanceWorkDir))
require.NoError(t, fileutils.WriteFile(memFS, filepath.Join(provenanceWorkDir, "grub2.spec"), []byte(`Name: grub2
%if 0
Version:
Release:
%endif
Version: 2.12
Release: 5%{?dist}
`), fileperms.PublicFile))

version, release, err := parseSpecVersionRelease(
memFS, filepath.Join(provenanceWorkDir, "grub2.spec"), spec.WithEditor(editor),
)
require.NoError(t, err)
assert.Equal(t, "2.12", version)
assert.Equal(t, "5%{?dist}", release)
})
}
}

func TestParseSpecVersionRelease_MissingFile(t *testing.T) {
_, _, err := parseSpecVersionRelease(afero.NewMemMapFs(), "/does-not-exist.spec")
require.Error(t, err)
Expand Down
Loading
Loading