The build runner leaves mesh-sdk for mesh-build-go (ADR 0593 §1), another repository the builder does not import; the tests answer asks as the builder needs them answered. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
95 lines
3.1 KiB
Go
95 lines
3.1 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"maps"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The build runner a build node runs an ask with is mesh-build-go's (novox/hq ADR 0593 §1), another repository's,
|
|
// which the builder does not import. These tests run an ask as the builder needs it run: fetch once at the job's
|
|
// fetch arguments with its package versions, build or copy each artifact, answer each one's digest and reference. They
|
|
// test the builder, not the runner.
|
|
|
|
// stepFunc is one step of a build module, as its tool answers it.
|
|
type stepFunc func(ctx context.Context, args map[string]any) (any, error)
|
|
|
|
// askSteps are a build module's steps an ask is run with.
|
|
type askSteps struct{ Fetch, Build, Copy stepFunc }
|
|
|
|
// runAsk runs a build ask with steps and answers its delivery.BuildAnswer, or the error that fails the step.
|
|
func runAsk(ctx context.Context, steps askSteps, ask delivery.StepAsk) (any, error) {
|
|
if ask.Build == nil {
|
|
return nil, fmt.Errorf("the ask %s holds no build job", ask.ID)
|
|
}
|
|
job := *ask.Build
|
|
fetch := job.Fetch
|
|
if len(ask.Packages) > 0 && fetch != nil {
|
|
fetch = maps.Clone(fetch)
|
|
fetch["packages"] = maps.Clone(ask.Packages)
|
|
}
|
|
answer := delivery.BuildAnswer{}
|
|
workspace := ""
|
|
for _, a := range job.Artifacts {
|
|
var out any
|
|
var err error
|
|
if a.From != "" {
|
|
if steps.Copy == nil {
|
|
return nil, fmt.Errorf("this build module copies no artifact (%s)", a.Artifact)
|
|
}
|
|
out, err = steps.Copy(ctx, map[string]any{"module": job.Module, "artifact": a.Artifact, "from": a.From})
|
|
} else {
|
|
if workspace == "" {
|
|
raw, err := asRaw(steps.Fetch(ctx, fetch))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var f struct {
|
|
Workspace string `json:"workspace"`
|
|
Merge string `json:"merge"`
|
|
Conflicts []string `json:"conflicts"`
|
|
}
|
|
if err := json.Unmarshal(raw, &f); err != nil || f.Workspace == "" {
|
|
return nil, fmt.Errorf("the fetch answered no workspace: %s", raw)
|
|
}
|
|
if _, candidate := fetch["base"]; candidate && (len(f.Conflicts) > 0 || f.Merge == "") {
|
|
return nil, fmt.Errorf("the head does not merge into the trunk: %s", strings.Join(f.Conflicts, ", "))
|
|
}
|
|
workspace = f.Workspace
|
|
}
|
|
args := map[string]any{"workspace": workspace, "module": job.Path, "artifact": a.Artifact}
|
|
if job.Publish {
|
|
args["publish"] = true
|
|
}
|
|
out, err = steps.Build(ctx, args)
|
|
}
|
|
raw, err := asRaw(out, err)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", a.Artifact, err)
|
|
}
|
|
var built struct {
|
|
Digest string `json:"digest"`
|
|
Reference string `json:"reference"`
|
|
}
|
|
if err := json.Unmarshal(raw, &built); err != nil || built.Digest == "" {
|
|
return nil, fmt.Errorf("the build of %s answered no digest: %s", a.Artifact, raw)
|
|
}
|
|
answer.Artifacts = append(answer.Artifacts, delivery.AskBuilt{Artifact: a.Artifact, Digest: built.Digest, Reference: built.Reference})
|
|
}
|
|
return answer, nil
|
|
}
|
|
|
|
func asRaw(v any, err error) (json.RawMessage, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r, ok := v.(json.RawMessage); ok {
|
|
return r, nil
|
|
}
|
|
return json.Marshal(v)
|
|
}
|