Skip to content

Commit ebe5b13

Browse files
committed
Toggle interface generation with (executables_implicit_empty_intf)
Signed-off-by: Craig Ferguson <me@craigfe.io>
1 parent af7ff03 commit ebe5b13

File tree

13 files changed

+64
-23
lines changed

13 files changed

+64
-23
lines changed

doc/dune-files.rst

+22-6
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ Starting from dune 2.0, dune mangles compilation units of executables by
9191
default. However, this can still be turned off using ``(wrapped_executables
9292
false)``
9393

94+
.. _executables_implicit_empty_intf:
95+
96+
executables_implicit_empty_intf
97+
-------------------------------
98+
99+
By default, executables defined via ``(executables(s) ...)`` or ``(test(s)
100+
...)`` stanzas are compiled with the interface file provided (e.g. ``.mli`` or
101+
``rei``). Since these modules cannot be used as library dependencies, it's
102+
common to give them empty interface files to strengthen the compiler's ability
103+
to detect unused values in these modules.
104+
105+
Starting from dune 2.8, an option is available to automatically generate empty
106+
interface files for executables and tests that don't already have them:
107+
108+
.. code:: scheme
109+
110+
(executables_implicit_empty_intf true)
111+
94112
.. _explicit-js-mode:
95113

96114
explicit_js_mode
@@ -611,9 +629,8 @@ binary at the same place as where ``ocamlc`` was found.
611629
Executables can also be linked as object or shared object files. See
612630
`linking modes`_ for more information.
613631

614-
Starting from dune 2.8, executable modules without interface files (e.g. `.mli`,
615-
`.rei`) are compiled against empty interfaces in order to improve dead code
616-
detection.
632+
Starting from dune 2.8, it's possible to automatically generate empty interface
633+
files for executables. See `executables_implicit_empty_intf`_.
617634

618635
``<optional-fields>`` are:
619636

@@ -1329,9 +1346,8 @@ running dune runtest you can use the following stanza:
13291346
(libraries alcotest mylib)
13301347
(action (run %{test} -e)))
13311348
1332-
Starting from dune 2.8, test modules without interface files (e.g. `.mli`,
1333-
`.rei`) are compiled against empty interfaces in order to improve dead code
1334-
detection.
1349+
Starting from dune 2.8, it's possible to automatically generate empty interface
1350+
files for test executables. See `executables_implicit_empty_intf`_.
13351351

13361352
test
13371353
----

src/dune_engine/dune_project.ml

+20
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ type t =
159159
; parsing_context : Univ_map.t
160160
; implicit_transitive_deps : bool
161161
; wrapped_executables : bool
162+
; executables_implicit_empty_intf : bool
162163
; dune_version : Dune_lang.Syntax.Version.t
163164
; allow_approx_merlin : bool
164165
; generate_opam_files : bool
@@ -210,6 +211,7 @@ let to_dyn
210211
; packages
211212
; implicit_transitive_deps
212213
; wrapped_executables
214+
; executables_implicit_empty_intf
213215
; dune_version
214216
; allow_approx_merlin
215217
; generate_opam_files
@@ -232,6 +234,7 @@ let to_dyn
232234
(Package.Name.Map.to_list packages) )
233235
; ("implicit_transitive_deps", bool implicit_transitive_deps)
234236
; ("wrapped_executables", bool wrapped_executables)
237+
; ("executables_implicit_empty_intf", bool executables_implicit_empty_intf)
235238
; ("dune_version", Dune_lang.Syntax.Version.to_dyn dune_version)
236239
; ("allow_approx_merlin", bool allow_approx_merlin)
237240
; ("generate_opam_files", bool generate_opam_files)
@@ -550,6 +553,9 @@ let implicit_transitive_deps_default ~lang:_ = true
550553
let wrapped_executables_default ~(lang : Lang.Instance.t) =
551554
lang.version >= (2, 0)
552555

556+
let executables_implicit_empty_intf_default ~(lang : Lang.Instance.t) =
557+
lang.version >= (3, 0)
558+
553559
let strict_package_deps_default ~(lang : Lang.Instance.t) =
554560
lang.version >= (3, 0)
555561

@@ -592,6 +598,9 @@ let infer ~dir packages =
592598
in
593599
let implicit_transitive_deps = implicit_transitive_deps_default ~lang in
594600
let wrapped_executables = wrapped_executables_default ~lang in
601+
let executables_implicit_empty_intf =
602+
executables_implicit_empty_intf_default ~lang
603+
in
595604
let explicit_js_mode = explicit_js_mode_default ~lang in
596605
let strict_package_deps = strict_package_deps_default ~lang in
597606
let root = dir in
@@ -603,6 +612,7 @@ let infer ~dir packages =
603612
; version = None
604613
; implicit_transitive_deps
605614
; wrapped_executables
615+
; executables_implicit_empty_intf
606616
; stanza_parser
607617
; project_file
608618
; extension_args
@@ -668,6 +678,9 @@ let parse ~dir ~lang ~opam_packages ~file =
668678
and+ wrapped_executables =
669679
field_o_b "wrapped_executables"
670680
~check:(Dune_lang.Syntax.since Stanza.syntax (1, 11))
681+
and+ executables_implicit_empty_intf =
682+
field_o_b "executables_implicit_empty_intf"
683+
~check:(Dune_lang.Syntax.since Stanza.syntax (2, 8))
671684
and+ allow_approx_merlin =
672685
field_o_b "allow_approximate_merlin"
673686
~check:(Dune_lang.Syntax.since Stanza.syntax (1, 9))
@@ -777,6 +790,10 @@ let parse ~dir ~lang ~opam_packages ~file =
777790
Option.value wrapped_executables
778791
~default:(wrapped_executables_default ~lang)
779792
in
793+
let executables_implicit_empty_intf =
794+
Option.value executables_implicit_empty_intf
795+
~default:(executables_implicit_empty_intf_default ~lang)
796+
in
780797
let strict_package_deps =
781798
Option.value strict_package_deps
782799
~default:(strict_package_deps_default ~lang)
@@ -816,6 +833,7 @@ let parse ~dir ~lang ~opam_packages ~file =
816833
; parsing_context
817834
; implicit_transitive_deps
818835
; wrapped_executables
836+
; executables_implicit_empty_intf
819837
; dune_version
820838
; allow_approx_merlin
821839
; generate_opam_files
@@ -865,6 +883,8 @@ let set_parsing_context t parser =
865883

