Skip to content

Commit 879b287

Browse files
committed
add prepare step, as opt-in experiment. also added opt-in experiment framework.
1 parent 80d7de1 commit 879b287

File tree

3 files changed

+103
-27
lines changed

3 files changed

+103
-27
lines changed

cmd/bob/bobfile.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ type Bobfile struct {
2323
DockerImages []DockerImageSpec `json:"docker_images"`
2424
Subrepos []SubrepoSpec `json:"subrepos,omitempty"`
2525
OsArches *OsArchesSpec `json:"os_arches"`
26+
Experiments experiments `json:"experiments_i_consent_to_breakage,omitempty"`
27+
}
28+
29+
type experiments struct {
30+
PrepareStep bool `json:"prepare_step"`
2631
}
2732

2833
type SubrepoSpec struct {
@@ -69,7 +74,38 @@ func (o *OsArchesSpec) AsBuildEnvVariables() []string {
6974
return ret
7075
}
7176

77+
/* Suppose you have three builders: 1) backend, 2) frontend and 3) documentation.
78+
Here's the order in which the commands are executed:
79+
80+
81+
Start ────────────────┐ ┌───────────────┐ ┌───────────────┐
82+
│ ▲ │ ▲ │
83+
┌──────────▼┐ │ ┌──────────▼┐ │ ┌──────────▼┐
84+
Backend │ Prepare ││ │ │ Build ││ │ │ Publish ││
85+
└──────────┼┘ │ └──────────┼┘ │ └──────────┼┘
86+
│ │ │ │ │
87+
│ │ │ │ │
88+
┌──────────▼┐ │ ┌──────────▼┐ │ ┌──────────▼┐
89+
Frontend │ Prepare ││ │ │ Build ││ │ │ Publish ││
90+
└──────────┼┘ │ └──────────┼┘ │ └──────────┼┘
91+
│ │ │ │ │
92+
│ │ │ │ │
93+
┌──────────▼┐ │ ┌──────────▼┐ │ ┌──────────▼┐
94+
Documentation │ Prepare ││ │ │ Build ││ │ │ Publish ││
95+
└──────────┼┘ │ └──────────┼┘ │ └──────────┼┘
96+
│ │ │ │ │
97+
│ │ │ │ │
98+
└─────┘ └─────┘ ▼
99+
100+
Rationale:
101+
102+
- backend needs some codegenerated stuff from documentation, like URLs so backend can link to documentation,
103+
so backend build can use stuff from documentation.prepare step.
104+
- you'll want to publish artefacts only if all builders succeeded (*.build before *.publish),
105+
so there's no unnecessary uploads.
106+
*/
72107
type BuilderCommands struct {
108+
Prepare []string `json:"prepare"`
73109
Build []string `json:"build"`
74110
Publish []string `json:"publish"`
75111
Dev []string `json:"dev"`
@@ -160,6 +196,10 @@ func validateBuilders(bobfile *Bobfile) error {
160196
}
161197

162198
alreadySeenNames[builder.Name] = Void{}
199+
200+
if len(builder.Commands.Prepare) > 0 && !bobfile.Experiments.PrepareStep {
201+
return fmt.Errorf("%s: you need to opt-in to prepare_step experiment", builder.Name)
202+
}
163203
}
164204

165205
return nil

cmd/bob/bobfile_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,39 @@ func TestAssertUniqueBuilderNames(t *testing.T) {
2929
},
3030
}
3131

