Fix detection of dynamic cycles (#988)
Signed-off-by: Jeremie Dimino <jeremie@dimino.org>
This commit is contained in:
parent
2ec8236d4e
commit
d7222ae1a6
|
@ -146,6 +146,9 @@ next
|
||||||
- New syntax for naming dependencies: `(deps (:x a b) (:y (glob_files *.c*)))`.
|
- New syntax for naming dependencies: `(deps (:x a b) (:y (glob_files *.c*)))`.
|
||||||
This replaces the use for `${<}` in dune files. (#950, @diml, @rgrinberg)
|
This replaces the use for `${<}` in dune files. (#950, @diml, @rgrinberg)
|
||||||
|
|
||||||
|
- Fix detection of dynamic cycles, which in particular may appear when
|
||||||
|
using `(package ..)` dependencies (#988, @diml)
|
||||||
|
|
||||||
1.0+beta20 (10/04/2018)
|
1.0+beta20 (10/04/2018)
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
|
@ -35,62 +35,6 @@ end
|
||||||
let files_in_source_tree_to_delete () =
|
let files_in_source_tree_to_delete () =
|
||||||
Promoted_to_delete.load ()
|
Promoted_to_delete.load ()
|
||||||
|
|
||||||
module Dependency_path = struct
|
|
||||||
type rule_info =
|
|
||||||
{ (* Reason why this rule was visited *)
|
|
||||||
requested_file : Path.t
|
|
||||||
; (* All targets of the rule *)
|
|
||||||
targets : Path.Set.t
|
|
||||||
}
|
|
||||||
|
|
||||||
type t =
|
|
||||||
{ dependency_path : rule_info list
|
|
||||||
; (* Union of all [targets] fields in [dependency_path]. Depending on any of these
|
|
||||||
means that there is a cycle. *)
|
|
||||||
targets_seen : Path.Set.t
|
|
||||||
}
|
|
||||||
|
|
||||||
let var = Fiber.Var.create ()
|
|
||||||
|
|
||||||
let empty =
|
|
||||||
{ dependency_path = []
|
|
||||||
; targets_seen = Path.Set.empty
|
|
||||||
}
|
|
||||||
|
|
||||||
let dependency_cycle last dep_path =
|
|
||||||
let rec build_loop acc dep_path =
|
|
||||||
match dep_path with
|
|
||||||
| [] -> assert false
|
|
||||||
| { requested_file; targets } :: dep_path ->
|
|
||||||
if Path.Set.mem targets last then
|
|
||||||
last :: acc
|
|
||||||
else
|
|
||||||
build_loop (requested_file :: acc) dep_path
|
|
||||||
in
|
|
||||||
let loop = build_loop [last] dep_path in
|
|
||||||
die "Dependency cycle between the following files:\n %s"
|
|
||||||
(String.concat ~sep:"\n--> "
|
|
||||||
(List.map loop ~f:Path.to_string))
|
|
||||||
|
|
||||||
let push requested_file ~targets ~f =
|
|
||||||
Fiber.Var.get var >>= fun x ->
|
|
||||||
let t = Option.value x ~default:empty in
|
|
||||||
if Path.Set.mem t.targets_seen requested_file then
|
|
||||||
dependency_cycle requested_file t.dependency_path;
|
|
||||||
let dependency_path =
|
|
||||||
{ requested_file; targets } :: t.dependency_path
|
|
||||||
in
|
|
||||||
let t =
|
|
||||||
{ dependency_path
|
|
||||||
; targets_seen = Path.Set.union targets t.targets_seen
|
|
||||||
}
|
|
||||||
in
|
|
||||||
let on_error exn =
|
|
||||||
Dep_path.reraise exn (Path requested_file)
|
|
||||||
in
|
|
||||||
Fiber.Var.set var t (Fiber.with_error_handler f ~on_error)
|
|
||||||
end
|
|
||||||
|
|
||||||
module Exec_status = struct
|
module Exec_status = struct
|
||||||
type rule_evaluation = (Action.t * Path.Set.t) Fiber.Future.t
|
type rule_evaluation = (Action.t * Path.Set.t) Fiber.Future.t
|
||||||
type rule_execution = unit Fiber.Future.t
|
type rule_execution = unit Fiber.Future.t
|
||||||
|
@ -172,6 +116,11 @@ module Internal_rule = struct
|
||||||
; loc : Loc.t option
|
; loc : Loc.t option
|
||||||
; dir : Path.t
|
; dir : Path.t
|
||||||
; mutable exec : Exec_status.t
|
; mutable exec : Exec_status.t
|
||||||
|
; (* Reverse dependencies discovered so far, labelled by the
|
||||||
|
requested target *)
|
||||||
|
mutable rev_deps : (Path.t * t) list
|
||||||
|
; (* Transitive reverse dependencies discovered so far. *)
|
||||||
|
mutable transitive_rev_deps : Id.Set.t
|
||||||
}
|
}
|
||||||
|
|
||||||
let compare a b = Id.compare a.id b.id
|
let compare a b = Id.compare a.id b.id
|
||||||
|
@ -183,6 +132,60 @@ module Internal_rule = struct
|
||||||
[if_file_exists] are resolved inside the [Build.t] value. *)
|
[if_file_exists] are resolved inside the [Build.t] value. *)
|
||||||
ignore (Lazy.force t.static_deps : Build_interpret.Static_deps.t);
|
ignore (Lazy.force t.static_deps : Build_interpret.Static_deps.t);
|
||||||
Build_interpret.lib_deps t.build
|
Build_interpret.lib_deps t.build
|
||||||
|
|
||||||
|
(* Represent the build goal given by the user. This rule is never
|
||||||
|
actually executed and is only used starting point of all
|
||||||
|
dependency paths. *)
|
||||||
|
let root =
|
||||||
|
{ id = Id.gen ()
|
||||||
|
; static_deps = lazy { rule_deps = Path.Set.empty
|
||||||
|
; action_deps = Path.Set.empty
|
||||||
|
}
|
||||||
|
; targets = Path.Set.empty
|
||||||
|
; context = None
|
||||||
|
; build = Build.return (Action.Progn [])
|
||||||
|
; mode = Standard
|
||||||
|
; loc = None
|
||||||
|
; dir = Path.root
|
||||||
|
; exec = Not_started { eval_rule = (fun _ -> assert false)
|
||||||
|
; exec_rule = (fun _ -> assert false)
|
||||||
|
}
|
||||||
|
; rev_deps = []
|
||||||
|
; transitive_rev_deps = Id.Set.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
let dependency_cycle ~last ~last_rev_dep ~last_requested_file =
|
||||||
|
let rec build_loop acc t =
|
||||||
|
if t.id = last.id then
|
||||||
|
last_requested_file :: acc
|
||||||
|
else
|
||||||
|
let requested_file, rev_dep =
|
||||||
|
Option.value_exn
|
||||||
|
(List.find t.rev_deps ~f:(fun (_, t) ->
|
||||||
|
Id.Set.mem t.transitive_rev_deps last.id))
|
||||||
|
in
|
||||||
|
build_loop (requested_file :: acc) rev_dep
|
||||||
|
in
|
||||||
|
let loop = build_loop [last_requested_file] last_rev_dep in
|
||||||
|
die "Dependency cycle between the following files:\n %s"
|
||||||
|
(String.concat ~sep:"\n--> "
|
||||||
|
(List.map loop ~f:Path.to_string))
|
||||||
|
|
||||||
|
let dep_path_var = Fiber.Var.create ()
|
||||||
|
|
||||||
|
let push_rev_dep t requested_file ~f =
|
||||||
|
Fiber.Var.get dep_path_var >>= fun x ->
|
||||||
|
let rev_dep = Option.value x ~default:root in
|
||||||
|
if Id.Set.mem rev_dep.transitive_rev_deps t.id then
|
||||||
|
dependency_cycle ~last:t ~last_rev_dep:rev_dep
|
||||||
|
~last_requested_file:requested_file;
|
||||||
|
t.rev_deps <- (requested_file, rev_dep) :: t.rev_deps;
|
||||||
|
t.transitive_rev_deps <-
|
||||||
|
Id.Set.union t.transitive_rev_deps rev_dep.transitive_rev_deps;
|
||||||
|
let on_error exn =
|
||||||
|
Dep_path.reraise exn (Path requested_file)
|
||||||
|
in
|
||||||
|
Fiber.Var.set dep_path_var t (Fiber.with_error_handler f ~on_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
module File_kind = struct
|
module File_kind = struct
|
||||||
|
@ -827,8 +830,9 @@ let rec compile_rule t ?(copy_source=false) pre_rule =
|
||||||
t.hook Rule_completed
|
t.hook Rule_completed
|
||||||
in
|
in
|
||||||
let rule =
|
let rule =
|
||||||
|
let id = Internal_rule.Id.gen () in
|
||||||
{ Internal_rule.
|
{ Internal_rule.
|
||||||
id = Internal_rule.Id.gen ()
|
id
|
||||||
; static_deps
|
; static_deps
|
||||||
; targets
|
; targets
|
||||||
; build
|
; build
|
||||||
|
@ -837,6 +841,8 @@ let rec compile_rule t ?(copy_source=false) pre_rule =
|
||||||
; mode
|
; mode
|
||||||
; loc
|
; loc
|
||||||
; dir
|
; dir
|
||||||
|
; transitive_rev_deps = Internal_rule.Id.Set.singleton id
|
||||||
|
; rev_deps = []
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
create_file_specs t target_specs rule ~copy_source
|
create_file_specs t target_specs rule ~copy_source
|
||||||
|
@ -1117,7 +1123,7 @@ and wait_for_file t fn =
|
||||||
die "File unavailable: %s" (Path.to_string_maybe_quoted fn)
|
die "File unavailable: %s" (Path.to_string_maybe_quoted fn)
|
||||||
|
|
||||||
and wait_for_file_found fn (File_spec.T file) =
|
and wait_for_file_found fn (File_spec.T file) =
|
||||||
Dependency_path.push fn ~targets:file.rule.targets ~f:(fun () ->
|
Internal_rule.push_rev_dep file.rule fn ~f:(fun () ->
|
||||||
match file.rule.exec with
|
match file.rule.exec with
|
||||||
| Not_started { eval_rule; exec_rule } ->
|
| Not_started { eval_rule; exec_rule } ->
|
||||||
Fiber.fork eval_rule
|
Fiber.fork eval_rule
|
||||||
|
@ -1391,18 +1397,18 @@ let build_rules_internal ?(recursive=false) t ~request =
|
||||||
Fiber.return ()
|
Fiber.return ()
|
||||||
else begin
|
else begin
|
||||||
rules_seen := Rule.Id.Set.add !rules_seen ir.id;
|
rules_seen := Rule.Id.Set.add !rules_seen ir.id;
|
||||||
(match ir.exec with
|
(Internal_rule.push_rev_dep ir fn ~f:(fun () ->
|
||||||
| Running { rule_evaluation; _ }
|
match ir.exec with
|
||||||
| Evaluating_rule { rule_evaluation; _ } ->
|
| Running { rule_evaluation; _ }
|
||||||
Fiber.return rule_evaluation
|
| Evaluating_rule { rule_evaluation; _ } ->
|
||||||
| Not_started { eval_rule; exec_rule } ->
|
Fiber.return rule_evaluation
|
||||||
Fiber.fork (fun () ->
|
| Not_started { eval_rule; exec_rule } ->
|
||||||
Dependency_path.push fn ~targets:ir.targets ~f:eval_rule)
|
Fiber.fork eval_rule
|
||||||
>>| fun rule_evaluation ->
|
>>| fun rule_evaluation ->
|
||||||
ir.exec <- Evaluating_rule { rule_evaluation
|
ir.exec <- Evaluating_rule { rule_evaluation
|
||||||
; exec_rule
|
; exec_rule
|
||||||
};
|
};
|
||||||
rule_evaluation)
|
rule_evaluation))
|
||||||
>>= fun rule_evaluation ->
|
>>= fun rule_evaluation ->
|
||||||
Fiber.fork (fun () ->
|
Fiber.fork (fun () ->
|
||||||
Fiber.Future.wait rule_evaluation
|
Fiber.Future.wait rule_evaluation
|
||||||
|
|
|
@ -595,6 +595,14 @@
|
||||||
test-cases/redirections
|
test-cases/redirections
|
||||||
(progn (run %{exe:cram.exe} -test run.t) (diff? run.t run.t.corrected)))))
|
(progn (run %{exe:cram.exe} -test run.t) (diff? run.t run.t.corrected)))))
|
||||||
|
|
||||||
|
(alias
|
||||||
|
(name reporting-of-cycles)
|
||||||
|
(deps (package dune) (source_tree test-cases/reporting-of-cycles))
|
||||||
|
(action
|
||||||
|
(chdir
|
||||||
|
test-cases/reporting-of-cycles
|
||||||
|
(progn (run %{exe:cram.exe} -test run.t) (diff? run.t run.t.corrected)))))
|
||||||
|
|
||||||
(alias
|
(alias
|
||||||
(name scope-bug)
|
(name scope-bug)
|
||||||
(deps (package dune) (source_tree test-cases/scope-bug))
|
(deps (package dune) (source_tree test-cases/scope-bug))
|
||||||
|
@ -763,6 +771,7 @@
|
||||||
(alias promote)
|
(alias promote)
|
||||||
(alias quoting)
|
(alias quoting)
|
||||||
(alias redirections)
|
(alias redirections)
|
||||||
|
(alias reporting-of-cycles)
|
||||||
(alias scope-bug)
|
(alias scope-bug)
|
||||||
(alias scope-ppx-bug)
|
(alias scope-ppx-bug)
|
||||||
(alias select)
|
(alias select)
|
||||||
|
@ -840,6 +849,7 @@
|
||||||
(alias promote)
|
(alias promote)
|
||||||
(alias quoting)
|
(alias quoting)
|
||||||
(alias redirections)
|
(alias redirections)
|
||||||
|
(alias reporting-of-cycles)
|
||||||
(alias scope-bug)
|
(alias scope-bug)
|
||||||
(alias scope-ppx-bug)
|
(alias scope-ppx-bug)
|
||||||
(alias select)
|
(alias select)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
(library
|
||||||
|
(name a1)
|
||||||
|
(public_name a.1)
|
||||||
|
(libraries b))
|
|
@ -0,0 +1,3 @@
|
||||||
|
(library
|
||||||
|
(name a2)
|
||||||
|
(public_name a.2))
|
|
@ -0,0 +1 @@
|
||||||
|
(lang dune 1.0)
|
|
@ -0,0 +1,4 @@
|
||||||
|
(library
|
||||||
|
(name b)
|
||||||
|
(public_name b)
|
||||||
|
(libraries a.2))
|
|
@ -0,0 +1 @@
|
||||||
|
(lang dune 1.0)
|
|
@ -0,0 +1,20 @@
|
||||||
|
(alias
|
||||||
|
(name package-cycle)
|
||||||
|
(deps (package a) (package b)))
|
||||||
|
|
||||||
|
(alias
|
||||||
|
(name simple-repro-case)
|
||||||
|
(deps x y))
|
||||||
|
|
||||||
|
(rule (copy %{read:u} y))
|
||||||
|
(rule (copy %{read:v} x))
|
||||||
|
|
||||||
|
(rule (with-stdout-to u (system "printf x")))
|
||||||
|
(rule (with-stdout-to v (system "printf y")))
|
||||||
|
|
||||||
|
(rule (progn (copy x2 x1) (cat x4)))
|
||||||
|
(rule (copy x3 x2))
|
||||||
|
(rule (copy %{read:x3-x2-dyn-dep} x3))
|
||||||
|
(rule (copy x3 x4))
|
||||||
|
|
||||||
|
(rule (with-stdout-to x3-x2-dyn-dep (system "printf x2")))
|
|
@ -0,0 +1 @@
|
||||||
|
(lang dune 1.0)
|
|
@ -0,0 +1,26 @@
|
||||||
|
These tests is a regression test for the detection of dynamic cycles.
|
||||||
|
|
||||||
|
In all tests, we have a cycle that only becomes apparent after we
|
||||||
|
start running things. In the past, the error was only reported during
|
||||||
|
the second run of dune.
|
||||||
|
|
||||||
|
$ dune build @package-cycle
|
||||||
|
Dependency cycle between the following files:
|
||||||
|
_build/.aliases/default/.a-files-00000000000000000000000000000000
|
||||||
|
--> _build/.aliases/default/.b-files-00000000000000000000000000000000
|
||||||
|
--> _build/.aliases/default/.a-files-00000000000000000000000000000000
|
||||||
|
[1]
|
||||||
|
|
||||||
|
$ dune build @simple-repro-case
|
||||||
|
Dependency cycle between the following files:
|
||||||
|
_build/default/x
|
||||||
|
--> _build/default/y
|
||||||
|
--> _build/default/x
|
||||||
|
[1]
|
||||||
|
|
||||||
|
$ dune build x1
|
||||||
|
Dependency cycle between the following files:
|
||||||
|
_build/default/x2
|
||||||
|
--> _build/default/x3
|
||||||
|
--> _build/default/x2
|
||||||
|
[1]
|
Loading…
Reference in New Issue