package build import ( "context" "fmt" "strconv" "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/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 { // tag is a check's build's tag (TagOf), empty for a trunk build; done the digests of its key when it is done already // (versions.BuildClaim.Done), nil otherwise. Number(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan, tag string, 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 // TagNumbered is told the versions a check's build of a tag ask's head was numbered (the ask is then marked numbered // in tag-asks, design 51 *Tagged versions*); nil tells nobody. TagNumbered func(ctx context.Context, c versions.BuildClaim, got versions.Numbering) } // 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, tag string, done map[string]string) (versions.Numbering, error) { c := ClaimOf(module, m, plan) c.Done = done if tag != "" { // A check's build of a head a tag ask names: its packages tagged, below the next version (design 51 *Tagged versions*). c.Tag, c.Head, c.Revision, c.Merge = tag, m.Commit, "", 0 } got, err := v.N.Number(ctx, c) if err == nil && tag != "" && len(got.Versions) > 0 && v.TagNumbered != nil { v.TagNumbered(ctx, c, got) } return got, err } 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) } // TagOf answers the cleaned tag a kept tag ask names for a repository's head (the builder's state tag-asks, design 51 // *Tagged versions*): a check's build of that head numbers its packages tagged at its claim. type TagOf func(ctx context.Context, repository, head string) (tag string, ok bool) // number numbers a build before its claim, a trunk build's artifacts or a check's build's packages under its tag: 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, tag string) (versions.Numbering, bool) { if b.Numbers == nil { return versions.Numbering{}, true } var done map[string]string if tag == "" { done = b.doneDigests(ctx, module, m.Fp2) } got, err := b.Numbers.Number(ctx, module, m, plan, tag, 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.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) } }