Every merge to the controller's repository planned the controller, the build seat's holder and the route proxy in three gated tiers, whatever it changed (issue 338). The planner now maps a merge's files onto the build source each module's newest trunk build said: a README moves nothing, the controller's command the controller alone, the proxy's program the proxy alone. A module with none said, or one an open plan has yet to build, is read whole as before. Sharing a repository draws no packages edge any more, and one recorded before neither widens nor orders a plan.
150 lines
5.3 KiB
Go
150 lines
5.3 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/builder"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// The kinds of edge in the catalogue's one dependency relation (novox/hq ADR 0162).
|
|
const (
|
|
// EdgeStandsOn: the module's artifact is built on the other's.
|
|
EdgeStandsOn = "stands-on"
|
|
// EdgePackages: the module's build read the other's repository. **No longer drawn** (novox/hq ADR 0267
|
|
// rule 4): sharing a repository is no dependency, and a changed file moves every module whose build
|
|
// source holds it, directly. Kept so an edge recorded or snapshotted before is read, and then neither
|
|
// widens nor orders a plan.
|
|
EdgePackages = "packages"
|
|
// EdgeBuiltBy: the module is built by the holder of the build-machine seat.
|
|
EdgeBuiltBy = "built-by"
|
|
// EdgeDeclared: the manifest's own `build.on`.
|
|
EdgeDeclared = "declared"
|
|
// EdgeWorkerOf: the module holds the build seat, whose worker the control plane defines
|
|
// (novox/hq issue 206). The one place a *running* order enters the graph: a build machine rolled
|
|
// before the controller that redefines its worker cannot bind it, and nothing can then build the
|
|
// controller that would end that — so the holder of the build seat follows the controller, and
|
|
// the controller is built by whichever build machine is running, as it always was.
|
|
EdgeWorkerOf = "worker-of"
|
|
)
|
|
|
|
// TheControlPlane is the module that defines every seat's worker on the bus.
|
|
const TheControlPlane = "mesh-controller"
|
|
|
|
// Edge is one dependency: From depends on To, in the way Kind says.
|
|
type Edge struct {
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
|
|
// Dependencies is the catalogue's dependency relation, whole: every module the mesh holds, with
|
|
// an edge to each module it depends on and the kind of dependency on the edge. One answer, so
|
|
// nothing else computes an edge (novox/hq ADR 0162) — the merge handler, `build --on` and the
|
|
// overview all read this.
|
|
//
|
|
// Three sources, one relation: a manifest's `build.on`; the artifacts the latest build was made
|
|
// against (an `artifact-store://<module>/…` reference is an edge to that module); and the build machine,
|
|
// which every source-built module is built by. The repositories a build read are no edge (novox/hq ADR
|
|
// 0267 rule 4).
|
|
func (i *Inventory) Dependencies(ctx context.Context) ([]Edge, error) {
|
|
entries, err := i.Catalogued(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
against, err := i.BuiltAgainst(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dependenciesOf(entries, against, nil), nil
|
|
}
|
|
|
|
// dependenciesOf is Dependencies over what was read, so a test can hand it a catalogue.
|
|
//
|
|
// `read` draws no edge (novox/hq ADR 0267 rule 4): what a build read moves it through its build source, in
|
|
// the planner, never through the relation.
|
|
func dependenciesOf(entries []Entry, against map[string][]string, _ map[string][]ReadRepository) []Edge {
|
|
known := map[string]bool{}
|
|
var builders []string
|
|
for _, e := range entries {
|
|
name := e.Manifest.Module
|
|
known[name] = true
|
|
if e.Manifest.ClaimsSeat("node-build-agent") || e.Manifest.ClaimsSeat("mesh-build-machine") {
|
|
builders = append(builders, name)
|
|
}
|
|
}
|
|
seen := map[Edge]bool{}
|
|
var out []Edge
|
|
add := func(from, to, kind string) {
|
|
if from == to || !known[to] {
|
|
return
|
|
}
|
|
e := Edge{From: from, To: to, Kind: kind}
|
|
if !seen[e] {
|
|
seen[e] = true
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
for _, e := range entries {
|
|
name := e.Manifest.Module
|
|
if e.Manifest.Build != nil {
|
|
for _, on := range e.Manifest.Build.On {
|
|
if on.Module != "" {
|
|
add(name, on.Module, EdgeDeclared)
|
|
}
|
|
}
|
|
// **A bundle stands on the toolchain it is compiled in** (novox/hq 04-ISSUES/211). A
|
|
// manifest names its toolchain by language, not in `build.on`, so the edge was implicit
|
|
// and a merge that moved the toolchain and a bundle together built both in one tier —
|
|
// the bundle against the toolchain as it was, recorded as built from the new commit. Read
|
|
// from the manifest, so it holds before any build has recorded what it stood on; and a
|
|
// toolchain that moves rebuilds every bundle compiled in it, which is what a toolchain
|
|
// carrying a bundle's dependencies requires.
|
|
for _, a := range e.Manifest.Build.Artifacts {
|
|
if a.Kind != catalogue.ArtifactBundle {
|
|
continue
|
|
}
|
|
if chain, err := builder.ToolchainFor(a.Language); err == nil {
|
|
add(name, chain.Base, EdgeStandsOn)
|
|
}
|
|
}
|
|
}
|
|
for _, ref := range against[name] {
|
|
if rest, ok := strings.CutPrefix(ref, catalogue.ArtifactStoreScheme); ok {
|
|
// A copy of an upstream image is no module's artifact (novox/hq ADR 0257): its
|
|
// repository is named for the image, and its first segment names no module.
|
|
if strings.HasPrefix(rest, builder.MirrorPrefix) {
|
|
continue
|
|
}
|
|
if base, _, found := strings.Cut(rest, "/"); found {
|
|
add(name, base, EdgeStandsOn)
|
|
}
|
|
}
|
|
}
|
|
if e.Source.Repository != "" {
|
|
for _, b := range builders {
|
|
add(name, b, EdgeBuiltBy)
|
|
}
|
|
}
|
|
}
|
|
if known[TheControlPlane] {
|
|
for _, b := range builders {
|
|
if b != TheControlPlane {
|
|
add(b, TheControlPlane, EdgeWorkerOf)
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(out, func(a, b int) bool {
|
|
if out[a].From != out[b].From {
|
|
return out[a].From < out[b].From
|
|
}
|
|
if out[a].To != out[b].To {
|
|
return out[a].To < out[b].To
|
|
}
|
|
return out[a].Kind < out[b].Kind
|
|
})
|
|
return out
|
|
}
|