Skip to content

Commit d39d868

Browse files
authored
feat: generate macros file during source prep (#382)
When preparing sources for a component COMP, `azldev` will now also write out `COMP.azl.macros` with any macros computed from the component's TOML-derived (and resolved) build configuration.
1 parent eb85640 commit d39d868

5 files changed

Lines changed: 298 additions & 8 deletions

File tree

internal/app/azldev/cmds/component/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func BuildComponent(
171171
return nil
172172
}, &err)
173173

174-
sourcePreparer, err := sources.NewPreparer(sourceManager)
174+
sourcePreparer, err := sources.NewPreparer(sourceManager, env.FS())
175175
if err != nil {
176176
return ComponentBuildResults{},
177177
fmt.Errorf("failed to create source preparer for component %q:\n%w", component.GetName(), err)

internal/app/azldev/cmds/component/preparesources.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) er
8484
return fmt.Errorf("failed to create source manager:\n%w", err)
8585
}
8686

87-
preparer, err := sources.NewPreparer(sourceManager)
87+
preparer, err := sources.NewPreparer(sourceManager, env.FS())
8888
if err != nil {
8989
return fmt.Errorf("failed to create source preparer:\n%w", err)
9090
}

internal/app/azldev/core/componentbuilder/componentbuilder_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func setupBuilder(t *testing.T) *componentBuilderTestParams {
4949

5050
sourceManager := sourceproviders_test.NewNoOpMockSourceManager(ctrl)
5151

52-
preparer, err := sources.NewPreparer(sourceManager)
52+
preparer, err := sources.NewPreparer(sourceManager, testEnv.Env.FS())
5353

5454
require.NoError(t, err)
5555

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
3045
type sourcePreparerImpl struct {
3146
sourceManager sourceproviders.SourceManager
47+
fs opctx.FS
3248
}
3349

3450
// NewPreparer creates a new [SourcePreparer] instance. All arguments are required.
3551
func 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

Comments
 (0)