package index import ( "fmt" "maps" "slices" "strings" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/sdk/trunk" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/views" ) // PackageUsersTool lists, per mesh package, its users and the series each accepts (DLV-I8; ADR 0606 ยง4; design 32 *A // package, its series, and the series its users accept*), so a user left on an older series is seen. const PackageUsersTool = "indexer_package_users" // PackageUser is one trunk module using a package: the series it accepts (Named when its manifest's `packages` names it, // else the default), that series' newest published version ("" while none is), and the version its fingerprint pins. type PackageUser struct { Module string `json:"module"` Accepts string `json:"accepts"` Named bool `json:"named"` Newest string `json:"newest"` Pinned string `json:"pinned,omitempty"` } // PackageUses is one package of indexer_package_users: its series held in the packages state, the newest each published // (by series), its Go module path for a Go package, and its users in module order. type PackageUses struct { Package string `json:"package"` GoModule string `json:"go-module,omitempty"` Series map[string]string `json:"series"` Users []PackageUser `json:"users"` } // PackageUsersOf answers indexer_package_users from the indexer's own states (modules, manifests, packages), for one // package or, with only empty, every package. A package is known by a series key of the packages state or by a name some // trunk manifest's `packages` names. A trunk module uses it when its manifest names it, or, for a Go package, when its // go.sum or its build's imports reach the package's Go module from another repository (a sibling by path is no user). // A value that cannot be read is an error, never a user silently left out. func PackageUsersOf(modules, manifests, packages views.Source, only string) ([]PackageUses, error) { known := map[string]*PackageUses{} get := func(name string) *PackageUses { if known[name] == nil { known[name] = &PackageUses{Package: name, Series: map[string]string{}, Users: []PackageUser{}} } return known[name] } keys, err := packages.Keys() if err != nil { return nil, fmt.Errorf("the packages state cannot be listed: %w", err) } for _, key := range keys { if !IsSeriesKey(key) { continue } e, ok, err := packages.Get(key) if err != nil { return nil, fmt.Errorf("packages.%s cannot be read: %w", key, err) } if !ok { continue } s, err := DecodeSeriesPackage(e.Value) if err != nil { return nil, fmt.Errorf("packages.%s holds what is not a series: %w", key, err) } p := get(s.Name) if s.GoModule != "" { p.GoModule = s.GoModule } p.Series[s.Series] = "" if s.Newest != nil { p.Series[s.Series] = s.Newest.Version } } type trunkMod struct { mod meshcatalog.ModulesByModuleValue manifest catalogue.Manifest } var mods []string held := map[string]trunkMod{} names, err := modules.Keys() if err != nil { return nil, fmt.Errorf("the modules state cannot be listed: %w", err) } for _, name := range names { e, ok, err := modules.Get(name) if err != nil { return nil, fmt.Errorf("modules.%s cannot be read: %w", name, err) } if !ok { continue } mod, err := meshcatalogclient.DecodeModule(e.Value) if err != nil || mod.Manifest == "" { continue // no manifest held for it } me, ok, err := manifests.Get(mod.Manifest) if err != nil { return nil, fmt.Errorf("the manifest of %s cannot be read: %w", name, err) } if !ok { continue } kept, err := meshcatalogclient.DecodeManifest(me.Value) if err != nil { return nil, fmt.Errorf("the manifest of %s: %w", name, err) } m, err := catalogue.Decode([]byte(kept.Manifest)) if err != nil { return nil, fmt.Errorf("the manifest of %s does not read: %w", name, err) } for pkg := range m.Packages { get(pkg) } held[name], mods = trunkMod{mod, m}, append(mods, name) } slices.Sort(mods) reaches := func(mod meshcatalog.ModulesByModuleValue, goModule string) bool { if goModule == "" || mod.Inputs2 == nil { return false } if repo, _, ok := trunk.PackageRepository(goModule); !ok || trunk.SameRepository(repo, mod.Repository) { return false } for _, p := range append(slices.Clone(mod.Inputs2.Packages), mod.Inputs2.Imports...) { if p == goModule || strings.HasPrefix(p, goModule+"/") { return true } } return false } var out []PackageUses for _, name := range slices.Sorted(maps.Keys(known)) { if only != "" && name != only { continue } p := known[name] for _, m := range mods { t := held[m] _, named := t.manifest.Packages[name] if !named && !reaches(t.mod, p.GoModule) { continue } accepts := t.manifest.AcceptedSeries(name) u := PackageUser{Module: m, Accepts: accepts, Named: named, Newest: p.Series[accepts]} if p.GoModule != "" { u.Pinned = t.mod.Packages[p.GoModule] } p.Users = append(p.Users, u) } out = append(out, *p) } if only != "" && len(out) == 0 { return nil, fmt.Errorf("no series of %s is held and no trunk manifest names it", only) } if out == nil { out = []PackageUses{} } return out, nil }