The builder finds a build module as the indexer's seed does: the build seat whose acting rule `build` has an `on` matching the artifact's language or kind:image, and that seat's holder, read from the seat list's definitions and the claims of the catalog half's manifests. Task.Module, every lane key, the unpublished modules, the toolchain sources, the ends watched, the sayer and builder_stuck (through `<seat>.steps`) derive from it; the floor's sources are the holders of the seats git and mesh-catalog. buildrun.GoBuildModule, NodeBuildModule, LanguageOf, buildstep.BuildModuleOf use, build.ImageBuildModule and StuckBuildModules are gone from the builder (the checker keeps its own names in two small files until its part reads them from the list). Builds: design 47 G §7, §428, §704, §204, A-82 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
126 lines
4.6 KiB
Go
126 lines
4.6 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
|
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
|
)
|
|
|
|
// **The builder's toolchain wait** (design 47 G §672, §267, §276): the builder holds an entry while its fingerprint names a
|
|
// toolchain version of its language other than the one the agreed toolchain of that language names.
|
|
const (
|
|
// WaitsTool is builder_waits, which reads the waits; ReleaseTool is builder_release, which builds the entry with the
|
|
// running build module (§276), a hand act written in the hand-act log.
|
|
WaitsTool = "builder_waits"
|
|
ReleaseTool = "builder_release"
|
|
// WaitsState is the builder's old state section of the waits, one key <module>.<fingerprint>.
|
|
WaitsState = "waits"
|
|
WaitsBucket = builds.BuilderModule + "_" + WaitsState
|
|
// ModeRelease is the Mode of a reasks value of builder_release.
|
|
ModeRelease = "release"
|
|
// WaitRan ends a wait when a toolchain-agreed names the version; WaitReleased when builder_release ended it.
|
|
WaitRan = "ran"
|
|
WaitReleased = "released"
|
|
)
|
|
|
|
// ToolchainWait is one wait: the entry's module and fingerprint, and the toolchain it waits for.
|
|
type ToolchainWait struct {
|
|
Module string `json:"module"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Language string `json:"language"`
|
|
Version string `json:"version"`
|
|
Since time.Time `json:"since"`
|
|
}
|
|
|
|
// On is what the wait waits for, as the wait's `on` says it.
|
|
func (w ToolchainWait) On() string { return "toolchain:" + w.Language + "@" + w.Version }
|
|
|
|
// Waits keeps the builder's waits: Hold writes one in the state waits and says wait-held, once per wait; End deletes it and
|
|
// says wait-ended with how, and answers whether one was held.
|
|
type Waits interface {
|
|
Hold(ctx context.Context, w ToolchainWait)
|
|
End(ctx context.Context, module, fp, how string) bool
|
|
}
|
|
|
|
// toolchainWait is the toolchain wait of a module's fingerprint, or false when no language of its plan names a version
|
|
// other than the agreed one (a language with no agreed toolchain is not a wait of §672).
|
|
func (b *Builder) toolchainWait(module string, m meshcatalog.ModulesByModuleValue, plan Plan) (ToolchainWait, bool) {
|
|
for _, bm := range plan.Modules() {
|
|
if !plan.NeedsToolchain(bm) {
|
|
continue
|
|
}
|
|
language, _ := b.Seats.LanguageOf(bm)
|
|
ag := sdkstep.ToolchainAgreement(b.Toolchains.Entries(language))
|
|
if want, ok := m.Toolchains[language]; ag.Wait == "" && ok && want.Version != ag.Toolchain.Version {
|
|
return ToolchainWait{Module: module, Fingerprint: m.Fp2, Language: language, Version: want.Version, Since: b.now()}, true
|
|
}
|
|
}
|
|
return ToolchainWait{}, false
|
|
}
|
|
|
|
// Release builds the held entry of module at fp with the running build module, now, and ends its wait released (design 47
|
|
// G §276, §672). It answers why nothing was done, or empty.
|
|
func (b *Builder) Release(ctx context.Context, module, fp, why, by string) string {
|
|
b.init()
|
|
key, err := builds.BuilderBuildKey(module, fp)
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
if b.Waits == nil {
|
|
return "this builder keeps no waits"
|
|
}
|
|
b.mu.Lock()
|
|
m, known := b.known[module]
|
|
b.mu.Unlock()
|
|
if !known || m.Fp2 != fp || m.Manifest == "" {
|
|
return fmt.Sprintf("module %s does not have the fingerprint %s now: the builder builds the module's current fingerprint", module, fp)
|
|
}
|
|
b.mu.Lock()
|
|
if b.released == nil {
|
|
b.released = map[string]bool{}
|
|
}
|
|
b.released[key] = true
|
|
b.mu.Unlock()
|
|
if !b.Waits.End(ctx, module, fp, WaitReleased) {
|
|
b.mu.Lock()
|
|
delete(b.released, key)
|
|
b.mu.Unlock()
|
|
return fmt.Sprintf("%s holds no toolchain wait: %s builds a failed key again", key, BuildAgainTool)
|
|
}
|
|
b.logf("%s: released by %s (%s): built with the running build module", key, by, why)
|
|
b.startOnce(ctx, key, module, m)
|
|
return ""
|
|
}
|
|
|
|
func (b *Builder) isReleased(key string) bool {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.released[key]
|
|
}
|
|
|
|
func (b *Builder) unrelease(key string) {
|
|
b.mu.Lock()
|
|
delete(b.released, key)
|
|
b.mu.Unlock()
|
|
}
|
|
|
|
// NewRelease is the value builder_release writes for a module's fingerprint: its key and the value.
|
|
func NewRelease(module, fp, why, by string, at time.Time) (string, []byte, error) {
|
|
key, err := builds.BuilderBuildKey(module, fp)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if strings.TrimSpace(why) == "" {
|
|
return "", nil, errors.New("say why the entry is released (why)")
|
|
}
|
|
data, err := json.Marshal(ReAsk{V: 1, Why: strings.TrimSpace(why), By: by, At: at.UTC(), Mode: ModeRelease, Module: module, FP: fp})
|
|
return key, data, err
|
|
}
|