Skip to content

Commit 2b4ab03

Browse files
committed
feat: use ocamldep for per-module inter-library dependency filtering (#4572)
Use the already-computed ocamldep output to filter inter-library dependencies on a per-module basis. Each module now only depends on .cmi/.cmx files from libraries it actually imports, rather than glob deps on all files from all dependent libraries. This eliminates unnecessary recompilation when a module in a dependency changes but the current module doesn't reference that library. The implementation: - Add read_immediate_deps_raw_of to ocamldep, returning raw module names without filtering against the stanza's module set - Move Hidden_deps from Includes (library-wide) to per-module in build_cm - Add Lib_index mapping module names to libraries, computed from requires_compile and requires_hidden - In build_cm, use ocamldep output + Lib_index to determine which libraries each module actually needs; fall back to all-library glob deps when filtering is not possible (Melange, virtual library implementations, singleton stanzas, alias/root modules) - For local unwrapped libraries, use per-file deps on specific .cmi/.cmx files rather than directory-wide globs Signed-off-by: Robin Bate Boerop <me@robinbb.com>
1 parent b68cf21 commit 2b4ab03

25 files changed

+356
-78
lines changed

src/dune_rules/compilation_context.ml

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,29 @@ open Memo.O
44
module Includes = struct
55
type t = Command.Args.without_targets Command.Args.t Lib_mode.Cm_kind.Map.t
66

7-
let make ~project ~opaque ~direct_requires ~hidden_requires lib_config
7+
let make ~project ~direct_requires ~hidden_requires lib_config
88
: _ Lib_mode.Cm_kind.Map.t
99
=
10-
(* TODO: some of the requires can filtered out using [ocamldep] info *)
1110
let open Resolve.Memo.O in
1211
let iflags direct_libs hidden_libs mode =
1312
Lib_flags.L.include_flags ~project ~direct_libs ~hidden_libs mode lib_config
1413
in
15-
let make_includes_args ~mode groups =
14+
let make_includes_args ~mode =
1615
(let+ direct_libs = direct_requires
1716
and+ hidden_libs = hidden_requires in
18-
Command.Args.S
19-
[ iflags direct_libs hidden_libs mode
20-
; Hidden_deps (Lib_file_deps.deps (direct_libs @ hidden_libs) ~groups)
21-
])
17+
iflags direct_libs hidden_libs mode)
2218
|> Resolve.Memo.args
2319
|> Command.Args.memo
2420
in
2521
{ ocaml =
26-
(let cmi_includes = make_includes_args ~mode:(Ocaml Byte) [ Ocaml Cmi ] in
22+
(let cmi_includes = make_includes_args ~mode:(Ocaml Byte) in
2723
{ cmi = cmi_includes
2824
; cmo = cmi_includes
29-
; cmx =
30-
(let+ direct_libs = direct_requires
31-
and+ hidden_libs = hidden_requires in
32-
Command.Args.S
33-
[ iflags direct_libs hidden_libs (Ocaml Native)
34-
; Hidden_deps
35-
(let libs = direct_libs @ hidden_libs in
36-
if opaque
37-
then
38-
List.map libs ~f:(fun lib ->
39-
( lib
40-
, if Lib.is_local lib
41-
then [ Lib_file_deps.Group.Ocaml Cmi ]
42-
else [ Ocaml Cmi; Ocaml Cmx ] ))
43-
|> Lib_file_deps.deps_with_exts
44-
else
45-
Lib_file_deps.deps
46-
libs
47-
~groups:[ Lib_file_deps.Group.Ocaml Cmi; Ocaml Cmx ])
48-
])
49-
|> Resolve.Memo.args
50-
|> Command.Args.memo
25+
; cmx = make_includes_args ~mode:(Ocaml Native)
5126
})
5227
; melange =
53-
{ cmi = make_includes_args ~mode:Melange [ Melange Cmi ]
54-
; cmj = make_includes_args ~mode:Melange [ Melange Cmi; Melange Cmj ]
55-
}
28+
(let mel_includes = make_includes_args ~mode:Melange in
29+
{ cmi = mel_includes; cmj = mel_includes })
5630
}
5731
;;
5832

@@ -91,6 +65,7 @@ type t =
9165
; parameters : Module_name.t list Resolve.Memo.t
9266
; instances : Parameterised_instances.t Resolve.Memo.t option
9367
; includes : Includes.t
68+
; lib_index : Lib_file_deps.Lib_index.t Resolve.Memo.t
9469
; preprocessing : Pp_spec.t
9570
; opaque : bool
9671
; js_of_ocaml : Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t
@@ -118,6 +93,7 @@ let requires_hidden t = t.requires_hidden
11893
let requires_link t = Memo.Lazy.force t.requires_link
11994
let parameters t = t.parameters
12095
let includes t = t.includes
96+
let lib_index t = t.lib_index
12197
let preprocessing t = t.preprocessing
12298
let opaque t = t.opaque
12399
let js_of_ocaml t = t.js_of_ocaml
@@ -240,8 +216,37 @@ let create
240216
; requires_link
241217
; implements
242218
; parameters
243-
; includes =
244-
Includes.make ~project ~opaque ~direct_requires ~hidden_requires ocaml.lib_config
219+
; includes = Includes.make ~project ~direct_requires ~hidden_requires ocaml.lib_config
220+
; lib_index =
221+
(let open Resolve.Memo.O in
222+
let* all_libs =
223+
let+ direct = direct_requires
224+
and+ hidden = hidden_requires in
225+
direct @ hidden
226+
in
227+
let+ entries =
228+
Resolve.Memo.List.concat_map all_libs ~f:(fun lib ->
229+
let* main = Lib.main_module_name lib in
230+
match main with
231+
| Some name ->
232+
Resolve.Memo.return [ name, ((lib : Lib.t), (None : Module.t option)) ]
233+
| None ->
234+
(match Lib_info.entry_modules (Lib.info lib) ~for_ with
235+
| External (Ok names) ->
236+
Resolve.Memo.return (List.map names ~f:(fun n -> n, (lib, None)))
237+
| External (Error e) -> Resolve.Memo.of_result (Error e)
238+
| Local ->
239+
Resolve.Memo.lift_memo
240+
(Memo.map
241+
(Dir_contents.modules_of_local_lib
242+
super_context
243+
(Lib.Local.of_lib_exn lib)
244+
~for_)
245+
~f:(fun mods ->
246+
List.map (Modules.entry_modules mods) ~f:(fun m ->
247+
Module.name m, (lib, Some m))))))
248+
in
249+
Lib_file_deps.Lib_index.create entries)
245250
; preprocessing
246251
; opaque
247252
; js_of_ocaml
@@ -333,7 +338,6 @@ let for_module_generated_at_link_time cctx ~requires ~module_ =
333338
let direct_requires = requires in
334339
Includes.make
335340
~project:(Scope.project cctx.scope)
336-
~opaque
337341
~direct_requires
338342
~hidden_requires
339343
cctx.ocaml.lib_config
@@ -344,6 +348,7 @@ let for_module_generated_at_link_time cctx ~requires ~module_ =
344348
; requires_link = Memo.lazy_ (fun () -> requires)
345349
; requires_compile = requires
346350
; includes
351+
; lib_index = Resolve.Memo.return Lib_file_deps.Lib_index.empty
347352
; modules
348353
}
349354
;;

