BL5 (watched-branch half): a build carries the branch and source revision of its catalog revision, and only the first watched branch reserves
An entry the builder takes from the catalog half carries its origin: the watched branch, the source revision of the catalog revision it came from and whether the branch is the first of the module's registration. A build of another branch reserves no version and is built and delivered by its fingerprint; a reservation of the first branch keeps the branch and the source revision for the publication. Builds: design 47 G §371, A-57 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -91,6 +91,12 @@ func (c *catalogHalf) Changed(ctx context.Context) func(branch, module string, e
|
||||
c.b.EntryGone(branch, first, module)
|
||||
return
|
||||
}
|
||||
origin := build.Origin{Branch: branch, First: first}
|
||||
if cat, ok := c.branches.Catalog(branch); ok {
|
||||
if half, ok := cat.View(); ok {
|
||||
origin.Source = half.Revision.Source
|
||||
}
|
||||
}
|
||||
var e meshcatalog.CatalogEntry
|
||||
if err := json.Unmarshal(entry, &e); err != nil || e.Module != module {
|
||||
c.logf("the catalog half of the branch %q holds what is not the entry of %s, left: %v", branch, module, err)
|
||||
@@ -99,7 +105,7 @@ func (c *catalogHalf) Changed(ctx context.Context) func(branch, module string, e
|
||||
if err := c.manifests.keep(e); err != nil {
|
||||
c.logf("%v", err)
|
||||
}
|
||||
c.b.Entry(ctx, branch, first, e)
|
||||
c.b.Entry(ctx, origin, e)
|
||||
c.mu.Lock()
|
||||
was := c.named[module]
|
||||
now := namesOf(e)
|
||||
|
||||
@@ -172,12 +172,12 @@ func (b *Builder) waitFill(module string, m meshcatalog.ModulesByModuleValue) bo
|
||||
|
||||
// reserve reserves the version of the build that starts; a reservation that cannot be made is said and the build goes on
|
||||
// unreserved, as an unnumbered build does.
|
||||
func (b *Builder) reserve(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan) (versions.ReservedVersion, bool) {
|
||||
func (b *Builder) reserve(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, o Origin) (versions.ReservedVersion, bool) {
|
||||
refs := make([]string, 0, len(plan.Tasks))
|
||||
for _, t := range plan.Tasks {
|
||||
refs = append(refs, module+":"+t.Artifact)
|
||||
}
|
||||
src := versions.Source{Repository: m.Repository, Commit: m.Commit}
|
||||
src := versions.Source{Repository: m.Repository, Commit: m.Commit, Branch: o.Branch, Revision: o.Source}
|
||||
for _, t := range plan.Tasks {
|
||||
src.GoPackage = src.GoPackage || (t.Kind == catalogue.ArtifactPackage && t.Language == "go")
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package build
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"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"
|
||||
@@ -21,12 +23,13 @@ type fakeReservations struct {
|
||||
reserved [][]string
|
||||
series []string
|
||||
built map[string]string
|
||||
sources []versions.Source
|
||||
}
|
||||
|
||||
func (f *fakeReservations) Filled() bool { return f.filled }
|
||||
|
||||
func (f *fakeReservations) Reserve(_ context.Context, module, series string, artifacts []string, _ versions.Source) (versions.ReservedVersion, error) {
|
||||
f.reserved, f.series = append(f.reserved, artifacts), append(f.series, series)
|
||||
func (f *fakeReservations) Reserve(_ context.Context, module, series string, artifacts []string, src versions.Source) (versions.ReservedVersion, error) {
|
||||
f.reserved, f.series, f.sources = append(f.reserved, artifacts), append(f.series, series), append(f.sources, src)
|
||||
return versions.ReservedVersion{Module: module, Series: series, Version: series + ".5"}, nil
|
||||
}
|
||||
|
||||
@@ -127,3 +130,34 @@ func TestADropOfAReservationIsAReasksValueOfItsOwnKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOnlyABuildOfTheFirstWatchedBranchReservesAndItCarriesItsCatalogRevision: design 47 G §371, A-57 — a build of the registration's
|
||||
// first watched branch reserves a version and the reservation carries the branch and the source revision of the catalog revision its
|
||||
// entry came from; a build of another watched branch reserves none and is built and done.
|
||||
func TestOnlyABuildOfTheFirstWatchedBranchReservesAndItCarriesItsCatalogRevision(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
origin Origin
|
||||
want []versions.Source
|
||||
}{
|
||||
{"the first branch", Origin{Branch: "main", Source: 41, First: true},
|
||||
[]versions.Source{{Repository: "novox/mesh-catalog", Commit: "7a74cee83f88e6585a5511c3cc53bdf0f5303d20", Branch: "main", Revision: 41}}},
|
||||
{"another branch", Origin{Branch: "beta", Source: 42}, nil},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
r := newRig(t, goBundle)
|
||||
r.mf.byDigest[mdigest('a')] = catalogue.Manifest{Module: "m", Version: "0.1", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{goBundle}}}
|
||||
res := &fakeReservations{filled: true}
|
||||
r.b.Reserved = res
|
||||
r.b.Entry(context.Background(), c.origin, meshcatalog.CatalogEntry{Module: "m", Repository: "novox/mesh-catalog", Path: "modules/m",
|
||||
Commit: "7a74cee83f88e6585a5511c3cc53bdf0f5303d20", Fp: fp1, ManifestDigest: mdigest('a'), Inputs: &meshcatalog.EntryInputs{Toolchains: CatalogToolchains(agreedToolchains)}})
|
||||
r.b.Wait()
|
||||
if v := valueOf(t, r.st, r.key); v.State != builds.BuildDone {
|
||||
t.Fatalf("build = %+v, want done", v)
|
||||
}
|
||||
if !reflect.DeepEqual(res.sources, c.want) {
|
||||
t.Errorf("reserved from %+v, want %+v", res.sources, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ type Builder struct {
|
||||
fillWaits sync.Map
|
||||
known map[string]meshcatalog.ModulesByModuleValue // by modules key: the first watched branch's
|
||||
heard map[string]meshcatalog.ModulesByModuleValue // by branch and module (take): what supersedes what
|
||||
origin map[string]Origin // by build key: the catalog revision its entry came from (take)
|
||||
inflight map[string]bool // by build key
|
||||
again map[string]meshcatalog.ModulesByModuleValue // by build key: the newest value that arrived while the key was being built
|
||||
plans map[string]planned // by manifest digest
|
||||
@@ -171,6 +172,7 @@ func (b *Builder) init() {
|
||||
b.sem = make(chan struct{}, limit)
|
||||
b.cands, b.via, b.trunkFP = map[string]meshcatalog.CandidatesByGroupValue{}, map[string]via{}, map[string]bool{}
|
||||
b.heard, b.known = map[string]meshcatalog.ModulesByModuleValue{}, map[string]meshcatalog.ModulesByModuleValue{}
|
||||
b.origin = map[string]Origin{}
|
||||
b.inflight, b.again, b.plans, b.tried = map[string]bool{}, map[string]meshcatalog.ModulesByModuleValue{}, map[string]planned{}, map[string]bool{}
|
||||
b.published, b.held, b.waiting = map[string]bool{}, map[string]bool{}, map[string]map[string]chan string{}
|
||||
b.hands = map[string]*handWatch{}
|
||||
@@ -198,20 +200,28 @@ func (b *Builder) Module(ctx context.Context, key string, value []byte) {
|
||||
b.logf("modules.%s holds what is not a module, left: %v", key, err)
|
||||
return
|
||||
}
|
||||
b.take(ctx, "", true, key, m)
|
||||
b.take(ctx, Origin{First: true}, key, m)
|
||||
}
|
||||
|
||||
// Origin is the catalog revision an entry came from (design 47 G §371, A-57): the watched branch it is of, as named, the source
|
||||
// revision the revision was taken in from, and whether the branch is the first of the module's registration, the only one whose
|
||||
// builds reserve a version and whose entry is the module's value for what the builder reads by name (a version, a build again).
|
||||
type Origin struct {
|
||||
Branch string
|
||||
Source int64
|
||||
First bool
|
||||
}
|
||||
|
||||
// Entry takes the catalog graph entry of one module on one watched branch in (design 47 G §348, §614) and builds its
|
||||
// fingerprint if it is to be built. first says the branch is the first of the module's registration, whose entry is the module's
|
||||
// value for what the builder reads by name (a version, a build again); an entry of another branch builds its fingerprint and
|
||||
// fingerprint if it is to be built. An entry of a branch other than the first builds its fingerprint, reserves none, and
|
||||
// supersedes only that branch's older one. An entry naming no module is said and left.
|
||||
func (b *Builder) Entry(ctx context.Context, branch string, first bool, e meshcatalog.CatalogEntry) {
|
||||
func (b *Builder) Entry(ctx context.Context, o Origin, e meshcatalog.CatalogEntry) {
|
||||
b.init()
|
||||
if e.Module == "" {
|
||||
b.logf("the catalog half of the branch %q holds an entry that names no module, left", branch)
|
||||
b.logf("the catalog half of the branch %q holds an entry that names no module, left", o.Branch)
|
||||
return
|
||||
}
|
||||
b.take(ctx, branch, first, e.Module, ModuleOf(e))
|
||||
b.take(ctx, o, e.Module, ModuleOf(e))
|
||||
}
|
||||
|
||||
// EntryGone takes a module's entry out of a branch's half (the module was dropped from it, or the branch is followed no more).
|
||||
@@ -225,12 +235,12 @@ func (b *Builder) EntryGone(branch string, first bool, module string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) take(ctx context.Context, branch string, first bool, key string, m meshcatalog.ModulesByModuleValue) {
|
||||
func (b *Builder) take(ctx context.Context, o Origin, key string, m meshcatalog.ModulesByModuleValue) {
|
||||
b.mu.Lock()
|
||||
hk := branch + "\x00" + key
|
||||
hk := o.Branch + "\x00" + key
|
||||
old, had := b.heard[hk]
|
||||
b.heard[hk] = m
|
||||
if first {
|
||||
if o.First {
|
||||
b.known[key] = m
|
||||
}
|
||||
var superseded context.CancelCauseFunc
|
||||
@@ -242,6 +252,7 @@ func (b *Builder) take(ctx context.Context, branch string, first bool, key strin
|
||||
if bk, err := builds.BuilderBuildKey(key, m.Fp2); err == nil && m.Fp2 != "" {
|
||||
// A fingerprint a watched branch carries is built from that branch's commit, whoever asked for it first.
|
||||
b.trunkFP[bk] = true
|
||||
b.origin[bk] = o
|
||||
delete(b.via, bk)
|
||||
}
|
||||
b.mu.Unlock()
|
||||
@@ -279,6 +290,17 @@ func ModuleOf(e meshcatalog.CatalogEntry) meshcatalog.ModulesByModuleValue {
|
||||
return m
|
||||
}
|
||||
|
||||
// originOf is the catalog revision the entry of a build key came from; a key no entry named is of the first branch, as a build
|
||||
// of the trunk's value was before branches were watched.
|
||||
func (b *Builder) originOf(key string) Origin {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if o, ok := b.origin[key]; ok {
|
||||
return o
|
||||
}
|
||||
return Origin{First: true}
|
||||
}
|
||||
|
||||
// NodesChanged is Reconsider after a toolchain moved, so a build that waited for the nodes to agree on one may go on.
|
||||
func (b *Builder) NodesChanged(ctx context.Context) { b.Reconsider(ctx) }
|
||||
|
||||
@@ -583,7 +605,8 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB
|
||||
return // a package member of the group is not built (design 47 G §373): its end looks at this key again
|
||||
}
|
||||
}
|
||||
reserving := trunk && planErr == nil && !nothing && b.reserves(ctx, module, m, plan)
|
||||
// Only a build of the registration's first watched branch reserves and releases a version (design 47 G §371).
|
||||
reserving := trunk && b.originOf(key).First && planErr == nil && !nothing && b.reserves(ctx, module, m, plan)
|
||||
if reserving && b.waitFill(module, m) {
|
||||
return
|
||||
}
|
||||
@@ -624,7 +647,7 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB
|
||||
var reserved versions.ReservedVersion
|
||||
hasReserved := false
|
||||
if reserving && cl.Build.State != builds.BuildDone {
|
||||
reserved, hasReserved = b.reserve(ctx, module, m, plan)
|
||||
reserved, hasReserved = b.reserve(ctx, module, m, plan, b.originOf(key))
|
||||
}
|
||||
var result builds.BuilderBuild
|
||||
if numbering.Refused != "" && cl.Build.State != builds.BuildDone {
|
||||
|
||||
@@ -54,7 +54,11 @@ type ReservedVersion struct {
|
||||
// package's, GoTagOf): empty for a module with no Go package (design 47 G §370).
|
||||
Repository string `json:"repository,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
// Branch and Source are the watched branch and the source revision of the catalog revision whose entry the build came from
|
||||
// (design 47 G §371, A-57): what `package-published` says of the publication.
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Source int64 `json:"source,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// Source is what a build reserving a version is made from (design 47 G §370): the repository and commit the git tag is
|
||||
@@ -62,6 +66,10 @@ type ReservedVersion struct {
|
||||
type Source struct {
|
||||
Repository, Commit string
|
||||
GoPackage bool
|
||||
// Branch is the watched branch of the catalog revision whose entry the build came from, and Revision the source revision of
|
||||
// that catalog revision (design 47 G §371, A-57), kept on the reservation for the publication.
|
||||
Branch string
|
||||
Revision int64
|
||||
}
|
||||
|
||||
// ReservedKey is the key of module's reservation of series (MAJOR.MINOR).
|
||||
@@ -136,7 +144,7 @@ func (r *Reserver) Reserve(ctx context.Context, module, series string, artifacts
|
||||
}
|
||||
}
|
||||
out := ReservedVersion{V: 1, Module: module, Series: series, Version: fmt.Sprintf("%s.%d", series, next+1), Artifacts: map[string]string{}, At: r.now(),
|
||||
Repository: src.Repository, Commit: src.Commit}
|
||||
Repository: src.Repository, Commit: src.Commit, Branch: src.Branch, Source: src.Revision}
|
||||
if src.GoPackage {
|
||||
out.Tag = GoTagOf(out.Version)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user