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

Map (re_export) to exports field in META files #10831

Merged
merged 9 commits into from
Aug 19, 2024
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
6 changes: 6 additions & 0 deletions doc/changes/10831.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Map `(re_export)` library dependencies to the `exports` field in `META` files,
and vice-versa. This field was proposed in to
https://discuss.ocaml.org/t/proposal-a-new-exports-field-in-findlib-meta-files/13947.
The field is included in Dune-generated `META` files only when the Dune lang
version is >= 3.17.
(#10831, @nojb)
5 changes: 5 additions & 0 deletions src/dune_findlib/package0.ml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ let requires t =
|> List.map ~f:(fun s -> Lib_name.parse_string_exn (Loc.none, s))
;;

let exports t =
Vars.get_words t.vars "exports" Ps.empty
|> List.map ~f:(fun s -> Lib_name.parse_string_exn (Loc.none, s))
;;

let ppx_runtime_deps t =
Vars.get_words t.vars "ppx_runtime_deps" preds
|> List.map ~f:(fun s -> Lib_name.parse_string_exn (Loc.none, s))
Expand Down
1 change: 1 addition & 0 deletions src/dune_findlib/package0.mli
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ val version : t -> string option
val description : t -> string option
val jsoo_runtime : t -> Path.t list
val requires : t -> Lib_name.t list
val exports : t -> Lib_name.t list
val ppx_runtime_deps : t -> Lib_name.t list
val kind : t -> Lib_kind.t
val archives : t -> Path.t list Mode.Dict.t
Expand Down
7 changes: 6 additions & 1 deletion src/dune_rules/findlib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,13 @@ let to_dune_library (t : Findlib.Package.t) ~dir_contents ~ext_lib ~external_loc
let main_module_name : Lib_info.Main_module_name.t = This None in
let enabled = Memo.return Lib_info.Enabled_status.Normal in
let requires =
let exports = Lib_name.Set.of_list (Findlib.Package.exports t) in
Findlib.Package.requires t
|> List.map ~f:(fun name -> Lib_dep.direct (add_loc name))
|> List.map ~f:(fun name ->
let lib_dep =
if Lib_name.Set.mem exports name then Lib_dep.re_export else Lib_dep.direct
in
lib_dep (add_loc name))
in
let ppx_runtime_deps = List.map ~f:add_loc (Findlib.Package.ppx_runtime_deps t) in
let special_builtin_support : (Loc.t * Lib_info.Special_builtin_support.t) option =
Expand Down
11 changes: 10 additions & 1 deletion src/dune_rules/gen_meta.ml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ end
let string_of_deps deps = Lib_name.Set.to_string_list deps |> String.concat ~sep:" "
let rule var predicates action value = Rule { var; predicates; action; value }
let requires ?(preds = []) pkgs = rule "requires" preds Set (string_of_deps pkgs)
let exports pkgs = rule "exports" [] Set (string_of_deps pkgs)

let ppx_runtime_deps ?(preds = []) pkgs =
rule "ppx_runtime_deps" preds Set (string_of_deps pkgs)
Expand Down Expand Up @@ -87,6 +88,7 @@ let gen_lib pub_name lib ~version =
in
let to_names = Lib_name.Set.of_list_map ~f:name in
let* lib_deps = Resolve.Memo.read_memo (Lib.requires lib) >>| to_names in
let* lib_re_exports = Resolve.Memo.read_memo (Lib.re_exports lib) >>| to_names in
let* ppx_rt_deps =
Lib.ppx_runtime_deps lib |> Memo.bind ~f:Resolve.read_memo |> Memo.map ~f:to_names
in
Expand All @@ -109,6 +111,12 @@ let gen_lib pub_name lib ~version =
List.concat
[ version
; [ description desc; requires ~preds lib_deps ]
; (if (match Lib.project lib with
| None -> true
| Some project -> Dune_project.dune_version project < (3, 17))
|| Lib_name.Set.is_empty lib_re_exports
then []
else [ exports lib_re_exports ])
; archives ~preds lib
; (if Lib_name.Set.is_empty ppx_rt_deps
then []
Expand Down Expand Up @@ -195,9 +203,10 @@ let gen ~(package : Package.t) ~add_directory_entry entries =
pub_name, entries)
| Deprecated_library_name
{ old_name = old_public_name, _; new_public_name = _, new_public_name; _ } ->
let deps = Lib_name.Set.singleton new_public_name in
Memo.return
( Pub_name.of_lib_name (Public_lib.name old_public_name)
, version @ [ requires (Lib_name.Set.singleton new_public_name) ] ))
, version @ [ requires deps; exports deps ] ))
in
let pkgs =
List.map pkgs ~f:(fun (pn, meta) ->
Expand Down
1 change: 1 addition & 0 deletions src/dune_rules/lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ let info t = t.info
let project t = t.project
let implements t = Option.map ~f:Memo.return t.implements
let requires t = Memo.return t.requires
let re_exports t = Memo.return t.re_exports
let ppx_runtime_deps t = Memo.return t.ppx_runtime_deps
let pps t = Memo.return t.pps

Expand Down
1 change: 1 addition & 0 deletions src/dune_rules/lib.mli
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ val wrapped : t -> Wrapped.t option Resolve.Memo.t
(** Direct library dependencies of this library *)
val requires : t -> t list Resolve.Memo.t

val re_exports : t -> t list Resolve.Memo.t
val ppx_runtime_deps : t -> t list Resolve.Memo.t
val pps : t -> t list Resolve.Memo.t

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ tests that the "old_public_name" field is evaluated lazily

$ dune_cmd cat $PWD/_install/lib/a/META
requires = "b"
exports = "b"

$ dune_cmd cat $PWD/_install/lib/a/dune-package | sed "s/(lang dune .*)/(lang dune <version>)/"
(lang dune <version>)
Expand Down Expand Up @@ -144,8 +145,10 @@ First the motivating case.

$ dune_cmd cat d/_build/install/default/lib/menhirLib/META
requires = "menhir.lib"
exports = "menhir.lib"
$ dune_cmd cat d/_build/install/default/lib/menhirSdk/META
requires = "menhir.sdk"
exports = "menhir.sdk"

$ find d/_build/install/default -name 'dune-package' | sort
d/_build/install/default/lib/dummy/dune-package
Expand Down Expand Up @@ -208,6 +211,7 @@ Checks that we can migrate top-level libraries across packages.

$ dune_cmd cat d/_build/install/default/lib/top1/META
requires = "q.bar"
exports = "q.bar"

Check that we can do it when the name of the new library is the same as the
old public name:
Expand All @@ -227,6 +231,7 @@ old public name:

$ dune_cmd cat d/_build/install/default/lib/top2/META
requires = "q.top2"
exports = "q.top2"

We check that there is an error when there is an actual ambiguity:

Expand Down Expand Up @@ -304,6 +309,7 @@ Qualified, deprecated old_public_name:
$ dune_cmd cat d/_build/install/default/lib/q/META
package "foo" (
requires = "p"
exports = "p"
)

$ find d/_build/install/default -name 'dune-package' | sort
Expand Down Expand Up @@ -345,9 +351,11 @@ Two libraries redirecting to the same library:
$ dune_cmd cat d/_build/install/default/lib/q/META
package "bar" (
requires = "p"
exports = "p"
)
package "foo" (
requires = "p"
exports = "p"
)

$ find d/_build/install/default -name 'dune-package' | sort
Expand Down
118 changes: 118 additions & 0 deletions test/blackbox-tests/test-cases/meta-exports.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
Check our handling of `exports` in META files. We begin with a test showing that
we can *consume* META files containing an `exports` field.

To do this, first we create a Findlib hierarchy containing two installed
packages, `foo` and `bar`. The package `foo` is empty and only exists to
re-export `bar`. The package `bar` consists of a bytecode library, `bar.cma`.

$ mkdir -p _install/foo
$ cat >_install/foo/META <<EOF
> requires = "bar"
> EOF

$ mkdir -p _install/bar
$ cat >_install/bar/META <<EOF
> archive(byte) = "bar.cma"
> EOF
$ cat >_install/bar/bar.ml <<EOF
> let x = 42
> EOF
$ ocamlc -a -o _install/bar/bar.cma _install/bar/bar.ml

We now define a Dune project that will consume `foo`.

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

$ cat >dune <<EOF
> (executable
> (name main)
> (modes byte)
> (libraries foo))
> EOF
$ cat >main.ml <<EOF
> let () = print_int Bar.x; print_newline ()
> EOF

Compilation works with `(implicit_transitive_deps)`:

$ OCAMLPATH=$(pwd)/_install dune exec ./main.exe
42

However, the compilation without `(implicit_transitive_deps)` fails:

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

$ OCAMLPATH=$(pwd)/_install dune exec ./main.exe
File "main.ml", line 1, characters 19-24:
1 | let () = print_int Bar.x; print_newline ()
^^^^^
Error: Unbound module Bar
[1]

Next, we add the `exports` field to `foo`'s `META` file:

$ cat >_install/foo/META <<EOF
> requires = "bar"
> exports = "bar"
> EOF

and compilation now works again:

$ OCAMLPATH=$(pwd)/_install dune exec ./main.exe
42

----------------------------------------------------------------

Next, we check that we can *produce* META files with the `export` field. To do
this, we define two Dune libraries `foo` and `bar`, where `foo` depends on `bar`
using `(re_export)`.

$ cat >dune-project.gen <<'EOF'
> cat <<EOF2
> (lang dune $VERSION)
> (package (name foo))
> (package (name bar))
> EOF2
> EOF

$ cat >dune <<EOF
> (library
> (public_name bar)
> (modules bar))
> (library
> (public_name foo)
> (libraries (re_export bar))
> (modules foo))
> EOF
$ true >bar.ml
$ true >foo.ml

First we try with dune version 3.16 (it should not generate the `exports` field):

$ VERSION=3.16 sh dune-project.gen >dune-project
$ dune build && dune install --libdir $(pwd)/_local
$ cat _local/foo/META
description = ""
requires = "bar"
archive(byte) = "foo.cma"
archive(native) = "foo.cmxa"
plugin(byte) = "foo.cma"
plugin(native) = "foo.cmxs"

Now with dune version 3.17 (it should generate the `exports` field):

$ VERSION=3.17 sh dune-project.gen >dune-project
$ dune build && dune install --libdir $(pwd)/_local
$ cat _local/foo/META
description = ""
requires = "bar"
exports = "bar"
archive(byte) = "foo.cma"
archive(native) = "foo.cmxa"
plugin(byte) = "foo.cma"
plugin(native) = "foo.cmxs"
2 changes: 1 addition & 1 deletion test/expect-tests/findlib_tests.ml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ let%expect_test _ =
let dyn = Dyn.list Lib_dep.to_dyn requires in
let pp = Dyn.pp dyn in
Format.printf "%a@." Pp.to_fmt pp;
[%expect {|[ "baz" ]|}]
[%expect {|[ re_export "baz"; "xyz" ]|}]
;;

(* Meta parsing/simplification *)
Expand Down
3 changes: 2 additions & 1 deletion test/unit-tests/findlib-db/foo/META
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
requires = "bar"
requires(ppx_driver) = "baz"
requires(ppx_driver) = "baz xyz"
exports = "baz wrong"
Loading