From d2861cb445db1ed82dc324d5c38166b42b8263df Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Mon, 17 Aug 2026 22:04:43 +0200 Subject: [PATCH 1/6] test(package-deps): snapshot independent root closures Record that a package root reached under a competing virtual implementation does not receive its own link closure. Signed-off-by: Ali Caglayan --- .../test-cases/package-materialization/dune | 1 + .../independent-roots.t | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 test/blackbox-tests/test-cases/package-materialization/independent-roots.t diff --git a/test/blackbox-tests/test-cases/package-materialization/dune b/test/blackbox-tests/test-cases/package-materialization/dune index a4dde991d06..aff5ae637dd 100644 --- a/test/blackbox-tests/test-cases/package-materialization/dune +++ b/test/blackbox-tests/test-cases/package-materialization/dune @@ -1,5 +1,6 @@ (cram (applies_to + independent-roots installed-package no-transitive-through-targets ocamlfind diff --git a/test/blackbox-tests/test-cases/package-materialization/independent-roots.t b/test/blackbox-tests/test-cases/package-materialization/independent-roots.t new file mode 100644 index 00000000000..4bc42644620 --- /dev/null +++ b/test/blackbox-tests/test-cases/package-materialization/independent-roots.t @@ -0,0 +1,100 @@ +Each library installed by an explicitly requested package is an independent +closure root. Combining the roots before resolving virtual implementations can +suppress a default implementation needed by one root. + + $ make_dune_project 3.24 + $ cat >>dune-project <<'EOF' + > (package (name roots)) + > (package (name virtual-support)) + > (package (name alternative-support)) + > EOF + + $ mkdir roots-a roots-b virtual-lib default-impl alternative-impl + $ cat >virtual-lib/dune <<'EOF' + > (library + > (name virtual_support) + > (public_name virtual-support) + > (wrapped false) + > (virtual_modules virtual_support) + > (default_implementation virtual-support.default)) + > EOF + $ echo 'val value : int' >virtual-lib/virtual_support.mli + + $ cat >default-impl/dune <<'EOF' + > (library + > (name default_impl) + > (public_name virtual-support.default) + > (implements virtual-support)) + > EOF + $ echo 'let value = 1' >default-impl/virtual_support.ml + + $ cat >alternative-impl/dune <<'EOF' + > (library + > (name alternative_impl) + > (public_name alternative-support) + > (implements virtual-support)) + > EOF + $ echo 'let value = 2' >alternative-impl/virtual_support.ml + +The first root selects the alternative implementation while the second root +uses the virtual library on its own and therefore needs the default. + + $ cat >roots-b/dune <<'EOF' + > (library + > (name roots_b) + > (public_name roots.b) + > (libraries virtual-support)) + > EOF + $ echo 'let value = Virtual_support.value' >roots-b/roots_b.ml + + $ cat >roots-a/dune <<'EOF' + > (library + > (name roots_a) + > (public_name roots.a) + > (libraries roots.b alternative-support)) + > EOF + $ echo 'let value = Roots_b.value' >roots-a/roots_a.ml + + $ cat >dune <<'EOF' + > (rule + > (target result) + > (deps (package roots)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -predicates byte -format "%d/%A" + > virtual-support.default)))) + > (rule + > (target alternative-result) + > (deps (package roots)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -predicates byte -format "%d/%A" + > alternative-support)))) + > EOF + +Both implementations and their archives must be present in the scoped layout. +The default is currently absent because `roots.b` is not closed independently. + + $ dune build result + File "dune", lines 1-7, characters 0-179: + 1 | (rule + 2 | (target result) + 3 | (deps (package roots)) + 4 | (action + 5 | (with-stdout-to %{target} + 6 | (run %{bin:ocamlfind} query -predicates byte -format "%d/%A" + 7 | virtual-support.default)))) + ocamlfind: Package `virtual-support.default' not found + [1] + + $ dune build alternative-result + File "dune", lines 8-14, characters 0-187: + 8 | (rule + 9 | (target alternative-result) + 10 | (deps (package roots)) + 11 | (action + 12 | (with-stdout-to %{target} + 13 | (run %{bin:ocamlfind} query -predicates byte -format "%d/%A" + 14 | alternative-support)))) + ocamlfind: Package `alternative-support' not found + [1] From e3b501d56675c91b120f16856a1f501498415c85 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Mon, 17 Aug 2026 10:14:26 +0200 Subject: [PATCH 2/6] test(package-deps): snapshot the missing workspace library closure Exercise library dependencies, private libraries, stubs, redirects, namespaces, virtual implementations, PPX runtime libraries, nested Dune consumers, installed packages, and only-packages masking. Record the current missing-library failures before materializing the closure. Signed-off-by: Ali Caglayan --- .../test-cases/package-materialization/dune | 1 + .../installed-package.t | 92 +++- .../package-materialization/ocamlfind.t | 6 +- .../strict-package-deps.t | 15 +- .../transitive-closure.t | 426 +++++++++++++++++- 5 files changed, 505 insertions(+), 35 deletions(-) diff --git a/test/blackbox-tests/test-cases/package-materialization/dune b/test/blackbox-tests/test-cases/package-materialization/dune index a4dde991d06..f46de8e60d2 100644 --- a/test/blackbox-tests/test-cases/package-materialization/dune +++ b/test/blackbox-tests/test-cases/package-materialization/dune @@ -3,5 +3,6 @@ installed-package no-transitive-through-targets ocamlfind + transitive-closure virtual-without-default) (deps %{bin:ocamlfind})) diff --git a/test/blackbox-tests/test-cases/package-materialization/installed-package.t b/test/blackbox-tests/test-cases/package-materialization/installed-package.t index 575329501e0..20aecd86316 100644 --- a/test/blackbox-tests/test-cases/package-materialization/installed-package.t +++ b/test/blackbox-tests/test-cases/package-materialization/installed-package.t @@ -2,42 +2,112 @@ Test that (deps (package ...)) works with externally installed packages. Installed packages (found via findlib) go through the Installed codepath, not the layout. The layout only applies to Local (workspace) packages. -Install package "a" into a prefix: +Install packages "a" and "b" into a prefix. Library `a` depends on library +`b`, so its installed metadata records `b` as a requirement. $ mkdir a consumer prefix $ cat >a/dune-project < (lang dune 3.24) > (package (name a)) + > (package (name b)) > EOF $ cat >a/dune < (library (public_name a)) + > (library + > (public_name a) + > (libraries b)) > EOF $ cat >a/a.ml < let msg = "hello from lib a" + > let value = B.value + 1 + > EOF + + $ mkdir a/b + + $ cat >a/b/dune < (library (public_name b)) + > EOF + + $ cat >a/b/b.ml < let value = 1 > EOF $ dune build --root a @install $ dune install --root a --prefix $PWD/prefix 2>/dev/null $ test -f prefix/lib/a/META + $ test -f prefix/lib/b/META Now create a consumer project that depends on the installed package. -The consumer uses (deps (package a)) and ocamlfind to verify the -package is findable: +The consumer uses `(deps (package a))` and external OCaml tooling to verify +that both `a` and its library dependency are findable: $ cat >consumer/dune-project < (lang dune 3.24) > EOF + $ cat >consumer/main.ml < let () = print_int A.value + > EOF + $ cat >consumer/dune <<'EOF' > (rule - > (deps (package a)) - > (action (with-stdout-to out - > (run ocamlfind query a)))) + > (target main.exe) + > (deps + > main.ml + > (package a)) + > (action + > (run ocamlfind ocamlc -package a -linkpkg -o %{target} main.ml))) + > EOF + + $ OCAMLPATH=$PWD/prefix/lib dune build --root consumer main.exe + $ consumer/_build/default/main.exe + 2 + +When `--only-packages` masks a workspace library in the closure, library +resolution falls back to its installed copy. The installed library remains on +the inherited `OCAMLPATH`; it is not rematerialized as workspace support. + + $ mkdir masked masked/a-src masked/b-src + + $ cat >masked/dune-project < (lang dune 3.24) + > (package (name a)) + > (package (name b)) + > EOF + + $ cat >masked/a-src/dune < (library + > (public_name a) + > (libraries b)) + > EOF + + $ cat >masked/a-src/a.ml < let value = B.value + 10 + > EOF + + $ cat >masked/b-src/dune < (library (public_name b)) + > EOF + + $ cat >masked/b-src/b.ml < let value = 100 + > EOF + + $ cat >masked/main.ml < let () = print_int A.value + > EOF + + $ cat >masked/dune <<'EOF' + > (rule + > (target main.exe) + > (deps + > main.ml + > (package a)) + > (action + > (run %{bin:ocamlfind} ocamlc -package a -linkpkg -o %{target} main.ml))) > EOF - $ OCAMLPATH=$PWD/prefix/lib/:$OCAMLPATH dune build --root consumer out - $ cat consumer/_build/default/out - $TESTCASE_ROOT/prefix/lib/a + $ OCAMLPATH=$PWD/prefix/lib dune build --root masked --only-packages a main.exe + $ masked/_build/default/main.exe + 11 diff --git a/test/blackbox-tests/test-cases/package-materialization/ocamlfind.t b/test/blackbox-tests/test-cases/package-materialization/ocamlfind.t index ed608c8c496..beff07f746f 100644 --- a/test/blackbox-tests/test-cases/package-materialization/ocamlfind.t +++ b/test/blackbox-tests/test-cases/package-materialization/ocamlfind.t @@ -39,9 +39,9 @@ the query against the layout's OCAMLPATH. $ dune build out -Immediate-deps-only: myutil is mylib's declared opam dependency but is -NOT in the layout for (deps (package mylib)). ocamlfind fails to find -it. +Current-behavior snapshot: myutil is mylib's declared opam dependency but is +not in the immediate-only layout for (deps (package mylib)), so ocamlfind +cannot find it. $ cat >dune <<'EOF' > (rule diff --git a/test/blackbox-tests/test-cases/package-materialization/strict-package-deps.t b/test/blackbox-tests/test-cases/package-materialization/strict-package-deps.t index 4c0d2f80a7a..615cad6d174 100644 --- a/test/blackbox-tests/test-cases/package-materialization/strict-package-deps.t +++ b/test/blackbox-tests/test-cases/package-materialization/strict-package-deps.t @@ -1,6 +1,6 @@ Test that (strict_package_deps) does not affect the install layout. The -layout always uses immediate deps only. strict_package_deps controls -validation in install_rules, not layout closure. +layout currently uses immediate package dependencies only. +strict_package_deps controls validation in install_rules, not the layout. $ cat >dune-project < (lang dune 3.24) @@ -13,7 +13,9 @@ validation in install_rules, not layout closure. $ mkdir foo-src bar-src baz-src $ cat >foo-src/dune < (library (public_name foo)) + > (library + > (public_name foo) + > (libraries bar)) > EOF $ cat >foo-src/foo.ml < EOF $ cat >bar-src/dune < (library (public_name bar)) + > (library + > (public_name bar) + > (libraries baz)) > EOF $ cat >bar-src/bar.ml <dune-project < (lang dune 3.24) - > (package (name foo) (depends bar)) - > (package (name bar) (depends baz)) +The intended library closure is narrower than `foo`'s package dependencies: +package dependencies can contain unrelated executables, data, or libraries, +and are not reliably available for all kinds of packages. + + $ make_dune_project 3.24 + $ cat >>dune-project < (package (name foo) (depends package-only-dep)) + > (package (name bar)) > (package (name baz)) + > (package (name namespace)) + > (package (name package-only-dep)) + > (package (name ppx-runtime)) + > (package (name redirect-root)) + > (package (name redirect-target)) + > (package (name stubbed)) + > (package (name test-ppx)) + > (package (name virtual-root)) + > (package (name virtual-support)) > EOF - $ mkdir foo-src bar-src baz-src + $ mkdir foo-src bar-src bar-private-src bar-unrelated-src baz-src namespace-src + $ mkdir namespace-unrelated-src package-only-dep-src + $ mkdir redirect-root-src redirect-target-src virtual-root-src + $ mkdir virtual-support-src virtual-support-impl-src stubbed-src $ cat >foo-src/dune < (library (public_name foo)) + > (library + > (public_name foo) + > (libraries bar namespace.selected)) > EOF $ cat >foo-src/foo.ml < let x = 1 + > let x = Bar.y + 1 > EOF $ cat >bar-src/dune < (library (public_name bar)) + > (library + > (public_name bar) + > (libraries baz bar_private stubbed)) + > (deprecated_library_name + > (old_public_name bar.old) + > (new_public_name bar)) > EOF $ cat >bar-src/bar.ml < let y = 2 + > let y = Baz.z + Bar_private.offset + Stubbed.value () + > EOF + +The installed form of `bar` needs its package-private library too. It is part +of the library closure even though it cannot be named as a public library in +the workspace. + + $ cat >bar-private-src/dune < (library + > (name bar_private) + > (package bar)) + > EOF + + $ cat >bar-private-src/bar_private.ml < let offset = 1 + > EOF + +The package that owns `bar` also contains an unrelated library. Requiring +`bar` must not make this sibling library available. + + $ cat >bar-unrelated-src/dune < (library + > (name bar_unrelated) + > (public_name bar.unrelated)) + > EOF + + $ cat >bar-unrelated-src/bar_unrelated.ml < let unused = () > EOF $ cat >baz-src/dune < let z = 3 > EOF +Only `namespace.selected`, not a top-level `namespace` library, is in the +closure. Findlib subpackages inherit their directory but not arbitrary +top-level variables, so the filtered META drops `top_marker`. + + $ cat >namespace-src/dune < (library + > (name selected) + > (public_name namespace.selected)) + > EOF + + $ cat >namespace-src/selected.ml < let unused = () + > EOF + + $ cat >namespace-unrelated-src/dune < (library + > (name unrelated) + > (public_name namespace.unrelated)) + > EOF + + $ cat >namespace-unrelated-src/unrelated.ml < let unused = () + > EOF + + $ cat >META.namespace.template < top_marker = "drop" + > # DUNE_GEN + > EOF + + $ cat >package-only-dep-src/dune < (library + > (name package_only_dep) + > (public_name package-only-dep)) + > EOF + + $ cat >package-only-dep-src/package_only_dep.ml < let unused = () + > EOF + +Library support includes native stubs and the stublibs entries needed to load +them from bytecode. + + $ cat >stubbed-src/dune < (library + > (public_name stubbed) + > (foreign_stubs + > (language c) + > (names stubbed_stubs))) + > EOF + + $ cat >stubbed-src/stubbed.ml < external value : unit -> int = "stubbed_value" + > EOF + + $ cat >stubbed-src/stubbed_stubs.c < #include + > CAMLprim value stubbed_value(value unit) + > { + > (void) unit; + > return Val_int(4); + > } + > EOF + +A deprecated name owned by an explicitly declared package may redirect to a +library in another package. The redirect target is a root of the library +closure even when the declared package has no libraries of its own. + + $ cat >redirect-root-src/dune < (deprecated_library_name + > (old_public_name redirect-root.old) + > (new_public_name redirect-target)) + > EOF + + $ cat >redirect-target-src/dune < (library + > (name redirect_target) + > (public_name redirect-target)) + > EOF + + $ cat >redirect-target-src/redirect_target.ml < let value = 42 + > EOF + +A virtual library's default implementation must belong to the same package as +the virtual library. It is nevertheless a separate library, and is part of the +link-time library closure selected by a consumer. + + $ cat >virtual-root-src/dune < (library + > (name virtual_root) + > (public_name virtual-root) + > (libraries virtual-support)) + > EOF + + $ cat >virtual-root-src/virtual_root.ml < let value = Virtual_support.value + > EOF + + $ cat >virtual-support-src/dune < (library + > (name virtual_support) + > (public_name virtual-support) + > (wrapped false) + > (virtual_modules virtual_support) + > (default_implementation virtual-support.default)) + > EOF + + $ cat >virtual-support-src/virtual_support.mli < val value : int + > EOF + + $ cat >virtual-support-impl-src/dune < (library + > (name virtual_support_default) + > (public_name virtual-support.default) + > (implements virtual-support)) + > EOF + + $ cat >virtual-support-impl-src/virtual_support.ml < let value = 42 + > EOF + +A PPX rewriter's runtime libraries are part of the library support closure +even though they are not ordinary `requires`. + + $ make_hello_ppx_runtime_fixture + + $ cat >hello/dune < (library + > (name hello) + > (public_name ppx-runtime)) + > EOF + + $ cat >hello_ppx/dune < (library + > (name hello_ppx) + > (public_name test-ppx) + > (kind ppx_rewriter) + > (ppx_runtime_libraries ppx-runtime) + > (ppx.driver (main Hello_ppx.main))) + > EOF + + $ cat >main.ml < let () = print_int Foo.x + > EOF + $ cat >dune <<'EOF' > (rule + > (target main.exe) + > (deps + > main.ml + > (package foo)) + > (action + > (run + > %{bin:ocamlfind} + > ocamlc + > -custom + > -package + > foo + > -linkpkg + > -o + > %{target} + > main.ml))) + > (rule + > (targets main.bc stubs-result) + > (deps + > main.ml + > (package foo)) + > (action + > (progn + > (run %{bin:ocamlfind} ocamlc -package foo -linkpkg -o main.bc main.ml) + > (with-stdout-to stubs-result (run %{bin:ocamlrun} main.bc))))) + > (rule + > (target redirect) > (deps (package foo)) - > (action (with-stdout-to out (echo "ok")))) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query bar.old)))) + > (rule + > (target namespace-marker) + > (deps (package foo)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -format "%(top_marker)" namespace)))) + > (rule + > (target ppx-runtime-marker) + > (deps (package test-ppx)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query ppx-runtime)))) + > (rule + > (target root-redirect) + > (deps (package redirect-root)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -recursive redirect-root.old)))) > EOF - $ dune build out +The package dependency does not currently supply the library closure needed +for external OCaml tooling to compile and link against `foo`. + + $ dune build main.exe && _build/default/main.exe + File "dune", lines 1-16, characters 0-179: + 1 | (rule + 2 | (target main.exe) + 3 | (deps + .... + 14 | -o + 15 | %{target} + 16 | main.ml))) + ocamlfind: Package `bar' not found - required by `foo' + [1] + $ dune build stubs-result && cat _build/default/stubs-result + File "dune", lines 17-25, characters 0-229: + 17 | (rule + 18 | (targets main.bc stubs-result) + 19 | (deps + 20 | main.ml + 21 | (package foo)) + 22 | (action + 23 | (progn + 24 | (run %{bin:ocamlfind} ocamlc -package foo -linkpkg -o main.bc main.ml) + 25 | (with-stdout-to stubs-result (run %{bin:ocamlrun} main.bc))))) + ocamlfind: Package `bar' not found - required by `foo' + [1] + $ dune build redirect + File "dune", lines 26-31, characters 0-126: + 26 | (rule + 27 | (target redirect) + 28 | (deps (package foo)) + 29 | (action + 30 | (with-stdout-to %{target} + 31 | (run %{bin:ocamlfind} query bar.old)))) + ocamlfind: Package `bar.old' not found + [1] + $ dune build namespace-marker && test -z "$(cat _build/default/namespace-marker)" + File "dune", lines 32-37, characters 0-160: + 32 | (rule + 33 | (target namespace-marker) + 34 | (deps (package foo)) + 35 | (action + 36 | (with-stdout-to %{target} + 37 | (run %{bin:ocamlfind} query -format "%(top_marker)" namespace)))) + ocamlfind: Package `namespace' not found + [1] + $ dune build ppx-runtime-marker + File "dune", lines 38-43, characters 0-145: + 38 | (rule + 39 | (target ppx-runtime-marker) + 40 | (deps (package test-ppx)) + 41 | (action + 42 | (with-stdout-to %{target} + 43 | (run %{bin:ocamlfind} query ppx-runtime)))) + ocamlfind: Package `ppx-runtime' not found + [1] + $ dune build root-redirect + File "dune", lines 44-49, characters 0-162: + 44 | (rule + 45 | (target root-redirect) + 46 | (deps (package redirect-root)) + 47 | (action + 48 | (with-stdout-to %{target} + 49 | (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + ocamlfind: Package `redirect-target' not found - required by `redirect-root.old' + [1] -Only foo appears, neither bar nor baz, even though foo declares -(depends bar) and bar declares (depends baz): +The same missing closure is visible to a nested Dune invocation: the +materialized `foo` metadata names `namespace.selected`, but its package is +absent from the layout. - $ dune rules --format=json _build/default/out | jq_dune '.[] | ruleDepFilePaths' | censor | grep dune-package | sort + $ mkdir consumer + $ cat >consumer/dune-project < (lang dune 3.24) + > EOF + + $ cat >consumer/dune < (executable + > (name main) + > (libraries foo virtual-root)) + > EOF + + $ cat >consumer/main.ml < let () = print_int (Foo.x + Virtual_root.value) + > EOF + + $ cat >>dune <<'EOF' + > (rule + > (target dune-package-result) + > (deps + > (package foo) + > (package virtual-root) + > (source_tree consumer)) + > (action + > (with-stdout-to %{target} + > (chdir consumer (run %{bin:dune} exec ./main.exe))))) + > EOF + + $ dune build dune-package-result 2>&1 | censor + File "$PWD/_build/install/default/.packages/$DIGEST/lib/foo/dune-package", line 14, characters 15-33: + 14 | (requires bar namespace.selected) + ^^^^^^^^^^^^^^^^^^ + Error: Library "namespace.selected" not found. + -> required by library "foo" in + $PWD/_build/install/default/.packages/$DIGEST/lib/foo + -> required by executable main in dune:2 + -> required by _build/default/.main.eobjs/native/dune__exe__Main.cmx + -> required by _build/default/main.exe + [1] + +The current layout contains only `foo`. It contains neither the libraries in +its library closure nor `package-only-dep` from package metadata. + + $ dune rules --format=json _build/default/main.exe | + > jq_dune '.[] | ruleDepFilePaths' | + > censor | + > grep dune-package | + > sort "_build/install/default/.packages/$DIGEST/lib/foo/dune-package" + +The required libraries' compiled interfaces are consequently not tracked. + + $ dune rules --format=json _build/default/main.exe | + > jq_dune '.[] | ruleDepFilePaths' | + > censor | + > grep -E 'lib/(bar/bar|bar/__private__/bar_private/.public_cmi/bar_private|baz/baz)\.cmi' | + > sort + [1] + +No artifact belonging to the unrelated sibling is a dependency of the action. + + $ dune rules --format=json _build/default/main.exe | + > jq_dune '.[] | ruleDepFilePaths' | + > grep bar_unrelated + [1] + +The unrelated library from package `bar` is not discoverable. + + $ cat >>dune <<'EOF' + > (rule + > (target unrelated) + > (deps (package foo)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query bar.unrelated)))) + > EOF + + $ dune build unrelated + File "dune", lines 59-64, characters 0-133: + 59 | (rule + 60 | (target unrelated) + 61 | (deps (package foo)) + 62 | (action + 63 | (with-stdout-to %{target} + 64 | (run %{bin:ocamlfind} query bar.unrelated)))) + ocamlfind: Package `bar.unrelated' not found + [1] From 0b86f0490eea2a1188a31a015b0e37869390c9e3 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Mon, 17 Aug 2026 22:06:10 +0200 Subject: [PATCH 3/6] test(package-deps): snapshot the missing Melange closure Record that a scoped package layout retains melange_requires metadata without materializing its referenced workspace library. Signed-off-by: Ali Caglayan --- .../test-cases/package-materialization/dune | 4 + .../package-materialization/melange-closure.t | 75 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/blackbox-tests/test-cases/package-materialization/melange-closure.t diff --git a/test/blackbox-tests/test-cases/package-materialization/dune b/test/blackbox-tests/test-cases/package-materialization/dune index a4dde991d06..43fbead5bf0 100644 --- a/test/blackbox-tests/test-cases/package-materialization/dune +++ b/test/blackbox-tests/test-cases/package-materialization/dune @@ -5,3 +5,7 @@ ocamlfind virtual-without-default) (deps %{bin:ocamlfind})) + +(cram + (applies_to melange-closure) + (deps %{bin:melc})) diff --git a/test/blackbox-tests/test-cases/package-materialization/melange-closure.t b/test/blackbox-tests/test-cases/package-materialization/melange-closure.t new file mode 100644 index 00000000000..9add7d61cd0 --- /dev/null +++ b/test/blackbox-tests/test-cases/package-materialization/melange-closure.t @@ -0,0 +1,75 @@ +A scoped package layout retains a library's Melange-only requirements in its +`dune-package` file, so it must materialize the corresponding Melange closure. + + $ make_dune_project 3.24 + $ cat >>dune-project <<'EOF' + > (using melange 0.1) + > (package (name melange-root)) + > (package (name melange-support)) + > EOF + + $ mkdir root support consumer + $ cat >support/dune <<'EOF' + > (library + > (name melange_support) + > (public_name melange-support) + > (modes melange)) + > EOF + $ echo 'let value = 42' >support/melange_support.ml + + $ cat >root/dune <<'EOF' + > (library + > (name melange_root) + > (public_name melange-root) + > (modes melange) + > (melange.libraries melange-support)) + > EOF + $ echo 'let value = Melange_support.value' >root/melange_root.ml + + $ cat >consumer/dune-project <<'EOF' + > (lang dune 3.24) + > (using melange 0.1) + > EOF + $ cat >consumer/dune <<'EOF' + > (melange.emit + > (target out) + > (emit_stdlib false) + > (modules main) + > (libraries melange-root)) + > EOF + $ echo 'let () = Js.log Melange_root.value' >consumer/main.ml + + $ cat >dune <<'EOF' + > (rule + > (target result) + > (deps + > (package melange-root) + > (source_tree consumer)) + > (action + > (with-stdout-to %{target} + > (chdir consumer (run %{bin:dune} build @melange))))) + > EOF + +The package-only layout leaves the serialized `melange_requires` edge pointing +at a library that is absent from the layout. + + $ dune build result 2>err + [1] + $ censor required by library "melange-root" in + $PWD/_build/install/default/.packages/$DIGEST/lib/melange-root + -> required by melange target out + -> required by alias melange + File "$PWD/_build/install/default/.packages/$DIGEST/lib/melange-root/dune-package", line 15, characters 19-34: + 15 | (melange_requires melange-support) + ^^^^^^^^^^^^^^^ + Error: Library "melange-support" not found. + -> required by melange target out + -> required by library "melange-root" in + $PWD/_build/install/default/.packages/$DIGEST/lib/melange-root + -> required by _build/default/out/main.js + -> required by alias melange From 49ceb5716e5c26f3dcaa26896fb79f36a46c2037 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Mon, 17 Aug 2026 20:28:31 +0200 Subject: [PATCH 4/6] test(package-deps): snapshot multi-hop library redirects Record that a scoped package dependency omits the intermediate metadata in a deprecated-library redirect chain. Signed-off-by: Ali Caglayan --- .../test-cases/package-materialization/dune | 1 + .../package-materialization/redirect-chain.t | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 test/blackbox-tests/test-cases/package-materialization/redirect-chain.t diff --git a/test/blackbox-tests/test-cases/package-materialization/dune b/test/blackbox-tests/test-cases/package-materialization/dune index a4dde991d06..85aeedad8bc 100644 --- a/test/blackbox-tests/test-cases/package-materialization/dune +++ b/test/blackbox-tests/test-cases/package-materialization/dune @@ -3,5 +3,6 @@ installed-package no-transitive-through-targets ocamlfind + redirect-chain virtual-without-default) (deps %{bin:ocamlfind})) diff --git a/test/blackbox-tests/test-cases/package-materialization/redirect-chain.t b/test/blackbox-tests/test-cases/package-materialization/redirect-chain.t new file mode 100644 index 00000000000..0c9fe706d15 --- /dev/null +++ b/test/blackbox-tests/test-cases/package-materialization/redirect-chain.t @@ -0,0 +1,82 @@ +A deprecated-library redirect chain needs metadata for every intermediate name. +A scoped package dependency currently materializes only the explicitly named +package. + + $ make_dune_project 3.24 + $ cat >>dune-project < (package (name redirect-root)) + > (package (name redirect-middle)) + > (package (name redirect-target)) + > EOF + + $ mkdir root middle target middle-unrelated target-unrelated + $ cat >root/dune <<'EOF' + > (deprecated_library_name + > (old_public_name redirect-root.old) + > (new_public_name redirect-middle.old)) + > EOF + + $ cat >middle/dune <<'EOF' + > (deprecated_library_name + > (old_public_name redirect-middle.old) + > (new_public_name redirect-target)) + > EOF + + $ cat >target/dune <<'EOF' + > (library + > (name redirect_target) + > (public_name redirect-target)) + > EOF + $ echo 'let value = 42' >target/redirect_target.ml + + $ cat >middle-unrelated/dune <<'EOF' + > (library + > (name middle_unrelated) + > (public_name redirect-middle.unrelated)) + > EOF + $ echo 'let value = ()' >middle-unrelated/unrelated.ml + + $ cat >target-unrelated/dune <<'EOF' + > (library + > (name target_unrelated) + > (public_name redirect-target.unrelated)) + > EOF + $ echo 'let value = ()' >target-unrelated/unrelated.ml + + $ cat >dune <<'EOF' + > (rule + > (target recursive) + > (deps (package redirect-root)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + > (rule + > (target no-middle-unrelated) + > (deps (package redirect-root)) + > (action + > (progn + > (bash "! %{bin:ocamlfind} query redirect-middle.unrelated >/dev/null 2>&1") + > (write-file %{target} "")))) + > (rule + > (target no-target-unrelated) + > (deps (package redirect-root)) + > (action + > (progn + > (bash "! %{bin:ocamlfind} query redirect-target.unrelated >/dev/null 2>&1") + > (write-file %{target} "")))) + > EOF + + $ dune build recursive + File "dune", lines 1-6, characters 0-158: + 1 | (rule + 2 | (target recursive) + 3 | (deps (package redirect-root)) + 4 | (action + 5 | (with-stdout-to %{target} + 6 | (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + ocamlfind: Package `redirect-middle.old' not found - required by `redirect-root.old' + [1] + +Unrelated siblings remain absent from the scoped layout. + + $ dune build no-middle-unrelated no-target-unrelated From 37604d01c719fb0d58f0bd62417545850bea884a Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Mon, 17 Aug 2026 22:08:37 +0200 Subject: [PATCH 5/6] test(package-deps): snapshot deprecated-package redirects Record that a support redirect owned by another package omits metadata generated for its deprecated package name. Signed-off-by: Ali Caglayan --- .../deprecated-package-redirect.t | 84 +++++++++++++++++++ .../test-cases/package-materialization/dune | 1 + 2 files changed, 85 insertions(+) create mode 100644 test/blackbox-tests/test-cases/package-materialization/deprecated-package-redirect.t diff --git a/test/blackbox-tests/test-cases/package-materialization/deprecated-package-redirect.t b/test/blackbox-tests/test-cases/package-materialization/deprecated-package-redirect.t new file mode 100644 index 00000000000..a413e501880 --- /dev/null +++ b/test/blackbox-tests/test-cases/package-materialization/deprecated-package-redirect.t @@ -0,0 +1,84 @@ +A selected redirect can belong to a deprecated package name. Its owning package +must then contribute the separate META and dune-package files generated under +that deprecated name. + + $ make_dune_project 3.24 + $ cat >>dune-project <<'EOF' + > (package (name redirect-root)) + > (package + > (name redirect-owner) + > (deprecated_package_names old-support)) + > (package (name redirect-target)) + > EOF + + $ mkdir root owner target consumer + $ cat >root/dune <<'EOF' + > (deprecated_library_name + > (old_public_name redirect-root.old) + > (new_public_name old-support.lib)) + > EOF + + $ cat >owner/dune <<'EOF' + > (deprecated_library_name + > (old_public_name old-support.lib) + > (new_public_name redirect-target)) + > EOF + + $ cat >target/dune <<'EOF' + > (library + > (name redirect_target) + > (public_name redirect-target)) + > EOF + $ echo 'let value = 42' >target/redirect_target.ml + + $ cat >consumer/dune-project <<'EOF' + > (lang dune 3.24) + > EOF + $ cat >consumer/dune <<'EOF' + > (executable + > (name main) + > (libraries redirect-root.old)) + > EOF + $ echo 'let () = print_int Redirect_target.value' >consumer/main.ml + + $ cat >dune <<'EOF' + > (rule + > (target result) + > (deps (package redirect-root)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + > (rule + > (target dune-package-result) + > (deps + > (package redirect-root) + > (source_tree consumer)) + > (action + > (with-stdout-to %{target} + > (chdir consumer (run %{bin:dune} exec ./main.exe))))) + > EOF + +The ordinary package metadata can name the intermediate redirect, but the +scoped layout currently has no metadata directory for its deprecated package. + + $ dune build result + File "dune", lines 1-6, characters 0-155: + 1 | (rule + 2 | (target result) + 3 | (deps (package redirect-root)) + 4 | (action + 5 | (with-stdout-to %{target} + 6 | (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + ocamlfind: Package `old-support.lib' not found - required by `redirect-root.old' + [1] + +The nested Dune consumer likewise cannot resolve the intermediate package. + + $ dune build dune-package-result + File "dune", line 3, characters 12-29: + 3 | (libraries redirect-root.old)) + ^^^^^^^^^^^^^^^^^ + Error: Library "redirect-root.old" not found. + -> required by _build/default/.main.eobjs/native/dune__exe__Main.cmx + -> required by _build/default/main.exe + [1] diff --git a/test/blackbox-tests/test-cases/package-materialization/dune b/test/blackbox-tests/test-cases/package-materialization/dune index a4dde991d06..66c83799e7f 100644 --- a/test/blackbox-tests/test-cases/package-materialization/dune +++ b/test/blackbox-tests/test-cases/package-materialization/dune @@ -1,5 +1,6 @@ (cram (applies_to + deprecated-package-redirect installed-package no-transitive-through-targets ocamlfind From 81f40b742341896208edba6becb50127e2825915 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Sun, 23 Aug 2026 16:57:12 +0200 Subject: [PATCH 6/6] refactor(package-deps): resolve library support with install rules Move scoped library-closure resolution behind the install-layout resolver so metadata-specific closure edges can be added without introducing dependencies from dependency evaluation back to rule-generation modules. Signed-off-by: Ali Caglayan --- src/dune_rules/dep_conf_eval.ml | 134 +----------------------------- src/dune_rules/install_layout.ml | 15 +++- src/dune_rules/install_layout.mli | 13 +-- src/dune_rules/install_rules.ml | 125 ++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 141 deletions(-) diff --git a/src/dune_rules/dep_conf_eval.ml b/src/dune_rules/dep_conf_eval.ml index 87465553894..520abd1586f 100644 --- a/src/dune_rules/dep_conf_eval.ml +++ b/src/dune_rules/dep_conf_eval.ml @@ -221,130 +221,6 @@ let package loc pkg_name (context : Build_context.t) ~dune_version = } ;; -let library_closure lib = - let open Memo.O in - Memo.parallel_map [ Compilation_mode.Ocaml; Melange ] ~f:(fun for_ -> - let* compile_closure = - Lib.closure [ lib ] ~linking:false ~for_ |> Resolve.Memo.read_memo - and* link_closure = - Lib.partial_link_closure [ lib ] ~for_ |> Resolve.Memo.read_memo - in - Memo.return (List.rev_append compile_closure link_closure)) - >>| List.concat - >>| Lib.Set.of_list - >>| Lib.Set.to_list -;; - -let workspace_redirects context = - let open Memo.O in - let* workspace_packages = Dune_load.packages () in - Package.Name.Map.keys workspace_packages - |> Memo.parallel_map ~f:(fun package -> - let+ { Scope.DB.Lib_entry.Set.deprecated_library_names; _ } = - Scope.DB.lib_entries_of_package context package - in - List.map - deprecated_library_names - ~f:(fun { Library_redirect.old_name; new_public_name; _ } -> - Public_lib.name (fst old_name), (package, snd new_public_name))) - >>| List.concat - >>| Lib_name.Map.of_list_reduce ~f:(fun redirect _ -> redirect) -;; - -let library_support_closure context packages = - let open Memo.O in - let* public_libs = Scope.DB.public_libs context - and* redirects_by_name = workspace_redirects context in - let collect_redirects name = - let rec loop name seen redirects = - if Lib_name.Set.mem seen name - then redirects - else ( - let seen = Lib_name.Set.add seen name in - match Lib_name.Map.find redirects_by_name name with - | None -> redirects - | Some (package, target) -> - let redirects = - if Package.Name.Set.mem packages package - then redirects - else - Install_layout.Redirect.Set.add - redirects - (Install_layout.Redirect.make ~package ~name) - in - loop target seen redirects) - in - loop name Lib_name.Set.empty Install_layout.Redirect.Set.empty - in - let* roots, redirects = - Package.Name.Set.to_list packages - |> Memo.parallel_map ~f:(fun package -> - let* { Scope.DB.Lib_entry.Set.libraries; deprecated_library_names } = - Scope.DB.lib_entries_of_package context package - in - let+ redirect_targets = - Memo.parallel_map deprecated_library_names ~f:(fun { new_public_name; _ } -> - let+ target = - Lib.DB.resolve public_libs new_public_name |> Resolve.Memo.read_memo - in - target, collect_redirects (snd new_public_name)) - in - ( List.rev_append - (List.rev_map libraries ~f:Lib.Local.to_lib) - (List.map redirect_targets ~f:fst) - , List.fold_left - redirect_targets - ~init:Install_layout.Redirect.Set.empty - ~f:(fun redirects (_, selected) -> - Install_layout.Redirect.Set.union redirects selected) )) - >>| List.split - >>| fun (roots, redirects) -> - ( List.concat roots - , List.fold_left - redirects - ~init:Install_layout.Redirect.Set.empty - ~f:Install_layout.Redirect.Set.union ) - in - let extra_dependencies libraries = - Memo.parallel_map libraries ~f:(fun lib -> - if Lib.is_local lib - then - Memo.parallel_map [ Compilation_mode.Ocaml; Melange ] ~f:(fun for_ -> - Lib.ppx_runtime_deps lib ~for_ |> Resolve.Memo.read_memo) - >>| List.concat - else Memo.return []) - >>| List.concat - in - let rec loop todo expanded inspected libraries = - match todo with - | [] -> Memo.return libraries - | lib :: todo -> - if Lib.Set.mem expanded lib - then loop todo expanded inspected libraries - else - let* closure = library_closure lib in - let expanded = Lib.Set.add expanded lib in - let uninspected = - List.filter closure ~f:(fun lib -> not (Lib.Set.mem inspected lib)) - in - let inspected = List.fold_left uninspected ~init:inspected ~f:Lib.Set.add in - let libraries = List.fold_left closure ~init:libraries ~f:Lib.Set.add in - let* extra_dependencies = extra_dependencies uninspected in - loop (List.rev_append extra_dependencies todo) expanded inspected libraries - in - let+ closure = loop roots Lib.Set.empty Lib.Set.empty Lib.Set.empty in - let libraries = - Lib.Set.fold closure ~init:Install_layout.Library.Set.empty ~f:(fun lib libraries -> - match Lib.is_local lib, Lib_info.package (Lib.info lib) with - | true, Some package when not (Package.Name.Set.mem packages package) -> - Install_layout.Library.Set.add - libraries - (Install_layout.Library.make ~package ~name:(Lib.name lib)) - | _ -> libraries) - in - libraries, redirects -;; - let rec dep expander : Dep_conf.t -> _ = function | Include s -> (* TODO this is wrong. we shouldn't allow bindings here if we are in an @@ -463,15 +339,7 @@ and combined_package_deps_builder expander pkgs = let* env = if Package.Name.Set.is_empty local_package_names then Action_builder.return Env.empty - else - let* support_libraries, support_redirects = - Action_builder.of_memo (library_support_closure context.name local_package_names) - in - Install_layout.env - context.name - local_package_names - support_libraries - support_redirects + else Install_layout.env context.name local_package_names in let dune_version = Expander.project expander |> Dune_project.dune_version in let+ () = diff --git a/src/dune_rules/install_layout.ml b/src/dune_rules/install_layout.ml index f1b55c12840..1a84cda7e36 100644 --- a/src/dune_rules/install_layout.ml +++ b/src/dune_rules/install_layout.ml @@ -38,6 +38,11 @@ end module Redirect = Library +type support = + { libraries : Library.Set.t + ; redirects : Redirect.Set.t + } + type request = { packages : Package.Name.Set.t ; libraries : Library.Set.t @@ -127,6 +132,7 @@ end type resolvers = { package_entries : Context_name.t -> Package.Name.t -> Install.Entry.Sourced.Unexpanded.t list Memo.t + ; library_support : Context_name.t -> Package.Name.Set.t -> support Memo.t ; library_entries : Context_name.t -> Library.Set.t -> Redirect.Set.t -> library_entries Memo.t } @@ -159,7 +165,7 @@ let compute_entries context_name root { packages; libraries; redirects } = ; "redirects", Dyn.list Redirect.to_dyn overlapping_redirects ]; let open Memo.O in - let { package_entries; library_entries } = Fdecl.get resolvers_fdecl in + let { package_entries; library_entries; _ } = Fdecl.get resolvers_fdecl in let resolve_entry (pkg, (s : Install.Entry.Sourced.Unexpanded.t)) = let install_paths = let roots = Install.Roots.opam_from_prefix Path.root ~relative:Path.relative in @@ -279,7 +285,12 @@ let env_for_request context_name request = Install.Roots.add_to_env roots Env.empty ;; -let env context_name packages libraries redirects = +let env context_name packages = + let open Action_builder.O in + let { library_support; _ } = Fdecl.get resolvers_fdecl in + let* { libraries; redirects } = + Action_builder.of_memo (library_support context_name packages) + in env_for_request context_name { packages; libraries; redirects } ;; diff --git a/src/dune_rules/install_layout.mli b/src/dune_rules/install_layout.mli index 3ac0db00d84..2001730341f 100644 --- a/src/dune_rules/install_layout.mli +++ b/src/dune_rules/install_layout.mli @@ -15,6 +15,11 @@ end module Redirect = Library +type support = + { libraries : Library.Set.t + ; redirects : Redirect.Set.t + } + type generated_entry = { package : Package.Name.t ; section : Section.t @@ -30,6 +35,7 @@ type library_entries = type resolvers = { package_entries : Context_name.t -> Package.Name.t -> Install.Entry.Sourced.Unexpanded.t list Memo.t + ; library_support : Context_name.t -> Package.Name.Set.t -> support Memo.t ; library_entries : Context_name.t -> Library.Set.t -> Redirect.Set.t -> library_entries Memo.t } @@ -42,12 +48,7 @@ val set_resolvers : resolvers -> unit entry the layout produces. Only the selected support libraries' install entries and metadata are included; their owning packages' other entries are not. *) -val env - : Context_name.t - -> Package.Name.Set.t - -> Library.Set.t - -> Redirect.Set.t - -> Env.t Action_builder.t +val env : Context_name.t -> Package.Name.Set.t -> Env.t Action_builder.t (** Engine dispatch for [_build/install//.packages/]. Called from [Gen_rules]; the layout dir is owned by this module. Resolves to: diff --git a/src/dune_rules/install_rules.ml b/src/dune_rules/install_rules.ml index c1238f9c907..a3f0cf68047 100644 --- a/src/dune_rules/install_rules.ml +++ b/src/dune_rules/install_rules.ml @@ -1369,6 +1369,130 @@ let install_entries sctx package = Package.Name.Map.Multi.find packages package ;; +let library_closure lib = + let open Memo.O in + Memo.parallel_map [ Compilation_mode.Ocaml; Melange ] ~f:(fun for_ -> + let* compile_closure = + Lib.closure [ lib ] ~linking:false ~for_ |> Resolve.Memo.read_memo + and* link_closure = + Lib.partial_link_closure [ lib ] ~for_ |> Resolve.Memo.read_memo + in + Memo.return (List.rev_append compile_closure link_closure)) + >>| List.concat + >>| Lib.Set.of_list + >>| Lib.Set.to_list +;; + +let workspace_redirects context = + let open Memo.O in + let* workspace_packages = Dune_load.packages () in + Package.Name.Map.keys workspace_packages + |> Memo.parallel_map ~f:(fun package -> + let+ { Scope.DB.Lib_entry.Set.deprecated_library_names; _ } = + Scope.DB.lib_entries_of_package context package + in + List.map + deprecated_library_names + ~f:(fun { Library_redirect.old_name; new_public_name; _ } -> + Public_lib.name (fst old_name), (package, snd new_public_name))) + >>| List.concat + >>| Lib_name.Map.of_list_reduce ~f:(fun redirect _ -> redirect) +;; + +let library_support_closure context packages = + let open Memo.O in + let* public_libs = Scope.DB.public_libs context + and* redirects_by_name = workspace_redirects context in + let collect_redirects name = + let rec loop name seen redirects = + if Lib_name.Set.mem seen name + then redirects + else ( + let seen = Lib_name.Set.add seen name in + match Lib_name.Map.find redirects_by_name name with + | None -> redirects + | Some (package, target) -> + let redirects = + if Package.Name.Set.mem packages package + then redirects + else + Install_layout.Redirect.Set.add + redirects + (Install_layout.Redirect.make ~package ~name) + in + loop target seen redirects) + in + loop name Lib_name.Set.empty Install_layout.Redirect.Set.empty + in + let* roots, redirects = + Package.Name.Set.to_list packages + |> Memo.parallel_map ~f:(fun package -> + let* { Scope.DB.Lib_entry.Set.libraries; deprecated_library_names } = + Scope.DB.lib_entries_of_package context package + in + let+ redirect_targets = + Memo.parallel_map deprecated_library_names ~f:(fun { new_public_name; _ } -> + let+ target = + Lib.DB.resolve public_libs new_public_name |> Resolve.Memo.read_memo + in + target, collect_redirects (snd new_public_name)) + in + ( List.rev_append + (List.rev_map libraries ~f:Lib.Local.to_lib) + (List.map redirect_targets ~f:fst) + , List.fold_left + redirect_targets + ~init:Install_layout.Redirect.Set.empty + ~f:(fun redirects (_, selected) -> + Install_layout.Redirect.Set.union redirects selected) )) + >>| List.split + >>| fun (roots, redirects) -> + ( List.concat roots + , List.fold_left + redirects + ~init:Install_layout.Redirect.Set.empty + ~f:Install_layout.Redirect.Set.union ) + in + let extra_dependencies libraries = + Memo.parallel_map libraries ~f:(fun lib -> + if Lib.is_local lib + then + Memo.parallel_map [ Compilation_mode.Ocaml; Melange ] ~f:(fun for_ -> + Lib.ppx_runtime_deps lib ~for_ |> Resolve.Memo.read_memo) + >>| List.concat + else Memo.return []) + >>| List.concat + in + let rec loop todo expanded inspected libraries = + match todo with + | [] -> Memo.return libraries + | lib :: todo -> + if Lib.Set.mem expanded lib + then loop todo expanded inspected libraries + else + let* closure = library_closure lib in + let expanded = Lib.Set.add expanded lib in + let uninspected = + List.filter closure ~f:(fun lib -> not (Lib.Set.mem inspected lib)) + in + let inspected = List.fold_left uninspected ~init:inspected ~f:Lib.Set.add in + let libraries = List.fold_left closure ~init:libraries ~f:Lib.Set.add in + let* extra_dependencies = extra_dependencies uninspected in + loop (List.rev_append extra_dependencies todo) expanded inspected libraries + in + let+ closure = loop roots Lib.Set.empty Lib.Set.empty Lib.Set.empty in + let libraries = + Lib.Set.fold closure ~init:Install_layout.Library.Set.empty ~f:(fun lib libraries -> + match Lib.is_local lib, Lib_info.package (Lib.info lib) with + | true, Some package when not (Package.Name.Set.mem packages package) -> + Install_layout.Library.Set.add + libraries + (Install_layout.Library.make ~package ~name:(Lib.name lib)) + | _ -> libraries) + in + { Install_layout.libraries; redirects } +;; + let library_install_entries sctx libraries redirects = let* entries_by_library = Stanzas_to_entries.library_entries sctx in let selected_libraries = @@ -1461,6 +1585,7 @@ let () = let open Memo.O in let* sctx = Super_context.find_exn context_name in install_entries sctx package) + ; library_support = library_support_closure ; library_entries = (fun context_name libraries redirects -> let open Memo.O in