Take a missing graph state as no graph yet and a Publisher's directory as the nearest module above it
The indexer's recompute and merge failed on listing a graph bucket not yet on the bus, and a package published from inside a module's directory had no publisher, so the dependents of a build module missed what imports its packages (review findings 3 and 4 of L3). Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -2,12 +2,14 @@ package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
)
|
||||
@@ -60,11 +62,18 @@ func graphNode(e graphEdges, providers func(string) []catalogue.Manifest) graph.
|
||||
}
|
||||
|
||||
// writeGraph writes the graph node of every module of edges whose value changed, at most GraphMost of them, then
|
||||
// deletes each graph key whose module the modules state no longer holds. Nil Graph writes nothing.
|
||||
// deletes each graph key whose module the modules state no longer holds. Nil Graph writes nothing, and neither does a
|
||||
// graph state that is not on the bus yet (process.ErrNotYet; main.go: until then no graph is written), said in the log;
|
||||
// a listing that fails otherwise skips the deletion pass, said, and never fails the merge or the recompute that wrote.
|
||||
func (ix *Indexer) writeGraph(ctx context.Context, edges map[string]graphEdges) error {
|
||||
if ix.Graph == nil {
|
||||
return nil
|
||||
}
|
||||
keys, listErr := ix.Graph.Keys(ctx)
|
||||
if errors.Is(listErr, process.ErrNotYet) {
|
||||
ix.logf("the module graph is not written: its state is not on the bus yet")
|
||||
return nil
|
||||
}
|
||||
names := slices.Sorted(maps.Keys(edges))
|
||||
if len(names) > GraphMost {
|
||||
ix.logf("the module graph holds at most %d modules: %d are known, so %s and the %d after it are not written",
|
||||
@@ -104,19 +113,21 @@ func (ix *Indexer) writeGraph(ctx context.Context, edges map[string]graphEdges)
|
||||
}
|
||||
}
|
||||
held, err := ix.Modules.Keys(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing the modules for the graph: %w", err)
|
||||
}
|
||||
keys, err := ix.Graph.Keys(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing the graph: %w", err)
|
||||
switch {
|
||||
case listErr != nil:
|
||||
ix.logf("the graph keys of modules the modules state no longer holds are not deleted: listing the graph: %v", listErr)
|
||||
return nil
|
||||
case err != nil:
|
||||
ix.logf("the graph keys of modules the modules state no longer holds are not deleted: listing the modules: %v", err)
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
if slices.Contains(held, key) {
|
||||
continue
|
||||
}
|
||||
if err := write(ctx, ix.Graph, key, func(_ []byte, found bool) ([]byte, bool, error) { return nil, found, nil }); err != nil {
|
||||
return fmt.Errorf("graph.%s: %w", key, err)
|
||||
ix.logf("graph.%s is not deleted: %v", key, err)
|
||||
continue
|
||||
}
|
||||
ix.logf("graph.%s deleted: the modules state holds no module %s", key, key)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
// key under the indexer; unbound, when set, is every read's error (a state not yet on the bus).
|
||||
type memStore struct {
|
||||
unbound error
|
||||
keysErr error // the listing's error
|
||||
vals map[string][]byte
|
||||
revs map[string]uint64
|
||||
next uint64
|
||||
@@ -36,6 +37,9 @@ func (m *memStore) Get(_ context.Context, key string) ([]byte, uint64, bool, err
|
||||
return v, m.revs[key], ok, nil
|
||||
}
|
||||
func (m *memStore) Keys(context.Context) ([]string, error) {
|
||||
if m.keysErr != nil {
|
||||
return nil, m.keysErr
|
||||
}
|
||||
var keys []string
|
||||
for k := range m.vals {
|
||||
keys = append(keys, k)
|
||||
|
||||
+17
-4
@@ -230,8 +230,9 @@ func (g Graph) Requirers(provision string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Publisher is the module of the graph whose repository and directory publish the mesh package pkg (PackageRepository),
|
||||
// or "" when the graph holds none.
|
||||
// Publisher is the module of the graph whose repository and directory publish the mesh package pkg (PackageRepository):
|
||||
// the module whose directory is the package's directory or the nearest one above it (the longest match, a package
|
||||
// published from inside a module's directory belonging to that module), or "" when the graph holds none.
|
||||
func (g Graph) Publisher(pkg string) string {
|
||||
repository, dir, ok := delivery.PackageRepository(pkg)
|
||||
if !ok {
|
||||
@@ -240,12 +241,24 @@ func (g Graph) Publisher(pkg string) string {
|
||||
if dir == "" {
|
||||
dir = delivery.RootPath
|
||||
}
|
||||
best, bestLen := "", -1
|
||||
for _, name := range slices.Sorted(maps.Keys(g)) {
|
||||
if n := g[name]; n.Repository == repository && n.Path == dir {
|
||||
n := g[name]
|
||||
if n.Repository != repository {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case n.Path == dir:
|
||||
return name
|
||||
case n.Path == delivery.RootPath:
|
||||
if bestLen < 0 {
|
||||
best, bestLen = name, 0
|
||||
}
|
||||
case strings.HasPrefix(dir, n.Path+"/") && len(n.Path) > bestLen:
|
||||
best, bestLen = name, len(n.Path)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return best
|
||||
}
|
||||
|
||||
// buildEdges are the modules n's build edges reach: its bases, the publisher of each of its packages, and the build
|
||||
|
||||
+16
-2
@@ -32,7 +32,11 @@ func TestGraphEdges(t *testing.T) {
|
||||
n.Bases, n.Packages, n.Toolchains = []string{"base"}, []string{"git.novox.be/novox/lib"}, gotool
|
||||
}),
|
||||
"top": node("novox/mesh-catalog", "modules/top", func(n *Node) { n.Bases = []string{"app"} }),
|
||||
"db": node("novox/mesh-catalog", "modules/db", func(n *Node) { n.Provides = []string{"database"} }),
|
||||
// A package published from inside a module's directory belongs to that module (ADR 0604 §1): users imports build-go's.
|
||||
"users": node("novox/other", "modules/users", func(n *Node) {
|
||||
n.Packages = []string{"git.novox.be/novox/mesh-catalog/modules/build-go/buildrun"}
|
||||
}),
|
||||
"db": node("novox/mesh-catalog", "modules/db", func(n *Node) { n.Provides = []string{"database"} }),
|
||||
"web": node("novox/mesh-catalog", "modules/web", func(n *Node) {
|
||||
n.Requires, n.Awaits = []string{"database", "db"}, []string{"database"}
|
||||
}),
|
||||
@@ -40,7 +44,7 @@ func TestGraphEdges(t *testing.T) {
|
||||
for module, want := range map[string][]string{
|
||||
"base": {"app", "top"},
|
||||
"lib": {"app", "top"},
|
||||
"build-go": {"app", "top"}, // build-go's own toolchain edge reaches itself, which is left out
|
||||
"build-go": {"app", "top", "users"}, // build-go's own toolchain edge reaches itself, which is left out
|
||||
"top": nil,
|
||||
"db": nil, // a provision edge is no build edge
|
||||
} {
|
||||
@@ -63,6 +67,16 @@ func TestGraphEdges(t *testing.T) {
|
||||
if got := g.Requirers("db"); !reflect.DeepEqual(got, []string{"web"}) {
|
||||
t.Errorf("requirers of db = %v", got)
|
||||
}
|
||||
for pkg, want := range map[string]string{
|
||||
"git.novox.be/novox/mesh-catalog/modules/build-go/buildrun": "build-go", // inside its directory
|
||||
"git.novox.be/novox/mesh-catalog/modules/build-go": "build-go", // its directory
|
||||
"git.novox.be/novox/mesh-catalog/modules/build-gone": "", // beside it, not inside
|
||||
"git.novox.be/novox/lib/sub": "lib", // a root module publishes what is below it
|
||||
} {
|
||||
if got := g.Publisher(pkg); got != want {
|
||||
t.Errorf("publisher of %s = %q, want %q", pkg, got, want)
|
||||
}
|
||||
}
|
||||
if g.Publisher("git.novox.be/novox/lib") != "lib" || g.Publisher("git.novox.be/novox/mesh-sdk/go") != "" {
|
||||
t.Error("a package's publisher is not the module at its repository and directory")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user