mesh/merge-gate head 38c87a7e4714 merged into trunk c2ec0a601acb — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 38c87a7e4714 merged into trunk c2ec0a601acb — fail: its merge-check.sh failed: --- FAIL: TestAWalkWhoseEveryBuildCameOutTheSameIsDone …
mesh/delivery-group group fix/a-module-moves-only-when-its-program-changes rejected: a member's own check failed
mesh/delivery superseded: a newer head of the same pull request
177 lines
6.1 KiB
Go
177 lines
6.1 KiB
Go
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 == edgeGroupOrder
|
|
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
|
|
}
|