From 2b2b3e4b8cdf5bb099cff0c3b1d60bfed875f0b7 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 13:23:00 +0100 Subject: [PATCH] =?UTF-8?q?mesh-build-go:=20the=20generated-files=20compar?= =?UTF-8?q?e=20finds=20a=20contracts=20module=20in=20any=20folder;=20a=20f?= =?UTF-8?q?older=20module's=20Go=20packages=20each=20publish=20under=20the?= =?UTF-8?q?ir=20own=20module=20path=20(ADR=200634=20=C2=A78;=20issue=20196?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- modules/mesh-build-go/buildstep/generated.go | 24 ++++++++ .../cmd/mesh-build-go/publish_test.go | 60 +++++++++++++++++++ .../mesh-build-go/cmd/mesh-build-go/test.go | 13 ++-- .../cmd/mesh-build-go/test_test.go | 35 ++++++++++- 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/modules/mesh-build-go/buildstep/generated.go b/modules/mesh-build-go/buildstep/generated.go index 2130550e8..202bff454 100644 --- a/modules/mesh-build-go/buildstep/generated.go +++ b/modules/mesh-build-go/buildstep/generated.go @@ -122,6 +122,30 @@ func generatedNames(dir string) (goModule, tsPackage string, err error) { return goModule, tsPackage, nil } +// ModuleDirs is every directory in tree that holds a module.json, in any folder (ADR 0634 §8: a module is found by its +// manifest, whether it lies at the root, under modules/ or in a folder of another module), the tree's own first, then +// sorted. Hidden directories, node_modules and testdata are not entered. +func ModuleDirs(tree string) ([]string, error) { + var out []string + err := filepath.WalkDir(tree, func(p string, e os.DirEntry, err error) error { + if err != nil { + return err + } + if e.IsDir() { + if n := e.Name(); p != tree && (strings.HasPrefix(n, ".") || n == "node_modules" || n == "testdata") { + return filepath.SkipDir + } + return nil + } + if e.Name() == "module.json" { + out = append(out, filepath.Dir(p)) + } + return nil + }) + sort.Strings(out) + return out, err +} + // CompareStep is AskSteps.Compare for a build module comparing root: it reads {"workspace", "module"}, the workspace's // tree where tree says it is (the build module's own layout), and answers {"out-of-step": [...]}. func CompareStep(root string, tree func(workspace string) string) StepFunc { diff --git a/modules/mesh-build-go/cmd/mesh-build-go/publish_test.go b/modules/mesh-build-go/cmd/mesh-build-go/publish_test.go index 221a9bd2e..f858a9ee1 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/publish_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/publish_test.go @@ -231,6 +231,66 @@ func TestAGoPackageIsBuiltAsAModuleZipAndPublishedUnderItsModulePath(t *testing. } } +// K-2 (ADR 0634 §8): a module in a folder of the repository, the folder holding more than one Go package, builds and +// publishes each as a root module's would: each artifact its own zip of its own directory under its own module path, +// the sibling package's directory (a Go module of its own) left out of the zip of the folder's. +func TestEachGoPackageOfAModuleInAFolderIsPublishedUnderItsOwnModulePath(t *testing.T) { + manifest := manifestWith(`{"name":"go-aone","kind":"package","language":"go","paths":["go/seats/aone"],"series":"0.1"}, +{"name":"go-btwo","kind":"package","language":"go","paths":["go/seats/btwo"],"series":"0.1"}, +{"name":"go-whole","kind":"package","language":"go","paths":["go"],"series":"0.1"}`) + m, states, _ := buildModule(t, manifest, "unused") + src := filepath.Join(m.Work, "ws1", "tree", "domain-a", "contracts") + for name, body := range map[string]string{ + "module.json": manifest, + "go/seats/aone/go.mod": "module git.example/c/domain-a/go/seats/aone\n\ngo 1.26\n", "go/seats/aone/a.go": "package aone\n", + "go/seats/btwo/go.mod": "module git.example/c/domain-a/go/seats/btwo\n\ngo 1.26\n", "go/seats/btwo/b.go": "package btwo\n", + "go/go.mod": "module git.example/c/domain-a/go\n\ngo 1.26\n", "go/w.go": "package w\n"} { + if err := os.MkdirAll(filepath.Dir(filepath.Join(src, name)), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil { + t.Fatal(err) + } + } + store := withStore(t, m) + for _, c := range []struct{ artifact, ref string }{ + {"go-aone", "git.example/c/domain-a/go/seats/aone@v0.1.3"}, + {"go-btwo", "git.example/c/domain-a/go/seats/btwo@v0.1.3"}, + {"go-whole", "git.example/c/domain-a/go@v0.1.3"}, + } { + for _, tool := range m.Tools(m.StepTools()) { + if tool.Name == "build_go_build" { + args := map[string]any{"workspace": "ws1", "module": "domain-a/contracts", "artifact": c.artifact, "version": "v0.1.3", "seat": GoPackageSeat} + if _, err := tool.Run(args); err != nil { + t.Fatal(err) + } + } + } + st := states.waitEnd(t) + var res BuildResult + if st.State != delivery.StepPassed { + t.Fatalf("%s: %s %q", c.artifact, st.State, st.Reason) + } + _ = json.Unmarshal(st.Result, &res) + if res.Reference != c.ref { + t.Errorf("%s published as %q, want %q", c.artifact, res.Reference, c.ref) + } + } + if len(store.published) != 3 { + t.Fatalf("published %+v", store.published) + } + in := store.published[2] + z, err := zip.NewReader(bytes.NewReader(store.blobs[in.Name+"@"+in.Digest]), int64(len(store.blobs[in.Name+"@"+in.Digest]))) + if err != nil { + t.Fatal(err) + } + for _, f := range z.File { + if strings.Contains(f.Name, "/seats/") { + t.Errorf("the zip of the folder's own package holds %s, a package of its own", f.Name) + } + } +} + // CAT-BG2 (design 51 *The versions*): the builder's version.numbered for a bundle or an archive publishes the held // digest under its version, sending nothing; a digest the holder no longer holds fails the event (offered again), // and what is not this module's to publish is let pass. diff --git a/modules/mesh-build-go/cmd/mesh-build-go/test.go b/modules/mesh-build-go/cmd/mesh-build-go/test.go index 5d1823f69..1e0d86f3a 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/test.go @@ -109,15 +109,14 @@ func (m *Module) test(ctx context.Context, args map[string]any) (any, error) { return res, nil } -// compareGenerated regenerates the generated Go files of each contracts module in the tree (the tree's own manifest and -// each modules//module.json; CAT-BG5) and fails the level on any out of step, Why naming each path. +// compareGenerated regenerates the generated Go files of each contracts module in the tree (every module.json in the tree, in +// any folder: ADR 0634 §8; CAT-BG5) and fails the level on any out of step, Why naming each path. func compareGenerated(tree string, res *TestResult) error { - manifests, _ := filepath.Glob(filepath.Join(tree, "modules", "*", "module.json")) - if _, err := os.Stat(filepath.Join(tree, "module.json")); err == nil { - manifests = append([]string{filepath.Join(tree, "module.json")}, manifests...) + dirs, err := buildstep.ModuleDirs(tree) + if err != nil { + return err } - for _, mf := range manifests { - dir := filepath.Dir(mf) + for _, dir := range dirs { paths, err := buildstep.OutOfStep(dir, buildstep.GeneratedRootGo) if err != nil { return err diff --git a/modules/mesh-build-go/cmd/mesh-build-go/test_test.go b/modules/mesh-build-go/cmd/mesh-build-go/test_test.go index b0206d387..95fe52fd6 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/test_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/test_test.go @@ -186,7 +186,6 @@ func TestUnitLevelOfAContractsModuleOutOfStepFailsNamingEachPath(t *testing.T) { tree := filepath.Join(m.Work, ws, "tree") for p, text := range map[string]string{ "module.json": `{"module":"c","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"generated-from":"."}]}}`, - "seats/index.json": `{"format":1,"seats":["one-store"]}`, "seats/one-store/seat.json": `{"format":1,"seat":"one-store","scope":"mesh","grain":"seat-list","decision":"ADR 1","verbs":[{"name":"query","input":{"type":"object"}}]}`, "go/go.mod": "module example.org/c/go\n", "go/stray.go": "package stray\n", @@ -212,6 +211,40 @@ func TestUnitLevelOfAContractsModuleOutOfStepFailsNamingEachPath(t *testing.T) { } } +// ADR 0634 §8: the same for a contracts module in a folder of a tree whose root holds a module of its own (found by its +// manifest, not by lying under modules/); a contracts module (a package artifact naming generated-from) whose generated Go files are out of step with +// the seat generator fails the unit level, each path named in its out-of-step and its why; its TypeScript is not this +// module's to compare. +func TestUnitLevelOfAContractsModuleInAFolderOutOfStepFailsNamingEachPath(t *testing.T) { + m, states, ws, _ := workspaceFor(t, "empty") + tree := filepath.Join(m.Work, ws, "tree") + for p, text := range map[string]string{ + "module.json": `{"module":"c","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"generated-from":"."}]}}`, + "seats/one-store/seat.json": `{"format":1,"seat":"one-store","scope":"mesh","grain":"seat-list","decision":"ADR 1","verbs":[{"name":"query","input":{"type":"object"}}]}`, + "go/go.mod": "module example.org/c/go\n", + "go/stray.go": "package stray\n", + "ts/package.json": `{"name":"@example/c"}`, + "ts/stray.ts": "// not this module's\n", + } { + full := filepath.Join(tree, "domain", filepath.FromSlash(p)) + _ = os.MkdirAll(filepath.Dir(full), 0o755) + if err := os.WriteFile(full, []byte(text), 0o644); err != nil { + t.Fatal(err) + } + } + res, st := runUnit(t, m, states, ws) + stray := false + for _, p := range res.OutOfStep { + stray = stray || p == "domain/go/stray.go" + if !strings.HasPrefix(p, "domain/go/") { + t.Errorf("%s is not under the folder's Go root", p) + } + } + if st.State != delivery.StepPassed || res.Passed || !stray || !strings.Contains(res.Why, "domain/go/stray.go") { + t.Errorf("result = %+v (%s %s)", res, st.State, st.Reason) + } +} + // A package that does not build fails the level with the compiler's words. func TestUnitLevelOfAPackageThatDoesNotBuildFailsWithTheCompilersLine(t *testing.T) { m, states, ws, _ := workspaceFor(t, "broken")