Files
mesh-delivery/modules/mesh-delivery-builder/internal/build/numbers.go
T
jschoubben 71a35a6376 MV1: move contracts and sdk under modules/, importers follow
The delivery repository's contracts and SDK live under modules/ as
mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths
renamed to match the folders (the TypeScript names already did), and
every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools
and mesh-sdk follows in the same group: a design as it should be, one
path per module, checked by the group's check building every importer.

Builds: design 47 G §427, §88

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:09:56 +01:00

194 lines
8.6 KiB
Go

package build
import (
"context"
"fmt"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"strconv"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/trunk"
"mesh-delivery-builder/internal/versions"
)
// Numbers numbers a trunk build at its claim (novox/hq design 51 *The versions*, ADR 0606, ADR 0617 §1) and records the
// digests it was built with. Nil numbers nothing.
type Numbers interface {
// done is the digests of the build's key when it is done already (versions.BuildClaim.Done), nil otherwise.
Number(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, done map[string]string) (versions.Numbering, error)
Built(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, got versions.Numbering, made []builds.BuiltArtifact) error
}
// VersionNumbers is Numbers over the builder's state versions.
type VersionNumbers struct {
N *versions.Numberer
}
// ClaimOf is the claim a module value's trunk build is numbered by: a package per source revision (Src), the rest per
// fingerprint, in merge order (Src).
func ClaimOf(module string, m meshcatalog.ModulesByModuleValue, plan Plan) versions.BuildClaim {
c := versions.BuildClaim{Module: module, Path: m.Path, Repository: trunk.RepositoryName(m.Repository), Fingerprint: m.Fp2,
Commit: m.Commit, Merge: uint64(m.Src)}
if uint64(m.Src) != 0 {
c.Revision = strconv.FormatUint(uint64(m.Src), 10)
}
for _, t := range plan.Tasks {
c.Artifacts = append(c.Artifacts, versions.ClaimArtifact{Artifact: t.Artifact, Kind: t.Kind, Language: t.Language})
}
return c
}
func (v VersionNumbers) Number(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, done map[string]string) (versions.Numbering, error) {
c := ClaimOf(module, m, plan)
c.Done = done
return v.N.Number(ctx, c)
}
func (v VersionNumbers) Built(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, got versions.Numbering, made []builds.BuiltArtifact) error {
digests := map[string]string{}
for _, a := range made {
digests[a.Name] = a.Digest
}
return v.N.Built(ctx, ClaimOf(module, m, plan), got, digests)
}
// number numbers a trunk build's artifacts before its claim: false
// when it waits for its floor (a package, design 51 *The floor*), and then nothing is claimed; the floor's wake looks at
// the module again.
func (b *Builder) number(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan) (versions.Numbering, bool) {
if b.Numbers == nil {
return versions.Numbering{}, true
}
done := b.doneDigests(ctx, module, m.Fp2)
got, err := b.Numbers.Number(ctx, module, m, plan, done)
for _, s := range got.Said {
b.logf("%s at %s: %s", module, m.Fp2, s.Line)
// And in the build log (design 51 *The versions*: it says a version was kept, and why), between the build's start and end.
// A look at a key already done builds nothing and has no start or end: a version kept there says nothing new, and is
// not logged (each later look, a first view answered or a name heard, would add one).
if b.Log != nil && !(done != nil && s.Why == versions.SaidKept) {
b.Log(builds.BuildLogEntry{Event: builds.BuildSaidEvent, Module: module, FP: m.Fp2, Commit: m.Commit, Node: b.Node,
At: time.Now().UTC(), Line: s.Line, Why: s.Why})
}
}
if err != nil {
// Said, and built as before: an unnumbered build is published by its digest (ADR 0617 §1), numbered at its next look.
b.logf("%s at %s: %v", module, m.Fp2, fmt.Errorf("not numbered, the versions cannot be used: %w", err))
return versions.Numbering{}, true
}
if got.Refused != "" {
b.logf("%s at %s: %s", module, m.Fp2, got.Refused)
return got, true
}
if got.Wait != "" {
b.logf("%s at %s is not built yet: %s", module, m.Fp2, got.Wait)
return got, false
}
return got, true
}
// doneDigests are the digests of a trunk build's key by artifact when the key is done, nil otherwise or when it cannot be
// read: a look at a done build numbers what it left unnumbered with them (versions.BuildClaim.Done).
func (b *Builder) doneDigests(ctx context.Context, module, fp string) map[string]string {
key, err := builds.BuilderBuildKey(module, fp)
if err != nil || b.Claimer.Store == nil {
return nil
}
raw, _, found, err := b.Claimer.Store.Get(ctx, key)
if err != nil || !found {
return nil
}
cur, err := builds.DecodeBuilderBuild(raw)
if err != nil || cur.State != builds.BuildDone {
return nil
}
out := map[string]string{}
for _, a := range cur.Artifacts {
if a.Digest != "" {
out[a.Name] = a.Digest
}
}
return out
}
// PackageNamed looks at a module again when the indexer's packages state names a name for one of its packages
// (versions.NamedModule): a done trunk build that left the package unnumbered, its name not known at its claim, is
// numbered then with the done build's digests, and version.numbered says them for its build module's late publish.
func (b *Builder) PackageNamed(ctx context.Context, module string) {
b.init()
b.mu.Lock()
m, ok := b.known[module]
b.mu.Unlock()
if ok {
b.consider(ctx, module, m)
}
}
// Reservations reserves the version of a trunk build at its start and records each artifact's digest (design 47 G §369).
// Nil reserves nothing.
type Reservations interface {
// Filled says the catalog said versions-filled: nothing is reserved before it.
Filled() bool
// Reserve reserves module's next version in series for the artifacts (their references, <module>:<artifact>).
Reserve(ctx context.Context, module, series string, artifacts []string, src versions.Source) (versions.ReservedVersion, error)
// Built records the digests of the artifacts built under the reserved version, by reference, and what each resolved and was
// built with (design 47 G §466, §673).
Built(ctx context.Context, module, version string, digests map[string]string, deps map[string][]meshbuilds.Dependency) error
}
// reserves says whether a look at a trunk build is one that reserves: the build has a series of the form MAJOR.MINOR
// (design 47 G §368) and artifacts, and its key is not done already.
func (b *Builder) reserves(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan) bool {
if b.Reserved == nil || len(plan.Tasks) == 0 {
return false
}
if strings.Count(plan.Series, ".") != 1 {
// G §505: such an entry (a manifest from before G, "1") reserves nothing and is built as a build without a version.
b.logf("%s: the catalog entry's version %q is not MAJOR.MINOR: no version is reserved, and the build is made and delivered without one", module, plan.Series)
return false
}
return b.doneDigests(ctx, module, m.Fp2) == nil
}
// waitFill holds a build that would reserve until the catalog says versions-filled, saying wait-held naming the fill once
// (design 47 G §369, §372). It answers whether the build waits.
func (b *Builder) waitFill(module string, m meshcatalog.ModulesByModuleValue) bool {
if b.Reserved.Filled() {
return false
}
if _, said := b.fillWaits.LoadOrStore(module+"."+m.Fp2, true); !said {
line := module + " at " + m.Fp2 + " is not built yet: wait-held, the catalog has not said versions-filled, and nothing is reserved before it"
b.logf("%s", line)
if b.Log != nil {
b.Log(builds.BuildLogEntry{Event: builds.BuildSaidEvent, Module: module, FP: m.Fp2, Commit: m.Commit, Node: b.Node,
At: time.Now().UTC(), Line: line, Why: "wait-held"})
}
}
return true
}
// 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, 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, Fingerprint: m.Fp2, Branch: o.Branch, Revision: o.Source}
for _, t := range plan.Tasks {
src.GoPackage = src.GoPackage || (t.Kind == catalogue.ArtifactPackage && t.Language == "go")
}
res, err := b.Reserved.Reserve(ctx, module, plan.Series, refs, src)
if err != nil {
b.logf("%s: no version is reserved for this build: %v", module, err)
return versions.ReservedVersion{}, false
}
b.logf("%s: version %s is reserved for this build", module, res.Version)
return res, true
}