diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 55150ab..8658b65 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -239,6 +239,9 @@ func one(ctx context.Context, run Runner, publish Publisher, // 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...) + if a.Target != "" { + invocation = append(invocation, "--target", a.Target) + } 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) diff --git a/internal/catalogue/manifest.go b/internal/catalogue/manifest.go index 0a6d581..2085d2d 100644 --- a/internal/catalogue/manifest.go +++ b/internal/catalogue/manifest.go @@ -380,6 +380,15 @@ type Artifact struct { // From is what it is built from, relative to the repository root: a Dockerfile for an image, // a directory for an archive. From string `json:"from"` + // Target is which stage of that Dockerfile to stop at, for a recipe that describes more than + // one image. + // + // **One source, two images, and they are meant to differ.** A toolchain image carries a + // compiler and everything a build needs; the image the same module *runs* in should carry + // neither. Describing both in one recipe keeps them in step — they share a base, a library + // version and an operating system — while letting each be built and published separately. + // Empty means the whole recipe, which is what a module with one image says by saying nothing. + Target string `json:"target,omitempty"` } // Kinds an artifact may be.