Better behavior when aliases have targets (#426)

Ignore the targets and report a warning.
This commit is contained in:
Jérémie Dimino 2018-01-19 22:17:11 +00:00 committed by GitHub
parent 92f9ce4edb
commit 5651eb80b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 105 deletions

View File

@ -876,112 +876,143 @@ module Infer = struct
end
open Outcome
let ( +@ ) acc fn = { acc with targets = S.add fn acc.targets }
let ( +< ) acc fn = { acc with deps = S.add fn acc.deps }
module type Pset = sig
type t
val empty : t
val diff : t -> t -> t
end
let rec infer acc t =
match t with
| Run (Ok prog, _) -> acc +< prog
| Run (Error _, _) -> acc
| Redirect (_, fn, t) -> infer (acc +@ fn) t
| Cat fn -> acc +< fn
| Write_file (fn, _) -> acc +@ fn
| Rename (src, dst) -> acc +< src +@ dst
| Copy (src, dst)
| Copy_and_add_line_directive (src, dst)
| Symlink (src, dst) -> acc +< src +@ dst
| Chdir (_, t)
| Setenv (_, _, t)
| Ignore (_, t) -> infer acc t
| Progn l -> List.fold_left l ~init:acc ~f:infer
| Digest_files l -> List.fold_left l ~init:acc ~f:(+<)
| Diff { optional; file1; file2 } ->
if optional then acc else acc +< file1 +< file2
| Echo _
| System _
| Bash _
| Remove_tree _
| Mkdir _ -> acc
module type Outcome = sig
type path_set
type t =
{ deps : path_set
; targets : path_set
}
end
let infer t =
let { deps; targets } = infer { deps = S.empty; targets = S.empty } t in
(* A file can be inferred as both a dependency and a target, for instance:
module type Primitives = sig
type path
type program
type outcome
val ( +@ ) : outcome -> path -> outcome
val ( +< ) : outcome -> path -> outcome
val ( +<! ) : outcome -> program -> outcome
end
{[
(progn (copy a b) (copy b c))
]}
*)
{ deps = S.diff deps targets; targets }
module Make
(Ast : Action_intf.Ast)
(Pset : Pset)
(Out : Outcome with type path_set := Pset.t)
(Prim : Primitives
with type path := Ast.path
with type program := Ast.program
with type outcome := Out.t) =
struct
open Ast
open Out
open Prim
let rec infer acc t =
match t with
| Run (prog, _) -> acc +<! prog
| Redirect (_, fn, t) -> infer (acc +@ fn) t
| Cat fn -> acc +< fn
| Write_file (fn, _) -> acc +@ fn
| Rename (src, dst) -> acc +< src +@ dst
| Copy (src, dst)
| Copy_and_add_line_directive (src, dst)
| Symlink (src, dst) -> acc +< src +@ dst
| Chdir (_, t)
| Setenv (_, _, t)
| Ignore (_, t) -> infer acc t
| Progn l -> List.fold_left l ~init:acc ~f:infer
| Digest_files l -> List.fold_left l ~init:acc ~f:(+<)
| Diff { optional; file1; file2 } ->
if optional then acc else acc +< file1 +< file2
| Echo _
| System _
| Bash _
| Remove_tree _
| Mkdir _ -> acc
let ( +@? ) acc fn =
match fn with
| Inl fn -> { acc with targets = S.add fn acc.targets }
| Inr _ -> acc
let ( +<? ) acc fn =
match fn with
| Inl fn -> { acc with deps = S.add fn acc.deps }
| Inr _ -> acc
let infer t =
let { deps; targets } =
infer { deps = Pset.empty; targets = Pset.empty } t
in
(* A file can be inferred as both a dependency and a target,
for instance:
let rec partial acc (t : Unexpanded.Partial.t) =
match t with
| Run (Inl (This prog), _) -> acc +< prog
| Run (_, _) -> acc
| Redirect (_, fn, t) -> partial (acc +@? fn) t
| Cat fn -> acc +<? fn
| Write_file (fn, _) -> acc +@? fn
| Rename (src, dst) -> acc +<? src +@? dst
| Copy (src, dst)
| Copy_and_add_line_directive (src, dst)
| Symlink (src, dst) -> acc +<? src +@? dst
| Chdir (_, t)
| Setenv (_, _, t)
| Ignore (_, t) -> partial acc t
| Progn l -> List.fold_left l ~init:acc ~f:partial
| Digest_files l -> List.fold_left l ~init:acc ~f:(+<?)
| Diff { optional; file1; file2 } ->
if optional then acc else acc +<? file1 +<? file2
| Echo _
| System _
| Bash _
| Remove_tree _
| Mkdir _ -> acc
{[
(progn (copy a b) (copy b c))
]}
*)
{ deps = Pset.diff deps targets; targets }
end [@@inline always]
let ( +@? ) acc fn =
match fn with
| Inl fn -> { acc with targets = S.add fn acc.targets }
| Inr sw -> Loc.fail (SW.loc sw) "Cannot determine this target statically."
include Make(Ast)(S)(Outcome)(struct
let ( +@ ) acc fn = { acc with targets = S.add fn acc.targets }
let ( +< ) acc fn = { acc with deps = S.add fn acc.deps }
let ( +<! ) acc prog =
match prog with
| Ok p -> acc +< p
| Error _ -> acc
end)
let rec partial_with_all_targets acc (t : Unexpanded.Partial.t) =
match t with
| Run (Inl (This prog), _) -> acc +< prog
| Run (_, _) -> acc
| Redirect (_, fn, t) -> partial_with_all_targets (acc +@? fn) t
| Cat fn -> acc +<? fn
| Write_file (fn, _) -> acc +@? fn
| Rename (src, dst) -> acc +<? src +@? dst
| Copy (src, dst)
| Copy_and_add_line_directive (src, dst)
| Symlink (src, dst) -> acc +<? src +@? dst
| Chdir (_, t)
| Setenv (_, _, t)
| Ignore (_, t) -> partial_with_all_targets acc t
| Progn l -> List.fold_left l ~init:acc ~f:partial_with_all_targets
| Digest_files l -> List.fold_left l ~init:acc ~f:(+<?)
| Diff { optional; file1; file2 } ->
if optional then acc else acc +<? file1 +<? file2
| Echo _
| System _
| Bash _
| Remove_tree _
| Mkdir _ -> acc
module Partial = Make(Unexpanded.Partial.Past)(S)(Outcome)(struct
let ( +@ ) acc fn =
match fn with
| Inl fn -> { acc with targets = S.add fn acc.targets }
| Inr _ -> acc
let ( +< ) acc fn =
match fn with
| Inl fn -> { acc with deps = S.add fn acc.deps }
| Inr _ -> acc
let ( +<! ) acc fn =
match (fn : Unexpanded.Partial.program) with
| Inl (This fn) -> { acc with deps = S.add fn acc.deps }
| Inl (Search _) | Inr _ -> acc
end)
module Partial_with_all_targets = Make(Unexpanded.Partial.Past)(S)(Outcome)(struct
let ( +@ ) acc fn =
match fn with
| Inl fn -> { acc with targets = S.add fn acc.targets }
| Inr sw -> Loc.fail (SW.loc sw) "Cannot determine this target statically."
let ( +< ) acc fn =
match fn with
| Inl fn -> { acc with deps = S.add fn acc.deps }
| Inr _ -> acc
let ( +<! ) acc fn =
match (fn : Unexpanded.Partial.program) with
| Inl (This fn) -> { acc with deps = S.add fn acc.deps }
| Inl (Search _) | Inr _ -> acc
end)
let partial ~all_targets t =
let acc = { deps = S.empty; targets = S.empty } in
let { deps; targets } =
if all_targets then
partial_with_all_targets acc t
else
partial acc t
in
{ deps = S.diff deps targets; targets }
if all_targets then
Partial_with_all_targets.infer t
else
Partial.infer t
module S_unexp = struct
type t = String_with_vars.t list
let empty = []
let diff a _ = a
end
module Outcome_unexp = struct
type t =
{ deps : S_unexp.t
; targets : S_unexp.t
}
end
module Unexp = Make(Unexpanded.Uast)(S_unexp)(Outcome_unexp)(struct
open Outcome_unexp
let ( +@ ) acc fn = { acc with targets = fn :: acc.targets }
let ( +< ) acc _ = acc
let ( +<! )= ( +< )
end)
let unexpanded_targets t =
(Unexp.infer t).targets
end

View File

@ -126,6 +126,9 @@ module Infer : sig
(** If [all_targets] is [true] and a target cannot be determined statically, fail *)
val partial : all_targets:bool -> Unexpanded.Partial.t -> Outcome.t
(** Return the list of targets of an unexpanded action. *)
val unexpanded_targets : Unexpanded.t -> String_with_vars.t list
end
module Promotion : sig

View File

@ -177,11 +177,19 @@ module Rule = struct
List.iter l ~f:(fun target ->
let path = Target.path target in
if Path.parent path <> dir then
Sexp.code_error "rule has targets in different directories"
[ "dir", Path.sexp_of_t dir
; "targets", Sexp.To_sexp.list Path.sexp_of_t
(List.map (x :: l) ~f:Target.path)
]);
match loc with
| None ->
Sexp.code_error "rule has targets in different directories"
[ "targets", Sexp.To_sexp.list Path.sexp_of_t
(List.map targets ~f:Target.path)
]
| Some loc ->
Loc.fail loc
"Rule has targets in different directories.\nTargets:\n%s"
(String.concat ~sep:"\n"
(List.map targets ~f:(fun t ->
sprintf "- %s"
(Target.path t |> Path.to_string_maybe_quoted)))));
dir
in
{ context

View File

@ -796,7 +796,7 @@ Add it to your jbuild file to remove this warning.
action
~dir
~dep_kind:Required
~targets:(Static [])
~targets:Alias
~scope)
(* +-----------------------------------------------------------------+

View File

@ -442,6 +442,7 @@ module Action = struct
type targets =
| Static of Path.t list
| Infer
| Alias
type resolved_forms =
{ (* Failed resolutions *)
@ -598,6 +599,7 @@ module Action = struct
| "@" -> begin
match targets_written_by_user with
| Infer -> Loc.fail loc "You cannot use ${@} with inferred rules."
| Alias -> Loc.fail loc "You cannot use ${@} in aliases."
| Static l -> Some (Paths (l, Split))
end
| _ -> expand_var_no_root sctx var)
@ -627,6 +629,14 @@ module Action = struct
let run sctx t ~dir ~dep_kind ~targets:targets_written_by_user ~scope
: (Path.t list, Action.t) Build.t =
let map_exe = map_exe sctx in
if targets_written_by_user = Alias then begin
match Action.Infer.unexpanded_targets t with
| [] -> ()
| x :: _ ->
let loc = String_with_vars.loc x in
Loc.warn loc "Aliases must not have targets, this target will be ignored.\n\
This will become an error in the future.";
end;
let t, forms =
expand_step1 sctx t ~dir ~dep_kind ~scope
~targets_written_by_user ~map_exe
@ -656,6 +666,11 @@ module Action = struct
]}
*)
{ deps; targets = Pset.union targets targets_written_by_user }
| Alias ->
let { Action.Infer.Outcome. deps; targets = _ } =
Action.Infer.partial t ~all_targets:false
in
{ deps; targets = Pset.empty }
in
let targets = Pset.elements targets in
List.iter targets ~f:(fun target ->

View File

@ -169,6 +169,7 @@ module Action : sig
type targets =
| Static of Path.t list
| Infer
| Alias (** This action is for an alias *)
(** The arrow takes as input the list of actual dependencies *)
val run