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) }