A manifest names its toolchain by language, not in build.on, so the planner did not know a bundle depends on the module that publishes its toolchain and built the two in one tier: the bundle against the old toolchain, recorded as built from the new commit. The edge is read from the manifest, so it holds before any build recorded it, and a toolchain that moves rebuilds every bundle compiled in it.
157 lines
5.3 KiB
Go
157 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 reads the other's repository.
|
|
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.
|
|
//
|
|
// Four 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); the repositories
|
|
// the latest build read (an edge to the module whose source that is); and the build machine, which
|
|
// every source-built module is built by.
|
|
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
|
|
}
|
|
read, err := i.ReadRepositories(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dependenciesOf(entries, against, read), nil
|
|
}
|
|
|
|
// dependenciesOf is Dependencies over what was read, so a test can hand it a catalogue.
|
|
func dependenciesOf(entries []Entry, against map[string][]string, read map[string][]ReadRepository) []Edge {
|
|
known := map[string]bool{}
|
|
byRepository := map[string][]string{}
|
|
var builders []string
|
|
for _, e := range entries {
|
|
name := e.Manifest.Module
|
|
known[name] = true
|
|
if r := repositoryKey(e.Source.Repository); r != "" {
|
|
byRepository[r] = append(byRepository[r], name)
|
|
}
|
|
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 {
|
|
if base, _, found := strings.Cut(rest, "/"); found {
|
|
add(name, base, EdgeStandsOn)
|
|
}
|
|
}
|
|
}
|
|
for _, r := range read[name] {
|
|
for _, other := range byRepository[repositoryKey(r.Repository)] {
|
|
add(name, other, EdgePackages)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
// repositoryKey is a repository as compared: lower-cased, without a trailing `.git`.
|
|
func repositoryKey(repository string) string {
|
|
return strings.ToLower(strings.TrimSuffix(strings.TrimSpace(repository), ".git"))
|
|
}
|