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 -H compiler flag #10644

Merged
merged 22 commits into from
Jun 28, 2024
Merged

Add support for -H compiler flag #10644

merged 22 commits into from
Jun 28, 2024

Conversation

nojb
Copy link
Collaborator

@nojb nojb commented Jun 11, 2024

This PR adds support to Dune to use the -H compiler flag introduced in ocaml/ocaml#12246, which provides good semantics to the (implicit_transitive_deps false).

This work was carried out by @MA0010 as part of an internship at LexiFi. I am opening this PR but @MA0010 will take the lead during the review process.

  • The first commit contains the meat of the change: we keep track of which dependencies are "direct" (= mentioned in the Dune file) and which dependencies are purely transitive. For direct dependencies we use -I and for transitive dependencies we use -H (of course, only for OCaml >= 5.2). The use of -H is also guarded by Dune version 3.17 (this is to minimize the possibility of breakage, also there are some corner cases where the change is not completely backwards-compatible).

  • The second commit adds an explicit unix dependency to some vendored libraries. This is needed because unix is mentioned in the source of these libraries, but the library is not mentioned in the Dune stanzas. The libraries are able to build before this PR because OCaml adds -I +unix implicitly (for recent enough versions of OCaml where unix is installed in the separate directory +unix). However, after this PR, the compiler passes -H +unix which disables the implicit addition of -I +unix breaking compilation (again, because Unix is a direct dependency of these libraries).

  • The third commit contains some tests. Writing the tests was a bit involved due to the need to have them pass both with versions of OCaml supporting -H and those that do not. Suggestions for improvement welcome.

  • The last commit bumps the Dune version to 3.17. We could consider not bumping the Dune language version since the new mode is almost always a strict improvement for users of (implicit_transitive_deps false) (modulo corner cases such as the one mentioned in the second bullet point). Opinions welcome.

Future work:

Fixes #9333
Fixes ocsigen/tyxml#274
Fixes #2733
Fixes #4963

@emillon
Copy link
Collaborator

emillon commented Jun 11, 2024

