From 23daa2c03ca23e4ec92301921e7105cea2a98162 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 18:14:30 +0200 Subject: [PATCH] =?UTF-8?q?Take=20a=20module's=20tree=20input=20from=20the?= =?UTF-8?q?=20paths=20its=20artifacts=20name,=20by=20mesh-sdk's=20NamedTre?= =?UTF-8?q?e,=20and=20keep=20their=20objects=20and=20each=20artifact's=20p?= =?UTF-8?q?aths,=20so=20a=20module=20outside=20them=20moves=20for=20no=20c?= =?UTF-8?q?hange=20there=20(issue=201758,=20I7=20and=20the=20indexer's=20I?= =?UTF-8?q?8,=20ADR=200499=20=C2=A72=20=C2=A73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A merged message or a preview without the objects gives the module none, naming the paths. Every other module keeps its directory's tree. Module.ArtifactPaths is filled for the checker (the lead's interface). --- .../internal/index/inputs.go | 48 +++++++++- .../internal/index/modules.go | 4 +- .../internal/index/named_test.go | 87 +++++++++++++++++++ .../internal/index/previews.go | 2 +- .../testdata/sdk-9cb4d59-mesh-sdk.module.json | 16 ++++ 5 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 modules/mesh-delivery-indexer/internal/index/named_test.go create mode 100644 modules/mesh-delivery-indexer/internal/index/testdata/sdk-9cb4d59-mesh-sdk.module.json diff --git a/modules/mesh-delivery-indexer/internal/index/inputs.go b/modules/mesh-delivery-indexer/internal/index/inputs.go index 2a53587..75cee02 100644 --- a/modules/mesh-delivery-indexer/internal/index/inputs.go +++ b/modules/mesh-delivery-indexer/internal/index/inputs.go @@ -37,6 +37,8 @@ type moduleInputs struct { 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. @@ -55,6 +57,15 @@ type baseFP func(module string) (fp, none string) // 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 := readManifest([]byte(raw)) if err != nil { return moduleInputs{None2: "the manifest cannot be read: " + err.Error()} @@ -62,10 +73,39 @@ func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) mo 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"} + 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) + } + } } - kept := delivery.KeptInputs2{Tree: tree} + 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) @@ -94,7 +134,7 @@ func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) mo } sort.Strings(kept.Bases) sort.Strings(missing) - out := moduleInputs{Kept: &kept} + out := moduleInputs{Kept: &kept, ArtifactPaths: artifactPaths} if len(langs) > 0 { out.Languages = langs } diff --git a/modules/mesh-delivery-indexer/internal/index/modules.go b/modules/mesh-delivery-indexer/internal/index/modules.go index 1503df6..137b035 100644 --- a/modules/mesh-delivery-indexer/internal/index/modules.go +++ b/modules/mesh-delivery-indexer/internal/index/modules.go @@ -60,8 +60,8 @@ func (ix *Indexer) applyMerged(ctx context.Context, f delivery.Forge) error { if next.Manifest == "" { next.None2 = "the manifest cannot be read: it cannot be kept as a value" } else { - in := inputsOf(name, raw, f.Trees[dir], f.Locks[dir], ix.baseFP(ctx)) - next.Inputs2, next.None2 = in.Kept, in.None2 + in := inputsOfNamed(name, raw, f.Trees[dir], f.Locks[dir], f.Paths[dir], ix.baseFP(ctx)) + next.Inputs2, next.None2, next.ArtifactPaths = in.Kept, in.None2, in.ArtifactPaths } overlay[key] = next } diff --git a/modules/mesh-delivery-indexer/internal/index/named_test.go b/modules/mesh-delivery-indexer/internal/index/named_test.go new file mode 100644 index 0000000..9fafd6b --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/named_test.go @@ -0,0 +1,87 @@ +package index + +import ( + "context" + "reflect" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +// ADR 0499 clauses 2 and 3 (I7): a module whose every artifact names its paths takes its tree input from them, by mesh-sdk's +// one function, and keeps their objects, so a computation from kept inputs gives the merge's value. The manifest is +// mesh-sdk's module.json at 9cb4d59 (its lib names src, test, package.json, package-lock.json and tsconfig.json). + +var sdkObjects = map[string]string{"src": "1111111111111111111111111111111111111111", "test": "2222222222222222222222222222222222222222", + "package.json": "3333333333333333333333333333333333333333", "package-lock.json": "4444444444444444444444444444444444444444", "tsconfig.json": "5555555555555555555555555555555555555555"} + +func TestAModuleNamingItsPathsTakesItsTreeFromThemAndKeepsTheirObjects(t *testing.T) { // I7, ADR 0499 clause 2 + ix, mods, _ := manifestsIndexer(t) + raw := fixture(t, "sdk-9cb4d59-mesh-sdk") + f := withManifests(mergedMsg("novox/mesh-sdk", map[string]string{"": "roottree"}), map[string]string{"": raw}, map[string]map[string]string{"": {"package-lock.json": avahiGoSum}}) + f.Paths = map[string]map[string]string{"": sdkObjects} + if err := ix.Apply(context.Background(), f); err != nil { + t.Fatal(err) + } + m, ok := moduleOf(t, mods, "mesh-sdk") + if !ok || m.Inputs2 == nil { + t.Fatalf("module = %+v (%v)", m, ok) + } + want, err := delivery.NamedTree(delivery.ManifestDigest([]byte(raw)), sdkObjects) + if err != nil { + t.Fatal(err) + } + if m.Inputs2.Tree != want || !reflect.DeepEqual(m.Inputs2.Paths, sdkObjects) { + t.Errorf("inputs = %+v, want the named tree %s and the objects kept", m.Inputs2, want) + } + if got := m.ArtifactPaths["lib"]; strings.Join(got, ",") != "src,test,package.json,package-lock.json,tsconfig.json" { + t.Errorf("artifact paths = %v, want lib's paths as its manifest names them", m.ArtifactPaths) + } + // Recomputed from what is kept, with no merge, the module is the same (ADR 0499 clause 2). + before := string(mods.vals["mesh-sdk"]) + if err := ix.Recompute(context.Background()); err != nil { + t.Fatal(err) + } + if string(mods.vals["mesh-sdk"]) != before { + t.Errorf("a recompute changed the module: %s, was %s", mods.vals["mesh-sdk"], before) + } +} + +func TestAMessageWithoutTheNamedPathsObjectsGivesTheModuleNoneNamingThem(t *testing.T) { // I7, ADR 0499 clause 3 + ix, mods, _ := manifestsIndexer(t) + raw := fixture(t, "sdk-9cb4d59-mesh-sdk") + f := withManifests(mergedMsg("novox/mesh-sdk", map[string]string{"": "roottree"}), map[string]string{"": raw}, nil) + if err := ix.Apply(context.Background(), f); err != nil { + t.Fatal(err) + } + m, _ := moduleOf(t, mods, "mesh-sdk") + if m.FP2 != "" || !strings.Contains(m.None2, "named paths") || !strings.Contains(m.None2, "tsconfig.json") { + t.Errorf("module = %+v, want none naming the paths the message did not carry", m) + } +} + +func TestAModuleNamingNoPathsKeepsItsDirectorysTree(t *testing.T) { // I7, ADR 0499 clause 2: every other module is unchanged + in := inputsOfNamed("avahi", catalogueManifest(t, "avahi"), "avahitree", map[string]string{"go.sum": avahiGoSum}, map[string]string{"x": "1111111111111111111111111111111111111111"}, noBases) + if in.Kept == nil || in.Kept.Tree != "avahitree" || in.Kept.Paths != nil { + t.Errorf("inputs = %+v, want the directory's tree and no paths", in.Kept) + } +} + +func TestACandidatesModuleTakesTheNamedPathsObjectsFromThePreview(t *testing.T) { // I8, ADR 0499 clause 2 (the merge preview) + ix, _, _ := manifestsIndexer(t) + raw := fixture(t, "sdk-9cb4d59-mesh-sdk") + digest, err := ix.keepManifest(context.Background(), "mesh-sdk", raw) + if err != nil { + t.Fatal(err) + } + named, _ := delivery.NamedTree(digest, sdkObjects) + with := delivery.Preview{Modules: map[string]delivery.PreviewModule{".": {Tree: named, Manifest: digest, Paths: sdkObjects}}} + without := delivery.Preview{Modules: map[string]delivery.PreviewModule{".": {Tree: "roottree", Manifest: digest}}} + if got := ix.candidateModule(context.Background(), "mesh-sdk", ".", without); !strings.Contains(got.None2, "named paths") { + t.Errorf("without the objects: %+v, want none naming the named paths", got) + } + if got := ix.candidateModule(context.Background(), "mesh-sdk", ".", with); strings.Contains(got.None2, "named paths") || got.Manifest != digest { + t.Errorf("with the objects: %+v, want them taken (none only for what else is missing)", got) + } +} diff --git a/modules/mesh-delivery-indexer/internal/index/previews.go b/modules/mesh-delivery-indexer/internal/index/previews.go index 1f804c4..55ec2a6 100644 --- a/modules/mesh-delivery-indexer/internal/index/previews.go +++ b/modules/mesh-delivery-indexer/internal/index/previews.go @@ -228,7 +228,7 @@ func (ix *Indexer) candidateModule(ctx context.Context, m, dir string, pv delive out.None2 = NonePreviewNoManifest return out } - in := inputsOf(m, kept.JSON, pm.Tree, pm.Locks, ix.baseFP(ctx)) + in := inputsOfNamed(m, kept.JSON, pm.Tree, pm.Locks, pm.Paths, ix.baseFP(ctx)) if in.Kept == nil { out.None2 = in.None2 return out diff --git a/modules/mesh-delivery-indexer/internal/index/testdata/sdk-9cb4d59-mesh-sdk.module.json b/modules/mesh-delivery-indexer/internal/index/testdata/sdk-9cb4d59-mesh-sdk.module.json new file mode 100644 index 0000000..404139a --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/testdata/sdk-9cb4d59-mesh-sdk.module.json @@ -0,0 +1,16 @@ +{ + "module": "mesh-sdk", + "version": "1", + "slug": "sdk", + "build": { + "artifacts": [ + { + "name": "lib", + "kind": "package", + "language": "typescript", + "paths": ["src", "test", "package.json", "package-lock.json", "tsconfig.json"] + } + ] + }, + "resources": [] +}