What the running builds serve is read when a changed build module is met: the node-engine's haves, the builder's build of each, its manifest. The modules it builds are composed from their manifests, as for any changed build module, and the composition's warnings name the verb. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
143 lines
6.4 KiB
Go
143 lines
6.4 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
|
)
|
|
|
|
// What the check builds of a candidate before it composes (novox/hq ADR 0603 §10). The builder builds every fingerprint the
|
|
// indexer's candidate carries, as a key of its own (builds.<module>.<fingerprint>), with the build modules that run; the
|
|
// check adds no build path of its own and waits on those keys (awaitBuilds), so the node-engines compose the candidate's
|
|
// modules with their builds. It does not wait on two kinds of changed module, and names them in its composition's warnings:
|
|
//
|
|
// - one built by a build module the change alters (the recipe of mesh-build-go or mesh-build-node enters the fingerprint
|
|
// of every module it builds, ADR 0530 §1): its candidate build is the running build module's, and the check cannot run
|
|
// the candidate's build module to build it (it runs it only for its own digest and tests, ADR 0535 §1), so it is not
|
|
// composed with a build of the changed build module; it is built again once the agreed toolchain moves (ADR 0603 §3).
|
|
// The build module's own build is waited on, as any other: the running build module builds its new source.
|
|
// - one that stands on a module the change alters (build.on): its candidate fingerprint names the base's trunk
|
|
// fingerprint, and no build module builds on another module's artifact (ADR 0487 §1), so its build stands on the
|
|
// trunk's base; the base's own build is waited on.
|
|
//
|
|
// A module the trunk holds no record of yet is not built by the builder until it is merged, so it is not waited on either.
|
|
//
|
|
// A changed build module whose manifest invokes a seat verb or module tool no running build serves is one the check cannot
|
|
// run (ADR 0614 §7): the modules it builds are composed from their manifests, as above, and the words name the verb.
|
|
|
|
// buildsOfTheCheck are the builds the check waits on before it composes (module to fingerprint) and the words for the
|
|
// changed modules it composes without waiting on their build, one line each, sorted. A manifest that cannot be read leaves
|
|
// every build waited on and nothing said of build modules or bases (nothing is said that was not read).
|
|
func (st *state) buildsOfTheCheck(ctx context.Context) (map[string]string, []string) {
|
|
wait := map[string]string{}
|
|
changed := map[string]catalogue.Manifest{}
|
|
readable := true
|
|
for _, p := range st.c.Pulls {
|
|
for module, cm := range p.Fps {
|
|
if cm.FP2 == "" || cm.Manifest == "" {
|
|
continue // the builder builds no fingerprint of it (candidates.go: none of the new form, or no manifest)
|
|
}
|
|
wait[module] = cm.FP2
|
|
if !readable || st.j.Read == nil {
|
|
readable = false
|
|
continue
|
|
}
|
|
m, err := st.j.Read.Manifest(ctx, cm.Manifest)
|
|
if err != nil {
|
|
st.j.logf("%s: the manifest of %s cannot be read, so the modules built with a changed build module or base are not named and every build is waited on: %v", st.c.Key, module, err)
|
|
readable = false
|
|
continue
|
|
}
|
|
changed[module] = m
|
|
}
|
|
}
|
|
var words []string
|
|
if st.j.Runs != nil {
|
|
var unheld []string
|
|
for _, module := range slices.Sorted(maps.Keys(wait)) {
|
|
if st.j.Runs.CommitOf(module) == "" {
|
|
unheld = append(unheld, module)
|
|
delete(wait, module)
|
|
}
|
|
}
|
|
if len(unheld) > 0 {
|
|
words = append(words, fmt.Sprintf("%s: the trunk holds no record of it yet, so the builder builds it once it is merged and the check composes it without a build (ADR 0603 §10)",
|
|
strings.Join(unheld, ", ")))
|
|
}
|
|
}
|
|
if !readable {
|
|
return wait, words
|
|
}
|
|
for _, bm := range slices.Sorted(maps.Keys(buildrun.LanguageOf)) {
|
|
if _, alters := changed[bm]; !alters {
|
|
continue
|
|
}
|
|
var built []string
|
|
for module, m := range changed {
|
|
if _, isBuildModule := buildrun.LanguageOf[module]; !isBuildModule && buildsWith(m, buildrun.LanguageOf[bm]) {
|
|
built = append(built, module)
|
|
delete(wait, module)
|
|
}
|
|
}
|
|
slices.Sort(built)
|
|
if len(built) > 0 {
|
|
words = append(words, fmt.Sprintf("the change alters the build module %s: %s, which it builds, are built by the running %s, not the changed one, which the check cannot run, so the check does not wait on their builds; they are built again once the agreed toolchain moves (ADR 0603 §3, §10)",
|
|
bm, strings.Join(built, ", "), bm))
|
|
}
|
|
if unserved := st.unservedVerbs(ctx, changed[bm]); len(unserved) > 0 {
|
|
what := "nothing it builds is in the change"
|
|
if len(built) > 0 {
|
|
what = strings.Join(built, ", ") + ", which it builds, are composed from their manifests"
|
|
}
|
|
words = append(words, fmt.Sprintf("the changed build module %s invokes %s, which no running build serves, so the check cannot run it: %s (ADR 0614 §7)",
|
|
bm, strings.Join(unserved, ", "), what))
|
|
}
|
|
}
|
|
g := graph.OfManifests(changed)
|
|
for _, base := range slices.Sorted(maps.Keys(changed)) {
|
|
if on := g.Dependents(base); len(on) > 0 {
|
|
for _, module := range on {
|
|
delete(wait, module)
|
|
}
|
|
words = append(words, fmt.Sprintf("the change alters %s, a base of %s: their builds stand on the trunk's %s, since no build module builds on a candidate's base, so the check waits on the build of %s alone (ADR 0603 §10)",
|
|
base, strings.Join(on, ", "), base, base))
|
|
}
|
|
}
|
|
return wait, words
|
|
}
|
|
|
|
// buildsWith says whether a manifest has a bundle in the language: the artifacts whose fingerprint names that language's
|
|
// toolchain and its build module's recipe (an archive is packed with no toolchain, ADR 0478 clause 1).
|
|
func buildsWith(m catalogue.Manifest, language string) bool {
|
|
return m.Build != nil && slices.ContainsFunc(m.Build.Artifacts, func(a catalogue.Artifact) bool {
|
|
return a.Kind == catalogue.ArtifactBundle && a.Language == language
|
|
})
|
|
}
|
|
|
|
// unservedVerbs are the seat verbs and module tools m invokes (graph.InvokesOf) that no running build serves, sorted; none
|
|
// when nothing reads what runs, or what runs cannot be read (said in the log: nothing is said that was not read).
|
|
func (st *state) unservedVerbs(ctx context.Context, m catalogue.Manifest) []string {
|
|
if st.j.Serving == nil {
|
|
return nil
|
|
}
|
|
served, err := st.j.Serving.Served(ctx)
|
|
if err != nil {
|
|
st.j.logf("%s: what the running builds serve cannot be read, so no build module is named for a verb it invokes: %v", st.c.Key, err)
|
|
return nil
|
|
}
|
|
var out []string
|
|
for _, v := range graph.InvokesOf(m) {
|
|
if !served(v) {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|