// 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 `.build.`, // 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 }