diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go b/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go index 3d36d7bae..489fd027a 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go @@ -172,7 +172,10 @@ func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) { } copyHostTree(t, hostTree, tree) if len(beside) > 0 { // the group's other repositories, laid beside the node-engine's tree as one workspace - work := "go 1.26.0\n\nuse (\n\t" + tree + "\n\t" + strings.Join(beside, "\n\t") + "\n)\n" + work, err := workspaceOf(append([]string{tree}, beside...)) + if err != nil { + return nil, err + } if err := os.WriteFile(filepath.Join(m.Work, ws, "go.work"), []byte(work), 0o600); err != nil { return nil, err } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/workspace_test.go b/modules/mesh-build-go/cmd/mesh-build-go/workspace_test.go new file mode 100644 index 000000000..91f71b700 --- /dev/null +++ b/modules/mesh-build-go/cmd/mesh-build-go/workspace_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// zeroVersion is the version a mesh package is required at in every go.mod of the mesh (ADR 0573 §2, §4). +const zeroVersion = "v0.0.0-00010101000000-000000000000" + +// workspaceOf is the go.work of the modules in dirs, as a test lays the group's repositories beside a tree: each used, and +// each also replaced at the zero version by its own directory. Go (from 1.27) looks the zero version of a required +// module up even where the module is a member of the workspace, once a build loads the module graph (any import outside +// the workspace); a versioned replace is the one form it takes without a lookup, and an unversioned one is refused for a +// member of the workspace (issue 1921: the composer's build fetched mesh-contracts from the network). +func workspaceOf(dirs []string) (string, error) { + var use, replace strings.Builder + seen := map[string]bool{} + for _, d := range dirs { + raw, err := os.ReadFile(filepath.Join(d, "go.mod")) + if err != nil { + return "", err + } + path := "" + for _, line := range strings.Split(string(raw), "\n") { + if f := strings.Fields(line); len(f) == 2 && f[0] == "module" { + path = strings.Trim(f[1], `"`) + break + } + } + if path == "" { + return "", fmt.Errorf("%s/go.mod names no module", d) + } + if seen[path] { + continue // the same module laid twice: go refuses a workspace that uses it twice + } + seen[path] = true + fmt.Fprintf(&use, "\t%s\n", d) + fmt.Fprintf(&replace, "replace %s %s => %s\n", path, zeroVersion, d) + } + return "go 1.26.0\n\nuse (\n" + use.String() + ")\n\n" + replace.String(), nil +} + +// Issue 1921 (the fourth group try): a workspace whose members require each other at the zero version loads its module +// graph with no lookup, so with nothing fetched (GOPROXY=off), as a composer built beside the group's heads does. +func TestAWorkspaceOfTheGroupResolvesTheZeroVersionWithNoLookup(t *testing.T) { + root := t.TempDir() + mod := func(name, body string) string { + dir := filepath.Join(root, name) + if err := os.MkdirAll(dir, 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(body), 0o600); err != nil { + t.Fatal(err) + } + return dir + } + a := mod("a", "module git.novox.be/novox/mesh-host\n\ngo 1.26.0\n\nrequire git.novox.be/novox/mesh-contracts/go/x "+zeroVersion+"\n") + b := mod("b", "module git.novox.be/novox/mesh-contracts/go/x\n\ngo 1.26.0\n") + work, err := workspaceOf([]string{a, b, b}) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "go.work"), []byte(work), 0o600); err != nil { + t.Fatal(err) + } + cmd := exec.Command("go", "list", "-m", "all") + cmd.Dir = a + cmd.Env = append(os.Environ(), "GOWORK="+filepath.Join(root, "go.work"), "GOPROXY=off", "GOFLAGS=", "GOTOOLCHAIN=local") + if out, err := cmd.CombinedOutput(); err != nil || !strings.Contains(string(out), "git.novox.be/novox/mesh-contracts/go/x") { + t.Fatalf("the workspace's module graph does not load with nothing fetched: %v\n%s\n%s", err, out, work) + } +}