diff --git a/doc/jbuild.rst b/doc/jbuild.rst index 01ac5cc2..216f4155 100644 --- a/doc/jbuild.rst +++ b/doc/jbuild.rst @@ -523,9 +523,11 @@ Jbuilder supports the following variables: In addition, ``(action ...)`` fields support the following special variables: - ``@`` expands to the list of target, separated by spaces +- ``!@`` same as ``@`` but with a split semantic (see below) - ``<`` expands to the first dependency, or the empty string if there are no dependencies - ``^`` expands to the list of dependencies, separated by spaces +- ``!^`` same as ``^`` but with a split semantic (see below) - ``path:`` expands to ```` - ``exe:`` is the same as ````, except when cross-compiling, in which case it will expand to ```` from the host build context @@ -558,6 +560,39 @@ transparently whether things are installed or not. Note that aliases are ignored by both ``${<}`` and ``${^}``. +Moreover ``${^}`` and ``${@}`` will always expand to a single +string. For instance in: + +.. code:: scheme + + (run foo ${^}) + +even if there are two dependencies ``a`` and ``b``, the produced +command will be equivalent to the shell command: + +.. code:: shell + + $ foo "a b" + +In order to *split* them, you can use ``${!^}`` and ``${!@}``. These +two forms are only available in ``(run ...)`` forms and can only be +used as a whole atom, i.e. they can't be used inside a quoted +atom. Replacing ``${^}`` by ``${!^}`` in the previous example would +produce a command equivalent to this shell command: + +.. code:: shell + + $ foo "a" "b" + +You can also use ``${!^}`` as program name, for instance: + +.. code:: scheme + + (rule + ((targets (result.txt)) + (deps (foo.exe (glob_files *.txt))) + (action (run ${!^})))) + Library dependencies -------------------- diff --git a/src/action.ml b/src/action.ml index 95f6c022..52823ad3 100644 --- a/src/action.ml +++ b/src/action.ml @@ -3,10 +3,12 @@ open Sexp.Of_sexp module Env_var_map = Context.Env_var_map +type split_or_concat = Split | Concat + type var_expansion = | Not_found | Path of Path.t - | Paths of Path.t list + | Paths of Path.t list * split_or_concat | Str of string let expand_str ~dir ~f template = @@ -14,9 +16,21 @@ let expand_str ~dir ~f template = match f var with | Not_found -> None | Path path -> Some (Path.reach ~from:dir path) - | Paths l -> Some (List.map l ~f:(Path.reach ~from:dir) |> String.concat ~sep:" ") + | Paths (l, _) -> Some (List.map l ~f:(Path.reach ~from:dir) |> String.concat ~sep:" ") | Str s -> Some s) +let expand_str_split ~dir ~f template = + match String_with_vars.just_a_var template with + | None -> [expand_str ~dir ~f template] + | Some var -> + match f var with + | Not_found -> [expand_str ~dir ~f template] + | Path path -> [Path.reach ~from:dir path] + | Str s -> [s] + | Paths (l, Concat) -> + [List.map l ~f:(Path.reach ~from:dir) |> String.concat ~sep:" "] + | Paths (l, Split) -> List.map l ~f:(Path.reach ~from:dir) + let expand_path ~dir ~f template = match String_with_vars.just_a_var template with | None -> expand_str ~dir ~f template |> Path.relative dir @@ -24,9 +38,9 @@ let expand_path ~dir ~f template = match f v with | Not_found -> expand_str ~dir ~f template |> Path.relative dir | Path p - | Paths [p] -> p + | Paths ([p], _) -> p | Str s -> Path.relative dir s - | Paths l -> + | Paths (l, _) -> List.map l ~f:(Path.reach ~from:dir) |> String.concat ~sep:" " |> Path.relative dir @@ -41,17 +55,19 @@ let expand_prog ctx ~dir ~f template = | None -> Utils.program_not_found ~context:ctx.name s in match String_with_vars.just_a_var template with - | None -> resolve (expand_str ~dir ~f template) + | None -> (resolve (expand_str ~dir ~f template), []) | Some v -> match f v with - | Not_found -> resolve (expand_str ~dir ~f template) + | Not_found -> (resolve (expand_str ~dir ~f template), []) | Path p - | Paths [p] -> p - | Str s -> resolve s - | Paths l -> - List.map l ~f:(Path.reach ~from:dir) - |> String.concat ~sep:" " - |> resolve + | Paths ([p], _) -> (p, []) + | Str s -> (resolve s, []) + | Paths (p :: args, Split) -> (p, List.map args ~f:(Path.reach ~from:dir)) + | Paths (l, _) -> + (List.map l ~f:(Path.reach ~from:dir) + |> String.concat ~sep:" " + |> resolve, + []) module Outputs = struct include Action_intf.Outputs @@ -197,8 +213,9 @@ module Unexpanded = struct let rec expand ctx dir t ~f : action = match t with | Run (prog, args) -> - Run (expand_prog ctx ~dir ~f prog, - List.map args ~f:(fun arg -> expand_str ~dir ~f arg)) + let prog, more_args = expand_prog ctx ~dir ~f prog in + Run (prog, + more_args @ List.concat_map args ~f:(expand_str_split ~dir ~f)) | Chdir (fn, t) -> let fn = expand_path ~dir ~f fn in Chdir (fn, expand ctx fn t ~f) diff --git a/src/action.mli b/src/action.mli index 0b60d8c0..b717b1df 100644 --- a/src/action.mli +++ b/src/action.mli @@ -1,9 +1,11 @@ open! Import +type split_or_concat = Split | Concat + type var_expansion = | Not_found | Path of Path.t - | Paths of Path.t list + | Paths of Path.t list * split_or_concat | Str of string module Outputs : module type of struct include Action_intf.Outputs end diff --git a/src/super_context.ml b/src/super_context.ml index 48ce9fc2..f18d5047 100644 --- a/src/super_context.ml +++ b/src/super_context.ml @@ -550,13 +550,14 @@ module Action = struct | Some exp -> exp | None -> match var_name with - | "@" -> Action.Paths targets + | "@" -> Action.Paths (targets, Concat) + | "!@" -> Action.Paths (targets, Split) | "<" -> (match deps with | [] -> Str "" (* CR-someday jdimino: this should be an error *) | dep :: _ -> Path dep) - | "^" -> - Paths deps + | "^" -> Paths (deps, Concat) + | "!^" -> Paths (deps, Split) | "ROOT" -> Path sctx.context.build_dir | var -> match expand_var_no_root sctx var with @@ -574,7 +575,7 @@ module Action = struct ~f:(fun ~key:_ ~data:exp acc -> match exp with | Action.Path p -> Path.Set.add p acc - | Paths ps -> Path.Set.union acc (Path.Set.of_list ps) + | Paths (ps, _) -> Path.Set.union acc (Path.Set.of_list ps) | Not_found | Str _ -> acc)) >>> Build.arr (fun paths -> ((), paths))