mesh/delivery merged: source.main moved to novox/mesh-delivery@ead9ee2d138d
mesh/check checking 699cbfb7c131 on ead9ee2d138d
192 lines
6.5 KiB
Go
192 lines
6.5 KiB
Go
package index
|
|
|
|
import (
|
|
"encoding/json"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Builds holds the controller's build records (mesh-controller_builds) for the two things the indexer still reads from
|
|
// them until release R4g, when build-agent goes (the lead's decision of R4e, 2026-10-22):
|
|
// - the Builder input of the builder's own module, which build-agent builds (TrunkOf; novox/hq ADR 0453 clause 5,
|
|
// issue 1727);
|
|
// - the directory of a module whose modules key has none (PathOf; issue 1614), filled once and changing no fingerprint.
|
|
//
|
|
// It lists no module and maps no directory to a module: the indexer's modules are those its own messages and bucket name
|
|
// (ADR 0501 clause 6, issue 1851). Safe for use from a watch and from the message handler at once.
|
|
type Builds struct {
|
|
mu sync.Mutex
|
|
records map[string]delivery.BuildRecord // by bucket key
|
|
}
|
|
|
|
// NewBuilds is an empty Builds.
|
|
func NewBuilds() *Builds { return &Builds{records: map[string]delivery.BuildRecord{}} }
|
|
|
|
// Put takes a key of the builds bucket and its value. Only a build record of a module (key `<module>.build.<id>`,
|
|
// delivery.BuildKey) counts; the newest-pointer keys and requests that knew no module are not records of one.
|
|
func (b *Builds) Put(key string, value []byte) {
|
|
if !strings.Contains(key, ".build.") {
|
|
return
|
|
}
|
|
var r delivery.BuildRecord
|
|
if err := json.Unmarshal(value, &r); err != nil || r.Module == "" || r.Repository == "" {
|
|
return
|
|
}
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.records[key] = r
|
|
}
|
|
|
|
// Delete takes a key out.
|
|
func (b *Builds) Delete(key string) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
delete(b.records, key)
|
|
}
|
|
|
|
func newer(a, b delivery.BuildRecord) bool {
|
|
return a.At.After(b.At) || (a.At.Equal(b.At) && a.ID > b.ID)
|
|
}
|
|
|
|
// TrunkOf is the newest successful trunk build record of a module, in whatever repository it was built from; nil when
|
|
// there is none (issue 1727).
|
|
func (b *Builds) TrunkOf(module string) *delivery.BuildRecord {
|
|
var best *delivery.BuildRecord
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
for _, r := range b.records {
|
|
if r.Module == module && r.IsTrunkBuild() && (best == nil || newer(r, *best)) {
|
|
c := r
|
|
best = &c
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// PathOf is the directory of a module of the repository as its newest build record of that repository names it, its
|
|
// newest successful trunk build's first ("." for the repository's root), and false when no record of the repository
|
|
// names the module (issue 1614).
|
|
func (b *Builds) PathOf(module, repository string) (string, bool) {
|
|
var best *delivery.BuildRecord
|
|
trunk := false
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
for _, r := range b.records {
|
|
if r.Module != module || !delivery.SameRepository(r.Repository, repository) {
|
|
continue
|
|
}
|
|
t := r.IsTrunkBuild()
|
|
if best == nil || (t && !trunk) || (t == trunk && newer(r, *best)) {
|
|
c := r
|
|
best, trunk = &c, t
|
|
}
|
|
}
|
|
if best == nil {
|
|
return "", false
|
|
}
|
|
return pathOf(strings.Trim(best.Path, "/")), true
|
|
}
|
|
|
|
// Registration is a module registered in the catalogue from its own repository, as the controller's newest outcome record
|
|
// of it names it: the repository, the directory of its manifest there ("" or "." for the root) and the commit it was
|
|
// built at.
|
|
type Registration struct {
|
|
Module, Repository, Path, Commit, Ref string
|
|
}
|
|
|
|
// registered is whether the record is an outcome that did not fail: what registers a module. Unlike IsTrunkBuild it
|
|
// asks for no recorded source inputs, which no build records since ADR 0530 clause 1, and for no particular branch: a
|
|
// repository's trunk is named main, master, or what its owner chose (the registered photos, de-spiegel and link2pay were
|
|
// built from nox-mesh, and every record of them names that ref).
|
|
func registered(r delivery.BuildRecord) bool {
|
|
return r.State == "" && r.Failed == ""
|
|
}
|
|
|
|
// RegistrationsOf are the places a module was registered from, by its outcome records: the newest record of each
|
|
// repository and directory (a module's name was once another repository's, as photos was a directory of the catalogue's).
|
|
// Records of a trunk's ref (main, master, trunk, none) come before those of another branch, then the newer first, so the
|
|
// first that holds the module's manifest is the one indexed.
|
|
func (b *Builds) RegistrationsOf(module string) []Registration {
|
|
type place struct{ repository, path string }
|
|
byPlace := map[place][]delivery.BuildRecord{}
|
|
b.mu.Lock()
|
|
for _, r := range b.records {
|
|
if r.Module == module && registered(r) {
|
|
k := place{delivery.RepositoryName(r.Repository), pathOf(strings.Trim(r.Path, "/"))}
|
|
byPlace[k] = append(byPlace[k], r)
|
|
}
|
|
}
|
|
b.mu.Unlock()
|
|
// Within one place the record is that of the ref a registration comes from, never a newer pull request's or try's: a
|
|
// trunk's ref (main, master, trunk, none), else the ref most of the place's records name (the registered photos, de-spiegel
|
|
// and link2pay were built from nox-mesh), the newer of two equal; then its newest record.
|
|
best := map[place]delivery.BuildRecord{}
|
|
for k, recs := range byPlace {
|
|
count, latest := map[string]int{}, map[string]delivery.BuildRecord{}
|
|
for _, r := range recs {
|
|
count[r.Ref]++
|
|
if cur, ok := latest[r.Ref]; !ok || newer(r, cur) {
|
|
latest[r.Ref] = r
|
|
}
|
|
}
|
|
var pick string
|
|
first := true
|
|
for ref, rec := range latest {
|
|
switch {
|
|
case first:
|
|
case trunkLike[ref] != trunkLike[pick]:
|
|
if !trunkLike[ref] {
|
|
continue
|
|
}
|
|
case count[ref] != count[pick]:
|
|
if count[ref] < count[pick] {
|
|
continue
|
|
}
|
|
case !newer(rec, latest[pick]):
|
|
continue
|
|
}
|
|
pick, first = ref, false
|
|
}
|
|
best[k] = latest[pick]
|
|
}
|
|
recs := slices.Collect(maps.Values(best))
|
|
slices.SortFunc(recs, func(x, y delivery.BuildRecord) int {
|
|
switch {
|
|
case trunkLike[x.Ref] != trunkLike[y.Ref]:
|
|
if trunkLike[x.Ref] {
|
|
return -1
|
|
}
|
|
return 1
|
|
case newer(x, y):
|
|
return -1
|
|
case newer(y, x):
|
|
return 1
|
|
}
|
|
return strings.Compare(x.Repository+x.Path, y.Repository+y.Path)
|
|
})
|
|
out := make([]Registration, 0, len(recs))
|
|
for _, r := range recs {
|
|
out = append(out, Registration{Module: r.Module, Repository: r.Repository, Path: r.Path, Commit: r.Commit, Ref: r.Ref})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Registered is the registrations of every module a record names, by module name.
|
|
func (b *Builds) Registered() []Registration {
|
|
b.mu.Lock()
|
|
names := map[string]bool{}
|
|
for _, r := range b.records {
|
|
names[r.Module] = true
|
|
}
|
|
b.mu.Unlock()
|
|
var out []Registration
|
|
for _, name := range slices.Sorted(maps.Keys(names)) {
|
|
out = append(out, b.RegistrationsOf(name)...)
|
|
}
|
|
return out
|
|
}
|