Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
438be82
test: opaque profile coverage for inter-library dep tracking
robinbb Apr 25, 2026
94e1fa1
test: scrub implementation-detail references from opaque test prose
robinbb Apr 25, 2026
d86f3ef
test: tighten opaque-mli-change rebuild assertions to exact counts
robinbb Apr 25, 2026
f45d971
test: clarify [unix] is shipped-with-OCaml, not part of [Stdlib]
robinbb Apr 25, 2026
8645681
test: observational baseline for an unreferenced transitive library dep
robinbb Apr 25, 2026
cd5ae67
test: record consumer rebuild count when shadowed dep lib changes
robinbb Apr 25, 2026
6bb9b8c
test: add single-module consumer unreferenced-dep regression guard
robinbb Apr 23, 2026
3d9edc0
test: rewrite as observational baseline with signature-change edit
robinbb Apr 23, 2026
92197dd
test: add sibling-module consumer unreferenced-dep regression guard
robinbb Apr 23, 2026
afa02eb
test: rewrite as observational baseline with signature-change edit
robinbb Apr 23, 2026
557845f
test: add baseline observational test for per-module rebuild count
robinbb Apr 23, 2026
11c10ab
test: make consumer multi-module to isolate per-module filter
robinbb Apr 23, 2026
f869374
test: narrow subst pattern to avoid matching paths
robinbb Apr 23, 2026
b709bd0
test: lower lang version to 3.0
robinbb Apr 23, 2026
468b217
test: lower lang version to 3.0
robinbb Apr 23, 2026
68272fd
test: lower lang version to 3.0
robinbb Apr 23, 2026
cad1dd3
feat: use ocamldep for per-module inter-library dependency filtering …
robinbb Apr 10, 2026
cb21b93
refactor: remove dead code for per-file unwrapped library deps
robinbb Apr 14, 2026
4db11c9
refactor: extract filtered_lib_deps helper; use in ocamlc_i
robinbb Apr 14, 2026
badcad7
refactor: replace close_over with Lib.closure
robinbb Apr 14, 2026
e7e689f
refactor: simplify lib_index to use entry_modules for all libraries
robinbb Apr 15, 2026
7fedad7
style: address review nitpicks
robinbb Apr 15, 2026
e576fec
fix: use of_string_opt for user-provided -open flag module names
robinbb Apr 15, 2026
bcd9613
fix: include Virtual modules in ocamldep reading for filtering
robinbb Apr 15, 2026
36a6513
fix: read both .ml and .mli ocamldep output for filtering
robinbb Apr 15, 2026
e8b6f83
refactor: deduplicate build_cm and ocamlc_i lib deps logic
robinbb Apr 17, 2026
7a8d972
refactor: remove redundant Module.has guards in ocamldep reading
robinbb Apr 17, 2026
9e712cb
refactor: memoize library closure
rgrinberg Apr 19, 2026
0379826
refactor: share parsed ocamldep output between readers
robinbb Apr 20, 2026
3627bac
fix: remove leftover Command.Args.empty in build_cm
robinbb Apr 20, 2026
82e9b80
fix: include wrapped_compat modules in library entry modules
robinbb Apr 22, 2026
0e3217b
test: give strict-package-deps.t real library references
robinbb Apr 22, 2026
ced04ec
fix: filter single-module consumers with library dependencies (#4572)
robinbb Apr 22, 2026
140d18d
test: update snapshots for single-module stanzas with library deps
robinbb Apr 22, 2026
8cc663b
test: update enabled_if and watching snapshots
robinbb Apr 22, 2026
2e749e6
perf: dedupe and sort Lib_index.filter_libs output
robinbb Apr 22, 2026
b22727a
fix: include hidden_requires in lib_index
robinbb Apr 22, 2026
2e51996
perf: memoise filtered-libs computation per module (#4572)
robinbb Apr 22, 2026
93e22b6
test: revert alias-cycle.t promote
robinbb Apr 22, 2026
f8671e7
Revert "perf: memoise filtered-libs computation per module (#4572)"
robinbb Apr 22, 2026
2f5e748
test: make alias-cycle.t walk-invariant
robinbb Apr 22, 2026
c0e7b3c
Cache parsed ocamldep builders by .d file path
robinbb Apr 21, 2026
da9e967
refactor: drop redundant Lib.closure intersection
robinbb Apr 22, 2026
8a5272b
perf: per-module deps within unwrapped libraries (#4572)
robinbb Apr 23, 2026
373b261
test: promote oxcaml library-field-parameters error chain
robinbb Apr 23, 2026
081cfc9
test: promote sibling-unreferenced-lib to post-feature count
robinbb Apr 23, 2026
a29893a
test: document transitive-glob limitation
robinbb Apr 24, 2026
7d8bad4
docs: clarify rule-dep mechanics in lib_deps_for_module
robinbb Apr 24, 2026
cd06c3d
feat: propagate tight per-module deps across library boundaries
robinbb Apr 24, 2026
4a05216
refactor: tighten helper naming and extract shared patterns
robinbb Apr 24, 2026
bb250fd
fix: read cross-library ocamldep output without basename check
robinbb Apr 24, 2026
3602014
refactor: drop [tight_subset], reuse [filter_libs_with_modules]
robinbb Apr 24, 2026
6e3d057
refactor: memoize has_virtual_impl per compilation context
robinbb Apr 24, 2026
619bc11
docs: explain the wrapped-library limitation and sketch follow-ons
robinbb Apr 24, 2026
ebd4c0d
docs: spell out "BFS" as "breadth-first walk" or "cross-library walk"
robinbb Apr 24, 2026
55b9436
refactor: address review feedback — clarifying comments, tests, small…
robinbb Apr 25, 2026
de6c3fd
test: use [let _ = ...] for consumer references in new cram tests
robinbb Apr 25, 2026
e2e3ddf
perf: memoize cctx.lib_index and has_virtual_impl with Memo.Lazy.t
robinbb Apr 25, 2026
92b1929
perf: skip [.ml]-side ocamldep reads when the [.cmi] doesn't carry them
robinbb Apr 25, 2026
f0a63d6
fix: drop unreached tight-eligible libraries from compile-rule deps
robinbb Apr 25, 2026
77b0f9c
test: promote opaque-mli-change rebuild counts under this PR's filter
robinbb Apr 25, 2026
40c0042
refactor: encode tight-eligibility in the Lib_index entry shape
robinbb Apr 25, 2026
1279dd4
style: expand wildcard match in skip_lib_deps
robinbb Apr 25, 2026
c521d40
fix: synthesise root_module references in the per-module filter
robinbb Apr 25, 2026
2e4da73
test: lock in incremental-rebuild for root_module-aliased deps
robinbb Apr 25, 2026
ed948a0
refactor: drop the basename check from ocamldep output parsing
robinbb Apr 25, 2026
3c4bf1c
refactor: make Lib_index.create's no_ocamldep parameter required
robinbb Apr 25, 2026
b09de85
refactor: drop module_kind_has_readable_ocamldep predicate
robinbb Apr 25, 2026
c73076d
refactor: build lib_index from direct_requires only
robinbb Apr 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/changes/added/14116.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Use `ocamldep` output to filter inter-library dependencies on a
per-module basis. Eliminates unnecessary recompilations when a
dependency library changes but the importing module doesn't
reference it, and, for unwrapped dependency libraries, when the
importing module references a different module of the same
library than the one that changed. (#14116, fixes #4572, @robinbb)
144 changes: 108 additions & 36 deletions src/dune_rules/compilation_context.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,29 @@ open Memo.O
module Includes = struct
type t = Command.Args.without_targets Command.Args.t Lib_mode.Cm_kind.Map.t

let make ~project ~opaque ~direct_requires ~hidden_requires lib_config
let make ~project ~direct_requires ~hidden_requires lib_config
: _ Lib_mode.Cm_kind.Map.t
=
(* TODO: some of the requires can filtered out using [ocamldep] info *)
let open Resolve.Memo.O in
let iflags direct_libs hidden_libs mode =
Lib_flags.L.include_flags ~project ~direct_libs ~hidden_libs mode lib_config
in
let make_includes_args ~mode groups =
let make_includes_args ~mode =
(let+ direct_libs = direct_requires
and+ hidden_libs = hidden_requires in
Command.Args.S
[ iflags direct_libs hidden_libs mode
; Hidden_deps (Lib_file_deps.deps (direct_libs @ hidden_libs) ~groups)
])
iflags direct_libs hidden_libs mode)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion: To facilitate the testing of revdeps, would it be possible for the -I/-H flags to use the filtered libraries? Currently the PR only refines the hidden deps but uses the same compiler flags, so a clean build can accidentally succeed (by accessing a file not listed in the hidden deps) while a rebuild might fail. If the -I/-H flags were restricted, then we would have more chance to catch any error when testing the revdeps build :)

It's not necessary because we could also enable sandboxing when testing the revdeps, but this might report issues unrelated to your PR.

(As a bonus, this would improve caching since introducing a new dependency wouldn't require a full rebuild :) )

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's tempting to do this as part of this same PR. But it's so hard to merge this as-is. I have created a follow-up PR to pursue this. It builds on this PR, obviously. Let's see whether we can develop confidence in this PR, in isolation, after testing. If not, maybe the follow-on PR (and not this one) can give the confidence. For now, I won't do this here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my earlier comment with a more pointed reason for the split:

The two changes have different failure modes. A bug in the dep filter currently produces over- or under-invalidation — recoverable, ultimately self-correcting on a dune clean. A bug in -I/-H narrowing produces compile errors. If they land together and a reverse dependency breaks, we have to bisect across both axes; in series, the same break is unambiguously attributable.

So the staging is: get the per-module filter established as the baseline, exercise the revdeps against that, then narrow the compile-time view on top — at which point the filter is independently confirmed and the only thing under test is the visibility tightening. That's strictly more diagnostic information than landing them simultaneously, and it matters most when something does go wrong on a downstream build, which is the situation we want to be quickest at diagnosing.

Your improvement is genuine and shouldn't be lost; the next PR after this one is the right place for it.

|> Resolve.Memo.args
|> Command.Args.memo
in
{ ocaml =
(let cmi_includes = make_includes_args ~mode:(Ocaml Byte) [ Ocaml Cmi ] in
(let cmi_includes = make_includes_args ~mode:(Ocaml Byte) in
{ cmi = cmi_includes
; cmo = cmi_includes
; cmx =
(let+ direct_libs = direct_requires
and+ hidden_libs = hidden_requires in
Command.Args.S
[ iflags direct_libs hidden_libs (Ocaml Native)
; Hidden_deps
(let libs = direct_libs @ hidden_libs in
if opaque
then
List.map libs ~f:(fun lib ->
( lib
, if Lib.is_local lib
then [ Lib_file_deps.Group.Ocaml Cmi ]
else [ Ocaml Cmi; Ocaml Cmx ] ))
|> Lib_file_deps.deps_with_exts
else
Lib_file_deps.deps
libs
~groups:[ Lib_file_deps.Group.Ocaml Cmi; Ocaml Cmx ])
])
|> Resolve.Memo.args
|> Command.Args.memo
; cmx = make_includes_args ~mode:(Ocaml Native)
})
; melange =
{ cmi = make_includes_args ~mode:Melange [ Melange Cmi ]
; cmj = make_includes_args ~mode:Melange [ Melange Cmi; Melange Cmj ]
}
(let mel_includes = make_includes_args ~mode:Melange in
{ cmi = mel_includes; cmj = mel_includes })
}
;;

Expand Down Expand Up @@ -91,6 +65,8 @@ type t =
; parameters : Module_name.t list Resolve.Memo.t
; instances : Parameterised_instances.t Resolve.Memo.t option
; includes : Includes.t
; lib_index : Lib_file_deps.Lib_index.t Resolve.t Memo.Lazy.t
; has_virtual_impl : bool Resolve.t Memo.Lazy.t
; preprocessing : Pp_spec.t
; opaque : bool
; js_of_ocaml : Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t
Expand Down Expand Up @@ -118,6 +94,8 @@ let requires_hidden t = t.requires_hidden
let requires_link t = Memo.Lazy.force t.requires_link
let parameters t = t.parameters
let includes t = t.includes
let lib_index t = Memo.Lazy.force t.lib_index
let has_virtual_impl t = Memo.Lazy.force t.has_virtual_impl
let preprocessing t = t.preprocessing
let opaque t = t.opaque
let js_of_ocaml t = t.js_of_ocaml
Expand Down Expand Up @@ -147,6 +125,72 @@ let parameters_main_modules parameters =
[ "param", Lib.to_dyn param ])
;;

(* Build a [Lib_index] from [all_libs] for the per-module inter-library
dependency filter.

For each library, entry module names are collected (for wrapped
libraries, the wrapper; for unwrapped, each public module). Hidden
libraries are included so that consumers which transitively
reference a hidden library's entry module still produce a build
dependency on it.

Local libraries whose ocamldep is short-circuited by
[Dep_rules.skip_ocamldep] (unwrapped single-module stanzas without
direct lib deps) are collected into [no_ocamldep] so the cross-
library walk does not try to read their nonexistent [.d] files. *)
(* [libs] should be the consumer's [direct_requires] — the libraries
whose entry modules can legitimately appear in the consumer's
ocamldep output (and, after the cross-library walk extends through
them, in cross-library ocamldep outputs that the BFS surfaces).
Hidden requires are intentionally excluded: the user has not
committed to them as direct dependencies, so the per-module
filter does not track them with per-module precision — they
fall to glob fallback in [lib_deps_for_module]'s post-walk
classification, which is the right behaviour for libs outside
the user's commitment surface. *)
let build_lib_index ~super_context ~libs ~for_ =
let open Resolve.Memo.O in
let+ per_lib =
Resolve.Memo.List.map libs ~f:(fun lib ->
match Lib_info.entry_modules (Lib.info lib) ~for_ with
| External (Ok names) ->
Resolve.Memo.return (List.map names ~f:(fun n -> n, lib, None), None)
| External (Error e) -> Resolve.Memo.of_result (Error e)
| Local ->
Resolve.Memo.lift_memo
(Memo.map
(Dir_contents.modules_of_local_lib
super_context
(Lib.Local.of_lib_exn lib)
~for_)
~f:(fun mods ->
(* [Some m] iff the lib is tight-eligible (local + unwrapped):
only then can downstream consumers issue per-module deps
on its [.cmi] files. [None] therefore covers both wrapped
locals and externals — in either case we don't have a
[Module.t] the per-module path can use. *)
let unwrapped =
match Lib_info.wrapped (Lib.info lib) with
| Some (This w) -> not (Wrapped.to_bool w)
| Some (From _) | None -> false
in
let entries =
List.map (Modules.entry_modules mods) ~f:(fun m ->
Module.name m, lib, if unwrapped then Some m else None)
in
let no_ocamldep_lib =
match Modules.as_singleton mods with
| Some _ when List.is_empty (Lib_info.requires (Lib.info lib) ~for_) ->
Some lib
| _ -> None
in
entries, no_ocamldep_lib)))
in
let entries = List.concat_map per_lib ~f:fst in
let no_ocamldep = List.filter_map per_lib ~f:snd |> Lib.Set.of_list in
Lib_file_deps.Lib_index.create ~no_ocamldep entries
;;

let create
~super_context
~scope
Expand Down Expand Up @@ -206,6 +250,23 @@ let create
let profile = Context.profile context in
eval_opaque ocaml profile opaque
in
let* has_library_deps =
(* Determine whether any library dependencies are declared, so that
single-module stanzas still run ocamldep when its output could
inform the per-module inter-library dependency filter. *)
let open Resolve.Memo.O in
let+ direct = direct_requires
and+ hidden = hidden_requires in
match direct, hidden with
| [], [] -> false
| _ -> true
in
let has_library_deps =
(* Unresolved dependency errors propagate later through the normal
compilation rules; here we conservatively behave as if libraries
are present. *)
Resolve.peek has_library_deps |> Result.value ~default:true
in
let+ dep_graphs =
Dep_rules.rules
~dir:(Obj_dir.dir obj_dir)
Expand All @@ -215,6 +276,7 @@ let create
~impl:implements
~modules
~for_
~has_library_deps
and+ bin_annot =
match bin_annot with
| Some b -> Memo.return b
Expand All @@ -240,8 +302,18 @@ let create
; requires_link
; implements
; parameters
; includes =
Includes.make ~project ~opaque ~direct_requires ~hidden_requires ocaml.lib_config
; includes = Includes.make ~project ~direct_requires ~hidden_requires ocaml.lib_config
; lib_index =
Memo.lazy_ (fun () ->
let open Resolve.Memo.O in
let* libs = direct_requires in
build_lib_index ~super_context ~libs ~for_)
; has_virtual_impl =
Memo.lazy_ (fun () ->
let open Resolve.Memo.O in
let+ direct = direct_requires
and+ hidden = hidden_requires in
List.exists (direct @ hidden) ~f:(fun lib -> Option.is_some (Lib.implements lib)))
; preprocessing
; opaque
; js_of_ocaml
Expand Down Expand Up @@ -333,7 +405,6 @@ let for_module_generated_at_link_time cctx ~requires ~module_ =
let direct_requires = requires in
Includes.make
~project:(Scope.project cctx.scope)
~opaque
~direct_requires
~hidden_requires
cctx.ocaml.lib_config
Expand All @@ -344,6 +415,7 @@ let for_module_generated_at_link_time cctx ~requires ~module_ =
; requires_link = Memo.lazy_ (fun () -> requires)
; requires_compile = requires
; includes
; lib_index = Memo.lazy_ (fun () -> Resolve.Memo.return Lib_file_deps.Lib_index.empty)
; modules
}
;;
Expand Down
7 changes: 7 additions & 0 deletions src/dune_rules/compilation_context.mli
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ val requires_hidden : t -> Lib.t list Resolve.Memo.t
val requires_compile : t -> Lib.t list Resolve.Memo.t
val parameters : t -> Module_name.t list Resolve.Memo.t
val includes : t -> Command.Args.without_targets Command.Args.t Lib_mode.Cm_kind.Map.t
val lib_index : t -> Lib_file_deps.Lib_index.t Resolve.Memo.t

(** [true] iff any library in the compilation context's direct or
hidden requires implements a virtual library. Memoized per
cctx; callers avoid re-scanning the lib list on each module. *)
val has_virtual_impl : t -> bool Resolve.Memo.t

val preprocessing : t -> Pp_spec.t
val opaque : t -> bool
val js_of_ocaml : t -> Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t
Expand Down
2 changes: 2 additions & 0 deletions src/dune_rules/dep_graph.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ type t =
}

let make ~dir ~per_module = { dir; per_module }
let dir t = t.dir
let mem t (m : Module.t) = Module_name.Unique.Map.mem t.per_module (Module.obj_name m)

let deps_of t (m : Module.t) =
match Module_name.Unique.Map.find t.per_module (Module.obj_name m) with
Expand Down
2 changes: 2 additions & 0 deletions src/dune_rules/dep_graph.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ val make
-> per_module:Module.t list Action_builder.t Module_name.Unique.Map.t
-> t

val dir : t -> Path.Build.t
val mem : t -> Module.t -> bool
val deps_of : t -> Module.t -> Module.t list Action_builder.t
val top_closed_implementations : t -> Module.t list -> Module.t list Action_builder.t

Expand Down
61 changes: 53 additions & 8 deletions src/dune_rules/dep_rules.ml
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,30 @@ let deps_of_vlib_module ~obj_dir ~vimpl ~dir ~sctx ~ml_kind ~for_ sourced_module
Ocamldep.read_deps_of ~obj_dir:vlib_obj_dir ~modules ~ml_kind ~for_ m
;;

(** Tests whether a set of modules is a singleton *)
(** Tests whether a set of modules is a singleton. *)
let has_single_file modules = Option.is_some @@ Modules.With_vlib.as_singleton modules

(** Tests whether ocamldep can be short-circuited for [modules]: true
for single-module stanzas that have no library dependencies.
The premise — "no consumer of ocamldep output can benefit" — was
valid before #4572; under that PR, cross-library consumers now
read a target library's ocamldep output as part of the
per-module inter-library dependency filter. Libraries identified
here must also be identified in
[Compilation_context.build_lib_index]'s [no_ocamldep_lib] check
so the cross-library walk knows their [.d] files will be
missing. Keeping the two in sync is fragile.

TODO: unify into a single predicate that both call sites
consult, so future changes to the condition can't drift. The
cleanest shape is probably to retire this short-circuit for
library stanzas entirely — libraries that could be consumed
cross-stanza should always run ocamldep — and keep the
optimisation only for executable/test stanzas. *)
let skip_ocamldep ~has_library_deps modules =
has_single_file modules && not has_library_deps
;;

let rec deps_of
~obj_dir
~modules
Expand All @@ -143,6 +164,7 @@ let rec deps_of
~sctx
~ml_kind
~for_
~has_library_deps
(m : Modules.Sourced_module.t)
=
let is_alias_or_root =
Expand All @@ -153,7 +175,7 @@ let rec deps_of
| Root | Alias _ -> true
| _ -> false)
in
if is_alias_or_root || has_single_file modules
if is_alias_or_root || skip_ocamldep ~has_library_deps modules
then Memo.return (Action_builder.return [])
else (
let skip_if_source_absent f sourced_module =
Expand All @@ -173,14 +195,18 @@ let rec deps_of
(deps_of_module ~modules ~sandbox ~sctx ~dir ~obj_dir ~ml_kind ~for_)
m
| Impl_of_virtual_module impl_or_vlib ->
deps_of ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx ~ml_kind ~for_
deps_of ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx ~ml_kind ~for_ ~has_library_deps
@@
let m = Ml_kind.Dict.get impl_or_vlib ml_kind in
(match ml_kind with
| Intf -> Imported_from_vlib m
| Impl -> Normal m))
;;

(* [read_deps_of_module] reports intra-stanza module dependencies. For
single-module stanzas that dependency graph is trivially empty
regardless of whether the stanza declares library dependencies, so we
keep the unconditional short-circuit here. *)
let read_deps_of_module ~modules ~obj_dir dep ~for_ =
let (Obj_dir.Module.Dep.Immediate (unit, _) | Transitive (unit, _)) = dep in
match Module.kind unit with
Expand Down Expand Up @@ -219,18 +245,37 @@ let dict_of_func_concurrently f =

let for_module ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx ~for_ module_ =
dict_of_func_concurrently
(deps_of ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx ~for_ (Normal module_))
(deps_of
~obj_dir
~modules
~sandbox
~impl
~dir
~sctx
~for_
~has_library_deps:true
(Normal module_))
;;

let rules ~obj_dir ~modules ~sandbox ~impl ~sctx ~dir ~for_ =
let rules ~obj_dir ~modules ~sandbox ~impl ~sctx ~dir ~for_ ~has_library_deps =
match Modules.With_vlib.as_singleton modules with
| Some m -> Memo.return (Dep_graph.Ml_kind.dummy m)
| None ->
| Some m when not has_library_deps -> Memo.return (Dep_graph.Ml_kind.dummy m)
| Some _ | None ->
dict_of_func_concurrently (fun ~ml_kind ->
let+ per_module =
Modules.With_vlib.obj_map modules
|> Parallel_map.parallel_map ~f:(fun _obj_name m ->
deps_of ~obj_dir ~modules ~sandbox ~impl ~sctx ~dir ~ml_kind ~for_ m)
deps_of
~obj_dir
~modules
~sandbox
~impl
~sctx
~dir
~ml_kind
~for_
~has_library_deps
m)
in
Dep_graph.make ~dir ~per_module)
|> Memo.map ~f:(Dep_graph.Ml_kind.for_module_compilation ~modules)
Expand Down
7 changes: 7 additions & 0 deletions src/dune_rules/dep_rules.mli
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ val for_module
-> Module.t
-> Module.t list Action_builder.t Ml_kind.Dict.t Memo.t

(** [has_library_deps] indicates whether the enclosing stanza declares any
library dependencies. When false, single-module stanzas short-circuit
ocamldep entirely (no build rule, no [.d]/[.all-deps] file). When
true, single-module stanzas run ocamldep so that the per-module
inter-library dependency filter can determine which libraries the
single module references. *)
val rules
: obj_dir:Path.Build.t Obj_dir.t
-> modules:Modules.With_vlib.t
Expand All @@ -21,6 +27,7 @@ val rules
-> sctx:Super_context.t
-> dir:Path.Build.t
-> for_:Compilation_mode.t
-> has_library_deps:bool
-> Dep_graph.Ml_kind.t Memo.t

val read_immediate_deps_of
Expand Down
Loading
Loading