Files
mesh-delivery/modules/mesh-delivery-builder/internal/build/waits.go
T
jschoubben 71a35a6376 MV1: move contracts and sdk under modules/, importers follow
The delivery repository's contracts and SDK live under modules/ as
mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths
renamed to match the folders (the TypeScript names already did), and
every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools
and mesh-sdk follows in the same group: a design as it should be, one
path per module, checked by the group's check building every importer.

Builds: design 47 G §427, §88

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:09:56 +01:00

126 lines
4.6 KiB
Go

package build
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
sdkstep "git.novox.be/novox/mesh-delivery/modules/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
}