diff --git a/CHANGES.md b/CHANGES.md index a8d14c7e9bc..8b796c8ce51 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -201,6 +201,9 @@ Unreleased - Dune no longer reads installed META files for libraries distributed with the compiler, instead using its own internal database. (#4946, @nojb) +- Add support for `(empty_module_interface_if_absent)` in executable and library + stanzas. (#4955, @nojb) + 2.9.1 (07/09/2021) ------------------ diff --git a/doc/dune-files.rst b/doc/dune-files.rst index 8be7927af14..660aba2394a 100644 --- a/doc/dune-files.rst +++ b/doc/dune-files.rst @@ -626,6 +626,11 @@ to use the :ref:`include_subdirs` stanza. is useful whenever a library is shadowed by a local module. The library may then still be accessible via this root module +- ``(empty_module_interface_if_absent)`` causes the generation of empty + interfaces for every module that does not have an interface file already. + Useful when modules are used solely for their side-effects. This field is + available since the 3.0 version of the dune language. + Note that when binding C libraries, dune doesn't provide special support for tools such as ``pkg-config``, however it integrates easily with :ref:`configurator` by @@ -825,6 +830,11 @@ files for executables. See `executables_implicit_empty_intf`_. flag if some of the libraries listed here are not referenced from any of the plugin modules. +- ``(empty_module_interface_if_absent)`` causes the generation of empty + interfaces for every module that does not have an interface file already. + Useful when modules are used solely for their side-effects. This field is + available since the 3.0 version of the dune language. + Linking modes ~~~~~~~~~~~~~ diff --git a/src/dune_rules/dune_file.ml b/src/dune_rules/dune_file.ml index eecaa215c65..6fa49d5281e 100644 --- a/src/dune_rules/dune_file.ml +++ b/src/dune_rules/dune_file.ml @@ -152,6 +152,7 @@ module Buildable = struct { loc : Loc.t ; modules : Ordered_set_lang.t ; modules_without_implementation : Ordered_set_lang.t + ; empty_module_interface_if_absent : bool ; libraries : Lib_dep.t list ; foreign_archives : (Loc.t * Foreign.Archive.t) list ; foreign_stubs : Foreign.Stubs.t list @@ -263,6 +264,9 @@ module Buildable = struct and+ root_module = field_o "root_module" (Dune_lang.Syntax.since Stanza.syntax (2, 8) >>> Module_name.decode_loc) + and+ empty_module_interface_if_absent = + field_b "empty_module_interface_if_absent" + ~check:(Dune_lang.Syntax.since Stanza.syntax (3, 0)) in let preprocess = let init = @@ -312,6 +316,7 @@ module Buildable = struct ; lint ; modules ; modules_without_implementation + ; empty_module_interface_if_absent ; foreign_stubs ; foreign_archives ; libraries diff --git a/src/dune_rules/dune_file.mli b/src/dune_rules/dune_file.mli index 773395d55fe..bc00aabdc55 100644 --- a/src/dune_rules/dune_file.mli +++ b/src/dune_rules/dune_file.mli @@ -42,6 +42,7 @@ module Buildable : sig { loc : Loc.t ; modules : Ordered_set_lang.t ; modules_without_implementation : Ordered_set_lang.t + ; empty_module_interface_if_absent : bool ; libraries : Lib_dep.t list ; foreign_archives : (Loc.t * Foreign.Archive.t) list ; foreign_stubs : Foreign.Stubs.t list diff --git a/src/dune_rules/exe_rules.ml b/src/dune_rules/exe_rules.ml index 238225e81b7..46a180c6f71 100644 --- a/src/dune_rules/exe_rules.ml +++ b/src/dune_rules/exe_rules.ml @@ -91,20 +91,6 @@ let o_files sctx ~dir ~expander ~(exes : Executables.t) ~linkages ~dir_contents in List.map o_files ~f:Path.build -let with_empty_intf ~sctx ~dir module_ = - let name = - Module.file module_ ~ml_kind:Impl - |> Option.value_exn - |> Path.set_extension ~ext:".mli" - in - let rule = - Action_builder.write_file - (Path.as_in_build_dir_exn name) - "(* Auto-generated by Dune *)" - in - let+ () = Super_context.add_rule sctx ~dir rule in - Module.add_file module_ Ml_kind.Intf (Module.File.make Dialect.ocaml name) - let executables_rules ~sctx ~dir ~expander ~dir_contents ~scope ~compile_info ~embed_in_plugin_libraries (exes : Dune_file.Executables.t) = (* Use "eobjs" rather than "objs" to avoid a potential conflict with a library @@ -138,17 +124,20 @@ let executables_rules ~sctx ~dir ~expander ~dir_contents ~scope ~compile_info let executable_names = List.map exes.names ~f:Module_name.of_string_allow_invalid in + let add_empty_intf = exes.buildable.empty_module_interface_if_absent in Modules.map_user_written modules ~f:(fun m -> let name = Module.name m in let* m = Pp_spec.pp_module_as pp name m in let add_empty_intf = + (add_empty_intf + || let project = Scope.project scope in Dune_project.executables_implicit_empty_intf project - && List.mem executable_names name ~equal:Module_name.equal + && List.mem executable_names name ~equal:Module_name.equal) && not (Module.has m ~ml_kind:Intf) in if add_empty_intf then - with_empty_intf ~sctx ~dir m + Module_compilation.with_empty_intf ~sctx ~dir m else Memo.Build.return m) in diff --git a/src/dune_rules/lib_rules.ml b/src/dune_rules/lib_rules.ml index 8699f67b890..967ef73c312 100644 --- a/src/dune_rules/lib_rules.ml +++ b/src/dune_rules/lib_rules.ml @@ -375,7 +375,13 @@ let cctx (lib : Library.t) ~sctx ~source_modules ~dir ~expander ~scope ~lib_name:(Some (snd lib.name)) in let+ modules = - Modules.map_user_written source_modules ~f:(Pp_spec.pp_module pp) + let add_empty_intf = lib.buildable.empty_module_interface_if_absent in + Modules.map_user_written source_modules ~f:(fun m -> + let* m = Pp_spec.pp_module pp m in + if add_empty_intf && not (Module.has m ~ml_kind:Intf) then + Module_compilation.with_empty_intf ~sctx ~dir m + else + Memo.Build.return m) in let modules = Vimpl.impl_modules vimpl modules in let requires_compile = Lib.Compile.direct_requires compile_info in diff --git a/src/dune_rules/module_compilation.ml b/src/dune_rules/module_compilation.ml index e9ea9cf0e87..772ea5b8bae 100644 --- a/src/dune_rules/module_compilation.ml +++ b/src/dune_rules/module_compilation.ml @@ -387,3 +387,18 @@ let build_all cctx ~dep_graphs = cctx in build_module cctx ~dep_graphs m) + +let with_empty_intf ~sctx ~dir module_ = + let name = + Module.file module_ ~ml_kind:Impl + |> Option.value_exn + |> Path.set_extension ~ext:".mli" + in + let rule = + Action_builder.write_file + (Path.as_in_build_dir_exn name) + "(* Auto-generated by Dune *)" + in + let open Memo.Build.O in + let+ () = Super_context.add_rule sctx ~dir rule in + Module.add_file module_ Ml_kind.Intf (Module.File.make Dialect.ocaml name) diff --git a/src/dune_rules/module_compilation.mli b/src/dune_rules/module_compilation.mli index 131f73c9093..2bcb3eba422 100644 --- a/src/dune_rules/module_compilation.mli +++ b/src/dune_rules/module_compilation.mli @@ -21,3 +21,6 @@ val ocamlc_i : val build_all : Compilation_context.t -> dep_graphs:Dep_graph.Ml_kind.t -> unit Memo.Build.t + +val with_empty_intf : + sctx:Super_context.t -> dir:Path.Build.t -> Module.t -> Module.t Memo.Build.t diff --git a/test/blackbox-tests/test-cases/buildable-empty-intf-if-absent.t/run.t b/test/blackbox-tests/test-cases/buildable-empty-intf-if-absent.t/run.t new file mode 100644 index 00000000000..58b34ca387c --- /dev/null +++ b/test/blackbox-tests/test-cases/buildable-empty-intf-if-absent.t/run.t @@ -0,0 +1,70 @@ +Generate empty interfaces when using (empty_module_interface_if_absent) + + $ cat >dune-project < (lang dune 3.0) + > EOF + +Executables + + $ cat >main.ml < module D = Dep + > EOF + + $ touch dep.ml + + $ cat >dune < (executable (name main) (empty_module_interface_if_absent)) + > EOF + + $ dune build ./main.exe + + $ cat _build/default/dep.mli + (* Auto-generated by Dune *) + +Libraries + + $ cat >dune < (library (name l) (modules a b) (empty_module_interface_if_absent)) + > EOF + + $ touch a.ml b.ml + + $ dune build + + $ cat _build/default/a.mli + (* Auto-generated by Dune *) + $ cat _build/default/b.mli + (* Auto-generated by Dune *) + +Version check + + $ cat >dune-project < (lang dune 2.9) + > EOF + + $ dune build + File "dune", line 1, characters 32-66: + 1 | (library (name l) (modules a b) (empty_module_interface_if_absent)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Error: 'empty_module_interface_if_absent' is only available since version 3.0 + of the dune language. Please update your dune-project file to have (lang dune + 3.0). + [1] + +Check that interfaces are *not* generated if field is not used + + $ cat >dune < (lang dune 3.0) + > EOF + + $ cat >dune < (library (name l) (modules a b)) + > EOF + + $ touch a.mli + + $ dune build + + $ test -f _build/default/a.mli + + $ test ! -f _build/default/b.mli