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 || isBuildersOwn(m) {
|
|
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
|
|
}
|