ocaml / dune

A composable build system for OCaml.
https://dune.build/
MIT License
1.6k stars 397 forks source link

Dependency analysis: Unnecessary/undesired compilation unit included in executable #753

Open mjambon opened 6 years ago

mjambon commented 6 years ago

There seems to be the following problem:

open Hellolib;;
Hello.hello ()  (* = Hellolib.Hello.hello () *)

results in any local hello.ml to be compiled and linked against our executable, even though we don't use this Hello in this particular program.

Suggested workaround: Rename the local hello.ml, e.g. hello_main.ml.

See https://github.com/mjambon/dune-dependencies for exact reproduction details.

ghost commented 6 years ago

Yh, unfortunately that's a limitation of using ocamldep. I suppose we could try codept

gares commented 5 years ago

May I ask if it is really a limitation of ocamldep or it is the way/time dune invokes it that makes the dependencies analysis incorrect? Because I've never seen this problem before using make and ocamldep by hand. Was I just lucky?

rgrinberg commented 5 years ago

There's definitely a limitation of ocamldep in play here. For Martin's module above, ocamldep -modules infers:

foobar.ml: Hello Hellolib

If there's a local hello.ml, then I think that dune will rightly infer that there's a dependency on this compilation unit.

On the other hand, in dune the potential for such conflicts is much higher. If you're using (wrapped true) (which is recommended), you will have many more unprefixed names in scope.

Now unfortunately, there's no workaround for this currently, but the team has discussed the issue. The most promising proposal is to have an "explicit imports" mode where you would have to use annotations to specify which compilation units you'd like to depend on. There's nothing concrete yet, but I intend to write a proposal shortly.

gares commented 5 years ago

Letting me write down the dependencies by hand would work for me

ghost commented 5 years ago

That's exactly the idea of explicit imports.

ghost commented 5 years ago

BTW, here is a temporary workaround:

module Hello = struct end
open Hellolib
Hello.hello ()
gares commented 5 years ago

I've checked the doc of ocamldep and indeed it says that -module does not resolve dependencies to files. To me this means that the output of -module is nonsensical, it does not respect the semantics of the language wrt binders.

I understand that dune does quite a few renaming behind the scenes, so "resolving to files" may be quite hard in that case but should be still possible (after the various preprocessing business, you could always to back to the orignal names for the sake of computing dependencies). Or one could just improve ocamldep and let it accept a "binding map" (eg module X is in file x.pp.ml, rather than x.ml). Am I missing something? (I'm quite a newbie to dune).

As I said before, for lpcic/elpi#24 that is just a few files I'm OK with writing the dependencies by hand. But I'm not so sure this solution can scale if you have many files.

EDIT: coqdep -> ocamldep

ghost commented 5 years ago

I don't believe this is related to the use -modules, I get the same behaviour without it:

$ cat foobar.ml 
open Hellolib;;
Hello.hello ()
$ touch hello.ml
$ ocamldep foobar.ml 
foobar.cmo : hello.cmo
foobar.cmx : hello.cmx
Octachron commented 5 years ago

This is not related to -modules: the main differences between -modules and the standard mode of ocamldep in this respect is that the standard mode has a postprocessing filter that only keep dependencies on files that appear in the included directories.

rgrinberg commented 5 years ago

As I said before, for LPCIC/elpi#24 that is just a few files I'm OK with writing the dependencies by hand. But I'm not so sure this solution can scale if you have many files.

Indeed it's quite an annoying issue, but it's fairly easy to avoid in practice. Use the hack Jeremie suggested, or simply don't define local modules that are named after libraries. Note that if you're writing a library, this should not force you to change your interface. As you can always create a proper name in the lib interface file.

E.g.

(library
 (name bar)
 (libraries foo)) ;; now we can't define foo.ml

But if we have:

$ ls
  bar.ml
  foo0.ml
$ cat bar.ml
module Foo = Foo0

All is well again. Not incredibly satisfying, but it shouldn't block any project from being ported.

gares commented 5 years ago

Oh, you are right then, ocamldep is just broken.

Wrt hacking the sources of a project in order to port it to a build system, I disagree, it is unacceptable to me. Hence I'm happy to wait for a proper way of declaring dependencies.

ghost commented 5 years ago

@gares, what problem are you seeing? Do you get extra modules linked in your executables, or does the compilation just fails because of circular dependencies?

gares commented 5 years ago

I was getting circular dependencies. Then with some fiddling (that I can't recall exactly, I was trying to understand what was provoking the cycle) I was getting a wrong order of modules being linked in the cmxa that in turn makes all the code linking the cmxa complain that some modules are not defined.