diff --git a/modules/mesh-delivery-builder/internal/build/reserve_test.go b/modules/mesh-delivery-builder/internal/build/reserve_test.go index d2c85fdf..ae1627f1 100644 --- a/modules/mesh-delivery-builder/internal/build/reserve_test.go +++ b/modules/mesh-delivery-builder/internal/build/reserve_test.go @@ -9,6 +9,7 @@ import ( "time" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/catalogue" "mesh-delivery-builder/internal/versions" @@ -65,7 +66,16 @@ func TestABuildReservesItsVersionAtItsStartOnceTheCatalogFilled(t *testing.T) { } res.filled = true + // Design 47 G §573: the build's ask carries the reserved version. + var askedVersions []string + r.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { + askedVersions = append(askedVersions, a.Version) + return r.answer(a) + } v := r.consider(t, "modules/m") + if len(askedVersions) == 0 || slices.ContainsFunc(askedVersions, func(v string) bool { return v != "0.1.5" }) { + t.Fatalf("the asks carried versions %q, want the reserved 0.1.5 on each", askedVersions) + } if v.State != builds.BuildDone || len(res.reserved) != 1 || !slices.Equal(res.reserved[0], []string{"m:tool"}) || res.series[0] != "0.1" { t.Fatalf("build %+v reserved %v in %v, want done and one reservation of m:tool in 0.1", v.State, res.reserved, res.series) } @@ -81,11 +91,19 @@ func TestABuildReservesItsVersionAtItsStartOnceTheCatalogFilled(t *testing.T) { r2 := newRig(t, goBundle) res2 := &fakeReservations{filled: true} r2.b.Reserved = res2 + var unversioned []string + r2.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { + unversioned = append(unversioned, a.Version) + return r2.answer(a) + } var journal []string r2.b.Logf = func(f string, a ...any) { journal = append(journal, fmt.Sprintf(f, a...)) } if v := r2.consider(t, "modules/m"); v.State != builds.BuildDone || len(res2.reserved) != 0 { t.Errorf("a manifest with no MAJOR.MINOR: state %s, reserved %v", v.State, res2.reserved) } + if len(unversioned) == 0 || slices.ContainsFunc(unversioned, func(v string) bool { return v != "" }) { + t.Errorf("a build reserving none asked with versions %q, want none", unversioned) + } // G §505: the builder writes in its journal, naming the module, that it reserved nothing. if !slices.ContainsFunc(journal, func(l string) bool { return strings.HasPrefix(l, "m: ") && strings.Contains(l, "not MAJOR.MINOR") }) { t.Errorf("journal %q, want a line naming m and saying its version is not MAJOR.MINOR", journal) diff --git a/modules/mesh-delivery-builder/internal/build/run.go b/modules/mesh-delivery-builder/internal/build/run.go index e17baad3..e99287a3 100644 --- a/modules/mesh-delivery-builder/internal/build/run.go +++ b/modules/mesh-delivery-builder/internal/build/run.go @@ -552,7 +552,7 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB b.mu.Lock() b.builds[key] = withdraw b.mu.Unlock() - result = b.build(run, module, m, plan, planErr, prior, only, b.notes(cl)) + result = b.build(run, module, m, plan, planErr, prior, only, b.notes(cl), reserved.Version) b.mu.Lock() delete(b.builds, key) b.mu.Unlock() @@ -646,6 +646,7 @@ func failed(format string, args ...any) builds.BuilderBuild { // lane is the build of a plan's tasks of one build module on one node. type lane struct { module, node string // node is the one that pulled the lane's ask + version string // the version the build reserved, MAJOR.MINOR.PATCH, empty for a build that reserves none (design 47 G §573) tasks []Task toolchain *builds.ToolchainUsed digests map[string]string // artifact -> digest @@ -656,7 +657,7 @@ type lane struct { err error } -func (b *Builder) build(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, planErr error, prior *builds.BuilderBuild, only map[string]bool, asked *askNotes) builds.BuilderBuild { +func (b *Builder) build(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, planErr error, prior *builds.BuilderBuild, only map[string]bool, asked *askNotes, version string) builds.BuilderBuild { switch { case planErr != nil: return failed("%s", planErr) @@ -697,7 +698,7 @@ func (b *Builder) build(ctx context.Context, module string, m meshcatalog.Module } } // One ask builds each fingerprint's part of a build module, on whichever node pulls it (ADR 0525 §3). - lanes = append(lanes, &lane{module: bm, tasks: tasks, toolchain: toolchain, digests: map[string]string{}, references: map[string]string{}}) + lanes = append(lanes, &lane{module: bm, version: version, tasks: tasks, toolchain: toolchain, digests: map[string]string{}, references: map[string]string{}}) } if key, err := builds.BuilderBuildKey(module, m.Fp2); err == nil { b.handLanes(key, len(lanes)) @@ -952,7 +953,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m mes if needsFetch(l.tasks) { job.Fetch = &req } - ask := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: language, Asker: builds.BuilderModule, At: time.Now(), Build: job} + ask := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: language, Asker: builds.BuilderModule, At: time.Now(), Version: l.version, Build: job} if language == sdkstep.AskGo && job.Fetch != nil && len(m.Packages) > 0 { // The versions of the mesh packages the fingerprint was computed with (novox/hq ADR 0573 §2): the build module writes // them into the workspace, whatever the repository's go.mod names.