Files
mesh-delivery/modules/mesh-delivery-builder/buildrun/toolchains.go
T
jschoubben 18f978e852 BL8: the builder hears the build seats' toolchain-reported, keeps them in toolchains-heard and says toolchain-agreed with digests
The builder agrees a language's toolchain from the three build seats'
toolchain-reported, kept in its state toolchains-heard (read back at its start),
forgets a node's on the node-engine seat's running with no fingerprint (stopped)
and never on a placement, says toolchain-agreed with its digests list only when
the agreed version changes; builder_toolchains and builder_toolchains_drop;
the manifest's reads of the build modules' toolchains are gone. The gate over
the assignments is now PlacedNodes, which only counts nodes.

Builds: design 47 G §492, §604, §546, §472, §574
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:49:02 +01:00

94 lines
3.1 KiB
Go

package buildrun
import (
"sort"
"sync"
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// LanguageOf is the language each build module's toolchain reports hold.
var LanguageOf = map[string]string{GoBuildModule: "go", NodeBuildModule: "typescript"}
// ToolchainsHeardState is the builder's old state section of the toolchains it heard (design 47 G §604, §546): one key
// <node>.<language>, the toolchain as the build seat said it.
const (
ToolchainsHeardState = "toolchains-heard"
ToolchainsHeardBucket = "mesh-delivery-builder_" + ToolchainsHeardState
)
// ToolchainSet holds each node's toolchain of a language, as the builder heard it (design 47 G §604). Safe for use from
// the consumer and from a build at once.
type ToolchainSet struct {
mu sync.Mutex
by map[string]map[string]buildstep.Toolchain // language -> node -> entry
}
// Set takes a node's toolchain in and answers whether the language's entries changed (a node's version, recipe or
// digest, or a node appearing).
func (s *ToolchainSet) Set(node string, tc buildstep.Toolchain) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.by == nil {
s.by = map[string]map[string]buildstep.Toolchain{}
}
if s.by[tc.Language] == nil {
s.by[tc.Language] = map[string]buildstep.Toolchain{}
}
cur, had := s.by[tc.Language][node]
s.by[tc.Language][node] = tc
return !had || cur.Version != tc.Version || cur.Recipe != tc.Recipe || cur.Digest != tc.Digest
}
// Delete takes a node's toolchain of a language out and answers whether it was held.
func (s *ToolchainSet) Delete(node, language string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, had := s.by[language][node]; !had {
return false
}
delete(s.by[language], node)
return true
}
// Entries are the nodes' entries of a language, in no particular order.
func (s *ToolchainSet) Entries(language string) []buildstep.NodeToolchain {
s.mu.Lock()
defer s.mu.Unlock()
var out []buildstep.NodeToolchain
for node, tc := range s.by[language] {
out = append(out, buildstep.NodeToolchain{Node: node, Toolchain: tc})
}
return out
}
// AgreedDigest is a digest of the agreed version and the nodes reporting it.
type AgreedDigest struct {
Digest string
Nodes []string
}
// Digests are the digests the nodes reporting the agreed toolchain's version report, sorted by digest, each with its
// nodes sorted (design 47 G §472, §574). A node that reports no digest is left out.
func Digests(entries []buildstep.NodeToolchain, agreed buildstep.Toolchain) []AgreedDigest {
by := map[string][]string{}
for _, e := range entries {
if e.Version == agreed.Version && e.Recipe == agreed.Recipe && e.Digest != "" {
by[e.Digest] = append(by[e.Digest], e.Node)
}
}
out := make([]AgreedDigest, 0, len(by))
for d, nodes := range by {
sort.Strings(nodes)
out = append(out, AgreedDigest{Digest: d, Nodes: nodes})
}
sort.Slice(out, func(i, j int) bool { return out[i].Digest < out[j].Digest })
return out
}
// The builder's tools over the toolchains it heard (design 47 G §604, §546).
const (
ToolchainsTool = "builder_toolchains"
ToolchainsDropTool = "builder_toolchains_drop"
)