mesh/merge-gate head 1666747a2246 merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 1666747a2246 merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
A fresh check asked when only the trunk moved rebuilt every image, though the reuse key said nothing it is built from changed: a controller check took about 12 minutes instead of 5.5. The check now keeps a build kept under its key when a verdict of the build seat names that key and every digest, names it as the build's own check so a walk takes it, and the cleanup keeps it while any check names it (novox/hq issue 703).
170 lines
6.7 KiB
Go
170 lines
6.7 KiB
Go
package builder
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// The builds a merge check makes for its merge (novox/hq ADR 0350 §1): every artifact of every module the delivery
|
|
// plan moves, built from the merged tree the check judged into the scratch namespace, kept under its reuse key, so
|
|
// the walk of the merge commit takes it instead of building again. Built after the check has judged, and only when
|
|
// it did not fail: a change that fails is not merged, and nothing it built would be taken.
|
|
|
|
// CheckBuildSpec is one module a check builds for its merge, in the walk's order (Tier).
|
|
type CheckBuildSpec struct {
|
|
Module string
|
|
// Repository, Path and Ref are where it is built from; Merged says from the merge the check made instead — the
|
|
// module is built from the repository checked, at the branch merged into.
|
|
Repository string
|
|
Path string
|
|
Ref string
|
|
Merged bool
|
|
Tier int
|
|
// Skip says why it cannot be built as the walk would build it: not built, and nothing after it.
|
|
Skip string
|
|
}
|
|
|
|
// CheckBuilding is what a check builds with: where it publishes and keeps its builds, and what a build is handed.
|
|
type CheckBuilding struct {
|
|
Publish Publisher
|
|
Scratch Scratch
|
|
Held map[string]string
|
|
Npmrc Npmrc
|
|
Seats map[string]string
|
|
// Named are the builds the build seat's verdicts name for the modules this check builds, of any pull request,
|
|
// within the week check builds are kept (novox/hq issue 703): a build kept under a module's key is kept again,
|
|
// instead of built, only when one of them names its key and every digest. The record alone is never enough.
|
|
Named []NamedBuild
|
|
}
|
|
|
|
// CheckBuilt is one module a check built for its merge, as its verdict lists it (§1): its key and its artifacts, or
|
|
// why it was not built.
|
|
type CheckBuilt struct {
|
|
Module string
|
|
Key string
|
|
Made []catalogue.Built
|
|
// NotBuilt says why it was not: no key, a build that failed, or a module after one of those.
|
|
NotBuilt string
|
|
Took time.Duration
|
|
// Kept is the check whose build this check kept instead of building, its key unchanged (novox/hq issue 703):
|
|
// the verdict names that build, by key and digest, as that check's. Empty when this check built it.
|
|
Kept string
|
|
}
|
|
|
|
// skipsTheBuilds is why a check builds nothing for its merge; empty when it builds.
|
|
func skipsTheBuilds(v CheckVerdict) string {
|
|
for _, l := range []*Layer{v.Gate, v.Repo} {
|
|
if l != nil && (l.Verdict == "fail" || l.Verdict == "error") {
|
|
return "the check did not pass, so nothing is built for its merge"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// buildForTheMerge builds each module the plan moves, tier by tier, into the scratch namespace, and keeps what it
|
|
// built under its keys. A module standing after one not built is not built either: what it stands on in the walk
|
|
// is not what it would stand on here. tree is the check's clone and merge the merge commit it made there.
|
|
func buildForTheMerge(ctx context.Context, run Runner, spec CheckSpec, with CheckBuilding, workspace, tree, merge string,
|
|
judged CheckVerdict, forge GitCredential, log Log, say func(step, format string, args ...any)) []CheckBuilt {
|
|
if len(spec.Builds) == 0 || with.Publish == nil || with.Scratch == nil {
|
|
return nil
|
|
}
|
|
ref := merge
|
|
if ref == "" {
|
|
ref = spec.Ref
|
|
}
|
|
held := map[string]string{}
|
|
for k, v := range with.Held {
|
|
held[k] = v
|
|
}
|
|
var out []CheckBuilt
|
|
var kept []CheckBuild
|
|
stopAt, stopWhy := -1, ""
|
|
for _, b := range spec.Builds {
|
|
began := time.Now()
|
|
if stopAt >= 0 && b.Tier > stopAt {
|
|
out = append(out, CheckBuilt{Module: b.Module, NotBuilt: stopWhy})
|
|
continue
|
|
}
|
|
if b.Skip != "" {
|
|
say("build", "%s not built for the merge: %s", b.Module, b.Skip)
|
|
out = append(out, CheckBuilt{Module: b.Module, NotBuilt: b.Skip})
|
|
if stopAt < 0 || b.Tier < stopAt {
|
|
stopAt, stopWhy = b.Tier, "it stands after "+b.Module+", which was not built for the merge"
|
|
}
|
|
continue
|
|
}
|
|
repository, at := b.Repository, b.Ref
|
|
if b.Merged {
|
|
repository, at = tree, ref
|
|
}
|
|
say("build", "for the merge: %s (tier %d)", b.Module, b.Tier)
|
|
var mirrored []string
|
|
result, err := build(ctx, run, scratchPublisher{to: with.Publish}, repository, b.Path, at,
|
|
filepath.Join(workspace, "for-the-merge"), held, with.Npmrc, forge, log, &mirrored,
|
|
&Reuse{keyFirst: true, Scratch: with.Scratch, Named: with.Named}, with.Seats)
|
|
built := CheckBuilt{Module: b.Module, Key: result.ReuseKey, Took: time.Since(began)}
|
|
switch {
|
|
case errors.Is(err, errNoKey):
|
|
built.NotBuilt = err.Error()
|
|
case err != nil:
|
|
built.NotBuilt = "its build failed: " + err.Error()
|
|
case result.ReuseKey == "":
|
|
built.NotBuilt = "it has no reuse key: " + result.NoKey
|
|
}
|
|
if built.NotBuilt != "" {
|
|
say("build", "%s not built for the merge: %s", b.Module, built.NotBuilt)
|
|
out = append(out, built)
|
|
if stopAt < 0 || b.Tier < stopAt {
|
|
stopAt, stopWhy = b.Tier, "it stands after "+b.Module+", which was not built for the merge"
|
|
}
|
|
continue
|
|
}
|
|
built.Made = result.Built
|
|
for _, m := range result.Built {
|
|
held[b.Module+"/"+m.Name] = m.Reference
|
|
}
|
|
if result.Reused != nil {
|
|
// **Kept, not built** (novox/hq issue 703): the earlier check's build, named as that check's, so the
|
|
// walk takes it as the verdict names it; this check's document names it too, so it stays while this
|
|
// check does (ADR 0350 §5).
|
|
built.Kept = result.Reused.From.Check
|
|
say("build", "%s kept for the merge under %s: %s", b.Module, twelve(result.ReuseKey[3:]),
|
|
result.Reused.From.Said())
|
|
out = append(out, built)
|
|
kept = append(kept, result.Reused.From)
|
|
continue
|
|
}
|
|
say("build", "%s built for the merge under %s (%s)", b.Module, twelve(result.ReuseKey[3:]), built.Took.Round(time.Second))
|
|
out = append(out, built)
|
|
if len(result.Built) > 0 {
|
|
kept = append(kept, CheckBuild{Key: result.ReuseKey, Module: b.Module, Parts: result.ReuseParts,
|
|
Made: result.Built, Pull: PullRef{Owner: spec.Owner, Repo: spec.Repo, Number: spec.Number},
|
|
Head: judged.Head, Trunk: judged.Trunk, Check: spec.ID, At: time.Now().UTC()})
|
|
}
|
|
}
|
|
_ = os.RemoveAll(filepath.Join(workspace, "for-the-merge"))
|
|
if len(kept) > 0 {
|
|
if err := with.Scratch.Keep(ctx, PullRef{Owner: spec.Owner, Repo: spec.Repo, Number: spec.Number}, spec.ID,
|
|
time.Now().UTC(), kept); err != nil {
|
|
// Built and not findable: the walk builds, and says no check built it. Said, never the check's verdict.
|
|
say("build", "the builds for the merge could not be kept, and the walk will build: %v", err)
|
|
if err := with.Scratch.Discard(ctx, kept); err != nil {
|
|
say("build", "and what they made could not all be let go of: %v", err)
|
|
}
|
|
for i := range out {
|
|
if out[i].NotBuilt == "" {
|
|
out[i].NotBuilt = fmt.Sprintf("built, and could not be kept: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|