From 48ad5ce947f5c0de60f5e0e2ae3ac05c47f21cc2 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 05:15:43 +0100 Subject: [PATCH] BG8, BN8, BI2: each artifact carries the version the ask names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Go program is linked with -X main.meshVersion beside main.builtFor, a Node program's package.json version is set, an image is labelled org.opencontainers.image.version; a build with no version stamps none. Builds: design 47 G §458, §573 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../mesh-build-go/cmd/mesh-build-go/build.go | 6 ++++++ .../cmd/mesh-build-go/build_test.go | 7 +++++++ .../cmd/mesh-build-image/build.go | 2 +- .../cmd/mesh-build-image/build_test.go | 4 ++-- .../cmd/mesh-build-node/build.go | 8 +++++++ .../cmd/mesh-build-node/build_test.go | 21 +++++++++++++++++++ 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/modules/mesh-build-go/cmd/mesh-build-go/build.go b/modules/mesh-build-go/cmd/mesh-build-go/build.go index d42fd7dbf..26f007085 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/build.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/build.go @@ -26,6 +26,8 @@ const ( recipeFlags = "-trimpath -buildvcs=false" recipeLinkerFlags = "-s -w" recipeSystemStamp = "main.builtFor" + // versionStamp is the Go variable a program carries its version in (design 47 G §458). + versionStamp = "main.meshVersion" // outDir is where a build leaves an artifact in the module's directory (builder.Out). outDir = ".mesh-build" ) @@ -153,6 +155,10 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) { if system := strings.TrimSpace(art.System); system != "" { linker += " -X " + recipeSystemStamp + "=" + system } + // A build asked with a version carries it in main.meshVersion beside main.builtFor; one with none stamps none (§458). + if version != "" { + linker += " -X " + versionStamp + "=" + version + } cmd := []string{"-C", dir, "build", "-ldflags", linker, "-o", filepath.Join(out, binary), "./" + strings.Trim(art.From, "./")} // Shut off (ADR 0448): the shared caches are written by a trunk build alone (publish), a candidate's go build writes // beneath a throwaway layer. diff --git a/modules/mesh-build-go/cmd/mesh-build-go/build_test.go b/modules/mesh-build-go/cmd/mesh-build-go/build_test.go index 29dda3fbc..0c3f6a989 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/build_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/build_test.go @@ -273,6 +273,13 @@ func TestTheBuildGetsTheVersionTheAskCarriesAsMeshVersion(t *testing.T) { } got := "" for _, r := range *calls { + // §458: the program is linked with main.meshVersion beside main.builtFor, and with no version none is stamped. + if strings.Contains(r.line, " build ") && strings.Contains(r.line, "-X main.meshVersion=") != (version != "") { + t.Errorf("ask version %q: the go build line is %q", version, r.line) + } + if version != "" && strings.Contains(r.line, " build ") && !strings.Contains(r.line, "-X main.meshVersion="+version) { + t.Errorf("ask version %q: the go build line lacks main.meshVersion: %q", version, r.line) + } if strings.HasPrefix(r.line, "go ") { for _, e := range r.env { if strings.HasPrefix(e, "MESH_VERSION=") { diff --git a/modules/mesh-build-image/cmd/mesh-build-image/build.go b/modules/mesh-build-image/cmd/mesh-build-image/build.go index 79b582095..14ebe5482 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/build.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/build.go @@ -172,7 +172,7 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) { argv = append(argv, imageArgs(manifest)...) // The version the build ask carries is the build argument MESH_VERSION (design 47 G §370). if version != "" { - argv = append(argv, "--build-arg", "MESH_VERSION="+version) + argv = append(argv, "--build-arg", "MESH_VERSION="+version, "--label", "org.opencontainers.image.version="+version) // the label of §458 } argv = append(argv, contextDir) if r := m.Run(ctx, m.env(), "buildah", argv...); r.Status != 0 || r.Err != "" { diff --git a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go index 7bbb850c4..1a533a77a 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go @@ -338,12 +338,12 @@ func TestTheImageBuildGetsTheVersionTheAskCarriesAsMeshVersion(t *testing.T) { t.Fatalf("step = %+v", st) } bud := f.called("bud") - if len(bud) != 1 || !strings.Contains(bud[0].line, "--build-arg MESH_VERSION=1.4.3") { + if len(bud) != 1 || !strings.Contains(bud[0].line, "--build-arg MESH_VERSION=1.4.3") || !strings.Contains(bud[0].line, "--label org.opencontainers.image.version=1.4.3") { t.Fatalf("bud = %+v", bud) } m, f, states = setup(t, same) runBuild(t, m, states, nil) - if bud := f.called("bud"); len(bud) != 1 || strings.Contains(bud[0].line, "MESH_VERSION") { + if bud := f.called("bud"); len(bud) != 1 || (strings.Contains(bud[0].line, "MESH_VERSION") || strings.Contains(bud[0].line, "image.version")) { t.Fatalf("a build with no version: %+v", bud) } } diff --git a/modules/mesh-build-node/cmd/mesh-build-node/build.go b/modules/mesh-build-node/cmd/mesh-build-node/build.go index 4147c76c5..b14f72172 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/build.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/build.go @@ -137,6 +137,14 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) { if err := copyPackages(filepath.Join(m.toolchainDir(), "node_modules"), filepath.Join(stage, "node_modules")); err != nil { return nil, fmt.Errorf("providing the toolchain's packages to the build: %w", err) } + // A program reads its own version from its package.json `version`, which is the version the ask carries (§458); the + // staged copy is the build's own, so nothing is restored. A build with no version, or a module with no package.json, + // keeps what it carries. + if _, err := os.Stat(filepath.Join(dir, "package.json")); err == nil && version != "" { + if _, err := setPackageVersion(dir, version); err != nil { + return nil, err + } + } out, final := Out(a.Name), Out(a.Name)+".bundled" if err := os.MkdirAll(filepath.Join(dir, out), 0o755); err != nil { return nil, err diff --git a/modules/mesh-build-node/cmd/mesh-build-node/build_test.go b/modules/mesh-build-node/cmd/mesh-build-node/build_test.go index 469482168..7c899578c 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/build_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/build_test.go @@ -328,3 +328,24 @@ func TestTheBundleBuildGetsTheVersionTheAskCarriesAsMeshVersion(t *testing.T) { } } } + +// Design 47 G §458 (BN8): a Node program reads its version from its package.json `version`, which is the version the +// ask carries; a build with no version keeps what the package.json carries. +func TestTheBundleCarriesTheAsksVersionInItsPackageJSON(t *testing.T) { + for _, c := range []struct{ version, want string }{{"1.4.3", "1.4.3"}, {"", "0.0.0"}} { + m, f, work := builtMachine(t, "") + workspaceWith(t, work, map[string]string{"modules/m/package.json": `{"name":"m","version":"0.0.0"}`}) + args := buildArgs() + if c.version != "" { + args["version"] = c.version + } + if _, err := m.build(context.Background(), args); err != nil { + t.Fatal(err) + } + _ = f + raw, err := os.ReadFile(filepath.Join(work, "w1", "stage-tools", "modules", "module", "package.json")) + if err != nil || !strings.Contains(string(raw), `"version": "`+c.want+`"`) && !strings.Contains(string(raw), `"version":"`+c.want+`"`) { + t.Errorf("ask version %q: the staged package.json is %q (%v), want version %s", c.version, raw, err, c.want) + } + } +}