ppx_module is an OCaml PPX and small runtime library for recording module instantiation graphs.
It is meant for codebases that use module functors heavily and need a way to inspect which instantiated module depends on which functor argument or captured outer context. The generated graph can be printed as Graphviz DOT.
Annotate a module binding with [@@ppx_module]:
module Domain = struct
let value = 1
end
[@@ppx_module]The PPX appends two values to the module body:
val ppx_module_name : string Lazy.t
val ppx_module_node : Ppx_module.node Lazy.tAt runtime, forcing ppx_module_node registers a node in the module graph. Ppx_module.pp can then print the graph rooted at that node.
The attribute can be bare:
[@@ppx_module]or can take a record payload:
[@@ppx_module { name = "Memory.Domain"; cluster = "memory"; parents = true }]Supported fields:
name: display name for the graph node. If omitted, the capitalized source filename is used.cluster: optional Graphviz cluster name. Some names have predefined colors: memory, scalar, address, offset, value, wrapper, and checks.parents: whether functor parameters should be recorded as parent dependencies. Defaults to true.For an annotated functor, named functor parameters are recorded as Parent edges:
module Make (Input : Ppx_module.LOGGED) : sig
include Ppx_module.LOGGED
end = struct
end
[@@ppx_module { name = "Make" }]The generated node for Make(Input) will point to Input.ppx_module_node.
Nested annotated modules inside an annotated functor also record outer functor parameters as Context edges:
module Outer (Ctx : Ppx_module.LOGGED) = struct
module Inner = struct
end
[@@ppx_module { name = "Inner" }]
end
[@@ppx_module { name = "Outer" }]Here, Inner records that it captured Ctx from its enclosing context.
Force a generated node, then pass it to Ppx_module.pp:
let () =
let root = Lazy.force My_module.ppx_module_node in
Format.printf "%a@." Ppx_module.pp rootThe output is Graphviz DOT:
digraph module_stack {
...
}To render it:
dune exec ./my_program.exe > graph.dot
dot -Tsvg graph.dot > graph.svgUse reachable-only mode when you do not want unrelated registered nodes:
Format.printf "%a@." (Ppx_module.pp ~mode:Ppx_module.Reachable) rootDisable source line numbers in node labels when you only want filenames:
Format.printf "%a@." (Ppx_module.pp ~line_numbers:false) rootIf an annotated module has an explicit signature and callers need access to the generated values, include Ppx_module.LOGGED:
module M : sig
include Ppx_module.LOGGED
val value : int
end = struct
let value = 1
end
[@@ppx_module { name = "M" }]Depend on the runtime library and enable the PPX:
(executable
(name main)
(libraries ppx_module)
(preprocess
(pps ppx_module.ppx)))For libraries:
(library
(name my_lib)
(libraries ppx_module)
(preprocess
(pps ppx_module.ppx)))Build and run the tests with:
dune runtestThe tests cover recursive modules, default and explicit names, clusters, functor parameter edges, disabled parent edges, context edges, and DOT output fragments.