src/dune_rules/compilation_context.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ val requires_hidden : t -> Lib.t list Resolve.Memo.t
6262
val requires_compile : t -> Lib.t list Resolve.Memo.t
6363
val parameters : t -> Module_name.t list Resolve.Memo.t
6464
val includes : t -> Command.Args.without_targets Command.Args.t Lib_mode.Cm_kind.Map.t
65+
val lib_index : t -> Lib_file_deps.Lib_index.t Resolve.Memo.t
6566
val preprocessing : t -> Pp_spec.t
6667
val opaque : t -> bool
6768
val js_of_ocaml : t -> Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t

src/dune_rules/dep_graph.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type t =
77
}
88

99
let make ~dir ~per_module = { dir; per_module }
10+
let dir t = t.dir
1011

1112
let deps_of t (m : Module.t) =
1213
match Module_name.Unique.Map.find t.per_module (Module.obj_name m) with

src/dune_rules/dep_graph.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ val make
99
-> per_module:Module.t list Action_builder.t Module_name.Unique.Map.t
1010
-> t
1111

12+
val dir : t -> Path.Build.t
1213
val deps_of : t -> Module.t -> Module.t list Action_builder.t
1314
val top_closed_implementations : t -> Module.t list -> Module.t list Action_builder.t
1415

src/dune_rules/lib_file_deps.ml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,66 @@ let deps_of_lib (lib : Lib.t) ~groups =
4848
|> Dep.Set.of_list
4949
;;
5050

51-
let deps_with_exts = Dep.Set.union_map ~f:(fun (lib, groups) -> deps_of_lib lib ~groups)
5251
let deps libs ~groups = Dep.Set.union_map libs ~f:(deps_of_lib ~groups)
5352

