Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for (empty_module_interface_if_absent) #4955

Merged
merged 4 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------

Expand Down
10 changes: 10 additions & 0 deletions doc/dune-files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific to this PR, but it's a shame we have to duplicate the doc. At some point we should re-organise the doc so we don't need this duplication. /cc @christinerose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed about duplicating doc and reorganization! Is dune-files.rst file duplicated in different PRs or in another directory in the repo?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplication is inside dune-files.rst. There are fields, such as empty_module_interafe_if_absent and a few other ones, that are allowed in both (library ...) and (executable ...) stanzas. Currently, these flags are described twice, once for each stanza.


Linking modes
~~~~~~~~~~~~~

Expand Down
5 changes: 5 additions & 0 deletions src/dune_rules/dune_file.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -312,6 +316,7 @@ module Buildable = struct
; lint
; modules
; modules_without_implementation
; empty_module_interface_if_absent
; foreign_stubs
; foreign_archives
; libraries
Expand Down
1 change: 1 addition & 0 deletions src/dune_rules/dune_file.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 5 additions & 16 deletions src/dune_rules/exe_rules.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/dune_rules/lib_rules.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions src/dune_rules/module_compilation.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions src/dune_rules/module_compilation.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Generate empty interfaces when using (empty_module_interface_if_absent)

$ cat >dune-project <<EOF
> (lang dune 3.0)
> EOF

Executables

$ cat >main.ml <<EOF
> module D = Dep
> EOF

$ touch dep.ml

$ cat >dune <<EOF
> (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 <<EOF
> (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 <<EOF
> (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 <<EOF
> (lang dune 3.0)
> EOF

$ cat >dune <<EOF
> (library (name l) (modules a b))
> EOF

$ touch a.mli

$ dune build

$ test -f _build/default/a.mli

$ test ! -f _build/default/b.mli