A module says what it is written in, and needs no Dockerfile
The bundle recipe: the one that both builds and packs. An archive packs a directory as it stands, so shipping compiled output meant compiling somewhere first — which meant a Dockerfile repeating the same incantation in every module. Two base arguments with no defaults, a working directory chosen so the SDK resolves upward, the compiler invoked by absolute path because the usual symlink is resolved away when the base image is assembled, a second stage, an environment variable naming the entrypoints. Most of the catalogue is unconverted and that is why; two conversions done in one session were each wrong twice with a working example open in the next window. A bundle says a language and a list of entrypoints. The mesh knows what the language implies. Anything a module could override there it would be writing a Dockerfile to override, so a toolchain is deliberately not configurable. Declared rather than inferred, both of them: guessing the language from which files are present makes a build depend on a directory listing, and guessing the entrypoints makes it change meaning when somebody adds a helper. A toolchain the mesh does not hold is refused before anything is compiled, naming what to build first — the same treatment a missing base already gets, because it is the same question and somebody can answer it. A language the mesh does not build is refused saying what would have worked, since the author is usually one word away. The list of languages is closed and adding to it is a decision. Every language is another implementation of the contracts every module shares, and those change rarely and cascade when they do (ADR 0039) — a mesh whose SDKs disagree about the envelope fails by ignoring messages rather than by failing to compile. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -88,12 +88,16 @@ func (m Manifest) Resolve(built []Built) (Manifest, error) {
|
||||
switch artifact.Kind {
|
||||
case ArtifactImage, ArtifactUpstream:
|
||||
filled["image"] = artifact.Reference
|
||||
case ArtifactArchive:
|
||||
case ArtifactArchive, ArtifactBundle:
|
||||
// The same on the wire: both are bytes fetched by digest and unpacked. They differ in
|
||||
// how they were made — one packed as it stood, the other compiled first — and a
|
||||
// machine has no reason to care which.
|
||||
filled["source"] = artifact.Reference
|
||||
filled["digest"] = artifact.Digest
|
||||
default:
|
||||
return Manifest{}, fmt.Errorf("%s: %q is a %q, and an artifact is %q, %q or %q",
|
||||
m.Module, named, artifact.Kind, ArtifactImage, ArtifactArchive, ArtifactUpstream)
|
||||
return Manifest{}, fmt.Errorf("%s: %q is a %q, and an artifact is %q, %q, %q or %q",
|
||||
m.Module, named, artifact.Kind, ArtifactImage, ArtifactArchive, ArtifactUpstream,
|
||||
ArtifactBundle)
|
||||
}
|
||||
out.Resources = append(out.Resources, filled)
|
||||
}
|
||||
@@ -119,15 +123,40 @@ func (b *Build) problems(module string) []string {
|
||||
}
|
||||
seen[a.Name] = true
|
||||
switch a.Kind {
|
||||
case ArtifactImage, ArtifactArchive, ArtifactUpstream:
|
||||
case ArtifactImage, ArtifactArchive, ArtifactUpstream, ArtifactBundle:
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a %q, and an artifact is %q, %q or %q",
|
||||
module, a.Name, a.Kind, ArtifactImage, ArtifactArchive, ArtifactUpstream))
|
||||
"%s: %q is a %q, and an artifact is %q, %q, %q or %q",
|
||||
module, a.Name, a.Kind, ArtifactImage, ArtifactArchive, ArtifactUpstream,
|
||||
ArtifactBundle))
|
||||
}
|
||||
if a.From == "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q says nothing about what it is built from", module, a.Name))
|
||||
// **A bundle is built from the module itself, so it says a language instead.** Everything
|
||||
// else names what it is built from: a Dockerfile, a directory, somebody else's reference.
|
||||
// A bundle's source is the module's own directory by definition, and what it needs to say
|
||||
// is which compiler — because the mesh chooses that, and cannot choose for a module that
|
||||
// has not said.
|
||||
if a.Kind == ArtifactBundle {
|
||||
if a.From != "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a bundle and names what it is built from (%q). A bundle is built "+
|
||||
"from the module's own directory; what it says is the language",
|
||||
module, a.Name, a.From))
|
||||
}
|
||||
if strings.TrimSpace(a.Language) == "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a bundle and says no language, so nothing can choose a compiler "+
|
||||
"for it", module, a.Name))
|
||||
}
|
||||
} else {
|
||||
if a.From == "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q says nothing about what it is built from", module, a.Name))
|
||||
}
|
||||
if a.Language != "" {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: %q is a %q and names a language. Only a bundle is compiled by the mesh; "+
|
||||
"everything else brings its own recipe", module, a.Name, a.Kind))
|
||||
}
|
||||
}
|
||||
// An upstream image is named, not read from the repository, so the path rule does not
|
||||
// apply to it — and applying it anyway would refuse every reference with a registry host
|
||||
|
||||
@@ -389,6 +389,26 @@ type Artifact struct {
|
||||
// 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"`
|
||||
|
||||
// Language is what this module's code is written in, for a bundle.
|
||||
//
|
||||
// **Declared, never guessed.** Inferring it from what files happen to be present makes a
|
||||
// module's build depend on a directory listing, and a module that adds a stray file builds
|
||||
// differently for a reason nobody can see. It is also the only thing a bundle needs to say:
|
||||
// everything else about the toolchain — which compiler, which flags, which base — is the
|
||||
// mesh's, and a module that could override it would be writing a Dockerfile again.
|
||||
//
|
||||
// Empty for every other kind, which do not compile.
|
||||
Language string `json:"language,omitempty"`
|
||||
|
||||
// Entrypoints are the compiled files a tool host should load from this module, relative to the
|
||||
// bundle's root.
|
||||
//
|
||||
// **Named rather than derived from which files exist**, for the same reason as the language:
|
||||
// the module knows what it serves, and a build that guesses would change meaning when
|
||||
// somebody adds a helper. An empty list is a bundle that is run rather than loaded — a
|
||||
// provisioner or a step, named by whatever runs it.
|
||||
Entrypoints []string `json:"entrypoints,omitempty"`
|
||||
}
|
||||
|
||||
// Kinds an artifact may be.
|
||||
@@ -397,6 +417,21 @@ const (
|
||||
ArtifactImage = "image"
|
||||
// ArtifactArchive is a directory in this repository, packed.
|
||||
ArtifactArchive = "archive"
|
||||
// ArtifactBundle is this module's own code, COMPILED by a toolchain and then packed.
|
||||
//
|
||||
// **The one recipe that both builds and packs**, and the reason it exists is the authoring
|
||||
// burden. An `archive` packs a directory as it stands, so shipping compiled output means
|
||||
// compiling somewhere first — which means a Dockerfile, repeating the same incantation in
|
||||
// every module: two base arguments, a working directory chosen so the SDK resolves upward, the
|
||||
// compiler invoked by absolute path because the usual symlink is resolved away when the base is
|
||||
// assembled, a second stage, an environment variable naming the entrypoints. Most of the
|
||||
// catalogue is unconverted and that is why.
|
||||
//
|
||||
// A bundle says what the module is written in and nothing about how. The mesh knows what a
|
||||
// language implies, which is the whole of the difference: a Dockerfile is right for software
|
||||
// that needs a particular base, and wrong for "compile my module's code", which is the same
|
||||
// operation every time.
|
||||
ArtifactBundle = "bundle"
|
||||
// ArtifactUpstream is an image somebody else built, mirrored into the mesh's own registry and
|
||||
// pinned by the digest it lands with.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user