53+
let deps_of_module (lib : Lib.t) (m : Module.t) ~cm_kinds =
54+
let obj_dir = Lib.info lib |> Lib_info.obj_dir in
55+
List.filter_map cm_kinds ~f:(fun kind ->
56+
Obj_dir.Module.cm_public_file obj_dir m ~kind |> Option.map ~f:(fun p -> Dep.file p))
57+
|> Dep.Set.of_list
58+
;;
59+
60+
let deps_of_entries ~opaque ~(cm_kind : Lib_mode.Cm_kind.t) entries =
61+
let groups_for lib =
62+
match cm_kind with
63+
| Ocaml Cmi | Ocaml Cmo -> [ Group.Ocaml Cmi ]
64+
| Ocaml Cmx ->
65+
if opaque && Lib.is_local lib
66+
then [ Group.Ocaml Cmi ]
67+
else [ Group.Ocaml Cmi; Group.Ocaml Cmx ]
68+
| Melange Cmi -> [ Group.Melange Cmi ]
69+
| Melange Cmj -> [ Group.Melange Cmi; Group.Melange Cmj ]
70+
in
71+
let cm_kinds_for lib =
72+
match cm_kind with
73+
| Ocaml Cmi | Ocaml Cmo -> [ Lib_mode.Cm_kind.Ocaml Cmi ]
74+
| Ocaml Cmx ->
75+
if opaque && Lib.is_local lib
76+
then [ Lib_mode.Cm_kind.Ocaml Cmi ]
77+
else [ Lib_mode.Cm_kind.Ocaml Cmi; Ocaml Cmx ]
78+
| Melange Cmi -> [ Lib_mode.Cm_kind.Melange Cmi ]
79+
| Melange Cmj -> [ Lib_mode.Cm_kind.Melange Cmi; Melange Cmj ]
80+
in
81+
Dep.Set.union_map entries ~f:(fun (lib, module_opt) ->
82+
match module_opt with
83+
| None -> deps_of_lib lib ~groups:(groups_for lib)
84+
| Some m -> deps_of_module lib m ~cm_kinds:(cm_kinds_for lib))
85+
;;
86+
87+
module Lib_index = struct
88+
type entry = Lib.t * Module.t option
89+
type t = { by_module_name : entry list Module_name.Map.t }
90+
91+
let empty = { by_module_name = Module_name.Map.empty }
92+
93+
let create entries =
94+
let by_module_name =
95+
List.fold_left entries ~init:Module_name.Map.empty ~f:(fun map (name, entry) ->
96+
Module_name.Map.update map name ~f:(function
97+
| None -> Some [ entry ]
98+
| Some entries -> Some (entry :: entries)))
99+
in
100+
{ by_module_name }
101+
;;
102+
103+
let filter_libs t ~referenced_modules =
104+
Module_name.Set.fold referenced_modules ~init:[] ~f:(fun name acc ->
105+
match Module_name.Map.find t.by_module_name name with
106+
| None -> acc
107+
| Some entries -> List.rev_append entries acc)
108+
;;
109+
end
110+
54111
type path_specification =
55112
| Allow_all
56113
| Disallow_external of Lib_name.t

src/dune_rules/lib_file_deps.mli

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,29 @@ end
1515
with extension [files] of libraries [libs]. *)
1616
val deps : Lib.t list -> groups:Group.t list -> Dep.Set.t
1717

18-
val deps_with_exts : (Lib.t * Group.t list) list -> Dep.Set.t
18+
(** [deps_of_entries ~opaque ~cm_kind entries] computes the file dependencies
19+
for the given library entries. When the module in an entry is [None], glob
20+
deps are used for the library. When [Some m], per-file deps on specific
21+
cm files are used. *)
22+
val deps_of_entries
23+
: opaque:bool
24+
-> cm_kind:Lib_mode.Cm_kind.t
25+
-> (Lib.t * Module.t option) list
26+
-> Dep.Set.t
27+
28+
module Lib_index : sig
29+
type entry = Lib.t * Module.t option
30+
type t
31+
32+
val empty : t
33+
34+
(** Create an index from a list of (module_name, entry) pairs. *)
35+
val create : (Module_name.t * entry) list -> t
36+
37+
(** Return the library entries whose module names appear in
38+
[referenced_modules]. *)
39+
val filter_libs : t -> referenced_modules:Module_name.Set.t -> entry list
40+
end
1941

2042
type path_specification =
2143
| Allow_all

0 commit comments

Comments
 (0)