diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 02e12de..625f694 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -877,6 +877,14 @@ func compile(ctx context.Context, run Runner, tree string, chain Toolchain, base, } invocation = append(invocation, chain.Compile...) + // What it was built for, linked in. The compile line carries `-ldflags` already; this appends a + // second one, which the Go linker accepts and merges. A host with no system refuses every + // declaration before it applies anything (novox/hq 04-ISSUES/161), and it is the artifact that + // knows — the target is a property of the artifact rather than of the recipe (ADR 0142). + if chain.SystemStamp != "" && strings.TrimSpace(a.System) != "" { + invocation = append(invocation, "-ldflags", + "-X "+chain.SystemStamp+"="+strings.TrimSpace(a.System)) + } if chain.OutputFlag != "" { // A compiler pointed at a package is told the file to write, not the directory: the name a // machine runs it by is not always the name of the package that built it. The host's command diff --git a/internal/builder/system_stamp_test.go b/internal/builder/system_stamp_test.go new file mode 100644 index 0000000..9c6b883 --- /dev/null +++ b/internal/builder/system_stamp_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "strings" + "testing" +) + +// A host built without knowing its system refuses every declaration before applying anything — +// safely, totally, and with nothing reporting it. The mesh built one, delivered it, started it, and +// it would have refused the first thing it was asked to do (novox/hq 04-ISSUES/161). + +func TestTheGoToolchainStampsTheArtifactsSystem(t *testing.T) { + chain, err := ToolchainFor("go") + if err != nil { + t.Fatal(err) + } + if chain.SystemStamp != "main.builtFor" { + t.Fatalf("the go toolchain fills %q", chain.SystemStamp) + } +} + +func TestALanguageWithNoPinnedSystemStampsNothing(t *testing.T) { + // Interpreted output is not pinned to a system, and a manifest declaring one for it is already + // refused. Nothing to fill. + for _, language := range []string{"typescript", "python"} { + chain, err := ToolchainFor(language) + if err != nil { + t.Fatal(err) + } + if chain.SystemStamp != "" { + t.Fatalf("%s fills %q, and its output is not pinned to a system", + language, chain.SystemStamp) + } + } +} + +func TestTheStampIsTheOneThingTakenFromTheArtifact(t *testing.T) { + // The toolchain accepts nothing else from the module — anything it could override it would be + // writing a Dockerfile to override. The system is the stated exception, because a compiled + // binary is per system and the artifact is what declares one (ADR 0142). + chain, err := ToolchainFor("go") + if err != nil { + t.Fatal(err) + } + joined := strings.Join(chain.Compile, " ") + if strings.Contains(joined, "${") || strings.Contains(joined, "%s") { + t.Fatalf("the compile line takes something from the module: %q", joined) + } +} diff --git a/internal/builder/toolchain.go b/internal/builder/toolchain.go index 2fbc589..abd479c 100644 --- a/internal/builder/toolchain.go +++ b/internal/builder/toolchain.go @@ -49,6 +49,18 @@ type Toolchain struct { // is named as it will be FOUND, inside the unpacked bundle, so the source is the same path with // the output directory taken off the front and this on the end. SourceExt string + // 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). + // + // **The one thing a toolchain takes from the artifact, and 0142 says why**: the target is a + // property of the artifact rather than of the recipe, because a compiled binary is per system + // and a toolchain that accepted it from the module would be accepting a build instruction. This + // is the narrow exception, named here rather than inferred. + // + // Empty for a language that compiles to nothing pinned. A host built without it refuses every + // declaration before applying anything — safely, totally, and with nothing reporting it + // (novox/hq 04-ISSUES/161). + SystemStamp string } // What a toolchain is pointed at. @@ -122,6 +134,9 @@ var toolchains = []Toolchain{ // directory holding one executable, which is what the delivery mechanism expects // (novox/hq ADR 0141). Unit: UnitPackage, + // The mesh's own Go components read the system they were built for from this variable, and + // refuse to touch a machine without one. + SystemStamp: "main.builtFor", }, { Language: "python",