Skip to content

Commit 30789fa

Browse files
authored
feat: allow specifying buildenv retention policy when building components (#328)
Adds new command-line argument to `azldev component build` to control whether (and when) build environment(s) created during the build operation are retained.
1 parent 81ed885 commit 30789fa

4 files changed

Lines changed: 170 additions & 9 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# azldev-preview - AZL Dev Tool Preview
22

33
Follow the existing code style and conventions for similar code. Refer to all of the documents listed below:
4-
- [CONTRIBUTING.md](./CONTRIBUTING.md) for general human and AI agent guidelines.
4+
- [CONTRIBUTING.md](../CONTRIBUTING.md) for general human and AI agent guidelines.
55
- `.github/instructions/*.instructions.md` instructions files for language-specific AI agent instructions.
66

77
**CRITICAL**: If these instructions are outdated or misleading, FIX THEM IMMEDIATELY! This document (`.github/copilot-instructions.md`) is to help coding agents (like you) to work efficiently and effectively. If you are lead astray or get confused, it is likely it will happen again to others. Help them out by improving this document (keep it SHORT AND CONCISE).

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

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import (
1313
"github.com/microsoft/azldev/internal/app/azldev/core/components"
1414
"github.com/microsoft/azldev/internal/app/azldev/core/sources"
1515
"github.com/microsoft/azldev/internal/app/azldev/core/workdir"
16+
"github.com/microsoft/azldev/internal/buildenv"
1617
"github.com/microsoft/azldev/internal/orchestration/artifacts"
1718
"github.com/microsoft/azldev/internal/orchestration/orchestrator"
1819
"github.com/microsoft/azldev/internal/projectconfig"
1920
"github.com/microsoft/azldev/internal/providers/sourceproviders"
21+
"github.com/microsoft/azldev/internal/utils/defers"
2022
"github.com/samber/lo"
2123
"github.com/spf13/cobra"
2224
)
@@ -27,6 +29,7 @@ type ComponentBuildOptions struct {
2729
ContinueOnError bool
2830
RunChecks bool
2931
SourcePackageOnly bool
32+
BuildEnvPolicy BuildEnvPreservePolicy
3033
}
3134

3235
// ComponentBuildResults summarizes the results of building a single component.
@@ -46,15 +49,18 @@ func buildOnAppInit(_ *azldev.App, parent *cobra.Command) {
4649
}
4750

4851
func NewBuildCmd() *cobra.Command {
49-
var options ComponentBuildOptions
52+
// Fill out options defaults.
53+
options := &ComponentBuildOptions{
54+
BuildEnvPolicy: BuildEnvPreserveOnFailure,
55+
}
5056

5157
cmd := &cobra.Command{
5258
Use: "build",
5359
Short: "Build packages for components",
5460
RunE: azldev.RunFuncWithExtraArgs(func(env *azldev.Env, args []string) (interface{}, error) {
5561
options.ComponentFilter.ComponentNamePatterns = append(options.ComponentFilter.ComponentNamePatterns, args...)
5662

57-
return SelectAndBuildComponents(env, &options)
63+
return SelectAndBuildComponents(env, options)
5864
}),
5965
ValidArgsFunction: components.GenerateComponentNameCompletions,
6066
}
@@ -64,6 +70,12 @@ func NewBuildCmd() *cobra.Command {
6470
"Continue building when some components fail")
6571
cmd.Flags().BoolVarP(&options.RunChecks, "check", "c", false, "Run package %check tests")
6672
cmd.Flags().BoolVar(&options.SourcePackageOnly, "srpm-only", false, "Build SRPM (source RPM) *only*")
73+
cmd.Flags().Var(&options.BuildEnvPolicy, "preserve-buildenv",
74+
fmt.Sprintf("Preserve build environment {%s, %s, %s}",
75+
BuildEnvPreserveOnFailure,
76+
BuildEnvPreserveAlways,
77+
BuildEnvPreserveNever,
78+
))
6779

6880
return cmd
6981
}
@@ -136,11 +148,8 @@ func BuildComponent(
136148
component components.Component,
137149
workDirFactory *workdir.Factory,
138150
options *ComponentBuildOptions,
139-
) (ComponentBuildResults, error) {
140-
var (
141-
rpmContentsProvider sourceproviders.RPMContentsProvider
142-
err error
143-
)
151+
) (results ComponentBuildResults, err error) {
152+
var rpmContentsProvider sourceproviders.RPMContentsProvider
144153

145154
// If the component has external sources, then we build an RPM contents provider;
146155
// otherwise, we pass nil to the preparer.
@@ -151,12 +160,23 @@ func BuildComponent(
151160
}
152161
}
153162

154-
buildEnv, err := workdir.MkComponentBuildEnvironment(env, workDirFactory, component.GetConfig(), "build")
163+
var buildEnv buildenv.RPMAwareBuildEnv
164+
165+
buildEnv, err = workdir.MkComponentBuildEnvironment(env, workDirFactory, component.GetConfig(), "build")
155166
if err != nil {
156167
return ComponentBuildResults{},
157168
fmt.Errorf("failed to create build environment for component %q: %w", component.GetName(), err)
158169
}
159170

171+
// Clean up the build environment before we return (unless we were asked not to do so).
172+
defer defers.HandleDeferError(func() error {
173+
if !options.BuildEnvPolicy.ShouldPreserve(err == nil) {
174+
return buildEnv.Destroy(env)
175+
}
176+
177+
return nil
178+
}, &err)
179+
160180
sourcePreparer := sources.NewPreparer(env, env, env.FS(), rpmContentsProvider)
161181
builder := componentbuilder.New(env, env.FS(), env, sourcePreparer, buildEnv, workDirFactory)
162182

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package component
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/spf13/pflag"
10+
)
11+
12+
// BuildEnvPreservePolicy defines policy for when to preserve auto-created build environments.
13+
type BuildEnvPreservePolicy string
14+
15+
const (
16+
// BuildEnvPreserveOnFailure indicates build environments should only be preserved for failed builds.
17+
BuildEnvPreserveOnFailure BuildEnvPreservePolicy = "on-failure"
18+
// BuildEnvPreserveAlways indicates all build environments should be preserved.
19+
BuildEnvPreserveAlways BuildEnvPreservePolicy = "always"
20+
// BuildEnvPreserveNever indicates build environments should *never* be preserved (i.e., always destroyed).
21+
BuildEnvPreserveNever BuildEnvPreservePolicy = "never"
22+
)
23+
24+
// Assert that BuildEnvPreservePolicy implements the [pflag.Value] interface.
25+
var _ pflag.Value = (*BuildEnvPreservePolicy)(nil)
26+
27+
func (f *BuildEnvPreservePolicy) String() string {
28+
return string(*f)
29+
}
30+
31+
// Set parses the format from a string; used by command-line parser.
32+
func (f *BuildEnvPreservePolicy) Set(value string) error {
33+
switch value {
34+
case string(BuildEnvPreserveOnFailure):
35+
*f = BuildEnvPreserveOnFailure
36+
case string(BuildEnvPreserveAlways):
37+
*f = BuildEnvPreserveAlways
38+
case string(BuildEnvPreserveNever):
39+
*f = BuildEnvPreserveNever
40+
case "":
41+
*f = BuildEnvPreserveOnFailure
42+
default:
43+
// Default to "on failure" but still return an error.
44+
*f = BuildEnvPreserveOnFailure
45+
46+
return fmt.Errorf("unsupported build environment preserve policy: %s", value)
47+
}
48+
49+
return nil
50+
}
51+
52+
// Type returns a descriptive string used in command-line help.
53+
func (f *BuildEnvPreservePolicy) Type() string {
54+
return "policy"
55+
}
56+
57+
// ShouldPreserve is a helper to decide whether to preserve a build environment, based on the policy
58+
// and the actual results of the build.
59+
func (f *BuildEnvPreservePolicy) ShouldPreserve(buildSucceeded bool) bool {
60+
switch *f {
61+
case BuildEnvPreserveOnFailure:
62+
return !buildSucceeded
63+
case BuildEnvPreserveAlways:
64+
return true
65+
case BuildEnvPreserveNever:
66+
return false
67+
default:
68+
return false
69+
}
70+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package component_test
5+
6+
import (
7+
"testing"
8+
9+
"github.com/microsoft/azldev/internal/app/azldev/cmds/component"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestStringification(t *testing.T) {
15+
policy := component.BuildEnvPreserveAlways
16+
assert.Equal(t, "always", policy.String())
17+
18+
policy = component.BuildEnvPreserveNever
19+
assert.Equal(t, "never", policy.String())
20+
21+
policy = component.BuildEnvPreserveOnFailure
22+
assert.Equal(t, "on-failure", policy.String())
23+
}
24+
25+
func TestSet(t *testing.T) {
26+
var policy component.BuildEnvPreservePolicy
27+
28+
err := policy.Set("always")
29+
require.NoError(t, err)
30+
assert.Equal(t, component.BuildEnvPreserveAlways, policy)
31+
32+
err = policy.Set("never")
33+
require.NoError(t, err)
34+
assert.Equal(t, component.BuildEnvPreserveNever, policy)
35+
36+
err = policy.Set("on-failure")
37+
require.NoError(t, err)
38+
assert.Equal(t, component.BuildEnvPreserveOnFailure, policy)
39+
40+
err = policy.Set("")
41+
require.NoError(t, err)
42+
assert.Equal(t, component.BuildEnvPreserveOnFailure, policy)
43+
44+
err = policy.Set("unsupported-value")
45+
require.Error(t, err)
46+
assert.Equal(t, component.BuildEnvPreserveOnFailure, policy)
47+
}
48+
49+
func TestType(t *testing.T) {
50+
var policy component.BuildEnvPreservePolicy
51+
52+
assert.Equal(t, "policy", policy.Type())
53+
}
54+
55+
func TestShouldPreserve(t *testing.T) {
56+
policy := component.BuildEnvPreserveAlways
57+
assert.True(t, policy.ShouldPreserve(true))
58+
assert.True(t, policy.ShouldPreserve(false))
59+
60+
policy = component.BuildEnvPreserveNever
61+
assert.False(t, policy.ShouldPreserve(true))
62+
assert.False(t, policy.ShouldPreserve(false))
63+
64+
policy = component.BuildEnvPreserveOnFailure
65+
assert.False(t, policy.ShouldPreserve(true))
66+
assert.True(t, policy.ShouldPreserve(false))
67+
68+
policy = "unsupported-value"
69+
assert.False(t, policy.ShouldPreserve(true))
70+
assert.False(t, policy.ShouldPreserve(false))
71+
}

0 commit comments

Comments
 (0)