OCaml Cyclical Build Dependencies: Difference between revisions
Lars.kurth (talk | contribs) (Migrated) |
Lars.kurth (talk | contribs) No edit summary |
||
(One intermediate revision by one other user not shown) | |||
Line 1: | Line 1: | ||
<!-- MoinMoin name: OCaml_Cyclical_Build_Dependencies --> |
|||
<!-- Comment: --> |
|||
<!-- WikiMedia name: OCaml Cyclical Build Dependencies --> |
|||
<!-- Page revision: 00000001 --> |
|||
<!-- Original date: Thu Oct 8 18:30:51 2009 (1255026651000000) --> |
|||
__NOTOC__ |
|||
<!-- ## Please edit system and help pages ONLY in the moinmaster wiki! For more --> |
|||
<!-- ## information, please see [[MoinMaster]]:[[MoinPagesEditorGroup]]. --> |
|||
<!-- ##master-page:[[Category:Template]] --> |
|||
<!-- ##master-date:Unknown-Date --> |
|||
<!-- #format wiki --> |
|||
<!-- #language en --> |
|||
== OCaml Cyclical Build Dependencies == |
|||
This page provides some suggestions for how to resolve cyclic build dependencies. |
This page provides some suggestions for how to resolve cyclic build dependencies. |
||
Line 18: | Line 5: | ||
OMake gives an error like the following: |
OMake gives an error like the following: |
||
<pre |
<pre> |
||
*** omake: deadlock on a.cmx |
*** omake: deadlock on a.cmx |
||
*** omake: is a dependency of b.cmx |
*** omake: is a dependency of b.cmx |
||
*** omake: is a dependency of a.cmx |
*** omake: is a dependency of a.cmx |
||
</pre> |
|||
Line 30: | Line 17: | ||
For example, the following set of modules exhibits this problem: |
For example, the following set of modules exhibits this problem: |
||
<pre |
<pre> |
||
a.ml |
a.ml |
||
let bar () = () |
let bar () = () |
||
let foo () = B.bar () |
let foo () = B.bar () |
||
let gip () = bar () |
let gip () = bar () |
||
</pre> |
|||
<pre |
<pre> |
||
b.ml |
b.ml |
||
let bar () = A.bar () |
let bar () = A.bar () |
||
</pre> |
|||
The edges in the call graph are: |
The edges in the call graph are: |
||
Line 65: | Line 52: | ||
A potential solution is to move a function into a separate module to break the cyclic dependency. The fix to the example above would be: |
A potential solution is to move a function into a separate module to break the cyclic dependency. The fix to the example above would be: |
||
<pre |
<pre> |
||
a_bar.ml |
a_bar.ml |
||
let bar () = ()a.ml |
let bar () = ()a.ml |
||
let foo () = B.bar () |
let foo () = B.bar () |
||
let gip () = A_bar.bar () |
let gip () = A_bar.bar () |
||
</pre> |
|||
<pre |
<pre> |
||
b.ml |
b.ml |
||
let bar () = A_bar.bar () |
let bar () = A_bar.bar () |
||
</pre> |
|||
The edges in the call graph are now |
The edges in the call graph are now |
||
Line 91: | Line 78: | ||
By passing the "callback" function as a parameter, there is no need for the reverse link in the call graph, breaking the cyclic dependency. The fix to the example above would be: |
By passing the "callback" function as a parameter, there is no need for the reverse link in the call graph, breaking the cyclic dependency. The fix to the example above would be: |
||
<pre |
<pre> |
||
a.ml |
a.ml |
||
let bar () = () |
let bar () = () |
||
let foo () = B.bar bar |
let foo () = B.bar bar |
||
let gip () = bar () |
let gip () = bar () |
||
</pre> |
|||
<pre |
<pre> |
||
b.ml |
b.ml |
||
let bar f = f () |
let bar f = f () |
||
</pre> |
|||
The edges in the call graph are now |
The edges in the call graph are now |
||
Line 113: | Line 100: | ||
In some situations, you need to pass a lot of "callback" functions and maybe also type declarations. In this case, it could be useful to use functors. |
In some situations, you need to pass a lot of "callback" functions and maybe also type declarations. In this case, it could be useful to use functors. |
||
<pre |
<pre> |
||
a.ml |
a.ml |
||
let bar () = () |
let bar () = () |
||
Line 122: | Line 109: | ||
let foo () = B.bar () |
let foo () = B.bar () |
||
end |
end |
||
</pre> |
|||
<pre |
<pre> |
||
b.ml |
b.ml |
||
module B = struct let bar () = A.bar () end |
module B = struct let bar () = A.bar () end |
||
module A = A(B) |
module A = A(B) |
||
let bar = B.bar |
let bar = B.bar |
||
</pre> |
|||
The edges in the call graph are now |
The edges in the call graph are now |
||
Line 143: | Line 130: | ||
Warning: this is a very ugly solution, try to not use it, but sometimes there are no other ways, so .... (and it is used inside the source code of xapi). |
Warning: this is a very ugly solution, try to not use it, but sometimes there are no other ways, so .... (and it is used inside the source code of xapi). |
||
<pre |
<pre> |
||
a.ml |
a.ml |
||
let bar () = () |
let bar () = () |
||
Line 149: | Line 136: | ||
let foo () = !B.bar () |
let foo () = !B.bar () |
||
let gip () = bar () |
let gip () = bar () |
||
</pre> |
|||
<pre |
<pre> |
||
b.ml |
b.ml |
||
let bar = ref (fun () -> ()) |
let bar = ref (fun () -> ()) |
||
</pre> |
|||
The edges in the call graph are now |
The edges in the call graph are now |
||
Line 164: | Line 151: | ||
[[Category:OCaml]] |
[[Category:OCaml]] |
||
[[Category: |
[[Category:XAPI Devel]] |
||
[[Category:Developers]] |
Latest revision as of 13:11, 11 July 2013
This page provides some suggestions for how to resolve cyclic build dependencies.
Symptom
OMake gives an error like the following:
*** omake: deadlock on a.cmx *** omake: is a dependency of b.cmx *** omake: is a dependency of a.cmx
Problem
You have a cyclic dependency between your modules, so OMake cannot find a linear order of compilation.
For example, the following set of modules exhibits this problem:
a.ml let bar () = () let foo () = B.bar () let gip () = bar ()
b.ml let bar () = A.bar ()
The edges in the call graph are:
A -> B
B -> A
which is cyclic.
Possible Solutions
Here are some potential ways around the problem.
Avoid the situation by careful design
Design the interfaces between modules such that there is a directed, acyclic flow in the graph of modules where edges are function calls. In the example above, the call back from module B to module A would be disallowed.
For example, in our codebase, we have ocaml/xapi/xapi.ml as the "main" module, calling functions in other modules: those modules should never contain functions which call functions back in ocaml/xapi/xapi.ml.
However, sometimes this approach is not possible if you are adding extra functionality to existing code.
Move a function into a new file
A potential solution is to move a function into a separate module to break the cyclic dependency. The fix to the example above would be:
a_bar.ml let bar () = ()a.ml let foo () = B.bar () let gip () = A_bar.bar ()
b.ml let bar () = A_bar.bar ()
The edges in the call graph are now
A -> B
A -> A_bar
B -> A_bar
which is acyclic.
Pass the function in as a parameter
By passing the "callback" function as a parameter, there is no need for the reverse link in the call graph, breaking the cyclic dependency. The fix to the example above would be:
a.ml let bar () = () let foo () = B.bar bar let gip () = bar ()
b.ml let bar f = f ()
The edges in the call graph are now
A -> B
which is acyclic.
Using functors
In some situations, you need to pass a lot of "callback" functions and maybe also type declarations. In this case, it could be useful to use functors.
a.ml let bar () = () let gip () = bar () module type B_sig = sig val bar : unit -> unit end module A (B : B_sig) = struct let foo () = B.bar () end
b.ml module B = struct let bar () = A.bar () end module A = A(B) let bar = B.bar
The edges in the call graph are now
B -> A
which is acyclic.
However, in this very simple example, it is not really a good idea to use functors
Using global references
Warning: this is a very ugly solution, try to not use it, but sometimes there are no other ways, so .... (and it is used inside the source code of xapi).
a.ml let bar () = () let _ = B.bar := bar let foo () = !B.bar () let gip () = bar ()
b.ml let bar = ref (fun () -> ())
The edges in the call graph are now
A -> B
which is acyclic.