package main import ( "context" "fmt" "slices" "sort" "strings" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) // **A module moves only when its program changes** (novox/hq issue 721). A merge check builds every module the // delivery plan moves and compares each with the build the mesh holds of it; a walk is cut without a module whose // check found its whole image the same as that build, while the mesh still holds it, and says so among the modules // it left unchanged. A merge of one sentence in a package a program imports but never reaches moves no build of it. // sameAsRunning are the modules of a walk its checks found the same as the build the mesh holds, each with what its // check said. **Only when every pull request the walk answers was checked at the head it merged, and every one of // those checks built the module and found it the same as the build the mesh still holds**: a merge the walk answers // without a pull request, a check of another head, a check that built it differently, or a held build that moved // since, leaves the module in the walk. func sameAsRunning(plan *inventory.Plan, builds []link.CheckedBuild, held map[string]string) map[string]string { if plan.Delivery == nil || len(plan.Delivery.Merges) == 0 { return nil } for _, m := range plan.Delivery.Merges { if _, ok := pullOf(m); !ok { return nil } } pulls := reuseAskOf(plan).Pulls heads := mergedHeadsOf(plan) out := map[string]string{} for name := range plan.Modules { said := "" for _, pull := range pulls { b, ok := checkedBuildOf(builds, pull, heads[pull], name) if !ok || !stillHeld(b.Built, name, held) { said = "" break } if said == "" { said = b.Built.Same } } if said != "" { out[name] = said } } if len(out) == 0 { return nil } return out } // checkedBuildOf is the newest build a check of the pull request at head made of the module; false when none did. func checkedBuildOf(builds []link.CheckedBuild, pull link.PullRef, head, module string) (link.CheckedBuild, bool) { if head == "" { return link.CheckedBuild{}, false } for _, b := range builds { if b.Pull == pull && b.Built.Module == module && link.SameHead(b.Head, head) { return b, true } } return link.CheckedBuild{}, false } // stillHeld is whether a check found every artifact of the module the same as a build the mesh still holds. func stillHeld(b link.CheckBuilt, module string, held map[string]string) bool { if b.Same == "" || b.NotBuilt != "" || len(b.Made) == 0 || len(b.SameAs) != len(b.Made) { return false } for _, m := range b.Made { was := pinnedDigest(b.SameAs[m.Name]) if was == "" || was != pinnedDigest(held[module+"/"+m.Name]) { return false } } return true } // pinnedDigest is a reference's pinned digest, empty when it is not pinned. func pinnedDigest(ref string) string { if i := strings.LastIndex(ref, "@"); i >= 0 { return ref[i+1:] } return "" } // leaveSameAsRunning takes from a walk being cut every module its checks found the same as the build the mesh holds // (sameAsRunning), 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 plan.Delivery == nil || len(plan.Modules) == 0 { return nil } pulls := reuseAskOf(plan).Pulls if len(pulls) == 0 { return nil } builds, err := checkedBuildsOf(ctx, pulls) if err != nil { fmt.Printf(" the checks' builds 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 } held, err := inv.RegisteredHeld(ctx) if err != nil { fmt.Printf(" what the mesh holds 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 } same := withDependentsOf(sameAsRunning(plan, builds, 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 }