109 lines
3.9 KiB
Go
109 lines
3.9 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery/buildrun"
|
|
)
|
|
|
|
// How a candidate's fingerprint is built (novox/hq ADR 0484 clause 3, issue 1684): the indexer writes, for each module a
|
|
// pull request touches, the fingerprint of the new form it has at the head merged onto the trunk (candidates.<group>), and
|
|
// the builder builds each, once, as a key of its own (builds.<module>.<fingerprint>) that the checker's line reads. The
|
|
// build is the trunk module's: the same build modules, the same nodes and capacity; what differs is where the source comes
|
|
// from: the fetch step is given the pull request's head, the ref that carries it and the trunk commit to merge it onto, as
|
|
// the checker's fetch is, and builds the merge's module directory.
|
|
|
|
// via is how a build's fetch step reaches its source when it is a candidate's: the ref that carries the head and the trunk
|
|
// commit the head is merged onto.
|
|
type via struct{ Ref, Base string }
|
|
|
|
// Candidate takes one value of the indexer's candidates bucket in (the key is the group) and builds the fingerprints it
|
|
// carries. A value that is not a candidate is said and left.
|
|
func (b *Builder) Candidate(ctx context.Context, key string, value []byte) {
|
|
b.init()
|
|
c, err := delivery.DecodeCandidate(value)
|
|
if err != nil {
|
|
b.logf("candidates.%s holds what is not a candidate, left: %v", key, err)
|
|
return
|
|
}
|
|
b.mu.Lock()
|
|
b.cands[key] = c
|
|
b.mu.Unlock()
|
|
b.considerCandidate(ctx, c)
|
|
}
|
|
|
|
// ForgetCandidate takes a candidate out (its key was deleted).
|
|
func (b *Builder) ForgetCandidate(key string) {
|
|
b.init()
|
|
b.mu.Lock()
|
|
delete(b.cands, key)
|
|
b.mu.Unlock()
|
|
}
|
|
|
|
func (b *Builder) considerCandidate(ctx context.Context, c delivery.Candidate) {
|
|
for _, p := range c.Pulls {
|
|
names := make([]string, 0, len(p.Fps))
|
|
for name := range p.Fps {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
cm := p.Fps[name]
|
|
if cm.FP2 == "" || cm.Manifest == "" {
|
|
continue // no fingerprint of the new form for it (a reason in none2) or no manifest to build by
|
|
}
|
|
b.mu.Lock()
|
|
trunk, known := b.known[name]
|
|
b.mu.Unlock()
|
|
if !known || trunk.Path == "" {
|
|
b.logf("candidates.%s: the module %s has no record of the trunk yet, so its fingerprint %s is not built", c.Group, name, cm.FP2)
|
|
continue
|
|
}
|
|
key, err := delivery.BuilderBuildKey(name, cm.FP2)
|
|
if err != nil {
|
|
b.logf("candidates.%s: %v", c.Group, err)
|
|
continue
|
|
}
|
|
m := delivery.Module{Repository: p.Repository, Commit: p.Head.Head, FP2: cm.FP2, Manifest: cm.Manifest, Path: trunk.Path,
|
|
Toolchains: b.agreedFor(ctx, cm.Manifest)}
|
|
if len(cm.Paths) > 0 {
|
|
// The named paths' objects the preview gave it: the fetch names the paths (runLane, ADR 0499 clause 4).
|
|
m.Inputs2 = &delivery.KeptInputs2{Paths: cm.Paths}
|
|
}
|
|
b.mu.Lock()
|
|
if _, onTrunk := b.trunkFP[key]; !onTrunk {
|
|
b.via[key] = via{Ref: fmt.Sprintf("refs/pull/%d/head", p.Number), Base: trunk.Commit}
|
|
}
|
|
b.mu.Unlock()
|
|
b.consider(ctx, name, m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// agreedFor are the toolchains the build nodes agree on now, for the languages of the manifest's plan: the toolchains the
|
|
// candidate's fingerprint was computed with, as the indexer recomputes it whenever they change. A language with none agreed
|
|
// is left out, and the build then waits for it as the trunk's does.
|
|
func (b *Builder) agreedFor(ctx context.Context, manifest string) map[string]delivery.ToolchainUsed {
|
|
plan, err, _ := b.planOf(ctx, manifest)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
out := map[string]delivery.ToolchainUsed{}
|
|
for _, bm := range plan.Modules() {
|
|
if !plan.NeedsToolchain(bm) {
|
|
continue
|
|
}
|
|
language := buildrun.LanguageOf[bm]
|
|
if tc, none := delivery.AgreedToolchain(b.Toolchains.Entries(language)); none == "" {
|
|
out[language] = delivery.ToolchainUsed{Version: tc.Version, Recipe: tc.Recipe}
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|