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
644 changes: 644 additions & 0 deletions docs/developer/rfc/001-conditional-aware-spec-editing.md

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions docs/user/reference/config/overlays.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,42 @@ the overlay always removes every section associated with the sub-package.
> an error is returned; use a `spec-search-replace` overlay to adjust the conditionals
> before removing the sub-package.

## Known Limitations

### Section-scoped operations and straddling conditionals

Spec overlays that target a specific section and package (e.g., `spec-remove-tag` with `package = "headless"`) rely on the structural tree parser to identify which lines belong to that section. In rare cases, a section header may live inside a `%if` wrapper while its content continues past the `%endif`:

```spec
%if 0%{!?scl:1}
%package headless
Requires: binutils
%endif
# ← content below is still part of %package headless in RPM's view,
# but the tree parser cannot associate it with the headless section
# because the section header is structurally inside the wrapper.
Recommends: default-yama-scope
```

In this pattern, section-scoped overlays (`spec-remove-tag`, `spec-add-tag`, `spec-update-tag`, `spec-insert-tag`) cannot find or modify tags that fall outside the `%endif`. Use `spec-search-replace` with a precise anchored regex instead:

```toml
# Instead of:
# type = "spec-remove-tag"
# package = "headless"
# tag = "Recommends"
# value = "default-yama-scope"

# Use:
type = "spec-search-replace"
regex = "^Recommends: default-yama-scope$"
replacement = "# Recommends: default-yama-scope (disabled)"
```

### Macro-generated sections

Specs that use macros like `%ghc_lib_subpackage`, `%pyproject_extras_subpkg`, or `%fontpkg` generate sections at build time that are invisible to the static parser. Section-scoped overlays cannot target these generated sections. Use `spec-search-replace` for modifications that need to reach macro-generated content.

## Validation

Overlay configurations are validated when the config file is loaded. Validation checks:
Expand Down
5 changes: 5 additions & 0 deletions internal/app/azldev/core/sources/overlays.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ func ApplySpecOverlay(overlay projectconfig.ComponentOverlay, openedSpec *spec.S
if err != nil {
return fmt.Errorf("failed to prepend lines to spec:\n%w", err)
}
case projectconfig.ComponentOverlayPrependAllSpecLines:
err := openedSpec.PrependLinesToAllSections(overlay.SectionName, overlay.PackageName, overlay.Lines)
if err != nil {
return fmt.Errorf("failed to prepend lines to all matching sections in spec:\n%w", err)
}
case projectconfig.ComponentOverlayAppendSpecLines:
err := openedSpec.AppendLinesToSection(overlay.SectionName, overlay.PackageName, overlay.Lines)
if err != nil {
Expand Down
17 changes: 2 additions & 15 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 Down Expand Up @@ -46,21 +45,9 @@ func GetReleaseTagValue(fs opctx.FS, specPath string) (string, error) {
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.GetTag("", "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
7 changes: 4 additions & 3 deletions internal/app/azldev/core/sources/sourceprep.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,16 +1046,17 @@ func generateFileHeaderOverlay() []projectconfig.ComponentOverlay {
}

// synthesizeCheckSkipOverlays generates overlays to disable the %check section if configured.
// When check.skip is true, it prepends an 'exit 0' to the %check section with a comment
// explaining why the section was disabled.
// When check.skip is true, it prepends an 'exit 0' to every %check section in the spec with
// a comment explaining why the section was disabled. Uses [ComponentOverlayPrependAllSpecLines]
// to handle specs that contain multiple %check sections gated by different conditionals.
func synthesizeCheckSkipOverlays(checkConfig projectconfig.CheckConfig) []projectconfig.ComponentOverlay {
if !checkConfig.Skip {
return nil
}

return []projectconfig.ComponentOverlay{
{
Type: projectconfig.ComponentOverlayPrependSpecLines,
Type: projectconfig.ComponentOverlayPrependAllSpecLines,
SectionName: "%check",
Lines: []string{
"# Check section disabled: " + checkConfig.SkipReason,
Expand Down
8 changes: 7 additions & 1 deletion internal/projectconfig/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (c *ComponentOverlay) ModifiesSpec() bool {
c.Type == ComponentOverlayUpdateSpecTag ||
c.Type == ComponentOverlayRemoveSpecTag ||
c.Type == ComponentOverlayPrependSpecLines ||
c.Type == ComponentOverlayPrependAllSpecLines ||
c.Type == ComponentOverlayAppendSpecLines ||
c.Type == ComponentOverlaySearchAndReplaceInSpec ||
c.Type == ComponentOverlayRemoveSection ||
Expand Down Expand Up @@ -159,6 +160,11 @@ const (
// ComponentOverlayPrependSpecLines is an overlay that prepends lines to a section in a spec; fails if the section
// doesn't exist.
ComponentOverlayPrependSpecLines ComponentOverlayType = "spec-prepend-lines"
// ComponentOverlayPrependAllSpecLines is an overlay that prepends lines to every section
// matching the given name and package in a spec. This is useful when a spec contains multiple
// sections with the same name (e.g., two %check sections gated by different conditionals)
// and all of them need the same modification. Fails if no matching section exists.
ComponentOverlayPrependAllSpecLines ComponentOverlayType = "spec-prepend-all-lines"
// ComponentOverlayAppendSpecLines is an overlay that appends lines to a section in a spec; fails if the section
// doesn't exist.
ComponentOverlayAppendSpecLines ComponentOverlayType = "spec-append-lines"
Expand Down Expand Up @@ -253,7 +259,7 @@ func (c *ComponentOverlay) Validate() error {
if c.Tag == "" {
return missingField("tag")
}
case ComponentOverlayPrependSpecLines, ComponentOverlayAppendSpecLines:
case ComponentOverlayPrependSpecLines, ComponentOverlayPrependAllSpecLines, ComponentOverlayAppendSpecLines:
if len(c.Lines) == 0 {
return missingField("lines")
}
Expand Down
Loading
Loading