Skip to content

Commit 0ae94b8

Browse files
authored
fix: account for conditionals in Source9999 placement (#466)
1 parent 4a9061a commit 0ae94b8

2 files changed

Lines changed: 180 additions & 22 deletions

File tree

internal/rpm/spec/edit.go

Lines changed: 108 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ func tagFamily(tag string) string {
187187
return lower[:end]
188188
}
189189

190+
// conditionalDepthChange returns +1 for lines that open a conditional block (%if, %ifarch, etc.),
191+
// -1 for %endif, and 0 for everything else. Comments are ignored.
192+
func conditionalDepthChange(rawLine string) int {
193+
trimmed := strings.TrimSpace(rawLine)
194+
if strings.HasPrefix(trimmed, "#") {
195+
return 0
196+
}
197+
198+
token := strings.Fields(trimmed)
199+
if len(token) == 0 {
200+
return 0
201+
}
202+
203+
lower := strings.ToLower(token[0])
204+
if lower == "%endif" {
205+
return -1
206+
}
207+
208+
if strings.HasPrefix(lower, "%if") {
209+
return 1
210+
}
211+
212+
return 0
213+
}
214+
190215
// InsertTag inserts a tag into the spec, placing it after the last existing tag from the
191216
// same "family" (e.g., Source9999 is placed after the last Source* tag). If no tags from
192217
// the same family exist, the tag is placed after the last tag of any kind. If there are no
@@ -196,6 +221,9 @@ func tagFamily(tag string) string {
196221
// (case-insensitive). For example, "Source0", "Source1", and "Source" all belong to the
197222
// "source" family.
198223
//
224+
// If the chosen insertion point falls inside a conditional block (%if/%endif), the tag is
225+
// placed after the closing %endif instead, so it remains unconditional.
226+
//
199227
// Note: When inserting into a sub-package (non-empty packageName), the corresponding
200228
// %package section must already exist in the spec; otherwise, an [ErrSectionNotFound]
201229
// error is returned.
@@ -205,30 +233,75 @@ func (s *Spec) InsertTag(packageName string, tag string, value string) error {
205233
family := tagFamily(tag)
206234
newLine := fmt.Sprintf("%s: %s", tag, value)
207235

208-
// First pass: find the best insertion point.
209-
// Track both the last same-family tag line and the last tag of any kind.
210-
lastFamilyTagLineNum := -1
211-
lastAnyTagLineNum := -1
212-
sectionFound := false
213-
214236
sectionName := ""
215237
if packageName != "" {
216238
sectionName = "%package"
217239
}
218240

241+
result, err := s.findInsertTagPosition(sectionName, packageName, family)
242+
if err != nil {
243+
return err
244+
}
245+
246+
// Determine insertion point: prefer same-family, then any tag, then fall back to AddTag.
247+
insertAfterLine := result.lastFamilyTagLineNum
248+
if insertAfterLine < 0 {
249+
insertAfterLine = result.lastAnyTagLineNum
250+
}
251+
252+
if insertAfterLine < 0 {
253+
// No tags at all — fall back to AddTag behavior.
254+
return s.AddTag(packageName, tag, value)
255+
}
256+
257+
// If the insertion point is inside a conditional block, move it forward past the
258+
// closing %endif so the new tag doesn't become conditional.
259+
insertAfterLine = s.skipPastConditional(insertAfterLine, result.sectionEndLineNum)
260+
261+
// Insert after the found line (0-indexed, so insertAfterLine+1).
262+
s.InsertLinesAt([]string{newLine}, insertAfterLine+1)
263+
264+
return nil
265+
}
266+
267+
// insertTagScanResult holds the results of scanning a spec for a tag insertion point.
268+
type insertTagScanResult struct {
269+
lastFamilyTagLineNum int
270+
lastAnyTagLineNum int
271+
sectionEndLineNum int
272+
}
273+
274+
// findInsertTagPosition scans the spec to find the best insertion point for a tag of the
275+
// given family within the specified section/package. Returns the scan results or an error
276+
// if the target section is not found.
277+
func (s *Spec) findInsertTagPosition(
278+
sectionName, packageName, family string,
279+
) (insertTagScanResult, error) {
280+
result := insertTagScanResult{
281+
lastFamilyTagLineNum: -1,
282+
lastAnyTagLineNum: -1,
283+
sectionEndLineNum: len(s.rawLines),
284+
}
285+
286+
sectionFound := false
287+
219288
err := s.Visit(func(ctx *Context) error {
220-
// Track whether the target section exists.
221289
if ctx.Target.TargetType == SectionStartTarget {
222290
if ctx.CurrentSection.SectName == sectionName && ctx.CurrentSection.Package == packageName {
223291
sectionFound = true
224292
}
225293
}
226294

295+
if ctx.Target.TargetType == SectionEndTarget {
296+
if ctx.CurrentSection.SectName == sectionName && ctx.CurrentSection.Package == packageName {
297+
result.sectionEndLineNum = ctx.CurrentLineNum
298+
}
299+
}
300+
227301
if ctx.Target.TargetType != SectionLineTarget {
228302
return nil
229303
}
230304

231-
// Only consider tags in the target section/package.
232305
if ctx.CurrentSection.SectName != sectionName || ctx.CurrentSection.Package != packageName {
233306
return nil
234307
}
@@ -242,37 +315,50 @@ func (s *Spec) InsertTag(packageName string, tag string, value string) error {
242315
return nil
243316
}
244317

245-
lastAnyTagLineNum = ctx.CurrentLineNum
318+
result.lastAnyTagLineNum = ctx.CurrentLineNum
246319

247320
if tagFamily(tagLine.Tag) == family {
248-
lastFamilyTagLineNum = ctx.CurrentLineNum
321+
result.lastFamilyTagLineNum = ctx.CurrentLineNum
249322
}
250323

251324
return nil
252325
})
253326
if err != nil {
254-
return fmt.Errorf("failed to scan spec for tag insertion point:\n%w", err)
327+
return result, fmt.Errorf("failed to scan spec for tag insertion point:\n%w", err)
255328
}
256329

257330
if !sectionFound {
258-
return fmt.Errorf("section %#q (package=%#q) not found:\n%w", sectionName, packageName, ErrSectionNotFound)
331+
return result, fmt.Errorf("section %#q (package=%#q) not found:\n%w", sectionName, packageName, ErrSectionNotFound)
259332
}
260333

261-
// Determine insertion point: prefer same-family, then any tag, then fall back to AddTag.
262-
insertAfterLine := lastFamilyTagLineNum
263-
if insertAfterLine < 0 {
264-
insertAfterLine = lastAnyTagLineNum
334+
return result, nil
335+
}
336+
337+
// skipPastConditional checks whether lineNum falls inside a conditional block by computing
338+
// the conditional nesting depth from the start of the file up to that line. If depth > 0,
339+
// it scans forward to find the %endif that brings depth back to 0 and returns that line
340+
// number. Otherwise it returns lineNum unchanged.
341+
func (s *Spec) skipPastConditional(lineNum int, sectionEnd int) int {
342+
// Compute conditional depth at the insertion point by scanning from the start.
343+
depth := 0
344+
for i := 0; i <= lineNum && i < len(s.rawLines); i++ {
345+
depth += conditionalDepthChange(s.rawLines[i])
265346
}
266347

267-
if insertAfterLine < 0 {
268-
// No tags at all — fall back to AddTag behavior.
269-
return s.AddTag(packageName, tag, value)
348+
if depth <= 0 {
349+
return lineNum
270350
}
271351

272-
// Insert after the found line (0-indexed, so insertAfterLine+1).
273-
s.InsertLinesAt([]string{newLine}, insertAfterLine+1)
352+
// Scan forward to find the %endif that closes the conditional.
353+
for i := lineNum + 1; i < sectionEnd && i < len(s.rawLines); i++ {
354+
depth += conditionalDepthChange(s.rawLines[i])
355+
if depth <= 0 {
356+
return i
357+
}
358+
}
274359

275-
return nil
360+
// Could not find a closing %endif within the section; return the original position.
361+
return lineNum
276362
}
277363

278364
// PrependLinesToSection prepends the given lines to the start of the specified section, placing

internal/rpm/spec/edit_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,78 @@ Source9999: macros.azl.macros
702702
703703
%description
704704
Some description
705+
`,
706+
tag: "Source9999",
707+
value: "macros.azl.macros",
708+
},
709+
{
710+
name: "insert after Source inside conditional block",
711+
input: `Name: test
712+
Source0: test-1.0.tar.gz
713+
%if %{with extra}
714+
Source31: extra-aarch64.h
715+
Source32: extra-x86_64.h
716+
%endif
717+
BuildRequires: gcc
718+
`,
719+
expectedOutput: `Name: test
720+
Source0: test-1.0.tar.gz
721+
%if %{with extra}
722+
Source31: extra-aarch64.h
723+
Source32: extra-x86_64.h
724+
%endif
725+
Source9999: macros.azl.macros
726+
BuildRequires: gcc
727+
`,
728+
tag: "Source9999",
729+
value: "macros.azl.macros",
730+
},
731+
{
732+
name: "insert after Source inside nested conditional",
733+
input: `Name: test
734+
Source0: test-1.0.tar.gz
735+
%if %{with jit}
736+
%ifarch x86_64
737+
Source31: jit-x86.h
738+
%endif
739+
Source32: jit-common.h
740+
%endif
741+
BuildRequires: gcc
742+
`,
743+
expectedOutput: `Name: test
744+
Source0: test-1.0.tar.gz
745+
%if %{with jit}
746+
%ifarch x86_64
747+
Source31: jit-x86.h
748+
%endif
749+
Source32: jit-common.h
750+
%endif
751+
Source9999: macros.azl.macros
752+
BuildRequires: gcc
753+
`,
754+
tag: "Source9999",
755+
value: "macros.azl.macros",
756+
},
757+
{
758+
name: "insert with mixed conditional and unconditional sources",
759+
input: `Name: test
760+
Source0: test-1.0.tar.gz
761+
Source1: extra.tar.gz
762+
%if %{with jit}
763+
Source31: jit-aarch64.h
764+
Source32: jit-x86_64.h
765+
%endif
766+
BuildRequires: gcc
767+
`,
768+
expectedOutput: `Name: test
769+
Source0: test-1.0.tar.gz
770+
Source1: extra.tar.gz
771+
%if %{with jit}
772+
Source31: jit-aarch64.h
773+
Source32: jit-x86_64.h
774+
%endif
775+
Source9999: macros.azl.macros
776+
BuildRequires: gcc
705777
`,
706778
tag: "Source9999",
707779
value: "macros.azl.macros",

0 commit comments

Comments
 (0)