Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/graph.go
T

123 lines
4.3 KiB
Go

package index
import (
"context"
"fmt"
"maps"
"slices"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The module graph (novox/hq ADR 0603 §2): at every recompute the indexer writes, in its state graph
// (delivery.GraphBucket), one key per module it holds the inputs of, its build and provision edges as that recompute
// computed them, and deletes the key of a module the modules state no longer holds. A key is written only when its value
// changes, so its history of 5 is five changes of the module's edges. The delivery view, the checker and the seat
// mesh-catalog's verbs read it; nothing stores an order (ADR 0603 §1).
// GraphMost is the most modules the graph holds (ADR 0603 §2): past it, the modules after the first GraphMost in name
// order are not written, and it is said in the log. The cap is the indexer's code, not a manifest field, until a judge
// that knows such a field runs (the lead's decision of L3).
const GraphMost = 2000
// graphEdges is what a recompute knows of one module for its graph node.
type graphEdges struct {
module string
cur delivery.Module
manifest catalogue.Manifest
fp, none string
tcs map[string]delivery.ToolchainUsed
pkgs delivery.PackageVersions
}
// graphNode is the graph node of e: its edges, the awaited provisions judged against providers (catalogue.Awaited, as
// the assigner judges them).
func graphNode(e graphEdges, providers func(string) []catalogue.Manifest) delivery.GraphNode {
if e.fp == "" && e.none == "" {
e.none = "its fingerprint was not computed"
}
n := delivery.GraphEdgesOf(e.manifest, providers)
n.FP, n.None, n.Repository, n.Path = e.fp, e.none, e.cur.Repository, e.cur.Path
n.Packages = slices.Sorted(maps.Keys(e.pkgs))
tcs := e.tcs
if len(tcs) == 0 {
tcs = e.cur.Toolchains // no fingerprint now: the toolchain it was last computed with still builds it
}
for language, t := range tcs {
builder, known := ToolchainModules[language]
if !known {
continue
}
if n.Toolchains == nil {
n.Toolchains = map[string]delivery.GraphToolchain{}
}
n.Toolchains[language] = delivery.GraphToolchain{Module: builder, Version: t.Version, Recipe: t.Recipe}
}
return n
}
// 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.
func (ix *Indexer) writeGraph(ctx context.Context, edges map[string]graphEdges) error {
if ix.Graph == nil {
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",
GraphMost, len(names), names[GraphMost], len(names)-GraphMost-1)
names = names[:GraphMost]
}
providers := func(provision string) []catalogue.Manifest {
var out []catalogue.Manifest
for _, name := range slices.Sorted(maps.Keys(edges)) {
if slices.Contains(edges[name].manifest.Offers(), provision) {
out = append(out, edges[name].manifest)
}
}
return out
}
for _, name := range names {
key, err := delivery.GraphKey(name)
if err != nil {
ix.logf("graph.%s is not written: %v", name, err)
continue
}
next := graphNode(edges[name], providers)
err = write(ctx, ix.Graph, key, func(value []byte, found bool) ([]byte, bool, error) {
if found {
if cur, err := delivery.DecodeGraphNode(value); err == nil && delivery.SameGraphNode(cur, next) {
return nil, false, nil
}
}
data, err := delivery.EncodeGraphNode(next)
if err != nil {
return nil, false, fmt.Errorf("graph.%s: %w", key, err)
}
return data, false, nil
})
if err != nil {
ix.logf("graph.%s is not written: %v", key, err)
}
}
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)
}
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 deleted: the modules state holds no module %s", key, key)
}
return nil
}