Skip to content

Commit fe54ec0

Browse files
authored
feat: prevent creating macro file if it would be empty (#473)
1 parent 2d22217 commit fe54ec0

File tree

2 files changed

+58
-33
lines changed

2 files changed

+58
-33
lines changed

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

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,23 @@ func (p *sourcePreparerImpl) PrepareSources(
102102

103103
if applyOverlays {
104104
// Emit computed macros to a macros file in the output directory.
105+
// If the build configuration produces no macros, no file is written and
106+
// macrosFileName will be empty, signaling postProcessSources to skip
107+
// injecting the macro load directive and Source9999 tag.
108+
var macrosFileName string
109+
105110
macrosFilePath, err := p.writeMacrosFile(component, outputDir)
106111
if err != nil {
107112
return fmt.Errorf("failed to write macros file for component %#q:\n%w",
108113
component.GetName(), err)
109114
}
110115

116+
if macrosFilePath != "" {
117+
macrosFileName = filepath.Base(macrosFilePath)
118+
}
119+
111120
// Apply any postprocessing to the sources in-place, in the output directory.
112-
err = p.postProcessSources(component, outputDir, filepath.Base(macrosFilePath))
121+
err = p.postProcessSources(component, outputDir, macrosFileName)
113122
if err != nil {
114123
return fmt.Errorf("failed to post-process sources for component %q:\n%w", component.GetName(), err)
115124
}
@@ -120,10 +129,13 @@ func (p *sourcePreparerImpl) PrepareSources(
120129

121130
// writeMacrosFile writes a macros file containing the resolved macros for a component.
122131
// This includes with/without flags converted to macro format, and any explicit defines.
123-
// The file is always created, even if empty (aside from the header comment). Returns
124-
// the path to the written macros file, which is guaranteed to be within the given outputDir.
132+
// If the build configuration produces no macros, no file is written and an empty path is
133+
// returned. Otherwise, the path to the written macros file is returned.
125134
func (p *sourcePreparerImpl) writeMacrosFile(component components.Component, outputDir string) (string, error) {
126135
contents := GenerateMacrosFileContents(component.GetConfig().Build)
136+
if contents == "" {
137+
return "", nil
138+
}
127139

128140
macrosFilePath := filepath.Join(outputDir, component.GetName()+MacrosFileExtension)
129141

@@ -156,11 +168,10 @@ func (p *sourcePreparerImpl) writeMacrosFile(component components.Component, out
156168
//
157169
// Note: RPM macro values can contain spaces without special escaping; everything
158170
// after the macro name (and separating whitespace) is treated as the macro body.
171+
//
172+
// If no macros remain after processing (empty config, or all macros removed via
173+
// undefines), an empty string is returned to signal that no macros file is needed.
159174
func GenerateMacrosFileContents(buildConfig projectconfig.ComponentBuildConfig) string {
160-
lines := []string{
161-
MacrosFileHeader,
162-
}
163-
164175
// Build a unified map of all macros. Later definitions override earlier ones.
165176
// Processing order: with flags -> without flags -> explicit defines.
166177
macros := make(map[string]string)
@@ -186,6 +197,14 @@ func GenerateMacrosFileContents(buildConfig projectconfig.ComponentBuildConfig)
186197
delete(macros, undef)
187198
}
188199

200+
if len(macros) == 0 {
201+
return ""
202+
}
203+
204+
lines := []string{
205+
MacrosFileHeader,
206+
}
207+
189208
// Sort macro names for deterministic output.
190209
macroNames := lo.Keys(macros)
191210
slices.Sort(macroNames)
@@ -215,23 +234,25 @@ func (p *sourcePreparerImpl) postProcessSources(
215234
return fmt.Errorf("failed to get absolute path for %#q:\n%w", specPath, err)
216235
}
217236

218-
// Compute any synthetic overlays required to load the macros file.
219-
macroOverlays, err := synthesizeMacroLoadOverlays(macrosFileName)
220-
if err != nil {
221-
return fmt.Errorf("failed to compute macros load overlays:\n%w", err)
237+
// Compute any synthetic overlays required to load the macros file, if one was written.
238+
if macrosFileName != "" {
239+
macroOverlays, macroErr := synthesizeMacroLoadOverlays(macrosFileName)
240+
if macroErr != nil {
241+
return fmt.Errorf("failed to compute macros load overlays:\n%w", macroErr)
242+
}
243+
244+
// Apply those overlays *first*, in sequence.
245+
for _, overlay := range macroOverlays {
246+
err = ApplyOverlayToSources(p.dryRunnable, p.fs, overlay, sourcesDirPath, absSpecPath)
247+
if err != nil {
248+
return fmt.Errorf("failed to apply system overlay to sources for component %#q:\n%w", component.GetName(), err)
249+
}
250+
}
222251
}
223252

224253
// Get the file header overlay.
225254
headerOverlay := generateFileHeaderOverlay()
226255

227-
// Apply those overlays *first*, in sequence.
228-
for _, overlay := range macroOverlays {
229-
err = ApplyOverlayToSources(p.dryRunnable, p.fs, overlay, sourcesDirPath, absSpecPath)
230-
if err != nil {
231-
return fmt.Errorf("failed to apply system overlay to sources for component %#q:\n%w", component.GetName(), err)
232-
}
233-
}
234-
235256
// Apply all overlays in sequence.
236257
for _, overlay := range component.GetConfig().Overlays {
237258
err = ApplyOverlayToSources(p.dryRunnable, p.fs, overlay, sourcesDirPath, absSpecPath)

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,16 @@ func TestPrepareSources_Success(t *testing.T) {
7171
macrosFileName := "test-component" + sources.MacrosFileExtension
7272
macrosFilePath := filepath.Join(testOutputDir, macrosFileName)
7373

74-
// Verify macros file was created.
74+
// Verify macros file was NOT created (empty config has no macros).
7575
exists, err := fileutils.Exists(ctx.FS(), macrosFilePath)
7676
require.NoError(t, err)
77-
assert.True(t, exists, "macros file should be created")
77+
assert.False(t, exists, "macros file should not be created when there are no macros")
7878

79-
// Verify spec loads macros.
79+
// Verify spec does NOT contain macro load or Source9999.
8080
specContents, err := fileutils.ReadFile(ctx.FS(), outputSpecPath)
8181
require.NoError(t, err)
82-
assert.Contains(t, string(specContents), "%{load:%{_sourcedir}/"+macrosFileName+"}")
82+
assert.NotContains(t, string(specContents), "%{load:%{_sourcedir}/"+macrosFileName+"}")
83+
assert.NotContains(t, string(specContents), "Source9999")
8384
}
8485

8586
func TestPrepareSources_SourceManagerError(t *testing.T) {
@@ -138,18 +139,24 @@ func TestPrepareSources_WritesMacrosFile(t *testing.T) {
138139
contents, err := fileutils.ReadFile(ctx.FS(), macrosFilePath)
139140
require.NoError(t, err)
140141
assert.Contains(t, string(contents), "%_with_feature 1")
142+
143+
// Verify spec has macro load directive and Source9999 tag.
144+
specPath := filepath.Join(testOutputDir, "my-package.spec")
145+
specContents, err := fileutils.ReadFile(ctx.FS(), specPath)
146+
require.NoError(t, err)
147+
148+
specStr := string(specContents)
149+
assert.Contains(t, specStr, "%{load:%{_sourcedir}/my-package"+sources.MacrosFileExtension+"}")
150+
assert.Contains(t, specStr, "Source9999")
141151
}
142152

143153
// Tests for GenerateMacrosFileContents - these test content generation in isolation.
144154

145155
func TestGenerateMacrosFileContents_EmptyConfig(t *testing.T) {
146156
contents := sources.GenerateMacrosFileContents(projectconfig.ComponentBuildConfig{})
147157

148-
// Should always have header comment.
149-
assert.Contains(t, contents, sources.MacrosFileHeader)
150-
151-
// Should end with newline.
152-
assert.Equal(t, '\n', rune(contents[len(contents)-1]))
158+
// Empty config should produce no macros file content.
159+
assert.Empty(t, contents)
153160
}
154161

155162
func TestGenerateMacrosFileContents_WithFlags(t *testing.T) {
@@ -299,7 +306,7 @@ func TestGenerateMacrosFileContents_UndefinesNonexistentMacroIsNoop(t *testing.T
299306
}
300307

301308
func TestGenerateMacrosFileContents_UndefinesAllMacros(t *testing.T) {
302-
// Undefining all macros should result in only the header.
309+
// Undefining all macros should produce no macros file content.
303310
contents := sources.GenerateMacrosFileContents(projectconfig.ComponentBuildConfig{
304311
With: []string{"tests"},
305312
Without: []string{"debug"},
@@ -309,10 +316,7 @@ func TestGenerateMacrosFileContents_UndefinesAllMacros(t *testing.T) {
309316
Undefines: []string{"_with_tests", "_without_debug", "dist"},
310317
})
311318

312-
assert.Contains(t, contents, sources.MacrosFileHeader)
313-
assert.NotContains(t, contents, "%_with_tests")
314-
assert.NotContains(t, contents, "%_without_debug")
315-
assert.NotContains(t, contents, "%dist")
319+
assert.Empty(t, contents)
316320
}
317321

318322
func TestGenerateMacrosFileContents_FullConfig(t *testing.T) {

0 commit comments

Comments
 (0)