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

148 lines
5.7 KiB
Go

package index
import (
"fmt"
"maps"
"slices"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The reasons a module has no fingerprint of the new form while the indexer writes none for any module (novox/hq
// ADR 0465 clause 2; issue 1444). Toolchains missing is delivery.ToolchainNoReport.
const (
// None2NoManifest is for a module whose directory the merged message carries no manifest of (an older bridge).
None2NoManifest = "the message carries no manifest"
// None2Builder is for a module with no artifact of a toolchain: only the builder's fingerprint is missing.
None2Builder = "builder's fingerprint not yet known"
// None2Context is for a module an artifact of which reads another repository's tree (today's form gives it none
// either, NoneContext).
None2Context = "context not pinned"
)
// lockFile is the lock file of a language's artifact that resolves packages when it is built, by name in the module's
// directory (the bridge reads these, internal/bridge LockFiles; issue 1450).
var lockFile = map[string]string{"go": "go.sum", "typescript": "package-lock.json"}
// moduleInputs is what a module's manifest and lock hashes give the indexer at a merge (issue 1450).
type moduleInputs struct {
// Kept is the new-form inputs the merge gave the module; nil when the manifest could not be read or gave none.
Kept *delivery.KeptInputs2
// Languages is the language of each artifact that has a toolchain (a bundle or a package of go or typescript), by
// artifact name. The toolchain versions are joined to them when the build modules report them (issue 1444, the
// watching half); the SDK's KeptInputs2 keeps no languages, so they are read from the manifest again then.
Languages map[string]string
// None2 is why the module has no fingerprint of the new form.
None2 string
}
// baseFP answers a base module's new-form fingerprint, or why it has none.
type baseFP func(module string) (fp, none string)
// inputsOf turns the manifest of a module (raw, as the forge answered it), the tree of its directory and the lock
// hashes of that directory (lock file name to hash) into its new-form inputs. The manifest is read by the catalogue's
// parser; a manifest it refuses, or one that is of another module, leaves Kept nil and says why.
//
// The rules are the controller's builder (mesh-controller internal/builder/builder.go): a bundle or a package of a
// language with a toolchain (go, typescript) is the artifact that has one; a package, and a bundle of those languages,
// resolves packages when it is built (wantsPackages, :1017) only when its language's lock file exists: it then has
// an entry in Locks with that file's hash, and an artifact whose lock file the merge did not carry has no entry (a
// dependency-free or vendored bundle resolves nothing, as mesh-sdk SourceInputs2 says of an artifact not in Locks;
// novox/hq ADR 0469 clause 5, issue 1459). A bundle that needs a lock and lacks one is refused where it is built, not
// here, so a fingerprint is never given for a build that could not run. An archive, an image and an upstream resolve
// nothing and have no toolchain. A base is a build.on entry that names a module.
func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) moduleInputs {
m, err := catalogue.ParseManifest([]byte(raw))
if err != nil {
return moduleInputs{None2: "the manifest cannot be read: " + err.Error()}
}
if m.Module != module {
return moduleInputs{None2: fmt.Sprintf("the manifest is of module %q, not %q", m.Module, module)}
}
if tree == "" {
return moduleInputs{None2: "the merge names no tree for the module's directory"}
}
kept := delivery.KeptInputs2{Tree: tree}
contexts := false
var missing []string
langs := languagesOf(m)
if m.Build != nil {
for _, a := range m.Build.Artifacts {
if a.Context != nil {
contexts = true
}
}
for _, name := range slices.Sorted(maps.Keys(langs)) {
// A lock file the merge did not carry leaves no entry: the artifact resolves nothing (ADR 0469 clause 5).
if h := locks[lockFile[langs[name]]]; h != "" {
if kept.Locks == nil {
kept.Locks = map[string]string{}
}
kept.Locks[name] = h
}
}
for _, on := range basesOf(m) {
if fp, none := base(on); fp != "" {
kept.Bases = append(kept.Bases, fp)
} else {
missing = append(missing, fmt.Sprintf("the base %s has no fingerprint (%s)", on, none))
}
}
}
sort.Strings(kept.Bases)
sort.Strings(missing)
out := moduleInputs{Kept: &kept}
if len(langs) > 0 {
out.Languages = langs
}
switch {
case contexts:
out.None2 = None2Context
case len(missing) > 0:
out.None2 = strings.Join(missing, "; ")
case len(langs) > 0:
out.None2 = delivery.ToolchainNoReport
default:
out.None2 = None2Builder
}
return out
}
// languagesOf is the language of each artifact of the manifest that has a toolchain (a bundle or a package of go or
// typescript), by artifact name: the artifacts a build module's toolchain enters the fingerprint of.
func languagesOf(m catalogue.Manifest) map[string]string {
langs := map[string]string{}
if m.Build == nil {
return langs
}
for _, a := range m.Build.Artifacts {
if a.Kind != catalogue.ArtifactBundle && a.Kind != catalogue.ArtifactPackage {
continue
}
if _, ok := lockFile[a.Language]; ok {
langs[a.Name] = a.Language
}
}
return langs
}
// basesOf is the modules the manifest names as bases (its build.on entries that name a module), each once, sorted.
func basesOf(m catalogue.Manifest) []string {
if m.Build == nil {
return nil
}
seen := map[string]bool{}
var out []string
for _, on := range m.Build.On {
if on.Module != "" && !seen[on.Module] {
seen[on.Module] = true
out = append(out, on.Module)
}
}
sort.Strings(out)
return out
}