@@ -7,11 +7,26 @@ import (
77 "context"
88 "errors"
99 "fmt"
10+ "path/filepath"
11+ "slices"
12+ "strings"
1013
1114 "github.com/microsoft/azldev/internal/app/azldev/core/components"
15+ "github.com/microsoft/azldev/internal/global/opctx"
16+ "github.com/microsoft/azldev/internal/projectconfig"
1217 "github.com/microsoft/azldev/internal/providers/sourceproviders"
18+ "github.com/microsoft/azldev/internal/utils/fileperms"
19+ "github.com/microsoft/azldev/internal/utils/fileutils"
20+ "github.com/samber/lo"
1321)
1422
23+ // MacrosFileExtension is the file extension used for azldev-generated macros files.
24+ const MacrosFileExtension = ".azl.macros"
25+
26+ // MacrosFileHeader is the comment header included at the top of generated macros files.
27+ const MacrosFileHeader = `# Macros file automatically generated by azldev.
28+ # Do not edit manually; changes will be overwritten.`
29+
1530// SourcePreparer is a utility for acquiring and preparing all input files required to build a component.
1631// This may include the component's spec file, any loose files that must accompany it, any payload source
1732// archives and patches required by the spec, etc. Preparation may also include any post-processing of these
@@ -29,18 +44,25 @@ type SourcePreparer interface {
2944// Standard implementation of the [SourcePreparer] interface.
3045type sourcePreparerImpl struct {
3146 sourceManager sourceproviders.SourceManager
47+ fs opctx.FS
3248}
3349
3450// NewPreparer creates a new [SourcePreparer] instance. All arguments are required.
3551func NewPreparer (
3652 sourceManager sourceproviders.SourceManager ,
53+ fs opctx.FS ,
3754) (SourcePreparer , error ) {
3855 if sourceManager == nil {
3956 return nil , errors .New ("source manager cannot be nil" )
4057 }
4158
59+ if fs == nil {
60+ return nil , errors .New ("filesystem interface cannot be nil" )
61+ }
62+
4263 return & sourcePreparerImpl {
4364 sourceManager : sourceManager ,
65+ fs : fs ,
4466 }, nil
4567}
4668
@@ -55,5 +77,78 @@ func (p *sourcePreparerImpl) PrepareSources(
5577 component .GetName (), err )
5678 }
5779
80+ // Emit computed macros to a macros file.
81+ err = p .writeMacrosFile (component , outputDir )
82+ if err != nil {
83+ return fmt .Errorf ("failed to write macros file for component %#q:\n %w" ,
84+ component .GetName (), err )
85+ }
86+
87+ return nil
88+ }
89+
90+ // writeMacrosFile writes a macros file containing the resolved macros for a component.
91+ // This includes with/without flags converted to macro format, and any explicit defines.
92+ // The file is always created, even if empty (aside from the header comment).
93+ func (p * sourcePreparerImpl ) writeMacrosFile (component components.Component , outputDir string ) error {
94+ contents := GenerateMacrosFileContents (component .GetConfig ().Build )
95+
96+ macrosFilePath := filepath .Join (outputDir , component .GetName ()+ MacrosFileExtension )
97+
98+ err := fileutils .WriteFile (p .fs , macrosFilePath , []byte (contents ), fileperms .PublicFile )
99+ if err != nil {
100+ return fmt .Errorf ("failed to write macros file %#q:\n %w" , macrosFilePath , err )
101+ }
102+
58103 return nil
59104}
105+
106+ // GenerateMacrosFileContents generates the contents of an RPM macros file from the given
107+ // build configuration. The output uses standard RPM macro file format (%name value) and
108+ // includes a header comment identifying the file as auto-generated.
109+ //
110+ // With flags are converted to %_with_FLAG 1 macros.
111+ // Without flags are converted to %_without_FLAG 1 macros.
112+ // Defines are emitted as %name value macros.
113+ //
114+ // All macros are collected into a single map and sorted alphabetically by name for
115+ // deterministic output. If the same macro is defined multiple times (e.g., via both
116+ // a with flag and an explicit define), the later definition wins. The order of processing
117+ // is: with flags, then without flags, then explicit defines.
118+ //
119+ // Note: RPM macro values can contain spaces without special escaping; everything
120+ // after the macro name (and separating whitespace) is treated as the macro body.
121+ func GenerateMacrosFileContents (buildConfig projectconfig.ComponentBuildConfig ) string {
122+ lines := []string {
123+ MacrosFileHeader ,
124+ }
125+
126+ // Build a unified map of all macros. Later definitions override earlier ones.
127+ // Processing order: with flags -> without flags -> explicit defines.
128+ macros := make (map [string ]string )
129+
130+ // Convert 'with' flags to macros: FLAG -> _with_FLAG = 1
131+ for _ , with := range buildConfig .With {
132+ macros ["_with_" + with ] = "1"
133+ }
134+
135+ // Convert 'without' flags to macros: FLAG -> _without_FLAG = 1
136+ for _ , without := range buildConfig .Without {
137+ macros ["_without_" + without ] = "1"
138+ }
139+
140+ // Add explicit macro definitions (these override any conflicting with/without flags).
141+ for name , value := range buildConfig .Defines {
142+ macros [name ] = value
143+ }
144+
145+ // Sort macro names for deterministic output.
146+ macroNames := lo .Keys (macros )
147+ slices .Sort (macroNames )
148+
149+ for _ , name := range macroNames {
150+ lines = append (lines , fmt .Sprintf ("%%%s %s" , name , macros [name ]))
151+ }
152+
153+ return strings .Join (lines , "\n " ) + "\n "
154+ }
0 commit comments