A module names the module its build stands on, not a copy of it
A fingerprint written into a recipe names one particular copy of the base — the copy on whichever machine the person typing it was using. On any other mesh that copy has never existed, so the build stops on its first line with a message about an image nobody can look up. Three modules in the catalogue were in exactly that state, and the line each of them replaced was equally dead. A module now names the module and artifact instead, and the mesh answers with what it holds. The builder is still a thing that clones, builds and answers: the answer travels with the question, because only the mesh knows what it has. A base the mesh has not built is refused before anything is built, naming which module has to exist first.
This commit is contained in:
@@ -67,7 +67,7 @@ type Result struct {
|
||||
// archive failed would otherwise leave half of itself in the store under a digest the mesh never
|
||||
// records — reachable, unreferenced, and indistinguishable from something in use.
|
||||
func Build(ctx context.Context, run Runner, publish Publisher,
|
||||
repository, path, ref, workspace string) (Result, error) {
|
||||
repository, path, ref, workspace string, held map[string]string) (Result, error) {
|
||||
|
||||
// Made rather than required. A builder that fails because the directory it was told to work
|
||||
// in does not exist is a builder that needs a setup step nobody documented.
|
||||
@@ -117,12 +117,19 @@ func Build(ctx context.Context, run Runner, publish Publisher,
|
||||
|
||||
var built []catalogue.Built
|
||||
if manifest.Build != nil {
|
||||
// What this module said it stands on, answered with what this mesh actually holds. Done
|
||||
// before anything is built, so a missing base is refused in front of the person who can
|
||||
// fix it rather than inside a build that stops on its own first line.
|
||||
args, err := standingOn(manifest, held)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
artifacts := append([]catalogue.Artifact{}, manifest.Build.Artifacts...)
|
||||
// Ordered, so two builds of one commit do the same work in the same sequence and their
|
||||
// logs can be compared.
|
||||
sort.Slice(artifacts, func(i, j int) bool { return artifacts[i].Name < artifacts[j].Name })
|
||||
for _, a := range artifacts {
|
||||
made, err := one(ctx, run, publish, manifest.Module, within, commit, a)
|
||||
made, err := one(ctx, run, publish, manifest.Module, within, commit, a, args)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
@@ -208,7 +215,7 @@ func against(within string, manifest catalogue.Manifest) []string {
|
||||
const ManifestName = "module.json"
|
||||
|
||||
func one(ctx context.Context, run Runner, publish Publisher,
|
||||
module, tree, commit string, a catalogue.Artifact) (catalogue.Built, error) {
|
||||
module, tree, commit string, a catalogue.Artifact, args []string) (catalogue.Built, error) {
|
||||
|
||||
switch a.Kind {
|
||||
case catalogue.ArtifactUpstream:
|
||||
@@ -229,7 +236,11 @@ func one(ctx context.Context, run Runner, publish Publisher,
|
||||
// release and a commit is what was actually built. The mesh pins the digest anyway; this
|
||||
// is only so a person looking at the build node can tell what is there.
|
||||
local := fmt.Sprintf("%s-%s:%s", module, a.Name, short(commit))
|
||||
if _, err := run(ctx, tree, "docker", "build", "-f", a.From, "-t", local, "."); err != nil {
|
||||
// The bases this module named, resolved to what this mesh holds. A recipe reads them as
|
||||
// build arguments, so a module says which module it stands on and never which copy.
|
||||
invocation := append([]string{"build", "-f", a.From, "-t", local}, args...)
|
||||
invocation = append(invocation, ".")
|
||||
if _, err := run(ctx, tree, "docker", invocation...); err != nil {
|
||||
return catalogue.Built{}, fmt.Errorf("%s: building %s failed: %w", module, a.Name, err)
|
||||
}
|
||||
reference, err := publish.PublishImage(ctx, local, module+"/"+a.Name)
|
||||
@@ -357,3 +368,38 @@ func Command(ctx context.Context, dir, name string, args ...string) (string, err
|
||||
}
|
||||
|
||||
var _ io.Writer = (*stringWriter)(nil)
|
||||
|
||||
// standingOn turns the bases a module named into build arguments for what this mesh holds.
|
||||
//
|
||||
// **Refused rather than defaulted** (novox/hq issue 044). A module naming a base the mesh has not
|
||||
// built cannot be built here yet, and the useful sentence names which module is missing — not the
|
||||
// one a container runtime produces when a recipe's first line refers to an image nobody has.
|
||||
//
|
||||
// The order is fixed so two builds of one commit invoke the same command.
|
||||
func standingOn(manifest catalogue.Manifest, held map[string]string) ([]string, error) {
|
||||
if manifest.Build == nil || len(manifest.Build.On) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
on := append([]catalogue.BuildsOn{}, manifest.Build.On...)
|
||||
sort.Slice(on, func(i, j int) bool { return on[i].Arg < on[j].Arg })
|
||||
|
||||
var args []string
|
||||
for _, base := range on {
|
||||
if base.Arg == "" || base.Module == "" || base.Artifact == "" {
|
||||
return nil, fmt.Errorf(
|
||||
"%s says its build stands on something, and does not say all of what: a base "+
|
||||
"needs the module, the artifact, and the build argument the recipe reads it "+
|
||||
"from", manifest.Module)
|
||||
}
|
||||
key := base.Module + "/" + base.Artifact
|
||||
reference, has := held[key]
|
||||
if !has {
|
||||
return nil, fmt.Errorf(
|
||||
"%s builds on %s, and this mesh has not built it. Build %s first — every module "+
|
||||
"in this toolchain stands on it, so it is the thing to have before anything "+
|
||||
"else", manifest.Module, key, base.Module)
|
||||
}
|
||||
args = append(args, "--build-arg", base.Arg+"="+reference)
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user