Skip to content

Commit 7cc00c1

Browse files
authored
test: verify incremental builds with alias re-exported libraries (#4572) (#14129)
## Summary Add test verifying that when a library re-exports a dependency via a module alias (`module Impl = Impl`), incremental builds correctly recompile consumers when the re-exported library's `.cmi` changes. - Soft changes (implementation only, no `.cmi` change) can safely skip recompilation due to `-opaque` - Hard changes (`.cmi` modified) must trigger recompilation to avoid inconsistent interface assumptions - The bug only triggers with multi-module executables (single-module executables skip ocamldep) Reproduction case from @art-w. Ref: #4572 ## Test plan - [x] Test passes on `main` (no filtering active, all deps present) Signed-off-by: Robin Bate Boerop <me@robinbb.com>
1 parent 4cb77b4 commit 7cc00c1

File tree

1 file changed

+94
-0
lines changed

1 file changed

+94
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
Incremental builds with library re-exporting a dependency via module alias.
2+
3+
When library "alias" re-exports library "impl" via (module Impl = Impl),
4+
a consumer that accesses Impl through Alias must be recompiled when
5+
impl.cmi changes. The -opaque flag means soft changes (implementation
6+
only, no cmi change) can safely skip recompilation, but cmi changes
7+
must always trigger it.
8+
9+
See: https://github.com/ocaml/dune/issues/4572
10+
11+
$ cat > dune-project <<EOF
12+
> (lang dune 3.23)
13+
> EOF
14+
15+
A library where we'll perform the changes:
16+
17+
$ mkdir impl
18+
$ cat > impl/dune <<EOF
19+
> (library (name impl))
20+
> EOF
21+
$ cat > impl/impl.ml <<EOF
22+
> let foo = "initial build"
23+
> EOF
24+
25+
Another library which exposes an alias to impl:
26+
27+
$ mkdir alias
28+
$ cat > alias/dune <<EOF
29+
> (library (name alias) (libraries impl))
30+
> EOF
31+
$ cat > alias/alias.ml <<EOF
32+
> module Impl = Impl
33+
> EOF
34+
35+
A binary which depends on Alias to access Impl. An empty unused file
36+
makes this a multi-module executable:
37+
38+
$ mkdir bin
39+
$ cat > bin/dune <<EOF
40+
> (executable (name main) (libraries alias))
41+
> EOF
42+
$ cat > bin/main.ml <<EOF
43+
> let () = print_endline Alias.Impl.foo
44+
> EOF
45+
$ touch bin/unused.ml
46+
47+
The first build succeeds:
48+
49+
$ dune exec ./bin/main.exe
50+
initial build
51+
52+
Soft update — impl.cmi is NOT modified (only implementation changes).
53+
With -opaque, skipping recompilation of main.ml is correct because
54+
main.ml doesn't depend on impl's implementation, only its interface:
55+
56+
$ cat > impl/impl.ml <<EOF
57+
> let foo = "second build, no change to cmi"
58+
> EOF
59+
60+
$ dune exec ./bin/main.exe
61+
second build, no change to cmi
62+
63+
main.cmx is NOT rebuilt (correct — only impl changed, and -opaque
64+
means we don't track impl's implementation):
65+
66+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("Main"))]'
67+
[]
68+
69+
unused.cmx is also NOT rebuilt (correct — it references nothing):
70+
71+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("Unused"))]'
72+
[]
73+
74+
Hard update — impl.cmi IS modified (new value added). main.ml must
75+
be recompiled because Alias re-exports Impl and the interface changed:
76+
77+
$ cat > impl/impl.ml <<EOF
78+
> let new_value = 42
79+
> let foo = "third build, forced a cmi update"
80+
> EOF
81+
82+
$ dune exec ./bin/main.exe
83+
third build, forced a cmi update
84+
85+
Main is rebuilt (necessary — impl.cmi changed and main.ml uses
86+
Impl through the Alias re-export):
87+
88+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("Main"))] | length | . > 0'
89+
true
90+
91+
Unused is NOT rebuilt (correct — it doesn't reference impl):
92+
93+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("Unused"))] | length'
94+
0

0 commit comments

Comments
 (0)