From 3192443dcb9b75ec991c826638d9cf4669c91ed6 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Sat, 9 May 2026 21:24:06 -0700 Subject: [PATCH 1/8] test: observational baseline for top-module cctx-wide cmi-dep over-invalidation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [dune ocaml top-module] derives a cctx via [Compilation_context.set_obj_dir] to a private obj_dir and drops [.mli] via [Module.set_source ~ml_kind:Intf None]. The derived cctx's compile rule pulls cmi-deps from the parent library's full [(libraries ...)] closure, so editing any [(libraries ...)] dep's interface invalidates the top-module artefact — even libraries referenced ONLY from the (dropped) [.mli]. This test pins the current REBUILT behaviour. A future change that filters [top_module]'s compile-rule cmi-deps to match the [.ml]'s actual references would flip the assertion to NOT REBUILT and require promotion. Reported and reproduced during review of #14474. Signed-off-by: Robin Bate Boerop --- .../top-module/cctx-wide-cmi-deps.t | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t new file mode 100644 index 00000000000..6fa26a2d1b8 --- /dev/null +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -0,0 +1,105 @@ +Observational baseline: [dune ocaml top-module] over-invalidates the +top-module compile when an unreferenced library's interface changes. + +[top_module.ml] derives a cctx via [Compilation_context.set_obj_dir] +to a private obj_dir, then calls [Module_compilation.build_module] on +a module value with [.mli] dropped via +[Module.set_source ~ml_kind:Intf None]. The derived cctx's compile +rule pulls cmi-deps from the parent library's full [(libraries ...)] +closure rather than from the actual references in [.ml] alone, so +editing any [(libraries ...)] dep's interface triggers a rebuild — +even libraries referenced ONLY from the (dropped) [.mli]. + +This test pins the current behaviour. A future change that filters +[top_module]'s compile-rule cmi-deps to match the [.ml]'s actual +references would flip the final assertion from REBUILT to +NOT REBUILT, and the test would need promotion. + + $ cat > dune-project < (lang dune 3.23) + > EOF + +[dep_for_intf] is referenced from [m.mli] only. [m.ml] never +mentions [Dep_for_intf]. + + $ mkdir dep_for_intf + $ cat > dep_for_intf/dune < (library (name dep_for_intf)) + > EOF + $ cat > dep_for_intf/dep_for_intf.ml < type t = Tag + > EOF + $ cat > dep_for_intf/dep_for_intf.mli < type t = Tag + > EOF + +[dep_for_impl] is referenced from [m.ml] only. [m.mli] never +mentions [Dep_for_impl]. + + $ mkdir dep_for_impl + $ cat > dep_for_impl/dune < (library (name dep_for_impl)) + > EOF + $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 + > EOF + $ cat > dep_for_impl/dep_for_impl.mli < val value : int + > EOF + +[mylib] declares both libraries in [(libraries ...)] but module +[m] splits them: [.ml] uses only [Dep_for_impl]; [.mli] uses only +[Dep_for_intf]. + + $ mkdir mylib + $ cat > mylib/dune < (library + > (name mylib) + > (libraries dep_for_intf dep_for_impl)) + > EOF + $ cat > mylib/m.mli < val tag : Dep_for_intf.t + > EOF + $ cat > mylib/m.ml < let _ = Dep_for_impl.value + > let tag = Dep_for_intf.Tag + > EOF + +Initial regular build, then [dune ocaml top-module mylib/m.ml]: + + $ dune build @check 2>&1 | head -5 + $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 + $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > before-impl-edit.mtime + +Control: edit [dep_for_impl]'s cmi (referenced from [m.ml]). The +top-module compile rebuilds — expected. + + $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 + > let extra = "x" + > EOF + $ cat > dep_for_impl/dep_for_impl.mli < val value : int + > val extra : string + > EOF + $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 + $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > after-impl-edit.mtime + $ if [ "$(cat before-impl-edit.mtime)" != "$(cat after-impl-edit.mtime)" ]; then echo "REBUILT"; else echo "NOT REBUILT"; fi + REBUILT + $ cp after-impl-edit.mtime before-intf-edit.mtime + +Probe: edit [dep_for_intf]'s cmi (NOT referenced from [m.ml] — the +only reference was in the discarded [m.mli]). The top-module +compile rebuilds despite the absence of any actual reference. This +is the over-invalidation under observation. + + $ cat > dep_for_intf/dep_for_intf.ml < type t = Tag | Other + > EOF + $ cat > dep_for_intf/dep_for_intf.mli < type t = Tag | Other + > EOF + $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 + $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > after-intf-edit.mtime + $ if [ "$(cat before-intf-edit.mtime)" != "$(cat after-intf-edit.mtime)" ]; then echo "REBUILT"; else echo "NOT REBUILT"; fi + REBUILT From a107885388771397b3668d5aff5b72d8af48bec6 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Sun, 10 May 2026 12:59:46 -0700 Subject: [PATCH 2/8] test(top-module): use [make_dune_project] helper Match the four sibling tests in this directory ([load-from-{exe,lib}.t], [load-with-{pp,ppx}.t]) which all use [make_dune_project] for [dune-project] generation. Pinned to [3.24] to track current dev behaviour. Addresses Copilot review at https://github.com/ocaml/dune/pull/14476#discussion_r3214325952. Signed-off-by: Robin Bate Boerop --- .../blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index 6fa26a2d1b8..d17afa79424 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -15,9 +15,7 @@ This test pins the current behaviour. A future change that filters references would flip the final assertion from REBUILT to NOT REBUILT, and the test would need promotion. - $ cat > dune-project < (lang dune 3.23) - > EOF + $ make_dune_project 3.24 [dep_for_intf] is referenced from [m.mli] only. [m.ml] never mentions [Dep_for_intf]. From e6d587478d3adea4f7f6e55a4652f8f679a1546d Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Sun, 10 May 2026 13:01:18 -0700 Subject: [PATCH 3/8] test(top-module): drop [head -5] pipe on initial build line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pipeline's exit status was [head]'s (always zero with input), laundering away [dune build]'s exit status. Failure modes: - [dune build @check] fails noisily: error text appears in pipe output, cram diff catches the unexpected output. Not masked. - [dune build @check] fails silently: pipe output empty, matches expected empty output. Masked. The cap added no value on success (output already empty); on failure it acts as a masking surface. Drop the pipe — the bare [$ dune build @check] catches both modes via the diff. Addresses Copilot review at https://github.com/ocaml/dune/pull/14476#discussion_r3214325947. Signed-off-by: Robin Bate Boerop --- test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index d17afa79424..aca789882d1 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -65,7 +65,7 @@ mentions [Dep_for_impl]. Initial regular build, then [dune ocaml top-module mylib/m.ml]: - $ dune build @check 2>&1 | head -5 + $ dune build @check $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > before-impl-edit.mtime From a9207cf9f3fe96cecc754f9c90434129eae0ce71 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Sun, 10 May 2026 13:05:21 -0700 Subject: [PATCH 4/8] test(top-module): isolate the over-invalidation premise from real refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously [m.ml] contained [let tag = Dep_for_intf.Tag], a real textual reference to [Dep_for_intf]. The topmod rule's rebuild on [dep_for_intf.cmi] change was therefore *legitimate*, not the cctx-wide-glob over-invalidation under test. The prose claimed [m.ml] never mentioned [Dep_for_intf]; the code contradicted it. Fix: change [Dep_for_intf.t] from a constructor variant ([type t = Tag]) to a primitive alias ([type t = int]). [m.ml] now constructs [let tag = 42] with no textual reference to [Dep_for_intf]: - ocamldep on [m.ml] reports no [Dep_for_intf] entry. - The topmod compile of [m.ml] (with [.mli] dropped) infers [tag : int] without needing [dep_for_intf.cmi] for type-checking. - The cctx-wide glob still pulls [dep_for_intf.cmi] into the topmod rule's deps — that is the over-invalidation. [Dep_for_intf]'s [.mli]-only Perturbation 2 edit also moves to a primitive shape (adds [val zero : t]), still mutating the cmi. Addresses Copilot review at https://github.com/ocaml/dune/pull/14476#discussion_r3214325942. Signed-off-by: Robin Bate Boerop --- .../test-cases/top-module/cctx-wide-cmi-deps.t | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index aca789882d1..27449607d08 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -25,10 +25,10 @@ mentions [Dep_for_intf]. > (library (name dep_for_intf)) > EOF $ cat > dep_for_intf/dep_for_intf.ml < type t = Tag + > type t = int > EOF $ cat > dep_for_intf/dep_for_intf.mli < type t = Tag + > type t = int > EOF [dep_for_impl] is referenced from [m.ml] only. [m.mli] never @@ -60,7 +60,7 @@ mentions [Dep_for_impl]. > EOF $ cat > mylib/m.ml < let _ = Dep_for_impl.value - > let tag = Dep_for_intf.Tag + > let tag = 42 > EOF Initial regular build, then [dune ocaml top-module mylib/m.ml]: @@ -92,10 +92,12 @@ compile rebuilds despite the absence of any actual reference. This is the over-invalidation under observation. $ cat > dep_for_intf/dep_for_intf.ml < type t = Tag | Other + > type t = int + > let zero = 0 > EOF $ cat > dep_for_intf/dep_for_intf.mli < type t = Tag | Other + > type t = int + > val zero : t > EOF $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > after-intf-edit.mtime From 09ca70639fba0570c2dbfe00eed1602e325d93b3 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Sun, 10 May 2026 13:16:42 -0700 Subject: [PATCH 5/8] test(top-module): replace [stat -c] mtime checks with trace-based assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [stat -c '%y'] is GNU coreutils — not portable to macOS/BSD [stat] (which uses [-f]). Replace the mtime-comparison harness with trace-based assertions that test the property directly: "did the topmod compile rule run during the second invocation?" Each [dune ocaml top-module] invocation now writes to a phase-specific trace file ([_build/trace-impl.csexp], [_build/trace-intf.csexp]). After each perturbation, the test queries the trace for any action whose target matches [\\.topmod/mylib/m\\.ml/mylib__M\\.cmo$]. A non-empty array means the rule re-evaluated; an empty array would mean it stayed cached. Drops [stat -c] entirely and asserts the property under observation rather than a file-system side-effect. Addresses Copilot review at https://github.com/ocaml/dune/pull/14476#discussion_r3215446948. Signed-off-by: Robin Bate Boerop --- .../top-module/cctx-wide-cmi-deps.t | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index 27449607d08..db865133fdb 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -67,10 +67,10 @@ Initial regular build, then [dune ocaml top-module mylib/m.ml]: $ dune build @check $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 - $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > before-impl-edit.mtime Control: edit [dep_for_impl]'s cmi (referenced from [m.ml]). The -top-module compile rebuilds — expected. +top-module compile rebuilds — expected. The trace contains the +[mylib__M.cmo] build action. $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 @@ -80,15 +80,19 @@ top-module compile rebuilds — expected. > val value : int > val extra : string > EOF - $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 - $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > after-impl-edit.mtime - $ if [ "$(cat before-impl-edit.mtime)" != "$(cat after-impl-edit.mtime)" ]; then echo "REBUILT"; else echo "NOT REBUILT"; fi - REBUILT - $ cp after-impl-edit.mtime before-intf-edit.mtime + $ dune ocaml top-module mylib/m.ml --trace-file=_build/trace-impl.csexp > /dev/null 2>&1 + $ dune trace cat --trace-file=_build/trace-impl.csexp | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("\\.topmod/mylib/m\\.ml/mylib__M\\.cmo$"))]' + [ + { + "target_files": [ + "_build/default/.topmod/mylib/m.ml/mylib__M.cmo" + ] + } + ] Probe: edit [dep_for_intf]'s cmi (NOT referenced from [m.ml] — the only reference was in the discarded [m.mli]). The top-module -compile rebuilds despite the absence of any actual reference. This +compile rebuilds despite the absence of any actual reference; this is the over-invalidation under observation. $ cat > dep_for_intf/dep_for_intf.ml < type t = int > val zero : t > EOF - $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 - $ stat -c '%y' _build/default/.topmod/mylib/m.ml/mylib__M.cmo > after-intf-edit.mtime - $ if [ "$(cat before-intf-edit.mtime)" != "$(cat after-intf-edit.mtime)" ]; then echo "REBUILT"; else echo "NOT REBUILT"; fi - REBUILT + $ dune ocaml top-module mylib/m.ml --trace-file=_build/trace-intf.csexp > /dev/null 2>&1 + $ dune trace cat --trace-file=_build/trace-intf.csexp | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("\\.topmod/mylib/m\\.ml/mylib__M\\.cmo$"))]' + [ + { + "target_files": [ + "_build/default/.topmod/mylib/m.ml/mylib__M.cmo" + ] + } + ] From b255fc010d94f097aca8ca83a8e8792c42ce055d Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Mon, 11 May 2026 13:49:37 -0700 Subject: [PATCH 6/8] test(top-module): adopt "Reproduction:" framing per review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace "Observational baseline: [dune ocaml top-module] over-invalidates the top-module compile when an unreferenced library's interface changes." with "Reproduction: `dune ocaml top-module` over-rebuilds when an unreferenced library's interface changes." — adopting the term, code formatting, and verb from ElectreAAS's suggestion, while keeping the original phrase about which library is involved. Addresses review at https://github.com/ocaml/dune/pull/14476#discussion_r3221878493. Signed-off-by: Robin Bate Boerop --- .../blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index db865133fdb..6c74ebaff66 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -1,5 +1,5 @@ -Observational baseline: [dune ocaml top-module] over-invalidates the -top-module compile when an unreferenced library's interface changes. +Reproduction: `dune ocaml top-module` over-rebuilds when an +unreferenced library's interface changes. [top_module.ml] derives a cctx via [Compilation_context.set_obj_dir] to a private obj_dir, then calls [Module_compilation.build_module] on From 8573e0535f9a9980fcc5bbdb7de506f055872679 Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Mon, 11 May 2026 14:44:02 -0700 Subject: [PATCH 7/8] test(top-module): incorporate review feedback Per ElectreAAS's review of #14476: - r3221893397: drop the "This test pins..." paragraph and fold its substance into the `Reproduction:` opener ("The test passes while the bug is present; promote when fixed."). Preserves the polarity signal and maintainer hint in fewer words. - r3221942293: reword Control and Probe prose to lead with the `.mli` change. Both sections edit both `.ml` and `.mli`; the `.ml` change is scaffolding so the package builds, while the `.mli` change is what alters the `.cmi` and triggers the rebuild under test. - r3221908837: simplify `mylib/m.ml` from two values to one (`let tag = Dep_for_impl.value`). Same cmi-deps; `tag` is now meaningful instead of throwaway. - r3221888985: trim dune-internals from the cctx paragraph (`Compilation_context.set_obj_dir`, `Module_compilation.build_module`, `Module.set_source`, "cctx"). Keep the dropped-`.mli` precondition and the cmi-deps-from-full-closure fact. Add a labelled "Code under test: `src/dune_rules/top_module.ml`" pointer. Also: convert remaining `[identifier]` code-spans to backticks (follow-up from the r3221878493 thread; backticks are the ~82% majority convention in this repo). Signed-off-by: Robin Bate Boerop --- .../top-module/cctx-wide-cmi-deps.t | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t index 6c74ebaff66..172770acee6 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t @@ -1,24 +1,20 @@ Reproduction: `dune ocaml top-module` over-rebuilds when an -unreferenced library's interface changes. +unreferenced library's interface changes. The test passes while +the bug is present; promote when fixed. -[top_module.ml] derives a cctx via [Compilation_context.set_obj_dir] -to a private obj_dir, then calls [Module_compilation.build_module] on -a module value with [.mli] dropped via -[Module.set_source ~ml_kind:Intf None]. The derived cctx's compile -rule pulls cmi-deps from the parent library's full [(libraries ...)] -closure rather than from the actual references in [.ml] alone, so -editing any [(libraries ...)] dep's interface triggers a rebuild — -even libraries referenced ONLY from the (dropped) [.mli]. +`dune ocaml top-module` synthesises a compile for `m.ml` with the +`.mli` dropped, so only `m.ml`'s actual references should drive +the cmi-deps. Instead, the cmi-deps come from the parent library's +full `(libraries ...)` closure, so editing any of those deps' +interfaces triggers a rebuild — even libraries referenced ONLY +from the (dropped) `.mli`. -This test pins the current behaviour. A future change that filters -[top_module]'s compile-rule cmi-deps to match the [.ml]'s actual -references would flip the final assertion from REBUILT to -NOT REBUILT, and the test would need promotion. +Code under test: `src/dune_rules/top_module.ml`. $ make_dune_project 3.24 -[dep_for_intf] is referenced from [m.mli] only. [m.ml] never -mentions [Dep_for_intf]. +`dep_for_intf` is referenced from `m.mli` only. `m.ml` never +mentions `Dep_for_intf`. $ mkdir dep_for_intf $ cat > dep_for_intf/dune < type t = int > EOF -[dep_for_impl] is referenced from [m.ml] only. [m.mli] never -mentions [Dep_for_impl]. +`dep_for_impl` is referenced from `m.ml` only. `m.mli` never +mentions `Dep_for_impl`. $ mkdir dep_for_impl $ cat > dep_for_impl/dune < val value : int > EOF -[mylib] declares both libraries in [(libraries ...)] but module -[m] splits them: [.ml] uses only [Dep_for_impl]; [.mli] uses only -[Dep_for_intf]. +`mylib` declares both libraries in `(libraries ...)` but module +`m` splits them: `.ml` uses only `Dep_for_impl`; `.mli` uses only +`Dep_for_intf`. $ mkdir mylib $ cat > mylib/dune < val tag : Dep_for_intf.t > EOF $ cat > mylib/m.ml < let _ = Dep_for_impl.value - > let tag = 42 + > let tag = Dep_for_impl.value > EOF -Initial regular build, then [dune ocaml top-module mylib/m.ml]: +Initial regular build, then `dune ocaml top-module mylib/m.ml`: $ dune build @check $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 -Control: edit [dep_for_impl]'s cmi (referenced from [m.ml]). The -top-module compile rebuilds — expected. The trace contains the -[mylib__M.cmo] build action. +Control: change `dep_for_impl`'s `.mli` (the matching `.ml` change +ensures the package builds). `m.ml` references `Dep_for_impl.value`, +so its top-module compile depends on `dep_for_impl.cmi`. Rebuild +expected. The trace contains the `mylib__M.cmo` build action. $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 @@ -90,10 +86,11 @@ top-module compile rebuilds — expected. The trace contains the } ] -Probe: edit [dep_for_intf]'s cmi (NOT referenced from [m.ml] — the -only reference was in the discarded [m.mli]). The top-module -compile rebuilds despite the absence of any actual reference; this -is the over-invalidation under observation. +Probe: change `dep_for_intf`'s `.mli` (the matching `.ml` change +ensures the package builds). `m.ml` never references +`Dep_for_intf` — the only reference was in the discarded +`m.mli`. The top-module compile rebuilds anyway; this is the +over-invalidation. $ cat > dep_for_intf/dep_for_intf.ml < type t = int From a90d023c8e0994e5a2855017b16fa8312d2ad2ee Mon Sep 17 00:00:00 2001 From: Robin Bate Boerop Date: Tue, 12 May 2026 07:59:28 -0700 Subject: [PATCH 8/8] test(top-module): pre-implement deps; edit only the .mli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewers asked why the edit steps rewrote both the `.ml` and the `.mli` when the test prose framed the scenario as an interface change (PR #14476, r3221942293, r3226815617). Switch to the "pre-implement, then expose" pattern: the `.ml` files carry the full set of definitions from the start; the initial `.mli` exposes a subset; the edit step adds a `val …` to the `.mli` alone. Signed-off-by: Robin Bate Boerop --- ...ps.t => over-rebuild-from-intf-only-dep.t} | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) rename test/blackbox-tests/test-cases/top-module/{cctx-wide-cmi-deps.t => over-rebuild-from-intf-only-dep.t} (80%) diff --git a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t b/test/blackbox-tests/test-cases/top-module/over-rebuild-from-intf-only-dep.t similarity index 80% rename from test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t rename to test/blackbox-tests/test-cases/top-module/over-rebuild-from-intf-only-dep.t index 172770acee6..c14eacd79ed 100644 --- a/test/blackbox-tests/test-cases/top-module/cctx-wide-cmi-deps.t +++ b/test/blackbox-tests/test-cases/top-module/over-rebuild-from-intf-only-dep.t @@ -22,6 +22,7 @@ mentions `Dep_for_intf`. > EOF $ cat > dep_for_intf/dep_for_intf.ml < type t = int + > let zero = 0 > EOF $ cat > dep_for_intf/dep_for_intf.mli < type t = int @@ -36,6 +37,7 @@ mentions `Dep_for_impl`. > EOF $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 + > let extra = "x" > EOF $ cat > dep_for_impl/dep_for_impl.mli < val value : int @@ -63,15 +65,11 @@ Initial regular build, then `dune ocaml top-module mylib/m.ml`: $ dune build @check $ dune ocaml top-module mylib/m.ml > /dev/null 2>&1 -Control: change `dep_for_impl`'s `.mli` (the matching `.ml` change -ensures the package builds). `m.ml` references `Dep_for_impl.value`, -so its top-module compile depends on `dep_for_impl.cmi`. Rebuild -expected. The trace contains the `mylib__M.cmo` build action. +Control: edit `dep_for_impl`'s `.mli` to expose `extra`. `m.ml` +references `Dep_for_impl.value`, so its top-module compile depends +on `dep_for_impl.cmi`. Rebuild expected. The trace contains the +`mylib__M.cmo` build action. - $ cat > dep_for_impl/dep_for_impl.ml < let value = 7 - > let extra = "x" - > EOF $ cat > dep_for_impl/dep_for_impl.mli < val value : int > val extra : string @@ -86,16 +84,11 @@ expected. The trace contains the `mylib__M.cmo` build action. } ] -Probe: change `dep_for_intf`'s `.mli` (the matching `.ml` change -ensures the package builds). `m.ml` never references -`Dep_for_intf` — the only reference was in the discarded -`m.mli`. The top-module compile rebuilds anyway; this is the -over-invalidation. +Probe: edit `dep_for_intf`'s `.mli` to expose `zero`. `m.ml` never +references `Dep_for_intf` — the only reference was in the +discarded `m.mli`. The top-module compile rebuilds anyway; this is +the over-invalidation. - $ cat > dep_for_intf/dep_for_intf.ml < type t = int - > let zero = 0 - > EOF $ cat > dep_for_intf/dep_for_intf.mli < type t = int > val zero : t