A TypeScript bundle carries what it runs with: the toolchain's runtime directory is copied into it (hq to-be 38 WP3, ADR 0188)
A bundle that compiled was not yet a bundle that ran. The compiler resolved `import "nats"` from the toolchain image's own node_modules and the pack took only what the compiler wrote, so what a machine unpacked could not find a single dependency — and Node would have read the bare `.js` as CommonJS besides. No TypeScript bundle had run live to show it; the runtime's own is the first that must. A toolchain now names a Dependencies directory in its image, copied whole into the output's root after the compile by a second run in the same image: for TypeScript /app/runtime, which the runtime's image puts a `"type": "module"` package.json and its pruned node_modules at. An older image without it fails the build by name rather than packing a bundle that starts nowhere. The SDK's and the runtime's dependencies, nothing module-specific yet: a skeleton, by ADR 0188 §5.
This commit is contained in:
@@ -968,6 +968,26 @@ func compile(ctx context.Context, run Runner, tree string, chain Toolchain,
|
|||||||
if _, err := run(ctx, tree, "docker", invocation...); err != nil {
|
if _, err := run(ctx, tree, "docker", invocation...); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if chain.Dependencies != "" {
|
||||||
|
// **What the bundle runs with, from the image it was compiled in** (Toolchain.Dependencies).
|
||||||
|
// A second run in the same image rather than a shell wrapped around the compiler: the
|
||||||
|
// compile line stays a plain command a reader can run by hand, and the copy is one more
|
||||||
|
// plain command beside it. Refused by name when the image carries no such directory — an
|
||||||
|
// older toolchain image — because a bundle packed without its dependencies starts nowhere
|
||||||
|
// and says so three layers away from here.
|
||||||
|
copying := []string{
|
||||||
|
"run", "--rm",
|
||||||
|
"--volume", tree + ":" + within,
|
||||||
|
"--workdir", within,
|
||||||
|
base,
|
||||||
|
"sh", "-c",
|
||||||
|
`test -d "$1" || { echo "the toolchain image carries no $1: it predates the mesh shipping a bundle's dependencies, rebuild $2 first" >&2; exit 1; }; cp -a "$1/." "$3/"`,
|
||||||
|
"dependencies", chain.Dependencies, chain.Base, out,
|
||||||
|
}
|
||||||
|
if _, err := run(ctx, tree, "docker", copying...); err != nil {
|
||||||
|
return "", fmt.Errorf("copying the %s dependencies a bundle runs with: %w", chain.Language, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return filepath.Join(tree, out), nil
|
return filepath.Join(tree, out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,41 @@ func TestABundleIsCompiledAndPackedWithNoDockerfile(t *testing.T) {
|
|||||||
if !strings.HasPrefix(digest, "sha256:") {
|
if !strings.HasPrefix(digest, "sha256:") {
|
||||||
t.Fatalf("the bundle was not pinned: %v", got.Manifest.Resources[0])
|
t.Fatalf("the bundle was not pinned: %v", got.Manifest.Resources[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **And what it runs with, from the image it was compiled in** (novox/hq to-be 38 WP3). A
|
||||||
|
// second run in the same toolchain image copies the toolchain's runtime directory — the
|
||||||
|
// `"type": "module"` package.json and the pruned node_modules — into the output's root, and
|
||||||
|
// refuses by name when the image carries none rather than packing a bundle that starts nowhere.
|
||||||
|
var copied string
|
||||||
|
for _, line := range r.ran {
|
||||||
|
if strings.HasPrefix(line, "docker run") && strings.Contains(line, "/app/runtime") {
|
||||||
|
copied = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if copied == "" {
|
||||||
|
t.Fatalf("the bundle's dependencies were not copied in after the compile:\n%s", strings.Join(r.ran, "\n"))
|
||||||
|
}
|
||||||
|
if !strings.Contains(copied, "mesh-tools/build@sha256:") || !strings.Contains(copied, "predates") ||
|
||||||
|
!strings.Contains(copied, Out("code")) {
|
||||||
|
t.Fatalf("the copy does not run in the same toolchain, refuse an older image by name, or land in the artifact's output: %s", copied)
|
||||||
|
}
|
||||||
|
if strings.Index(strings.Join(r.ran, "\n"), "--outDir") > strings.Index(strings.Join(r.ran, "\n"), "/app/runtime") {
|
||||||
|
t.Fatal("the dependencies were copied before the compile wrote its output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A language whose bundle carries its own dependencies copies nothing in: a Go binary is static.
|
||||||
|
func TestOnlyALanguageWithARuntimeDirectoryCopiesDependenciesIn(t *testing.T) {
|
||||||
|
ts, _ := ToolchainFor("typescript")
|
||||||
|
if ts.Dependencies != "/app/runtime" {
|
||||||
|
t.Fatalf("typescript bundles run with %q", ts.Dependencies)
|
||||||
|
}
|
||||||
|
for _, language := range []string{"go", "python"} {
|
||||||
|
chain, _ := ToolchainFor(language)
|
||||||
|
if chain.Dependencies != "" {
|
||||||
|
t.Fatalf("%s copies %q into every bundle, and its bundles carry their own", language, chain.Dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **Refused before anything is built, naming what to build first.** A base the mesh has not built
|
// **Refused before anything is built, naming what to build first.** A base the mesh has not built
|
||||||
@@ -145,9 +180,13 @@ func TestTwoBundlesInOneModuleArePackedSeparately(t *testing.T) {
|
|||||||
t.Fatalf("a module with two bundles did not build: %v", err)
|
t.Fatalf("a module with two bundles did not build: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiled into two different places.
|
// Compiled into two different places. Only the compile lines: the copy of each bundle's
|
||||||
|
// dependencies names the same directory again, deliberately.
|
||||||
var outputs []string
|
var outputs []string
|
||||||
for _, line := range r.ran {
|
for _, line := range r.ran {
|
||||||
|
if !strings.Contains(line, "--outDir") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, part := range strings.Fields(line) {
|
for _, part := range strings.Fields(line) {
|
||||||
if strings.HasPrefix(part, ".mesh-build/") {
|
if strings.HasPrefix(part, ".mesh-build/") {
|
||||||
outputs = append(outputs, part)
|
outputs = append(outputs, part)
|
||||||
|
|||||||
@@ -57,6 +57,23 @@ type Toolchain struct {
|
|||||||
// carrying its debug info. The mistake was believing a comment rather than reading the file it
|
// carrying its debug info. The mistake was believing a comment rather than reading the file it
|
||||||
// produced (novox/hq 04-ISSUES/161).
|
// produced (novox/hq 04-ISSUES/161).
|
||||||
LinkerFlags []string
|
LinkerFlags []string
|
||||||
|
// Dependencies is a directory inside the toolchain image whose contents a bundle in this
|
||||||
|
// language runs with, copied whole into the compiled output's root after the compile.
|
||||||
|
//
|
||||||
|
// **A bundle that compiles is not yet a bundle that runs.** The compiler resolves `import
|
||||||
|
// "nats"` from the toolchain image's own node_modules and the pack takes only what the compiler
|
||||||
|
// wrote, so what a machine unpacked could not find a single dependency — and no TypeScript bundle
|
||||||
|
// had ever run live to show it (novox/hq to-be 38 WP3). For TypeScript the directory holds a
|
||||||
|
// `package.json` saying `"type": "module"` — Node reads a bare `.js` as CommonJS otherwise, so a
|
||||||
|
// bundle with its dependencies and without that line still fails to start — and the pruned,
|
||||||
|
// production-only node_modules the runtime itself ships with: the SDK's and the runtime's
|
||||||
|
// dependencies, and nothing module-specific yet (novox/hq ADR 0188 §5: a skeleton; a module's
|
||||||
|
// own npm dependencies are a later step). Empty for a language whose bundle carries its own —
|
||||||
|
// a Go binary is static, a Python bundle is installed with its dependencies.
|
||||||
|
//
|
||||||
|
// A toolchain image without the directory fails the build by name rather than packing a bundle
|
||||||
|
// that starts nowhere: the image predates this and must be rebuilt first.
|
||||||
|
Dependencies string
|
||||||
// SystemStamp is the variable this language's linker fills with the artifact's declared system,
|
// SystemStamp is the variable this language's linker fills with the artifact's declared system,
|
||||||
// for a language whose binaries are pinned to one at link time (novox/hq ADR 0005).
|
// for a language whose binaries are pinned to one at link time (novox/hq ADR 0005).
|
||||||
//
|
//
|
||||||
@@ -118,9 +135,10 @@ var toolchains = []Toolchain{
|
|||||||
"--module", "NodeNext", "--moduleResolution", "NodeNext",
|
"--module", "NodeNext", "--moduleResolution", "NodeNext",
|
||||||
"--target", "ES2022", "--rootDir", ".",
|
"--target", "ES2022", "--rootDir", ".",
|
||||||
},
|
},
|
||||||
OutputFlag: "--outDir",
|
OutputFlag: "--outDir",
|
||||||
Unit: UnitSources,
|
Unit: UnitSources,
|
||||||
SourceExt: ".ts",
|
SourceExt: ".ts",
|
||||||
|
Dependencies: "/app/runtime",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Language: "go",
|
Language: "go",
|
||||||
|
|||||||
Reference in New Issue
Block a user