DLV-I20 (builder): a build's end names the generated files its build module found out of step
The builder reads out-of-step from each lane's step result, a failed step's too, and writes it on the build's end entry (BuildLogEntry.OutOfStep, J3's mesh-sdk field), where the indexer raises generated-types-out-of-step for a trunk (the lead's ruling on A2i's finding 2). BuildAnswer names no such field yet, so it is read by name and shape. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -34,3 +34,45 @@ func importsABuildAnswersReachTheDoneKeyAndTheLog(t *testing.T) {
|
||||
t.Fatalf("log %+v, want the imports on the end alone", logged)
|
||||
}
|
||||
}
|
||||
|
||||
// Design 26 (the lead's ruling on A2i's finding 2): the out-of-step files a contracts module's build module answers, in a
|
||||
// failed step's result too, reach the log's end entry, sorted and each once, where the indexer raises its finding; a
|
||||
// build that answers none names none.
|
||||
func outOfStepFilesABuildAnswersReachTheLog(t *testing.T) {
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
fail bool
|
||||
files []string
|
||||
want string
|
||||
}{
|
||||
{"a passed build naming files", false, []string{"go/b.go", "go/a.go", "go/b.go"}, "go/a.go,go/b.go"},
|
||||
{"a failed build naming files", true, []string{"ts/x.ts"}, "ts/x.ts"},
|
||||
{"a build naming none", false, nil, ""},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
r := newRig(t, goBundle)
|
||||
r.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
step := r.answer(a)
|
||||
answer := map[string]any{}
|
||||
_ = json.Unmarshal(step.Result, &answer)
|
||||
if row.files != nil {
|
||||
answer["out-of-step"] = row.files
|
||||
}
|
||||
step.Result, _ = json.Marshal(answer)
|
||||
if row.fail {
|
||||
step.State, step.Reason = delivery.StepFailed, "the generated files are out of step"
|
||||
}
|
||||
return step
|
||||
}
|
||||
var logged []delivery.BuildLogEntry
|
||||
var mu sync.Mutex
|
||||
r.b.Node, r.b.Log = "novox", func(e delivery.BuildLogEntry) { mu.Lock(); logged = append(logged, e); mu.Unlock() }
|
||||
r.consider(t, "modules/m")
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(logged) != 2 || strings.Join(logged[1].OutOfStep, ",") != row.want || len(logged[0].OutOfStep) != 0 {
|
||||
t.Fatalf("log %+v, want the end naming %q", logged, row.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,10 @@ type Builder struct {
|
||||
plans map[string]planned // by manifest digest
|
||||
cands map[string]delivery.Candidate // by candidates key (candidates.go)
|
||||
via map[string]via // by build key: a candidate's fetch (candidates.go)
|
||||
trunkFP map[string]bool // by build key: a trunk module value carries this fingerprint
|
||||
tried map[string]bool // by build key: looked at since this process started
|
||||
// outOfStep are, by build key, the out-of-step files a build's lanes answered, until its end entry takes them.
|
||||
outOfStep map[string][]string
|
||||
trunkFP map[string]bool // by build key: a trunk module value carries this fingerprint
|
||||
tried map[string]bool // by build key: looked at since this process started
|
||||
// published are the package versions `<name>@<version>` a done key of this bucket names as a package's reference: a key
|
||||
// waiting for one is built once it is here (ADR 0500 clause 6).
|
||||
published map[string]bool
|
||||
@@ -490,7 +492,9 @@ func (b *Builder) try(ctx context.Context, module string, m delivery.Module) {
|
||||
b.logf("%s: the build could not be written: %v", cl.Key, err)
|
||||
return
|
||||
}
|
||||
b.logBuild(LogEnded(module, m, b.refOf(module, m.FP2), b.Node, b.now(), result))
|
||||
ended := LogEnded(module, m, b.refOf(module, m.FP2), b.Node, b.now(), result)
|
||||
ended.OutOfStep = b.takeOutOfStep(key) // the generated files the build module's compare found out of step (design 26)
|
||||
b.logBuild(ended)
|
||||
if result.State == delivery.BuildFailed {
|
||||
b.logf("%s: failed: %s", cl.Key, result.Error)
|
||||
}
|
||||
@@ -536,6 +540,7 @@ type lane struct {
|
||||
digests map[string]string // artifact -> digest
|
||||
references map[string]string // artifact -> the reference an image was pushed to (a trunk build only)
|
||||
imports []string // the mesh packages the lane's programs import, as its build module answered them
|
||||
outOfStep []string // the generated files its build module's compare found out of step, passed or failed
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -591,6 +596,9 @@ func (b *Builder) build(ctx context.Context, module string, m delivery.Module, p
|
||||
go func() { defer wg.Done(); b.runLane(ctx, l, repository, m, module, asked) }()
|
||||
}
|
||||
wg.Wait()
|
||||
if key, err := delivery.BuilderBuildKey(module, m.FP2); err == nil {
|
||||
b.keepOutOfStep(key, lanes)
|
||||
}
|
||||
var errs []string
|
||||
for _, l := range lanes {
|
||||
if l.err != nil {
|
||||
@@ -861,6 +869,15 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m del
|
||||
return
|
||||
}
|
||||
l.node = step.Node
|
||||
// The build module's compare of a contracts module's generated files (design 26, CAT-BG5/BN2) answers the files out
|
||||
// of step as `out-of-step`, in a failed step's result too. Read beside the build's answer: the SDK's BuildAnswer
|
||||
// names no such field yet (a request for mesh-sdk), so it is read by its name and shape here.
|
||||
var compared struct {
|
||||
OutOfStep []string `json:"out-of-step"`
|
||||
}
|
||||
if len(step.Result) > 0 && json.Unmarshal(step.Result, &compared) == nil {
|
||||
l.outOfStep = compared.OutOfStep
|
||||
}
|
||||
if step.State != delivery.StepPassed {
|
||||
l.err = fmt.Errorf("%s on %s: the ask %s %s: %s", l.module, step.Node, ask.ID, step.State, step.Reason)
|
||||
return
|
||||
@@ -959,6 +976,36 @@ func LogEnded(module string, m delivery.Module, ref, by string, at time.Time, re
|
||||
return e
|
||||
}
|
||||
|
||||
// keepOutOfStep holds, by build key, the out-of-step files the lanes' build modules answered, sorted and each once, until
|
||||
// the build's end entry takes them (takeOutOfStep); a build with none holds nothing.
|
||||
func (b *Builder) keepOutOfStep(key string, lanes []*lane) {
|
||||
seen := map[string]bool{}
|
||||
for _, l := range lanes {
|
||||
for _, f := range l.outOfStep {
|
||||
seen[f] = true
|
||||
}
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if len(seen) == 0 {
|
||||
delete(b.outOfStep, key)
|
||||
return
|
||||
}
|
||||
if b.outOfStep == nil {
|
||||
b.outOfStep = map[string][]string{}
|
||||
}
|
||||
b.outOfStep[key] = slices.Sorted(maps.Keys(seen))
|
||||
}
|
||||
|
||||
// takeOutOfStep answers the out-of-step files held for a build key and forgets them.
|
||||
func (b *Builder) takeOutOfStep(key string) []string {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
files := b.outOfStep[key]
|
||||
delete(b.outOfStep, key)
|
||||
return files
|
||||
}
|
||||
|
||||
// importsOf are the mesh packages the lanes' programs import, as their build modules answered them (novox/hq ADR 0593
|
||||
// §2): sorted and each once; nil when none answered any. A lane kept from a prior build answered none, so a build that
|
||||
// kept one reports only what it built.
|
||||
|
||||
@@ -222,6 +222,7 @@ var (
|
||||
|
||||
func TestABundleAndAnArchiveAreBuiltOnOneNodeAndDone(t *testing.T) { // 1607/AC7, ADR 0478 clauses 1, 4
|
||||
t.Run("the imports a build answers reach the done key and the log", importsABuildAnswersReachTheDoneKeyAndTheLog)
|
||||
t.Run("the out-of-step files a build answers reach the log", outOfStepFilesABuildAnswersReachTheLog)
|
||||
r := newRig(t, goBundle, archiveA, tsBundle)
|
||||
// ADR 0584: a third node agrees with novox and ace's typescript is the odd one; the build is done and its step says ace sits out.
|
||||
r.tools.Put(buildrun.NodeBuildModule, "g14.typescript", tcValue(t, "typescript", "tsc5"), false)
|
||||
|
||||
Reference in New Issue
Block a user