Skip to content

Commit 1419105

Browse files
authored
feat: prepare for parallel-safe mock (#327)
* Add `GetInfo()` and `Destroy()` methods to `BuildEnv` interface. * Revert generics usage in factory.go to mitigate type errors. * Update `componentbuilder` to take in an already-constructed build environment.
1 parent 55ea248 commit 1419105

12 files changed

Lines changed: 238 additions & 81 deletions

File tree

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ import (
99
"fmt"
1010

1111
"github.com/microsoft/azldev/internal/app/azldev"
12-
"github.com/microsoft/azldev/internal/app/azldev/core/buildenvfactory"
1312
"github.com/microsoft/azldev/internal/app/azldev/core/componentbuilder"
1413
"github.com/microsoft/azldev/internal/app/azldev/core/components"
1514
"github.com/microsoft/azldev/internal/app/azldev/core/sources"
1615
"github.com/microsoft/azldev/internal/app/azldev/core/workdir"
17-
"github.com/microsoft/azldev/internal/buildenv"
1816
"github.com/microsoft/azldev/internal/orchestration/artifacts"
1917
"github.com/microsoft/azldev/internal/orchestration/orchestrator"
2018
"github.com/microsoft/azldev/internal/projectconfig"
@@ -91,35 +89,32 @@ func SelectAndBuildComponents(env *azldev.Env, options *ComponentBuildOptions,
9189

9290
func buildComponents(
9391
env *azldev.Env, components *components.ComponentSet, options *ComponentBuildOptions,
94-
) (results []ComponentBuildResults, err error) {
92+
) ([]ComponentBuildResults, error) {
9593
if env.WorkDir() == "" {
96-
return results, errors.New("can't build packages without valid work dir")
94+
return nil, errors.New("can't build packages without valid work dir")
9795
}
9896

9997
if env.OutputDir() == "" {
100-
return results, errors.New("can't build packages without valid output dir")
98+
return nil, errors.New("can't build packages without valid output dir")
10199
}
102100

103101
workDirFactory, err := workdir.NewFactory(env.FS(), env.WorkDir(), env.ConstructionTime())
104102
if err != nil {
105-
return results, fmt.Errorf("failed to create work dir factory: %w", err)
106-
}
107-
108-
buildEnvFactory, err := buildenvfactory.NewMockRootFactoryForEnv(env)
109-
if err != nil {
110-
return results, fmt.Errorf("failed to create mock root factory: %w", err)
103+
return nil, fmt.Errorf("failed to create work dir factory: %w", err)
111104
}
112105

113106
orchestrator, err := orchestrator.NewOrchestrator(env.FS(),
114107
orchestrator.WithBuildDir(env.WorkDir()),
115108
orchestrator.WithOutputDir(env.OutputDir()),
116109
)
117110
if err != nil {
118-
return results, fmt.Errorf("failed to create orchestrator: %w", err)
111+
return nil, fmt.Errorf("failed to create orchestrator: %w", err)
119112
}
120113

114+
results := make([]ComponentBuildResults, 0, components.Len())
115+
121116
for _, component := range components.Components() {
122-
componentResults, buildErr := BuildComponent(env, orchestrator, component, workDirFactory, buildEnvFactory, options)
117+
componentResults, buildErr := BuildComponent(env, orchestrator, component, workDirFactory, options)
123118
if buildErr != nil {
124119
buildErr = fmt.Errorf("failed to build %q: %w", component.GetName(), buildErr)
125120
}
@@ -140,7 +135,6 @@ func BuildComponent(
140135
orchestrator *orchestrator.Orchestrator,
141136
component components.Component,
142137
workDirFactory *workdir.Factory,
143-
buildEnvFactory *buildenv.MockRootFactory,
144138
options *ComponentBuildOptions,
145139
) (ComponentBuildResults, error) {
146140
var (
@@ -157,8 +151,14 @@ func BuildComponent(
157151
}
158152
}
159153

154+
buildEnv, err := workdir.MkComponentBuildEnvironment(env, workDirFactory, component.GetConfig(), "build")
155+
if err != nil {
156+
return ComponentBuildResults{},
157+
fmt.Errorf("failed to create build environment for component %q: %w", component.GetName(), err)
158+
}
159+
160160
sourcePreparer := sources.NewPreparer(env, env, env.FS(), rpmContentsProvider)
161-
builder := componentbuilder.New(env, env.FS(), env, sourcePreparer, buildEnvFactory, workDirFactory)
161+
builder := componentbuilder.New(env, env.FS(), env, sourcePreparer, buildEnv, workDirFactory)
162162

163163
return buildComponentUsingBuilder(env, orchestrator, component, builder,
164164
options.SourcePackageOnly, options.RunChecks,

internal/app/azldev/core/buildenvfactory/factory.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@ import (
1212
"github.com/microsoft/azldev/internal/projectconfig"
1313
)
1414

15+
// NewFactoryForEnv creates a new factory that can produce a build environment of the given type.
16+
func NewFactoryForEnv(env *azldev.Env, buildEnvType buildenv.EnvType) (buildenv.Factory, error) {
17+
switch buildEnvType {
18+
case buildenv.EnvTypeMock:
19+
return NewMockRootFactoryForEnv(env)
20+
default:
21+
return nil, fmt.Errorf("unknown build environment type '%s'", buildEnvType)
22+
}
23+
}
24+
1525
// NewRPMAwareFactoryForEnv creates a new RPM-aware factory that can produce build environments of
1626
// the given type.
17-
func NewRPMAwareFactoryForEnv(env *azldev.Env, buildEnvType buildenv.EnvType) (
18-
factory buildenv.RPMAwareFactory, err error,
19-
) {
27+
func NewRPMAwareFactoryForEnv(env *azldev.Env, buildEnvType buildenv.EnvType) (buildenv.RPMAwareFactory, error) {
2028
switch buildEnvType {
2129
case buildenv.EnvTypeMock:
2230
return NewMockRootFactoryForEnv(env)
@@ -26,11 +34,11 @@ func NewRPMAwareFactoryForEnv(env *azldev.Env, buildEnvType buildenv.EnvType) (
2634
}
2735

2836
// NewMockRootFactory creates a new instance of [mockRootFactory] with the given environment.
29-
func NewMockRootFactoryForEnv(env *azldev.Env) (factory *buildenv.MockRootFactory, err error) {
37+
func NewMockRootFactoryForEnv(env *azldev.Env) (*buildenv.MockRootFactory, error) {
3038
var distroVerDef projectconfig.DistroVersionDefinition
3139

3240
// Get the distro we're building for.
33-
_, distroVerDef, err = env.Distro()
41+
_, distroVerDef, err := env.Distro()
3442
if err != nil {
3543
return nil, fmt.Errorf("failed to resolve distro for build: %w", err)
3644
}
@@ -52,7 +60,7 @@ func NewMockRootFactoryForEnv(env *azldev.Env) (factory *buildenv.MockRootFactor
5260
}
5361

5462
// Get set up with mock.
55-
factory, err = buildenv.NewMockRootFactory(env, mockConfigPath)
63+
factory, err := buildenv.NewMockRootFactory(env, mockConfigPath)
5664
if err != nil {
5765
return nil, fmt.Errorf("failed to create mock root factory: %w", err)
5866
}

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

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ type ComponentBuilder interface {
3535
}
3636

3737
type Builder struct {
38-
dryRunnable opctx.DryRunnable
39-
fs opctx.FS
40-
eventListener opctx.EventListener
41-
sourcePreparer sources.SourcePreparer
42-
buildEnvFactory buildenv.RPMAwareFactory
43-
workDirFactory *workdir.Factory
38+
dryRunnable opctx.DryRunnable
39+
fs opctx.FS
40+
eventListener opctx.EventListener
41+
sourcePreparer sources.SourcePreparer
42+
buildEnv buildenv.RPMAwareBuildEnv
43+
workDirFactory *workdir.Factory
4444
}
4545

4646
// Ensure that [Builder] implements the [ComponentBuilder] interface.
@@ -51,28 +51,23 @@ func New(
5151
fs opctx.FS,
5252
eventListener opctx.EventListener,
5353
sourcePreparer sources.SourcePreparer,
54-
buildEnvFactory buildenv.RPMAwareFactory,
54+
buildEnv buildenv.RPMAwareBuildEnv,
5555
workDirFactory *workdir.Factory,
5656
) *Builder {
5757
return &Builder{
58-
dryRunnable: dryRunnable,
59-
fs: fs,
60-
eventListener: eventListener,
61-
sourcePreparer: sourcePreparer,
62-
buildEnvFactory: buildEnvFactory,
63-
workDirFactory: workDirFactory,
58+
dryRunnable: dryRunnable,
59+
fs: fs,
60+
eventListener: eventListener,
61+
sourcePreparer: sourcePreparer,
62+
buildEnv: buildEnv,
63+
workDirFactory: workDirFactory,
6464
}
6565
}
6666

6767
func (b *Builder) BuildSourcePackage(
6868
ctx context.Context, component components.Component, outputDir string,
6969
) (packagePath string, err error) { // NOTE: We intentionally name the returns for better self-documentation.
70-
buildEnv, err := b.buildEnvFactory.CreateEnv(buildenv.CreateOptions{})
71-
if err != nil {
72-
return "", fmt.Errorf("failed to create build environment for component %q: %w", component.GetName(), err)
73-
}
74-
75-
packagePath, err = b.buildSRPM(ctx, component, buildEnv, outputDir)
70+
packagePath, err = b.buildSRPM(ctx, component, b.buildEnv, outputDir)
7671
if err != nil {
7772
return packagePath, fmt.Errorf("failed to build source package for component %#q: %w", component.GetName(), err)
7873
}
@@ -84,12 +79,7 @@ func (b *Builder) BuildBinaryPackage(
8479
ctx context.Context,
8580
component components.Component, sourcePackagePath, outputDir string, runChecks bool,
8681
) (packagePaths []string, err error) { // NOTE: We intentionally name the returns for better self-documentation.
87-
buildEnv, err := b.buildEnvFactory.CreateEnv(buildenv.CreateOptions{})
88-
if err != nil {
89-
return nil, fmt.Errorf("failed to create build environment for component %q: %w", component.GetName(), err)
90-
}
91-
92-
packagePaths, err = b.buildRPM(ctx, component, sourcePackagePath, buildEnv, outputDir, runChecks)
82+
packagePaths, err = b.buildRPM(ctx, component, sourcePackagePath, b.buildEnv, outputDir, runChecks)
9383
if err != nil {
9484
return packagePaths, fmt.Errorf("failed to build binary package for component %#q: %w", component.GetName(), err)
9585
}

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ type componentBuilderTestParams struct {
3232
ctrl *gomock.Controller
3333
testEnv *testutils.TestEnv
3434

35-
sourcePreparer sources.SourcePreparer
36-
buildEnvFactory *buildenv_testutils.MockRPMAwareFactory
37-
buildEnv *buildenv_testutils.MockRPMAwareBuildEnv
38-
workDirFactory *workdir.Factory
39-
builder *componentbuilder.Builder
35+
sourcePreparer sources.SourcePreparer
36+
buildEnv *buildenv_testutils.MockRPMAwareBuildEnv
37+
workDirFactory *workdir.Factory
38+
builder *componentbuilder.Builder
4039
}
4140

4241
func setupBuilder(t *testing.T) *componentBuilderTestParams {
@@ -56,22 +55,20 @@ func setupBuilder(t *testing.T) *componentBuilderTestParams {
5655
sourcePreparer: sources.NewPreparer(
5756
testEnv.DryRunnable, testEnv.Env, testEnv.Env.FS(), rpmContentsProvider,
5857
),
59-
buildEnvFactory: buildenv_testutils.NewMockRPMAwareFactory(ctrl),
60-
workDirFactory: workDirFactory,
58+
workDirFactory: workDirFactory,
6159
}
6260

6361
// Create a mocked-up RPM-aware build environment. Other method can be used to configure
6462
// it to simulate build behavior.
6563
params.buildEnv = buildenv_testutils.NewMockRPMAwareBuildEnv(ctrl)
66-
params.buildEnvFactory.EXPECT().CreateEnv(gomock.Any()).AnyTimes().Return(params.buildEnv, nil)
6764

6865
// Create a builder that we'll test.
6966
params.builder = componentbuilder.New(
7067
testEnv.Env,
7168
testEnv.Env.FS(),
7269
testEnv.Env,
7370
params.sourcePreparer,
74-
params.buildEnvFactory,
71+
params.buildEnv,
7572
params.workDirFactory,
7673
)
7774

internal/app/azldev/core/specs/spec.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/microsoft/azldev/internal/app/azldev/core/workdir"
1414
"github.com/microsoft/azldev/internal/projectconfig"
1515
"github.com/microsoft/azldev/internal/rpm"
16+
"github.com/microsoft/azldev/internal/utils/defers"
1617
)
1718

1819
// ComponentSpec provides abstract access to the build specification of a software component.
@@ -87,6 +88,9 @@ func (s *componentSpec) Parse() (specInfo *ComponentSpecDetails, err error) {
8788
return nil, fmt.Errorf("failed to create build environment for component %q: %w", s.componentConfig.Name, err)
8889
}
8990

91+
// Clean up the build environment before we return.
92+
defer defers.HandleDeferError(func() error { return buildEnv.Destroy(s.env) }, &err)
93+
9094
// Extract the build options from the component; we'll need to honor them even in rpmspec
9195
// in order to get the correct spec information.
9296
buildOptions := rpm.BuildOptions{

internal/app/azldev/core/workdir/workdir.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/google/uuid"
1314
"github.com/microsoft/azldev/internal/app/azldev"
1415
"github.com/microsoft/azldev/internal/app/azldev/core/buildenvfactory"
1516
"github.com/microsoft/azldev/internal/buildenv"
@@ -80,7 +81,7 @@ func MkComponentBuildEnvironment(
8081
factory *Factory,
8182
component *projectconfig.ComponentConfig,
8283
label string,
83-
) (buildEnv buildenv.BuildEnv, err error) {
84+
) (buildEnv buildenv.RPMAwareBuildEnv, err error) {
8485
// Create a work directory for us to place the environment under.
8586
workDir, err := factory.Create(component, label)
8687
if err != nil {
@@ -95,11 +96,13 @@ func MkComponentBuildEnvironment(
9596
return nil, fmt.Errorf("failed to create mock root factory: %w", err)
9697
}
9798

98-
// Create the build environment.
99-
buildEnv, err = buildEnvFactory.CreateEnv(
99+
// Create the build environment with an auto-generated name.
100+
buildEnv, err = buildEnvFactory.CreateRPMAwareEnv(
100101
buildenv.CreateOptions{
102+
Name: uuid.New().String(),
101103
Dir: buildEnvDir,
102104
UserCreated: false,
105+
Description: fmt.Sprintf("Build: %s (%s)", component.Name, label),
103106
},
104107
)
105108
if err != nil {

internal/buildenv/buildenv.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package buildenv
77

88
import (
99
"context"
10+
"encoding/json"
1011
"fmt"
12+
"time"
1113

1214
"github.com/microsoft/azldev/internal/global/opctx"
1315
"github.com/microsoft/azldev/internal/rpm/mock"
@@ -17,8 +19,16 @@ import (
1719
// environment. A build environment is a self-contained, intentionally constructed environment that
1820
// can be used to run build-related operations. A mock root instance is an example of a build environment.
1921
type BuildEnv interface {
22+
// GetInfo returns a [BuildEnvInfo] structure that contains information about the build
23+
// environment. This information can be used to identify the build environment and its properties.
24+
GetInfo() BuildEnvInfo
25+
2026
// CreateCmd builds a new [exec.Cmd] for running the given command within the build environment.
2127
CreateCmd(ctx context.Context, args []string, options RunOptions) (cmd opctx.Cmd, err error)
28+
29+
// Destroy permanently (and irreversibly) destroys the build environment, removing its files from
30+
// the filesystem.
31+
Destroy(ctx opctx.Ctx) error
2232
}
2333

2434
// SRPMBuildOptions encapsulates options that may be specified when building a source RPM package
@@ -96,3 +106,36 @@ func (f *EnvType) Set(value string) error {
96106
func (f *EnvType) Type() string {
97107
return "buildenv type"
98108
}
109+
110+
// BuildEnvInfo is a simple data structure that contains information about a [BuildEnv].
111+
type BuildEnvInfo struct {
112+
// Name is the human-readable name moniker for the build environment.
113+
Name string `json:"Name"`
114+
115+
// UserCreated indicates whether the build environment was created on behalf of a direct user request.
116+
// If this is false, the build environment was created automatically as part of a larger operation.
117+
UserCreated bool `json:"UserCreated"`
118+
119+
// Type is the type of build environment. This is used to determine which technology can be used
120+
// to interact with the build environment.
121+
Type EnvType `json:"Type"`
122+
123+
// CreationTime is the time when the build environment was created. This can be useful for users to
124+
// better understanding their environments.
125+
CreationTime time.Time `json:"CreationTime"`
126+
127+
// Dir is the directory under which the build environment's files are stored.
128+
Dir string `json:"Dir"`
129+
130+
// Description optionally provides a human-readable description of the environment's purpose.
131+
Description string `json:"Description"`
132+
}
133+
134+
func (i *BuildEnvInfo) Serialize() (data []byte, err error) {
135+
data, err = json.MarshalIndent(i, "", " ")
136+
if err != nil {
137+
return nil, fmt.Errorf("failed to serialize build environment info: %w", err)
138+
}
139+
140+
return data, nil
141+
}

0 commit comments

Comments
 (0)