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 + +Filtered support metadata preserves properties from a package's META template. + + $ cat >META.bar.template < support_marker = "kept" + > # DUNE_GEN + > 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 marker) + > (deps (package foo)) + > (action + > (with-stdout-to %{target} + > (run %{bin:ocamlfind} query -format "%(support_marker)" bar)))) + > (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 + +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 marker && cat _build/default/marker + File "dune", lines 26-31, characters 0-148: + 26 | (rule + 27 | (target marker) + 28 | (deps (package foo)) + 29 | (action + 30 | (with-stdout-to %{target} + 31 | (run %{bin:ocamlfind} query -format "%(support_marker)" bar)))) + ocamlfind: Package `bar' not found + [1] + $ dune build redirect + File "dune", lines 32-37, characters 0-126: + 32 | (rule + 33 | (target redirect) + 34 | (deps (package foo)) + 35 | (action + 36 | (with-stdout-to %{target} + 37 | (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 38-43, characters 0-160: + 38 | (rule + 39 | (target namespace-marker) + 40 | (deps (package foo)) + 41 | (action + 42 | (with-stdout-to %{target} + 43 | (run %{bin:ocamlfind} query -format "%(top_marker)" namespace)))) + ocamlfind: Package `namespace' not found + [1] + $ dune build ppx-runtime-marker + File "dune", lines 44-49, characters 0-145: + 44 | (rule + 45 | (target ppx-runtime-marker) + 46 | (deps (package test-ppx)) + 47 | (action + 48 | (with-stdout-to %{target} + 49 | (run %{bin:ocamlfind} query ppx-runtime)))) + ocamlfind: Package `ppx-runtime' not found + [1] + $ dune build root-redirect + File "dune", lines 50-55, characters 0-162: + 50 | (rule + 51 | (target root-redirect) + 52 | (deps (package redirect-root)) + 53 | (action + 54 | (with-stdout-to %{target} + 55 | (run %{bin:ocamlfind} query -recursive redirect-root.old)))) + ocamlfind: Package `redirect-target' not found - required by `redirect-root.old' + [1] + +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. + + $ 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 out + $ dune build dune-package-result && cat _build/default/dune-package-result + File "$TESTCASE_ROOT/_build/install/default/.packages/5adbe0a031471be6bc6c673728b09e6d/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 + $TESTCASE_ROOT/_build/install/default/.packages/5adbe0a031471be6bc6c673728b09e6d/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] -Only foo appears, neither bar nor baz, even though foo declares -(depends bar) and bar declares (depends baz): +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/out | jq_dune '.[] | ruleDepFilePaths' | censor | grep dune-package | sort + $ 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 65-70, characters 0-133: + 65 | (rule + 66 | (target unrelated) + 67 | (deps (package foo)) + 68 | (action + 69 | (with-stdout-to %{target} + 70 | (run %{bin:ocamlfind} query bar.unrelated)))) + ocamlfind: Package `bar.unrelated' not found + [1]