Merge origin/main: the trunk's fetch downloads each placed sibling's module graph (mesh-catalog #624) over the group's earlier version (issue 1921, milestone 47/G)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -83,7 +84,8 @@ func TestASiblingsCommitChangesTheDigestOfAPackageWhoseTestsReachAReaderOfTheSib
|
|||||||
|
|
||||||
// The fetch writes the siblings it placed, which the digest reads.
|
// The fetch writes the siblings it placed, which the digest reads.
|
||||||
func TestTheFetchRecordsTheSiblingsItPlaced(t *testing.T) {
|
func TestTheFetchRecordsTheSiblingsItPlaced(t *testing.T) {
|
||||||
f := &fakeStep{gomods: "go.mod\n"}
|
// Every checkout writes this go.mod: the placed sibling is a Go module at its root, so the fetch downloads its graph.
|
||||||
|
f := &fakeStep{gomods: "go.mod\n", files: map[string]string{"go.mod": "module git.novox.be/novox/mesh-sdk/go\n\ngo 1.27.1\n"}}
|
||||||
m, states := fetchModule(t, f)
|
m, states := fetchModule(t, f)
|
||||||
args := map[string]any{"repository": "novox/mesh-controller", "commit": commitSHA, "beside": []any{
|
args := map[string]any{"repository": "novox/mesh-controller", "commit": commitSHA, "beside": []any{
|
||||||
map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA}}}
|
map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA}}}
|
||||||
@@ -101,6 +103,37 @@ func TestTheFetchRecordsTheSiblingsItPlaced(t *testing.T) {
|
|||||||
if err != nil || json.Unmarshal(raw, &placed) != nil || len(placed) != 1 || placed[0].Repository != "novox/mesh-sdk" || placed[0].Commit != commitSHA {
|
if err != nil || json.Unmarshal(raw, &placed) != nil || len(placed) != 1 || placed[0].Repository != "novox/mesh-sdk" || placed[0].Commit != commitSHA {
|
||||||
t.Errorf("the record is %s (%v)", raw, err)
|
t.Errorf("the record is %s (%v)", raw, err)
|
||||||
}
|
}
|
||||||
|
// The placed sibling's graph is downloaded once, over the workspace the fetch laid for it (downloadBeside).
|
||||||
|
want := "go -C " + filepath.Join(m.Work, res.Workspace, "beside", "mesh-sdk") + " mod download"
|
||||||
|
n := 0
|
||||||
|
for _, c := range f.calls {
|
||||||
|
if c.line == want {
|
||||||
|
n++
|
||||||
|
if !strings.Contains(strings.Join(c.env, "\n"), "GOWORK="+filepath.Join(m.Work, res.Workspace, "beside-0.work")) {
|
||||||
|
t.Errorf("%q ran without the sibling's workspace: %q", want, c.env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The go.work's go directive is the highest of the laid modules.
|
||||||
|
if work, _ := os.ReadFile(filepath.Join(m.Work, res.Workspace, "beside-0.work")); !strings.HasPrefix(string(work), "go 1.27.1\n") {
|
||||||
|
t.Errorf("the workspace file begins %q, want go 1.27.1", work)
|
||||||
|
}
|
||||||
|
// No sibling a Go module at its root: nothing is walked, so a directory the walk cannot read does not fail the fetch.
|
||||||
|
plain := t.TempDir()
|
||||||
|
locked := filepath.Join(plain, "beside", "docs", "private")
|
||||||
|
if err := os.MkdirAll(locked, 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(locked, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { os.Chmod(locked, 0o700) })
|
||||||
|
if err := m.downloadBeside(context.Background(), plain, filepath.Join(plain, "tree"), nil); err != nil {
|
||||||
|
t.Errorf("a fetch with no Go-module sibling walked the siblings: %v", err)
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
t.Errorf("%q ran %d times, want once:\n%s", want, n, strings.Join(f.lines(), "\n"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The checker's agreement: the integration level runs and digests under the tags integration and isolation, so the
|
// The checker's agreement: the integration level runs and digests under the tags integration and isolation, so the
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ const zeroVersion = "v0.0.0-00010101000000-000000000000"
|
|||||||
func workspaceOf(first string, candidates []string) (string, error) {
|
func workspaceOf(first string, candidates []string) (string, error) {
|
||||||
paths := map[string]string{}
|
paths := map[string]string{}
|
||||||
for _, d := range candidates {
|
for _, d := range candidates {
|
||||||
path, _, err := goModOf(d)
|
path, _, _, err := goModOf(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -34,11 +36,12 @@ func workspaceOf(first string, candidates []string) (string, error) {
|
|||||||
}
|
}
|
||||||
var use, replace strings.Builder
|
var use, replace strings.Builder
|
||||||
laid := map[string]bool{}
|
laid := map[string]bool{}
|
||||||
|
highest := "1.26.0" // the go directive is the highest among the laid modules, never lower than this
|
||||||
queue := []string{first}
|
queue := []string{first}
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
d := queue[0]
|
d := queue[0]
|
||||||
queue = queue[1:]
|
queue = queue[1:]
|
||||||
path, needs, err := goModOf(d)
|
path, needs, goV, err := goModOf(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -46,6 +49,9 @@ func workspaceOf(first string, candidates []string) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
laid[path] = true
|
laid[path] = true
|
||||||
|
if goNewer(goV, highest) {
|
||||||
|
highest = goV
|
||||||
|
}
|
||||||
fmt.Fprintf(&use, "\t%s\n", d)
|
fmt.Fprintf(&use, "\t%s\n", d)
|
||||||
fmt.Fprintf(&replace, "replace %s %s => %s\n", path, zeroVersion, d)
|
fmt.Fprintf(&replace, "replace %s %s => %s\n", path, zeroVersion, d)
|
||||||
for _, need := range needs {
|
for _, need := range needs {
|
||||||
@@ -59,28 +65,48 @@ func workspaceOf(first string, candidates []string) (string, error) {
|
|||||||
queue = append(queue, dir)
|
queue = append(queue, dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "go 1.26.0\n\nuse (\n" + use.String() + ")\n\n" + replace.String(), nil
|
return "go " + highest + "\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.
|
// goNewer reports whether the go directive a is higher than b (dotted numbers; a missing part is 0).
|
||||||
func goModOf(d string) (path string, needs []string, err error) {
|
func goNewer(a, b string) bool {
|
||||||
|
x, y := strings.Split(a, "."), strings.Split(b, ".")
|
||||||
|
for i := 0; i < len(x) || i < len(y); i++ {
|
||||||
|
var p, q int
|
||||||
|
if i < len(x) {
|
||||||
|
p, _ = strconv.Atoi(x[i])
|
||||||
|
}
|
||||||
|
if i < len(y) {
|
||||||
|
q, _ = strconv.Atoi(y[i])
|
||||||
|
}
|
||||||
|
if p != q {
|
||||||
|
return p > q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// goModOf is the module path of the go.mod in d, the modules it requires at the zero version and its go directive.
|
||||||
|
func goModOf(d string) (path string, needs []string, goV string, err error) {
|
||||||
raw, err := os.ReadFile(filepath.Join(d, "go.mod"))
|
raw, err := os.ReadFile(filepath.Join(d, "go.mod"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
for _, line := range strings.Split(string(raw), "\n") {
|
||||||
f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require "))
|
f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require "))
|
||||||
switch {
|
switch {
|
||||||
case len(f) == 2 && f[0] == "module" && path == "":
|
case len(f) == 2 && f[0] == "module" && path == "":
|
||||||
path = strings.Trim(f[1], `"`)
|
path = strings.Trim(f[1], `"`)
|
||||||
|
case len(f) == 2 && f[0] == "go" && goV == "":
|
||||||
|
goV = f[1]
|
||||||
case len(f) >= 2 && f[1] == zeroVersion:
|
case len(f) >= 2 && f[1] == zeroVersion:
|
||||||
needs = append(needs, strings.Trim(f[0], `"`))
|
needs = append(needs, strings.Trim(f[0], `"`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return "", nil, fmt.Errorf("%s/go.mod names no module", d)
|
return "", nil, "", fmt.Errorf("%s/go.mod names no module", d)
|
||||||
}
|
}
|
||||||
return path, needs, nil
|
return path, needs, goV, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadBeside downloads, where the network is, the Go modules of the graph that the tests of a workspace's siblings
|
// downloadBeside downloads, where the network is, the Go modules of the graph that the tests of a workspace's siblings
|
||||||
@@ -92,21 +118,30 @@ func goModOf(d string) (path string, needs []string, err error) {
|
|||||||
func (m *Module) downloadBeside(ctx context.Context, root, tree string, dirs []string) error {
|
func (m *Module) downloadBeside(ctx context.Context, root, tree string, dirs []string) error {
|
||||||
beside := filepath.Join(root, "beside")
|
beside := filepath.Join(root, "beside")
|
||||||
entries, err := os.ReadDir(beside)
|
entries, err := os.ReadDir(beside)
|
||||||
if err != nil {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return nil // no siblings placed
|
return nil // no siblings placed
|
||||||
}
|
}
|
||||||
var candidates, roots []string
|
if err != nil {
|
||||||
for _, d := range dirs {
|
return err
|
||||||
candidates = append(candidates, filepath.Join(tree, filepath.FromSlash(d)))
|
|
||||||
}
|
}
|
||||||
|
var candidates, roots, all []string
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
dir := filepath.Join(beside, e.Name())
|
dir := filepath.Join(beside, e.Name())
|
||||||
|
all = append(all, dir)
|
||||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||||
roots = append(roots, dir)
|
roots = append(roots, dir)
|
||||||
}
|
}
|
||||||
filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
|
}
|
||||||
|
if len(roots) == 0 {
|
||||||
|
return nil // no sibling is a Go module at its root: nothing is walked, nothing downloaded
|
||||||
|
}
|
||||||
|
for _, d := range dirs {
|
||||||
|
candidates = append(candidates, filepath.Join(tree, filepath.FromSlash(d)))
|
||||||
|
}
|
||||||
|
for _, dir := range all {
|
||||||
|
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
if d.IsDir() && (d.Name() == ".git" || d.Name() == "testdata" || d.Name() == "node_modules") {
|
if d.IsDir() && (d.Name() == ".git" || d.Name() == "testdata" || d.Name() == "node_modules") {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
@@ -116,6 +151,9 @@ func (m *Module) downloadBeside(ctx context.Context, root, tree string, dirs []s
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for i, r := range roots {
|
for i, r := range roots {
|
||||||
work, err := workspaceOf(r, candidates)
|
work, err := workspaceOf(r, candidates)
|
||||||
|
|||||||
Reference in New Issue
Block a user