Hi, thanks for this PR and the detailed description. There are a couple things we can land first:

  • the 3.17 language version (we'll need it anyway and 3.16 has branched)
  • the fix for unix is useful anyway because it triggers a warning on 5.x and we're going to bump the test suite to use this version soon

Regarding your question:

Writing the tests was a bit involved due to the need to have them pass both with versions of OCaml supporting -H and those that do not. Suggestions for improvement welcome.

Yes. the reason it's painful is that we're only testing a single version of the compiler in CI, but you can add tests that require 5.x and mark them with (enabled_if (>= %{ocaml_version} 5.2)). This means that make test will work for them locally, and will work on 5.x in CI when we bump the CI version. Similarly for tests that rely on 4.x. They'll become dead tests when we bump the tested version but that can still be useful for some time.

@MA0010
Copy link
Contributor

MA0010 commented Jun 12, 2024

Hello, thanks for the comment.

  • the 3.17 language version (we'll need it anyway and 3.16 has branched)
  • the fix for unix is useful anyway because it triggers a warning on 5.x and we're going to bump the test suite to use this version soon.

I have opened a separate PR to land the changes you proposed (see #10645). As you mentioned, those can be merged independently of the feature to be added through this PR. I will remove them from this PR as soon as they get merged successfully.

@@ -235,3 +235,5 @@ module Local : sig

include Comparable_intf.S with type key := t
end

module Tbl : Hashtbl.S with type key := t
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need this since our Table module is polymorphic. let table = Table.create (module Lib) should work


val include_flags
: ?project:Dune_project.t
-> ?direct:(Lib.t -> bool)
Copy link
Member

Choose a reason for hiding this comment

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

Why can't we just pass a list: ([ Direct | Indirect] * Lib.t) list as an argument? I'm not a fan of passing around callbacks.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hello,
I have removed the passed callback. Instead, now two lists are passed, one containing the direct dependencies (requires_direct) and the other contains the hidden dependencies (requires_hidden). The requires_hidden is also exposed in Compilation_context.t, and so can be used in other places when needed.

The code have changed a little bit, and the diff in lib_flags.ml is now smaller.

Review appreciated!

else requires_compile
then Memo.Lazy.force requires_link, Resolve.Memo.return (fun _ -> true)
else if Version.supports_hidden_includes ocaml.version
&& Dune_project.dune_version project >= (3, 17)
Copy link
Member

Choose a reason for hiding this comment

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

I think this mode still needs to be guarded behind implicit_transitive_deps. Once we experiment with it more, we can enable it by default for newer compilers perhaps.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, but I do not seem to have understood well your point. The new feature is invoked only when implicit_transitive_deps is false, according to the written logic, or am I missing something?

Thanks for the review.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that wasn't very clear at all. Let me explain the behavior I would like to achieve:

When we're using (implicit_transitive_deps false) and OCaml >= 5.2.0, we should use use your new implementation with the -H flag.

When we're using (implicit_transitive_deps false) and OCaml < 5.2.0, we should go back to using the old implementation of hiding -I flags.

When we're using (implicit_transitive_deps true), it should disable using -H flags completely.

The issue with the current behavior is that users do expect upgrading to 3.17 to be relatively easy. With this change, they will now need to look at all their transitive deps. While I agree that it's a good thing, it's too much of a breakage to introduce in a minor version bump.

Therefore, I suggest that we guard this behind a feature flag. We already have a feature flag though implicit_transitive_deps, so we can just reuse it.

@rgrinberg
Copy link
Member

@voodoos could you review the implementation as well? I think you needed something similar for merlin. It would be good to see what can be shared.

@anmonteiro anmonteiro self-requested a review June 13, 2024 21:46
@MA0010 MA0010 force-pushed the hidden_deps branch 2 times, most recently from 514d4f3 to af51c9a Compare June 14, 2024 14:48
Copy link
Collaborator

@voodoos voodoos left a comment

Choose a reason for hiding this comment

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

I think the way dependencies are exposed right now is too restricting. The Lib.Compile.t API only provide us with either require_compile, the list of direct dependencies, or require_link, the list of all transitive dependencies including the direct ones.

This means that we have to compute the disjunction require_link \ require_compile to get the actual "hidden" or "extra" dependencies. Right now this is needed in 3 places: rules for indexation (#10422), rules for merlin config (#10535) and this PR.

One issue is that require_compile and require_link are lists which prevent an efficient computation of the disjunction. In #10422 and #10535 this is done in a somewhat inefficient way using multiple calls to List.mem. This PR introduces a more efficient approach by using a set (the (lib, unit) Hashtbl.t) to perform more efficient membership tests.

I think we should update Lib.Compile.t to either provide an additional requires_extra list of libs that is computed in an efficient way and share the result, or have requires_compile and requires_link be Sets so that consumers can efficiently compute the disjunction without too much code duplication

This would allow the different places using the "extra deps" to do it in a uniform way.

@MA0010
Copy link
Contributor

MA0010 commented Jun 18, 2024

Concerning the last two commits, they contain edits based on the reviews and comments (cc @voodoos @rgrinberg), resting the main goal of keeping track of hidden deps unchanged. Those dependencies are now tracked by a new list hidden_requires exposed in the Compilation_context.t type. In short:

  • The first of the new commits keeps the previous approach of using hashtables to compute the hidden dependencies, but now instead of being passed by a callback, they are stored in list requires_hidden and exposed in Compilation_context.t for external use cc (@voodoos). This list is then used to insert the -H flag for the correct deps.

  • The second one simply removes the custom hashtable module from lib.ml, in favor of the already defined Table module (@rgrinberg). Also, the ocaml_index.ml file is adapted to use the new requires_hidden list, however in a naive way, and maybe can be used to adapt the machinery better (cc @voodoos )

to_iflags
(include_paths ?project ts_direct { lib_mode = mode; melange_emit = false })
in
Command.Args.S [ direct_includes; hidden_includes ]
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this re-ordering the include flags? e.g. if we have (libraries x y), we'd have closure(x) closure(y), and now we have x y closure(x) closure(y)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If I understand things correctly, I don't think anything is being re-ordered; instead some extra -H flags are being added at the "end" of the command-line when implicit_transitive_deps is false:

  • If implicit_transitive_deps is true, then direct_includes is requires_link (from Compilation_context), and hidden_includes is empty; this is the same as today, and nothing changes.
  • If implicit_transitive_deps is false (and OCaml is recent enough, etc) direct_includes is requires_compile (from Compilation_context), and hidden_includes contains some extra flags relative to today, but the order of direct_includes does not change.

Do you agree with this understanding @MA0010?

Comment on lines 166 to 173
let hidden_includes =
to_hflags
(include_paths ?project ts_hidden { lib_mode = mode; melange_emit = false })
in
let direct_includes =
to_iflags
(include_paths ?project ts_direct { lib_mode = mode; melange_emit = false })
in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Let's share the code for the two definitions.

Comment on lines 95 to 101
let to_hflags dirs =
Command.Args.S
(Path.Set.fold dirs ~init:[] ~f:(fun dir acc ->
Command.Args.Path dir :: A "-H" :: acc)
|> List.rev)
;;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Let's share the code with to_iflags above.

~project
~opaque
~direct_requires
~(hidden_requires : Lib_flags.L.t Resolve.Memo.t)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
~(hidden_requires : Lib_flags.L.t Resolve.Memo.t)
~hidden_requires

Signed-off-by: HasanA <[email protected]>
@@ -85,10 +85,10 @@ let link_deps sctx t mode =
module L = struct
type nonrec t = Lib.t list

let to_iflags dirs =
let to_iflags ?(flag = "-I") dirs =
Copy link
Collaborator

Choose a reason for hiding this comment

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

In our codebase we often try to avoid default flags, as the defaults are often easy to miss and in this case "to_iflags" which generates -H flags is somewhat strange.

As such I suggest making this a labelled to_flag function and

let to_ifags = to_flags "-I"
let to_hflags = to_flags "-H"

This prevents having to change all the code while allowing an easy way to get an -H flag.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hello, thanks for the remark. You are right, it is even safer to do as you suggested. I have pushed the edits.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You might not even need to expose to_hflags in the mli if nobody outside the module is going to use it. That way the compiler can warn about unused functions in case the code stops using to_hflags.

Copy link
Contributor

Choose a reason for hiding this comment

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

You are right for now to_hflags is not used anywhere outside lib_flags.mli, but I exposed it for the sake of symmetry (since if to_iflags is to be used somewhere in some later work, to_hflags would be also needed).

But maybe it is better for now to remove it from the interface.

in
let sandbox = Sandbox_config.no_special_requirements in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this change needed?

let open Resolve.Memo.O in
let+ requires = requires_compile
and+ requires_link = Memo.Lazy.force requires_link in
let requires_table = Table.create (module Lib) 500 in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

500 seems too large; I suggest 5.

Comment on lines 1 to 6
- Add support for the -H flag (introduced in Ocaml compiler 5.2) in dune
(only supported for lang versions 3.17 and higher). This adaptation gives
the correct semantics for `implicit_transitive_deps = false` mode, since then
transitive dependencies are included with -H flag and so are not directly accessible
but visible for the compiler. (#10644, fixes (#9333, #ocsigen/tyxml#274, #2733, #4963),
@MA0100)
Copy link
Collaborator Author

@nojb nojb Jun 24, 2024

Choose a reason for hiding this comment

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

Suggested change
- Add support for the -H flag (introduced in Ocaml compiler 5.2) in dune
(only supported for lang versions 3.17 and higher). This adaptation gives
the correct semantics for `implicit_transitive_deps = false` mode, since then
transitive dependencies are included with -H flag and so are not directly accessible
but visible for the compiler. (#10644, fixes (#9333, #ocsigen/tyxml#274, #2733, #4963),
@MA0100)
- Add support for the -H flag (introduced in OCaml compiler 5.2) in dune
(only supported for lang versions 3.17 and higher). This adaptation gives
the correct semantics for `(implicit_transitive_deps false)`.
(#10644, fixes #9333, ocsigen/tyxml#274, #2733, #4963, @MA0100)

let* req_link = Compilation_context.requires_link cctx in
let+ req_compile = Compilation_context.requires_compile cctx in
List.filter req_link ~f:(fun l -> not (List.exists req_compile ~f:(Lib.equal l)))
Compilation_context.requires_hidden cctx
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This doesn't seem quite right. For new versions of OCaml (>= 5.2) and version of the Dune language >= 3.17, Dune will pass -H to the compiler and these flags will be recorded in the .cmt files and will, in turn, be read by ocaml-index. Accordingly, in these cases we do not need to do anything special here. It is only for backwards compatibility that we can keep the above logic (ie if OCaml is < 5.2 or if the Dune language veresion if < 3.17). Also, the flags should all be passed with -I, not -H in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@voodoos sounds OK?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, that's completely right. If Dune >= 3.17 there is no more additional flags to pass to the indexer. (Note that indexing is not available prior to OCaml 5.2, so there is no need to check that here.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the confirmation! So only the check for OCaml lang version < 3.17 is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks, the current version looks good to me

@nojb
Copy link
Collaborator Author

nojb commented Jun 24, 2024

Friendly ping @anmonteiro: are you planning to review the PR? I feel this is getting ready to be merged, so do not hesitate to leave a note if you are planning to do so (which would be appreciated, of course!).

Signed-off-by: HasanA <[email protected]>
@MA0010
Copy link
Contributor

MA0010 commented Jun 27, 2024

I have completed the suggested edits. However, I request a final look at the tests before merging. For now, I have separated the hidden-deps tests into two tests, each one guarded by the correct OCaml version. However, this causes code duplication, and there may be a way to guard individual components of the same test behind the OCaml version.

Comment on lines 163 to 165
let () =
List.iter ~f:(fun lib -> Table.set requires_table lib ()) requires_compile
in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
let () =
List.iter ~f:(fun lib -> Table.set requires_table lib ()) requires_compile
in
List.iter ~f:(fun lib -> Table.set requires_table lib ()) requires_compile;

(executable
(name run)
(modules run)
(libraries bar))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Missing newline at EOF.

@@ -0,0 +1,2 @@
open Tyxml.Html
let _ = p [ a [ txt "a" ] ]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Missing newline at EOF.

@@ -0,0 +1,3 @@
(executable
(name run)
(libraries tyxml))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Missing newline at EOF.

@@ -0,0 +1 @@
let _ = Bar.y
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Missing newline at EOF.

Comment on lines 23 to 24


Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change

> EOF
$ echo "$(getincludes I)"
.foo.objs/byte.foo.objs/byte.foo.objs/native

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change

$ dune build --root=./tyxml
Entering directory 'tyxml'
Leaving directory 'tyxml'

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change

Comment on lines 23 to 25



Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change

> EOF
$ echo "$(getincludes I)"
.foo.objs/byte.foo.objs/byte.foo.objs/native

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change

@nojb
Copy link
Collaborator Author

nojb commented Jun 27, 2024

@MA0010 I left a few cosmetic tweaks to do before merging, otherwise this is almost ready.

I would put both tests under a common directory hidden-deps.t though.

MA0010 and others added 6 commits June 27, 2024 15:06
@nojb
Copy link
Collaborator Author

nojb commented Jun 27, 2024

I think all issues have been addressed and the PR is ready to go.

@emillon: would you like to do the honours? (as I have been supervising the PR, I prefer not to merge it myself).

@nojb
Copy link
Collaborator Author

nojb commented Jun 27, 2024

I think all issues have been addressed and the PR is ready to go.

@emillon: would you like to do the honours? (as I have been supervising the PR, I prefer not to merge it myself).

Tha PR should be squash-merged by the way.

Signed-off-by: HasanA <[email protected]>
@emillon
Copy link
Collaborator

emillon commented Jun 28, 2024

@nojb yes, I'll give it a last look, squash and merge.

@emillon
Copy link
Collaborator

emillon commented Jun 28, 2024

I can't push to the original branch but that should be fine.

@nojb
Copy link
Collaborator Author

nojb commented Jun 28, 2024

I can't push to the original branch but that should be fine.

I think this was my fault (I opened the PR from a branch in @MA0010's fork which disables this mechanism).

@MA0010 can you fix the remaining issue so that we can merge? Thanks!

@emillon
Copy link
Collaborator

emillon commented Jun 28, 2024

the last issues are just cosmetic - newlines etc and the changelog can be fixed at release time.
Thanks a lot to everyone involved here. This is the consequence of a multi-year effort that started in dune 1.7 (5 years ago), made its way to the compiler, and back here!

@emillon emillon merged commit d35fc98 into ocaml:main Jun 28, 2024
28 checks passed
@kit-ty-kate
Copy link
Member

Thank you so much for this work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants