The composer's test workspace lays what the composer requires, found in the group's trees and the catalogue's own (issue 1921)
The group check of 8a80e8e86858 laid every Go module beside the check, and one of them (mesh-delivery's mesh-grants, which now requires disk-load's diskreadings) required a catalogue module the catalogue under check never lays beside itself: Go looked its zero version up and the composer did not build. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -26,8 +26,9 @@ import (
|
||||
|
||||
// nodeEngineTree is the node-engine's repository as the checker places it beside this check (MESH_CHECK_BESIDE, the
|
||||
// repository mesh-host), and with it every Go module of the other repositories placed there (mesh-sdk, mesh-contracts,
|
||||
// mesh-delivery, mesh-networking: the node-engine's composer requires them at the zero version, so they resolve from the
|
||||
// workspace as the group lays them, wherever the composer lives). The test
|
||||
// mesh-delivery, mesh-networking) and of the catalogue under check, as candidates: the workspace lays those the
|
||||
// node-engine's composer requires at the zero version, transitively (workspaceOf), so they resolve as the group lays
|
||||
// them, wherever the composer lives. The test
|
||||
// builds the node-engine's real composer command from that tree; it does not stand one in. With no beside directory it
|
||||
// fails, and with the node-engine beside but no composer command in it it fails: a composer test that could not build its
|
||||
// composer proves nothing. With a beside directory that holds no node-engine at all it skips aloud (NOT JUDGED), since a
|
||||
@@ -51,6 +52,7 @@ func nodeEngineTree(t *testing.T) (tree string, modules []string) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var roots []string
|
||||
for _, e := range entries {
|
||||
if e.Name() == "mesh-host" || !dirExists(filepath.Join(beside, e.Name())) {
|
||||
continue
|
||||
@@ -59,6 +61,14 @@ func nodeEngineTree(t *testing.T) (tree string, modules []string) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots = append(roots, root)
|
||||
}
|
||||
// The catalogue under check is never beside itself; its modules are found here, after the group's others.
|
||||
catalogue, err := filepath.Abs(filepath.Join("..", "..", "..", ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, root := range append(roots, catalogue) {
|
||||
filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -172,7 +182,7 @@ 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, err := workspaceOf(append([]string{tree}, beside...))
|
||||
work, err := workspaceOf(tree, beside)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -12,39 +12,77 @@ import (
|
||||
// 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"))
|
||||
// workspaceOf is the go.work of the module in first and of every module it requires at the zero version, transitively,
|
||||
// each found among the modules in candidates by its module path: 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). Only what first needs is laid (issue 1921, the group check
|
||||
// of 8a80e8e86858): a module laid beside that requires one no tree holds (mesh-grants requiring a catalogue module, the
|
||||
// catalogue being the repository under check) made Go fetch that module's zero version and fail, though the composer
|
||||
// needs neither. A requirement at the zero version no candidate holds is an error naming it and who requires it.
|
||||
func workspaceOf(first string, candidates []string) (string, error) {
|
||||
paths := map[string]string{}
|
||||
for _, d := range candidates {
|
||||
path, _, err := goModOf(d)
|
||||
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 _, seen := paths[path]; !seen { // the same module laid twice: the first is kept
|
||||
paths[path] = d
|
||||
}
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("%s/go.mod names no module", d)
|
||||
}
|
||||
var use, replace strings.Builder
|
||||
laid := map[string]bool{}
|
||||
queue := []string{first}
|
||||
for len(queue) > 0 {
|
||||
d := queue[0]
|
||||
queue = queue[1:]
|
||||
path, needs, err := goModOf(d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if seen[path] {
|
||||
continue // the same module laid twice: go refuses a workspace that uses it twice
|
||||
if laid[path] {
|
||||
continue
|
||||
}
|
||||
seen[path] = true
|
||||
laid[path] = true
|
||||
fmt.Fprintf(&use, "\t%s\n", d)
|
||||
fmt.Fprintf(&replace, "replace %s %s => %s\n", path, zeroVersion, d)
|
||||
for _, need := range needs {
|
||||
if laid[need] {
|
||||
continue
|
||||
}
|
||||
dir, ok := paths[need]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%s (%s) requires %s at the zero version, and no tree laid beside holds it", path, d, need)
|
||||
}
|
||||
queue = append(queue, dir)
|
||||
}
|
||||
}
|
||||
return "go 1.26.0\n\nuse (\n" + use.String() + ")\n\n" + replace.String(), nil
|
||||
}
|
||||
|
||||
// goModOf is the module path of the go.mod in d and the modules it requires at the zero version.
|
||||
func goModOf(d string) (path string, needs []string, err error) {
|
||||
raw, err := os.ReadFile(filepath.Join(d, "go.mod"))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
for _, line := range strings.Split(string(raw), "\n") {
|
||||
f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require "))
|
||||
switch {
|
||||
case len(f) == 2 && f[0] == "module" && path == "":
|
||||
path = strings.Trim(f[1], `"`)
|
||||
case len(f) >= 2 && f[1] == zeroVersion:
|
||||
needs = append(needs, strings.Trim(f[0], `"`))
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
return "", nil, fmt.Errorf("%s/go.mod names no module", d)
|
||||
}
|
||||
return path, needs, 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) {
|
||||
@@ -61,10 +99,15 @@ func TestAWorkspaceOfTheGroupResolvesTheZeroVersionWithNoLookup(t *testing.T) {
|
||||
}
|
||||
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})
|
||||
// The group check of 8a80e8e86858: a module beside requiring one no tree holds is not laid, so nothing looks it up.
|
||||
c := mod("c", "module git.novox.be/novox/mesh-delivery/modules/g\n\ngo 1.26.0\n\nrequire (\n\tgit.novox.be/novox/mesh-catalog/modules/d "+zeroVersion+"\n)\n")
|
||||
work, err := workspaceOf(a, []string{b, c, b})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(work, c) {
|
||||
t.Errorf("the workspace lays %s, which the composer does not require:\n%s", c, work)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "go.work"), []byte(work), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -74,4 +117,8 @@ func TestAWorkspaceOfTheGroupResolvesTheZeroVersionWithNoLookup(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
// A requirement at the zero version no tree holds is said before Go looks it up, naming it.
|
||||
if _, err := workspaceOf(c, []string{a, b}); err == nil || !strings.Contains(err.Error(), "mesh-catalog/modules/d") {
|
||||
t.Errorf("a requirement no tree holds: %v, want an error naming it", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user