mesh/delivery delivered
mesh/check pass 32542ebc14c3 on 7a88eb0a5609 | unit 122 run, 366 kept | integration 132 run, 414 kept | composition passed on novox
189 lines
7.6 KiB
Go
189 lines
7.6 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"
|
|
// None2Pending is for a module with no artifact of a toolchain whose fingerprint the recompute of its chain gives (its
|
|
// bases' fingerprints; no builder input since ADR 0530 §1).
|
|
None2Pending = "the fingerprint is computed with its chain"
|
|
// 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
|
|
// ArtifactPaths are the paths each artifact naming them names, as its manifest names them (delivery.Module.ArtifactPaths).
|
|
ArtifactPaths map[string][]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 {
|
|
return inputsOfNamed(module, raw, tree, locks, nil, base)
|
|
}
|
|
|
|
// inputsOfNamed is inputsOf with the git object of each path the message names for the module's directory (delivery
|
|
// Forge.Paths, PreviewModule.Paths). A module whose every artifact names its paths takes its tree input from them and its
|
|
// manifest, by mesh-sdk's one function (delivery.NamedTree), and keeps their objects, so a computation from kept inputs
|
|
// gives the merge's value (novox/hq ADR 0499 clause 2); a message that does not carry the object of each such path gives it
|
|
// none, naming the paths (clause 3). Every other module keeps its directory's tree, whatever objects are given.
|
|
func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, base baseFP) moduleInputs {
|
|
m, err := catalogue.ParseManifestForInputs([]byte(raw))
|
|
if err != nil {
|
|
return moduleInputs{None2: None2Unreadable + err.Error()}
|
|
}
|
|
if m.Module != module {
|
|
return moduleInputs{None2: fmt.Sprintf("the manifest is of module %q, not %q", m.Module, module)}
|
|
}
|
|
var artifactPaths map[string][]string
|
|
if m.Build != nil {
|
|
for _, a := range m.Build.Artifacts {
|
|
if len(a.Paths) > 0 {
|
|
if artifactPaths == nil {
|
|
artifactPaths = map[string][]string{}
|
|
}
|
|
artifactPaths[a.Name] = slices.Clone(a.Paths)
|
|
}
|
|
}
|
|
}
|
|
var named map[string]string
|
|
if paths, every := m.NamedPaths(); every {
|
|
var missing []string
|
|
named = map[string]string{}
|
|
for _, p := range paths {
|
|
if objects[p] == "" {
|
|
missing = append(missing, p)
|
|
continue
|
|
}
|
|
named[p] = objects[p]
|
|
}
|
|
if len(missing) > 0 {
|
|
return moduleInputs{None2: "the message carries no git object of the named paths " + strings.Join(missing, ", "), ArtifactPaths: artifactPaths}
|
|
}
|
|
if tree, err = delivery.NamedTree(delivery.ManifestDigest([]byte(raw)), named); err != nil {
|
|
return moduleInputs{None2: err.Error(), ArtifactPaths: artifactPaths}
|
|
}
|
|
}
|
|
if tree == "" {
|
|
return moduleInputs{None2: "the merge names no tree for the module's directory", ArtifactPaths: artifactPaths}
|
|
}
|
|
kept := delivery.KeptInputs2{Tree: tree, Paths: named}
|
|
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, ArtifactPaths: artifactPaths}
|
|
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 = None2Pending
|
|
}
|
|
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
|
|
}
|