diff --git a/src/action.ml b/src/action.ml index 81c349b7..8a29d9b7 100644 --- a/src/action.ml +++ b/src/action.ml @@ -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 ( + 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 + 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 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 + acc +@? fn - | Rename (src, dst) -> acc + acc + partial acc t - | Progn l -> List.fold_left l ~init:acc ~f:partial - | Digest_files l -> List.fold_left l ~init:acc ~f:(+ - if optional then acc else acc + 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 +< 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 + acc +@? fn - | Rename (src, dst) -> acc + acc + 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:(+ - if optional then acc else acc + 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 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 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 ( + 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 diff --git a/src/build_interpret.ml b/src/build_interpret.ml index 5e02002b..0dd3d37a 100644 --- a/src/build_interpret.ml +++ b/src/build_interpret.ml @@ -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 diff --git a/src/gen_rules.ml b/src/gen_rules.ml index 54748014..3e8506e9 100644 --- a/src/gen_rules.ml +++ b/src/gen_rules.ml @@ -796,7 +796,7 @@ Add it to your jbuild file to remove this warning. action ~dir ~dep_kind:Required - ~targets:(Static []) + ~targets:Alias ~scope) (* +-----------------------------------------------------------------+ diff --git a/src/super_context.ml b/src/super_context.ml index 323a7bd5..64c68521 100644 --- a/src/super_context.ml +++ b/src/super_context.ml @@ -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 -> diff --git a/src/super_context.mli b/src/super_context.mli index cb4f20cf..4b8e3304 100644 --- a/src/super_context.mli +++ b/src/super_context.mli @@ -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