137 lines
4.5 KiB
Go
137 lines
4.5 KiB
Go
// Package index keeps the candidate groups (novox/hq ADR 0437 §5) the forge bridge's pull messages make.
|
|
package index
|
|
|
|
import (
|
|
"encoding/json"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Builds holds, from the controller's builds bucket (mesh-controller_builds), which module a directory of a
|
|
// repository is: the module of the newest build record naming that repository and path (BuildRecord.Path). 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
|
|
byDir map[string]delivery.BuildRecord // by repository and path; nil when stale
|
|
mods map[string]*moduleBuilds // by module; nil when stale
|
|
}
|
|
|
|
// moduleBuilds is what the builds say of one module: its newest record of any kind, and its newest successful trunk
|
|
// build that recorded its inputs.
|
|
type moduleBuilds struct {
|
|
any delivery.BuildRecord
|
|
trunk *delivery.BuildRecord
|
|
}
|
|
|
|
// 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
|
|
b.byDir, b.mods = nil, nil
|
|
}
|
|
|
|
// Delete takes a key out.
|
|
func (b *Builds) Delete(key string) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if _, ok := b.records[key]; ok {
|
|
delete(b.records, key)
|
|
b.byDir, b.mods = nil, nil
|
|
}
|
|
}
|
|
|
|
func dirKey(repository, dir string) string { return repository + "\x00" + strings.Trim(dir, "/") }
|
|
|
|
// Module is the module a build record of the repository names the directory as its path, if one does.
|
|
func (b *Builds) Module(repository, dir string) (string, bool) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.byDir == nil {
|
|
b.byDir = map[string]delivery.BuildRecord{}
|
|
for _, r := range b.records {
|
|
k := dirKey(r.Repository, r.Path)
|
|
if old, ok := b.byDir[k]; !ok || r.At.After(old.At) || (r.At.Equal(old.At) && r.Module < old.Module) {
|
|
b.byDir[k] = r
|
|
}
|
|
}
|
|
}
|
|
r, ok := b.byDir[dirKey(repository, dir)]
|
|
return r.Module, ok
|
|
}
|
|
|
|
// ModuleOf is the module of a changed directory: the one a build record of the repository names it the path of; a
|
|
// directory no build record names is the directory's last element (the catalogue's rule for nearly every manifest).
|
|
func (b *Builds) ModuleOf(repository, dir string) string {
|
|
if m, ok := b.Module(repository, dir); ok {
|
|
return m
|
|
}
|
|
return path.Base(strings.Trim(dir, "/"))
|
|
}
|
|
|
|
// Known is a module the builds know of in a repository: its directory there and its newest successful trunk build (ref
|
|
// main, state built, with source inputs) if any. The newest key of a module is any state and ref, so it is not what
|
|
// finds this (mesh-sdk go/delivery/buildrecord.go NewestKey); the records are.
|
|
type Known struct {
|
|
Module string
|
|
Repository string
|
|
Path string
|
|
Trunk *delivery.BuildRecord
|
|
}
|
|
|
|
func newer(a, b delivery.BuildRecord) bool {
|
|
return a.At.After(b.At) || (a.At.Equal(b.At) && a.ID > b.ID)
|
|
}
|
|
|
|
// Modules are the modules built from a repository, by name. A module whose newest successful trunk build is of the
|
|
// repository belongs to it with that record's path; a module with no such build belongs to the repository of its newest
|
|
// record of any kind.
|
|
func (b *Builds) Modules(repository string) []Known {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.mods == nil {
|
|
b.mods = map[string]*moduleBuilds{}
|
|
for _, r := range b.records {
|
|
m := b.mods[r.Module]
|
|
if m == nil {
|
|
m = &moduleBuilds{any: r}
|
|
b.mods[r.Module] = m
|
|
} else if newer(r, m.any) {
|
|
m.any = r
|
|
}
|
|
if r.Ref == "main" && r.State == "built" && r.SourceInputs != nil && (m.trunk == nil || newer(r, *m.trunk)) {
|
|
c := r
|
|
m.trunk = &c
|
|
}
|
|
}
|
|
}
|
|
var out []Known
|
|
for name, m := range b.mods {
|
|
k := Known{Module: name, Repository: m.any.Repository, Path: strings.Trim(m.any.Path, "/"), Trunk: m.trunk}
|
|
if m.trunk != nil {
|
|
k.Repository, k.Path = m.trunk.Repository, strings.Trim(m.trunk.Path, "/")
|
|
}
|
|
if k.Repository == repository {
|
|
out = append(out, k)
|
|
}
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Module < out[j].Module })
|
|
return out
|
|
}
|