From 7e326e9d18800c61478d7287e8c79a797ee10035 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Mon, 18 May 2026 10:46:58 -0700 Subject: [PATCH] test: cross-library .cmi regression baselines (4 fixtures) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four fixtures pinning the consumer-compile cmi-dep behaviour today, when the cctx-wide cmi glob over a dep lib's objdir covers the consumer's transitive `.cmi` needs regardless of whether `ocamldep` can see the chain syntactically. Each fixture hides the leaf's name from `ocamldep` through a different mechanism: - `cross-lib-preprocess-barrier.t` — `(preprocess (action ...))` - `cross-lib-pps-runtime-no-ocamldep-barrier.t` — `(preprocess (pps X))` + `ppx_runtime_libraries` - `cross-lib-instrumentation-barrier.t` — `(instrumentation (backend X))` - `cross-lib-open-flag-barrier.t` — `(flags (-open M))` Three of the four assert build success under `--sandbox=copy`. The instrumentation case additionally pins today's wide cmi glob over `middle`'s objdir in `dune rules` output. The forthcoming per-module narrowing work (#14492) will validate that each construct continues to work soundly under tighter per-module dep tracking, and will flip the instrumentation case's `jq` expected output from "glob present" to "glob absent". Signed-off-by: Robin Bate Boerop --- .../ppx/dune | 13 ++++ .../ppx/dune-project | 3 + .../ppx/hello.ml | 1 + .../ppx/hello_ppx.ml | 48 ++++++++++++++ .../cross-lib-instrumentation-barrier.t/run.t | 58 ++++++++++++++++ .../cross-lib-open-flag-barrier.t | 54 +++++++++++++++ ...ross-lib-pps-runtime-no-ocamldep-barrier.t | 66 +++++++++++++++++++ .../cross-lib-preprocess-barrier.t | 50 ++++++++++++++ 8 files changed, 293 insertions(+) create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune-project create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello.ml create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello_ppx.ml create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/run.t create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-open-flag-barrier.t create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-pps-runtime-no-ocamldep-barrier.t create mode 100644 test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-preprocess-barrier.t diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune new file mode 100644 index 00000000000..495828633bd --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune @@ -0,0 +1,13 @@ +(library + (name hello_ppx) + (public_name hello.ppx) + (kind ppx_rewriter) + (ppx_runtime_libraries hello) + (libraries ppxlib) + (modules hello_ppx)) + +(library + (public_name hello) + (modules hello) + (instrumentation.backend + (ppx hello.ppx))) diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune-project b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune-project new file mode 100644 index 00000000000..fcd12b3a8a1 --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/dune-project @@ -0,0 +1,3 @@ +(lang dune 3.24) + +(package (name hello)) diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello.ml b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello.ml new file mode 100644 index 00000000000..7f63d09714f --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello.ml @@ -0,0 +1 @@ +let hello s = print_endline (Printf.sprintf "Hello from %s!" s) diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello_ppx.ml b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello_ppx.ml new file mode 100644 index 00000000000..ffcd0b355a0 --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/ppx/hello_ppx.ml @@ -0,0 +1,48 @@ +open Ast_helper + +let place = ref None +let file = ref None + +let read_file () = + match !file with + | None -> "" + | Some s -> + let ic = open_in s in + (match input_line ic with + | exception End_of_file -> + close_in ic; + "" + | s -> + close_in ic; + s) +;; + +let impl str = + let arg = + match !place with + | None -> Exp.ident (Location.mknoloc (Longident.Lident "__MODULE__")) + | Some s -> Exp.constant (Const.string (Printf.sprintf "%s (%s)" s (read_file ()))) + in + Str.eval + (Exp.apply + (Exp.ident + (Location.mknoloc + (Longident.Ldot + ( { txt = Longident.Lident "Hello"; loc = Location.none } + , { txt = "hello"; loc = Location.none } )))) + [ Nolabel, arg ]) + :: str +;; + +let () = + Ppxlib.Driver.add_arg + "-place" + (Arg.String (fun s -> place := Some s)) + ~doc:"PLACE where to say hello from"; + Ppxlib.Driver.add_arg + "-file" + (Arg.String (fun s -> file := Some s)) + ~doc:"Add info from file" +;; + +let () = Ppxlib.Driver.register_transformation_using_ocaml_current_ast ~impl "hello" diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/run.t b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/run.t new file mode 100644 index 00000000000..4c5eb7bfaf6 --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/run.t @@ -0,0 +1,58 @@ +Regression baseline for an unwrapped library declaring +`(instrumentation (backend X))` without `--instrument-with` at +build time. Today, the consumer's compile rule depends on the +lib's full `.cmi` glob over its objdir. + + $ make_dune_project 3.24 + +`middle` declares an instrumentation backend but no +`(preprocess ...)`. With instrumentation disabled at build time, +no `.pp.ml` files are produced. + + $ mkdir leaf + $ cat > leaf/dune < (library (name leaf)) + > EOF + $ cat > leaf/leaf.ml < type t = int + > let zero : t = 0 + > EOF + + $ mkdir middle + $ cat > middle/dune < (library + > (name middle) + > (wrapped false) + > (libraries leaf) + > (instrumentation (backend hello))) + > EOF + $ cat > middle/middle.mli < val identity : Leaf.t -> Leaf.t + > EOF + $ cat > middle/middle.ml < let identity x = x + > EOF + + $ mkdir consumer + $ cat > consumer/dune < (executable (name consumer) (libraries middle)) + > EOF + $ cat > consumer/consumer.ml < let _ = Middle.identity 0 + > EOF + +Build without `--instrument-with`. Today this succeeds with the +cctx-wide `.cmi` glob covering `leaf.cmi` through `middle`'s objdir +chain. + + $ dune build consumer/consumer.exe + +The consumer's compile rule today carries a wide `.cmi` glob over +`middle`'s objdir. + + $ dune rules --root . --format=json --deps '%{cmo:consumer/consumer}' > deps.json + + $ jq -r 'include "dune"; .[] | depsGlobs + > | select(.dir | endswith("middle/.middle.objs/byte")) + > | .dir + " " + .predicate' < deps.json + _build/default/middle/.middle.objs/byte *.cmi diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-open-flag-barrier.t b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-open-flag-barrier.t new file mode 100644 index 00000000000..741a60dec8e --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-open-flag-barrier.t @@ -0,0 +1,54 @@ +A consumer references a constructor of a leaf library's type +through an intermediate library compiled with +`(flags (:standard -open Prelude))`. The intermediate's source +never names `Prelude` (the open hides it), and the consumer never +names `Prelude` either. Pins that the consumer's compile correctly +tracks `prelude`'s `.cmi` as a sandbox-required dep. + + $ make_dune_project 3.24 + +`prelude` exposes a sum type: + + $ mkdir prelude + $ cat > prelude/dune < (library (name prelude)) + > EOF + $ cat > prelude/prelude.ml < type color = Red | Green | Blue + > EOF + +`middle` depends on `prelude` and is compiled with +`(flags (:standard -open Prelude))`, exposing +`val pick : unit -> color` whose `color` resolves through the open +to `Prelude.color`: + + $ mkdir middle + $ cat > middle/dune < (library + > (name middle) + > (libraries prelude) + > (flags (:standard -open Prelude))) + > EOF + $ cat > middle/middle.mli < val pick : unit -> color + > EOF + $ cat > middle/middle.ml < let pick () = Green + > EOF + +`consumer` depends on `middle` and `prelude` and pattern-matches on +the result of `Middle.pick` against the bare constructors `Green`, +`Red`, `Blue`. `ocamldep` on `middle.{ml,mli}` and `consumer.ml` +reports no `Prelude` token in either case. + + $ mkdir consumer + $ cat > consumer/dune < (executable (name consumer) (libraries middle prelude)) + > EOF + $ cat > consumer/consumer.ml < let () = match Middle.pick () with + > | Green -> print_endline "g" + > | Red | Blue -> print_endline "nb" + > EOF + + $ dune build --sandbox=copy consumer/consumer.exe diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-pps-runtime-no-ocamldep-barrier.t b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-pps-runtime-no-ocamldep-barrier.t new file mode 100644 index 00000000000..70e9245c3ba --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-pps-runtime-no-ocamldep-barrier.t @@ -0,0 +1,66 @@ +A single-module local library with no `(libraries ...)` but with +`(preprocess (pps X))` has non-empty *resolved* requires (X's +runtime libs added via `add_pp_runtime_deps`). Pins that the +consumer's compile correctly tracks the ppx runtime lib's `.cmi` +as a sandbox-required dep, even though the consumer never names +the runtime lib syntactically. + + $ make_dune_project 3.24 + +`hello` is the ppx runtime lib (single-module unwrapped, no library +deps of its own). It exposes `Hello.t`: + + $ mkdir hello + $ cat > hello/dune < (library (name hello)) + > EOF + $ cat > hello/hello.ml < type t = int + > let zero : t = 0 + > EOF + +`hello_ppx` is a no-op ppx_rewriter declaring `hello` as its +`ppx_runtime_libraries`: + + $ mkdir hello_ppx + $ cat > hello_ppx/dune < (library + > (name hello_ppx) + > (kind ppx_rewriter) + > (ppx_runtime_libraries hello) + > (libraries ppxlib)) + > EOF + $ cat > hello_ppx/hello_ppx.ml < let () = + > Ppxlib.Driver.register_transformation_using_ocaml_current_ast + > ~impl:(fun s -> s) "noop" + > EOF + +`middle` is single-module, has no `(libraries ...)`, and uses +`(preprocess (pps hello_ppx))`. Its interface mentions `Hello.t`: + + $ mkdir middle + $ cat > middle/dune < (library + > (name middle) + > (preprocess (pps hello_ppx))) + > EOF + $ cat > middle/middle.mli < val helper : Hello.t -> Hello.t + > EOF + $ cat > middle/middle.ml < let helper x = x + > EOF + +`consumer` depends on `middle`; references `Middle.helper` but +never names `Hello` in source. + + $ mkdir consumer + $ cat > consumer/dune < (executable (name consumer) (libraries middle)) + > EOF + $ cat > consumer/consumer.ml < let _ = Middle.helper 0 + > EOF + + $ dune build --sandbox=copy consumer/consumer.exe diff --git a/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-preprocess-barrier.t b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-preprocess-barrier.t new file mode 100644 index 00000000000..d9abc5028e5 --- /dev/null +++ b/test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-preprocess-barrier.t @@ -0,0 +1,50 @@ +A consumer references a module from a preprocessed library; that +preprocessed module's interface mentions a type from a leaf library +that the consumer never names syntactically. Pins that the consumer's +compile correctly tracks `leaf`'s `.cmi` as a sandbox-required dep. + + $ make_dune_project 3.24 + +`leaf` exposes `Leaf.t`: + + $ mkdir leaf + $ cat > leaf/dune < (library (name leaf)) + > EOF + $ cat > leaf/leaf.ml < type t = int + > let zero : t = 0 + > EOF + +`middle` depends on `leaf`; its single module is preprocessed via +`(preprocess (action ...))`, and its interface mentions `Leaf.t`: + + $ mkdir middle + $ cat > middle/dune < (library + > (name middle) + > (libraries leaf) + > (preprocess (action (run cat %{input-file})))) + > EOF + $ cat > middle/middle.mli < val identity : Leaf.t -> Leaf.t + > EOF + $ cat > middle/middle.ml < let identity x = x + > EOF + +`consumer` depends on `middle`; references `Middle.identity` but +never names `Leaf` in source. Applying `Middle.identity` to a +literal `0` forces the compiler to unify `int` with `Leaf.t`, which +requires loading `leaf.cmi`. `ocamldep` on this source reports +only `Middle`, since `Leaf` is not named. + + $ mkdir consumer + $ cat > consumer/dune < (executable (name consumer) (libraries middle)) + > EOF + $ cat > consumer/consumer.ml < let _ = Middle.identity 0 + > EOF + + $ dune build --sandbox=copy consumer/consumer.exe