32-
assert.Assert(t, validateBuilders(bobfileEmpty) == nil)
33-
assert.Assert(t, validateBuilders(bobfileUniques) == nil)
32+
assert.Ok(t, validateBuilders(bobfileEmpty))
33+
assert.Ok(t, validateBuilders(bobfileUniques))
3434
assert.EqualString(t,
3535
validateBuilders(bobfileNonUniques).Error(),
3636
"duplicate builder name: foobar")
3737
}
38+
39+
func TestConsentToBreakage(t *testing.T) {
40+
properConsent := &Bobfile{
41+
Builders: []BuilderSpec{
42+
{
43+
Name: "foobar",
44+
Commands: BuilderCommands{
45+
Prepare: []string{"foo"},
46+
},
47+
},
48+
},
49+
Experiments: experiments{
50+
PrepareStep: true,
51+
},
52+
}
53+
54+
missingConsent := &Bobfile{
55+
Builders: []BuilderSpec{
56+
{
57+
Name: "foobar",
58+
Commands: BuilderCommands{
59+
Prepare: []string{"foo"},
60+
},
61+
},
62+
},
63+
}
64+
65+
assert.Ok(t, validateBuilders(properConsent))
66+
assert.EqualString(t, validateBuilders(missingConsent).Error(), "foobar: you need to opt-in to prepare_step experiment")
67+
}

cmd/bob/build.go

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,11 @@ func build(buildCtx *BuildContext) error {
236236
}
237237
}
238238

239-
// build builders (TODO: check cache so this is not done unless necessary?)
239+
// build builders.
240+
//
241+
// it would be cool to not invoke Docker if this is cached anyway, but the analysis would have
242+
// to include modification check for the Dockerfile and all of its build context, so we're just
243+
// best off calling Docker build because it is the best at detecting cache invalidation.
240244
for _, builder := range buildCtx.Bobfile.Builders {
241245
builder := builder // pin
242246

@@ -259,38 +263,40 @@ func build(buildCtx *BuildContext) error {
259263
}
260264
}
261265

262-
// two passes:
263-
// 1) run all builds with all phases except publish
264-
// 2) run all builds with only publish phase (but only if publish requested)
266+
// three-pass process. the flow is well documented in *BuilderCommands* type
267+
pass := func(opDesc string, getCommand func(cmds BuilderCommands) []string) error {
268+
for _, builder := range buildCtx.Bobfile.Builders {
269+
if buildCtx.BuilderNameFilter != "" && builder.Name != buildCtx.BuilderNameFilter {
270+
continue
271+
}
265272

266-
// 1)
267-
for _, builder := range buildCtx.Bobfile.Builders {
268-
if buildCtx.BuilderNameFilter != "" && builder.Name != buildCtx.BuilderNameFilter {
269-
continue
270-
}
273+
cmd := getCommand(builder.Commands)
274+
if len(cmd) == 0 { // no command for this step specified
275+
continue
276+
}
271277

272-
if len(builder.Commands.Build) == 0 {
273-
continue
278+
if err := runBuilder(builder, buildCtx, opDesc, cmd); err != nil {
279+
return fmt.Errorf("%s.%s: %w", builder.Name, opDesc, err)
280+
}
274281
}
275282

276-
if err := runBuilder(builder, buildCtx, "build", builder.Commands.Build); err != nil {
277-
return err
278-
}
283+
return nil
279284
}
280285

281-
// 2)
282-
for _, builder := range buildCtx.Bobfile.Builders {
283-
if buildCtx.BuilderNameFilter != "" && builder.Name != buildCtx.BuilderNameFilter {
284-
continue
285-
}
286+
preparePass := func(cmds BuilderCommands) []string { return cmds.Prepare }
287+
buildPass := func(cmds BuilderCommands) []string { return cmds.Build }
288+
publishPass := func(cmds BuilderCommands) []string { return cmds.Publish }
286289

287-
if !buildCtx.PublishArtefacts || len(builder.Commands.Publish) == 0 {
288-
continue
289-
}
290+
if err := pass("prepare", preparePass); err != nil {
291+
return err // err context ok
292+
}
290293

291-
if err := runBuilder(builder, buildCtx, "publish", builder.Commands.Publish); err != nil {
292-
return err
293-
}
294+
if err := pass("build", buildPass); err != nil {
295+
return err // err context ok
296+
}
297+
298+
if err := pass("publish", publishPass); err != nil {
299+
return err // err context ok
294300
}
295301

296302
dockerLoginCache := newDockerRegistryLoginCache()

0 commit comments

Comments
 (0)