The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
156 lines
5.4 KiB
Go
156 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// **A module moves only when its source changes** (novox/hq issue 721; from R4e by its fingerprint, ADR 0453 §6, ADR
|
|
// 0499; issue 1851, A9). A walk is cut without a module whose fingerprint at the commit the walk carries is the
|
|
// fingerprint of the build the mesh holds of it, and says so among the modules it left unchanged: a merge of a file no
|
|
// module reads moves nothing. A build the mesh holds of the old form (build-agent's `src1:`) has no fingerprint to
|
|
// compare: the module stays in the walk, and the rule of the standing build (equal digests, no move) judges it once its
|
|
// key is taken.
|
|
|
|
// sameByFingerprint are the modules whose fingerprint now (`src2:`, the indexer's trunk value at the walk's commit) is
|
|
// the fingerprint of the build the mesh holds, each with why; one whose either is unknown, or of the old form, is not.
|
|
func sameByFingerprint(now, held map[string]string) map[string]string {
|
|
out := map[string]string{}
|
|
for m, fp := range now {
|
|
if strings.HasPrefix(fp, "src2:") && held[m] == fp {
|
|
out[m] = fmt.Sprintf("unchanged: its fingerprint %s is the build the mesh holds", short(fp))
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
// heldFingerprint is the source fingerprint of the build the mesh holds of a module: the newest build of the commit
|
|
// it is registered at.
|
|
func heldFingerprint(ctx context.Context, inv *inventory.Inventory, module string) (string, error) {
|
|
src, err := inv.SourceOf(ctx, module)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fps, err := inv.BuildSourceFingerprints(ctx, module)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for commit, fp := range fps {
|
|
if sameCommitID(commit, src.BuiltFrom) {
|
|
return fp, nil
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// leaveSameAsRunning takes from a walk being cut every module whose fingerprint is unchanged (sameByFingerprint), and
|
|
// every dependent in the walk only for one of those (withDependentsOf): what was taken, with why. **Anything not read
|
|
// leaves the walk as it was**, said.
|
|
func leaveSameAsRunning(ctx context.Context, inv *inventory.Inventory, plan *inventory.Plan, moved []string,
|
|
edges []inventory.Edge) map[string]string {
|
|
if len(plan.Modules) == 0 {
|
|
return nil
|
|
}
|
|
entries, err := inv.Catalogued(ctx)
|
|
if err != nil {
|
|
fmt.Printf(" the catalogue could not be read, so every module moves: %v\n", err)
|
|
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
|
|
return nil
|
|
}
|
|
keys, closeKeys, err := openPlanKeys()
|
|
if err != nil {
|
|
fmt.Printf(" the indexer's fingerprints could not be read, so every module moves: %v\n", err)
|
|
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
|
|
return nil
|
|
}
|
|
defer closeKeys()
|
|
byName := map[string]inventory.Entry{}
|
|
for _, e := range entries {
|
|
byName[e.Manifest.Module] = e
|
|
}
|
|
now, held := map[string]string{}, map[string]string{}
|
|
for m := range plan.Modules {
|
|
e, known := byName[m]
|
|
if !known {
|
|
continue
|
|
}
|
|
v, err := keys.ModuleAt(ctx, m, plan.CommitOn(e.Source.Repository, followedBranch(e.Source.Ref)))
|
|
if err != nil || v.FP2 == "" {
|
|
continue // not written yet, or none: the module moves, and its key is waited for in the walk
|
|
}
|
|
fp, err := heldFingerprint(ctx, inv, m)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
now[m], held[m] = v.FP2, fp
|
|
}
|
|
same := withDependentsOf(sameByFingerprint(now, held), plan, moved, edges)
|
|
if len(same) == 0 {
|
|
return nil
|
|
}
|
|
names := make([]string, 0, len(same))
|
|
for n := range same {
|
|
names = append(names, n)
|
|
}
|
|
sort.Strings(names)
|
|
withhold(plan, names, nil)
|
|
for _, n := range names {
|
|
fmt.Printf(" %s left out: %s\n", n, same[n])
|
|
}
|
|
return same
|
|
}
|
|
|
|
// withDependentsOf adds to the modules found the same every module of the walk that is in it only as a dependent of
|
|
// one of them (novox/hq ADR 0267 rule 3): **a dependent is left out when its base comes out the same**, since the
|
|
// base it stands on stays the build the mesh holds. A module a merge moves itself, or a dependent of a base that
|
|
// still moves, stays. moved are the modules the walk's merges move; edges the graph the walk was planned along.
|
|
func withDependentsOf(same map[string]string, plan *inventory.Plan, moved []string, edges []inventory.Edge) map[string]string {
|
|
if len(same) == 0 {
|
|
return same
|
|
}
|
|
var still []string
|
|
for _, n := range moved {
|
|
if same[n] == "" {
|
|
still = append(still, n)
|
|
}
|
|
}
|
|
keep := map[string]bool{}
|
|
for _, n := range reachableFrom(still, edges) {
|
|
keep[n] = true
|
|
}
|
|
// Until nothing more is found: a dependent of a dependent left out is left out too.
|
|
for grew := true; grew; {
|
|
grew = false
|
|
for n := range plan.Modules {
|
|
if keep[n] || same[n] != "" {
|
|
continue
|
|
}
|
|
var bases []string
|
|
for _, e := range edges {
|
|
ordering := e.Kind == inventory.EdgeBuiltBy || e.Kind == inventory.EdgeWorkerOf ||
|
|
e.Kind == inventory.EdgePackages || e.Kind == edgeChangeOrder
|
|
if !ordering && e.From == n && same[e.To] != "" && !slices.Contains(bases, e.To) {
|
|
bases = append(bases, e.To)
|
|
}
|
|
}
|
|
sort.Strings(bases)
|
|
if len(bases) == 0 {
|
|
// In the walk for no base found the same: whatever brought it in is not said here, so it moves.
|
|
continue
|
|
}
|
|
same[n] = "unchanged: it is in the walk only for " + strings.Join(bases, ", ") +
|
|
", which came out the same as the build the mesh holds (ADR 0267 rule 3)"
|
|
grew = true
|
|
}
|
|
}
|
|
return same
|
|
}
|