OCaml Cyclical Build Dependencies: Difference between revisions

From Xen
Jump to navigationJump to search
(Migrated)
 
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><nowiki>
<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
</nowiki></pre>
</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><nowiki>
<pre>
a.ml
a.ml
let bar () = ()
let bar () = ()
let foo () = B.bar ()
let foo () = B.bar ()
let gip () = bar ()
let gip () = bar ()
</nowiki></pre>
</pre>




<pre><nowiki>
<pre>
b.ml
b.ml
let bar () = A.bar ()
let bar () = A.bar ()
</nowiki></pre>
</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><nowiki>
<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 ()
</nowiki></pre>
</pre>




<pre><nowiki>
<pre>
b.ml
b.ml
let bar () = A_bar.bar ()
let bar () = A_bar.bar ()
</nowiki></pre>
</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><nowiki>
<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 ()
</nowiki></pre>
</pre>




<pre><nowiki>
<pre>
b.ml
b.ml
let bar f = f ()
let bar f = f ()
</nowiki></pre>
</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><nowiki>
<pre>
a.ml
a.ml
let bar () = ()
let bar () = ()
Line 122: Line 109:
let foo () = B.bar ()
let foo () = B.bar ()
end
end
</nowiki></pre>
</pre>




<pre><nowiki>
<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
</nowiki></pre>
</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><nowiki>
<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 ()
</nowiki></pre>
</pre>




<pre><nowiki>
<pre>
b.ml
b.ml
let bar = ref (fun () -> ())
let bar = ref (fun () -> ())
</nowiki></pre>
</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:XCP]]
[[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.