Skip to content

Commit ba6c6ec

Browse files
authored
feat(overlays): add patch-add and patch-remove (#440)
1 parent 106ece2 commit ba6c6ec

10 files changed

Lines changed: 1146 additions & 71 deletions

File tree

docs/reference/overlays.md

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ These overlays modify `.spec` files using the structured spec parser, allowing p
2121
| `spec-prepend-lines` | Prepends lines to the start of a section; **fails if section doesn't exist** | `lines` |
2222
| `spec-append-lines` | Appends lines to the end of a section; **fails if section doesn't exist** | `lines` |
2323
| `spec-search-replace` | Regex-based search and replace on spec content | `regex` |
24+
| `patch-add` | Adds a patch file and registers it in the spec (PatchN tag or %patchlist) | `source` |
25+
| `patch-remove` | Removes patch files and their spec references matching a glob pattern | `file` |
2426

2527
### File Overlays
2628

@@ -55,8 +57,8 @@ successfully makes a replacement to at least one matching file.
5557
| Regex | `regex` | Regular expression pattern to match | `spec-search-replace`, `file-search-replace` |
5658
| Replacement | `replacement` | Literal replacement text; capture group references like `$1` are **not** expanded. Omit or leave empty to delete matched text. | `spec-search-replace`, `file-search-replace`, `file-rename` |
5759
| Lines | `lines` | Array of text lines to insert | `spec-prepend-lines`, `spec-append-lines`, `file-prepend-lines` |
58-
| File | `file` | The name of the non-spec file to modify or add | `file-prepend-lines`, `file-search-replace`, `file-add`, `file-remove`, `file-rename` |
59-
| Source | `source` | Path to source file for `file-add`; relative paths are relative to the config file | `file-add` |
60+
| File | `file` | The name of the non-spec file to modify or add | `file-prepend-lines`, `file-search-replace`, `file-add`, `file-remove`, `file-rename`, `patch-add` (optional), `patch-remove` |
61+
| Source | `source` | Path to source file for `file-add` and `patch-add`; relative paths are relative to the config file | `file-add`, `patch-add` |
6062

6163
> **Note:** For `file-rename`, the `replacement` field is a **filename only** (not a path). The file is renamed within its current directory.
6264
@@ -198,6 +200,61 @@ file = "oldname.conf"
198200
replacement = "newname.conf"
199201
```
200202

203+
### Adding a Patch
204+
205+
The `patch-add` overlay copies a patch file into the component's sources and registers it
206+
in the spec. If the spec has a `%patchlist` section, the filename is appended there; otherwise,
207+
a `PatchN` tag is added with the next available number.
208+
209+
```toml
210+
[[components.mypackage.overlays]]
211+
type = "patch-add"
212+
source = "patches/fix-build-flags.patch"
213+
description = "Fix compiler flags for our toolchain"
214+
```
215+
216+
By default, the destination filename is the basename of `source`. Use `file` to override:
217+
218+
```toml
219+
[[components.mypackage.overlays]]
220+
type = "patch-add"
221+
source = "patches/0001-upstream-fix.patch"
222+
file = "fix-upstream-bug.patch"
223+
description = "Rename upstream patch for clarity"
224+
```
225+
226+
### Removing a Patch
227+
228+
The `patch-remove` overlay removes patch references from the spec (`PatchN` tags and/or
229+
`%patchlist` entries) and deletes the matching patch files from the component's sources.
230+
The `file` field is a glob pattern matched against patch filenames.
231+
232+
```toml
233+
[[components.mypackage.overlays]]
234+
type = "patch-remove"
235+
file = "fix-old-bug.patch"
236+
description = "Remove patch that is no longer needed"
237+
```
238+
239+
Glob patterns can remove multiple patches at once:
240+
241+
```toml
242+
[[components.mypackage.overlays]]
243+
type = "patch-remove"
244+
file = "CVE-*.patch"
245+
description = "Remove CVE patches that are now upstream"
246+
```
247+
248+
> **Note:** `patch-remove` does not remove `%patchN` application lines from `%prep`.
249+
> If the spec uses individual `%patch` directives rather than `%autosetup`, you may need
250+
> a `spec-search-replace` overlay to remove those lines as well. Similarly, `%autopatch`
251+
> has `-m` and `-M` options referencing specific patch numbers and will need targeted
252+
> adjustments.
253+
254+
> **Limitation:** `patch-add` auto-assigns PatchN numbers by scanning existing numeric
255+
> `PatchN` tags. Macro-based tag numbering (e.g., `Patch%{n}`) is not expanded and may
256+
> conflict with auto-assigned numbers.
257+
201258
## Validation
202259

203260
Overlay configurations are validated when the config file is loaded. Validation checks:

internal/app/azldev/core/sources/overlays.go

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,17 @@ func ApplyOverlayToSources(
4545
overlay projectconfig.ComponentOverlay,
4646
sourcesDirPath, specPath string,
4747
) error {
48-
if overlay.IsSpecOverlay() {
49-
return ApplySpecOverlayToFileInPlace(fs, overlay, specPath)
48+
// Apply the spec component, if any.
49+
if overlay.ModifiesSpec() {
50+
err := ApplySpecOverlayToFileInPlace(fs, overlay, specPath)
51+
if err != nil {
52+
return err
53+
}
54+
}
55+
56+
// Apply the non-spec file component, if any.
57+
if !overlay.ModifiesNonSpecFiles() {
58+
return nil
5059
}
5160

5261
// Create a destination filesystem confined to the sources directory.
@@ -107,7 +116,7 @@ func ApplySpecOverlayToFileInPlace(fs opctx.FS, overlay projectconfig.ComponentO
107116
// ApplySpecOverlay applies a spec-based overlay to an opened spec. An error is returned if a non-spec
108117
// overlay is provided.
109118
//
110-
//nolint:cyclop // This function's cyclomatic complexity is inflated by the big switch.
119+
//nolint:cyclop,funlen // This function's complexity is inflated by the big switch over overlay types.
111120
func ApplySpecOverlay(overlay projectconfig.ComponentOverlay, openedSpec *spec.Spec) error {
112121
//nolint:exhaustive // We intentionally ignore non-spec overlay types.
113122
switch overlay.Type {
@@ -148,6 +157,21 @@ func ApplySpecOverlay(overlay projectconfig.ComponentOverlay, openedSpec *spec.S
148157
if err != nil {
149158
return fmt.Errorf("failed to search and replace in spec:\n%w", err)
150159
}
160+
case projectconfig.ComponentOverlayAddPatch:
161+
destFilename := overlay.Filename
162+
if destFilename == "" {
163+
destFilename = filepath.Base(overlay.Source)
164+
}
165+
166+
err := openedSpec.AddPatchEntry(overlay.PackageName, destFilename)
167+
if err != nil {
168+
return fmt.Errorf("failed to add patch entry to spec:\n%w", err)
169+
}
170+
case projectconfig.ComponentOverlayRemovePatch:
171+
err := openedSpec.RemovePatchEntry(overlay.PackageName, overlay.Filename)
172+
if err != nil {
173+
return fmt.Errorf("failed to remove patch entry from spec:\n%w", err)
174+
}
151175
default:
152176
return fmt.Errorf("invalid overlay type found: %s", overlay.Type)
153177
}
@@ -161,20 +185,26 @@ func ApplySpecOverlay(overlay projectconfig.ComponentOverlay, openedSpec *spec.S
161185
func applyNonSpecOverlay(
162186
dryRunnable opctx.DryRunnable, sourceFS, destFS opctx.FS, overlay projectconfig.ComponentOverlay,
163187
) error {
164-
// See if it's a type of overlay for which `filename` is a literal filename and
165-
// never a pattern.
166-
if overlay.Type == projectconfig.ComponentOverlayAddFile {
167-
// As a precaution, make sure we're not being asked to add a .spec file. To update a spec,
168-
// a spec-specific overlay should be used.
188+
// Types whose `filename` is a literal filename (not a glob pattern).
189+
//nolint:exhaustive // Only literal-filename types are listed; all others use glob matching.
190+
switch overlay.Type {
191+
case projectconfig.ComponentOverlayAddFile:
169192
if isSpecFile(overlay.Filename) {
170193
return fmt.Errorf("non-spec overlay not supported on .spec file: %#q", overlay.Filename)
171194
}
172195

173196
return applyNonSpecOverlayToFile(dryRunnable, sourceFS, destFS, overlay, overlay.Filename)
174-
}
197+
case projectconfig.ComponentOverlayAddPatch:
198+
filename := overlay.Filename
199+
if filename == "" {
200+
filename = filepath.Base(overlay.Source)
201+
}
175202

176-
// For all other cases, `filename` is treated as a glob pattern.
177-
return applyNonSpecOverlayToMatchingFiles(dryRunnable, sourceFS, destFS, overlay)
203+
return applyNonSpecOverlayToFile(dryRunnable, sourceFS, destFS, overlay, filename)
204+
default:
205+
// For all other cases, `filename` is treated as a glob pattern.
206+
return applyNonSpecOverlayToMatchingFiles(dryRunnable, sourceFS, destFS, overlay)
207+
}
178208
}
179209

180210
func applyNonSpecOverlayToMatchingFiles(
@@ -227,7 +257,8 @@ func applyNonSpecOverlayToMatchingFiles(
227257
func overlayTypeSupportsMultipleFiles(overlayType projectconfig.ComponentOverlayType) bool {
228258
return overlayType == projectconfig.ComponentOverlayPrependLinesToFile ||
229259
overlayType == projectconfig.ComponentOverlaySearchAndReplaceInFile ||
230-
overlayType == projectconfig.ComponentOverlayRemoveFile
260+
overlayType == projectconfig.ComponentOverlayRemoveFile ||
261+
overlayType == projectconfig.ComponentOverlayRemovePatch
231262
}
232263

233264
// applyNonSpecOverlayToFile applies the given overlay to the specified non-spec file.
@@ -249,12 +280,12 @@ func applyNonSpecOverlayToFile(
249280
if err != nil {
250281
return fmt.Errorf("failed to search and replace in file %#q:\n%w", filePath, err)
251282
}
252-
case projectconfig.ComponentOverlayAddFile:
283+
case projectconfig.ComponentOverlayAddFile, projectconfig.ComponentOverlayAddPatch:
253284
err := addNonSpecFile(dryRunnable, sourceFS, destFS, overlay, filePath)
254285
if err != nil {
255286
return err
256287
}
257-
case projectconfig.ComponentOverlayRemoveFile:
288+
case projectconfig.ComponentOverlayRemoveFile, projectconfig.ComponentOverlayRemovePatch:
258289
err := destFS.Remove(filePath)
259290
if err != nil {
260291
return fmt.Errorf("failed to remove file %#q:\n%w", filePath, err)

0 commit comments

Comments
 (0)