866884
let wrapped_executables t = t.wrapped_executables
867885

886+
let executables_implicit_empty_intf t = t.executables_implicit_empty_intf
887+
868888
let () =
869889
let open Dune_lang.Decoder in
870890
Extension.register_simple Action_plugin.syntax (return []);

src/dune_engine/dune_project.mli

+2
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ val dune_version : t -> Dune_lang.Syntax.Version.t
168168

169169
val wrapped_executables : t -> bool
170170

171+
val executables_implicit_empty_intf : t -> bool
172+
171173
val strict_package_deps : t -> bool
172174

173175
val cram : t -> bool

src/dune_rules/exe_rules.ml

+6-6
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ let with_empty_intf ~sctx ~dir module_ =
102102
Super_context.add_rule sctx ~dir rule;
103103
Module.add_file module_ Ml_kind.Intf (Module.File.make Dialect.ocaml name)
104104

105-
let executables_rules ~sctx ~dune_version ~dir ~expander ~dir_contents ~scope
106-
~compile_info ~embed_in_plugin_libraries (exes : Dune_file.Executables.t) =
105+
let executables_rules ~sctx ~dir ~expander ~dir_contents ~scope ~compile_info
106+
~embed_in_plugin_libraries (exes : Dune_file.Executables.t) =
107107
(* Use "eobjs" rather than "objs" to avoid a potential conflict with a library
108108
of the same name *)
109109
let obj_dir = Dune_file.Executables.obj_dir exes ~dir in
@@ -132,7 +132,8 @@ let executables_rules ~sctx ~dune_version ~dir ~expander ~dir_contents ~scope
132132
let name = Module.name m in
133133
let m = Preprocessing.pp_module_as pp name m in
134134
let add_empty_intf =
135-
dune_version >= (2, 8)
135+
let project = Scope.project scope in
136+
Dune_project.executables_implicit_empty_intf project
136137
&& List.mem name ~set:executable_names
137138
&& not (Module.has m ~ml_kind:Intf)
138139
in
@@ -226,9 +227,8 @@ let rules ~sctx ~dir ~dir_contents ~scope ~expander
226227
~optional:exes.optional ~forbidden_libraries:exes.forbidden_libraries
227228
in
228229
let f () =
229-
executables_rules exes ~sctx ~dune_version ~dir ~dir_contents ~scope
230-
~expander ~compile_info
231-
~embed_in_plugin_libraries:exes.embed_in_plugin_libraries
230+
executables_rules exes ~sctx ~dir ~dir_contents ~scope ~expander
231+
~compile_info ~embed_in_plugin_libraries:exes.embed_in_plugin_libraries
232232
in
233233
Buildable_rules.gen_select_rules sctx compile_info ~dir;
234234
Bootstrap_info.gen_rules sctx exes ~dir compile_info;

test/blackbox-tests/test-cases/implicit-empty-executables.t/run.t test/blackbox-tests/test-cases/executables-implicit-empty-intf.t/run.t

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
Executables with no corresponding `.mli` file will have one generated for them
22
by Dune:
33

4-
$ echo "(lang dune 2.8)" > dune-project
4+
$ cat >dune-project <<EOF
5+
> (lang dune 2.8)
6+
> (executables_implicit_empty_intf true)
7+
> EOF
8+
59
$ dune build ./bin/executable.exe
610
File "bin/executable.ml", line 1, characters 4-10:
711
1 | let unused = Dependency.a
@@ -25,20 +29,19 @@ as will test binaries:
2529
$ cat _build/default/test/test.mli
2630
(* Auto-generated by Dune *)
2731

28-
Empty `.mli` files are only generated when lang version >= 2.8:
29-
30-
$ dune clean
31-
$ echo "(lang dune 2.7)" > dune-project
32-
$ dune build ./bin/executable.exe
33-
$ dune runtest
34-
$ test ! -f _build/default/bin/executable.mli
35-
$ test ! -f _build/default/test/test.mli
36-
3732
If an executable already has an interface, it is preserved:
3833

3934
$ dune clean
40-
$ echo "(lang dune 2.8)" > dune-project
4135
$ dune build ./bin_with_intf/executable.exe
4236
$ cat _build/default/bin_with_intf/executable.mli
4337
val a : int
4438

39+
Generation of empty `.mli` files is disabled by default prior to lang 3.0:
40+
41+
$ dune clean
42+
$ echo >dune-project "(lang dune 2.8)"
43+
$ dune build ./bin/executable.exe
44+
$ dune runtest
45+
$ test ! -f _build/default/bin/executable.mli
46+
$ test ! -f _build/default/test/test.mli
47+

0 commit comments

Comments
 (0)