Let "concat" or "split" be a quality of the variable (#336)

* Let variables say whether they are Concat or Split

To concatenate the contents of a split variable, put it in a string:
"${var} ".

Fixes #300

See also https://github.com/janestreet/jbuilder/issues/408

* Issue a deprecation warning for ${!...}

* Treat ${CC}, ${<}, ${^} and ${read-lines:...} as split vars

* Change ${!^} into ${^} for this project jbuild rules
This commit is contained in:
Christophe Troestler 2018-01-15 10:32:40 +01:00 committed by Jérémie Dimino
parent 3fea0db9cd
commit f8617b5721
6 changed files with 101 additions and 76 deletions

View File

@ -579,8 +579,9 @@ Jbuilder supports the following variables:
the toplevel directory of your project and as long as you have at
least one ``<package>.opam`` file there, ``SCOPE_ROOT`` is
independant of the workspace configuration
- ``CC`` is the C compiler command line being used in the current
build context
- ``CC`` is the C compiler command line (list made of the compiler
name followed by its flags) that was used to compile OCaml in the
current build context
- ``CXX`` is the C++ compiler command line being used in the
current build context
- ``ocaml_bin`` is the path where ``ocamlc`` lives
@ -654,41 +655,55 @@ generated by an OCaml program via:
#. Expansion of lists
Forms that expands to list of items, such as ``${^}``, ``${@}`` or
``${read-lines:...}`` will always expand to a single string where
elements are separated by spaces. Inside ``(run <prog> <arguments>)``
forms you can however split the items as several arguments by
prefixing the variable with ``!``. Such forms can only be used as a
whole atom, i.e. they can't be used inside a quoted atom.
For instance in:
Forms that expands to list of items, such as ``${CC}``, ``${^}``,
``${@}`` or ``${read-lines:...}``, are suitable to be used in, say,
``(run <prog> <arguments>)``. 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"
However, if you replace ``${^}`` by ``${!^}`` in the previous example
the command produced would be equivalent to this shell command:
if there are two dependencies ``a`` and ``b``, the produced command
will be equivalent to the shell command:
.. code:: shell
$ foo "a" "b"
You can also use ``${!^}`` as program name, for instance:
If you want the two dependencies to be passed as a single argument,
you have to quote the variable as in:
.. code:: scheme
(run foo "${^} ")
(for now the final space is necessary)
which is equivalent to the following shell command:
.. code:: shell
$ foo "a b "
(the items of the list are concatenated with space).
Note that, since ``${^}`` is a list of items, the first one may be
used as a program name, for instance:
.. code:: scheme
(rule
((targets (result.txt))
(deps (foo.exe (glob_files *.txt)))
(action (run ${!^}))))
(action (run ${^}))))
Here is another example:
.. code:: scheme
(rule
((targets (foo.exe))
(deps (foo.c))
(action (run ${CC} -o ${@} ${<} -lfoolib))))
Library dependencies
--------------------

View File

@ -221,7 +221,7 @@ module Var_expansion = struct
module Concat_or_split = struct
type t =
| Concat (* default *)
| Split (* ${!...} *)
| Split (* the variable is a "split" list of items *)
end
open Concat_or_split
@ -244,15 +244,13 @@ module Var_expansion = struct
| Paths (l, Concat) -> [concat (List.map l ~f:(string_of_path ~dir))]
let to_string ~dir = function
| Strings (_, Split) | Paths (_, Split) -> assert false
| Strings (l, Concat) -> concat l
| Paths (l, Concat) -> concat (List.map l ~f:(string_of_path ~dir))
| Strings (l, _) -> concat l
| Paths (l, _) -> concat (List.map l ~f:(string_of_path ~dir))
let to_path ~dir = function
| Strings (_, Split) | Paths (_, Split) -> assert false
| Strings (l, Concat) -> path_of_string ~dir (concat l)
| Paths ([p], Concat) -> p
| Paths (l, Concat) ->
| Strings (l, _) -> path_of_string ~dir (concat l)
| Paths ([p], _) -> p
| Paths (l, _) ->
path_of_string ~dir (concat (List.map l ~f:(string_of_path ~dir)))
let to_prog_and_args ~dir exp : Unresolved.Program.t * string list =
@ -388,13 +386,13 @@ module Unexpanded = struct
| Remove_tree x ->
Remove_tree (E.path ~dir ~f x)
| Mkdir x -> begin
match x with
| Inl path -> Mkdir path
| Inr tmpl ->
let path = E.path ~dir ~f x in
check_mkdir (SW.loc tmpl) path;
Mkdir path
end
match x with
| Inl path -> Mkdir path
| Inr tmpl ->
let path = E.path ~dir ~f x in
check_mkdir (SW.loc tmpl) path;
Mkdir path
end
| Digest_files x ->
Digest_files (List.map x ~f:(E.path ~dir ~f))
end

View File

@ -4,7 +4,7 @@ module Var_expansion : sig
module Concat_or_split : sig
type t =
| Concat (* default *)
| Split (* ${!...} *)
| Split (* the variable is a "split" list of items *)
end
type t =

View File

@ -853,7 +853,7 @@ module Menhir = struct
; S.virt_var __POS__ ("path-no-dep:" ^ merge_into)
]
; t.flags
; [ S.virt_var __POS__ "!^" ]
; [ S.virt_var __POS__ "^" ]
]))
; fallback = Not_possible
; locks = []

View File

@ -52,7 +52,7 @@ type t =
; mutable known_targets_by_src_dir_so_far : String_set.t Path.Map.t
; libs_vfile : (module Vfile_kind.S with type t = Lib.t list)
; cxx_flags : string list
; vars : string String_map.t
; vars : Action.Var_expansion.t String_map.t
; ppx_dir : Path.t
; ppx_drivers : (string, Path.t) Hashtbl.t
; external_dirs : (Path.t, External_dir.t) Hashtbl.t
@ -83,7 +83,13 @@ let expand_vars t ~scope ~dir s =
| "ROOT" -> Some (Path.reach ~from:dir t.context.build_dir)
| "SCOPE_ROOT" ->
Some (Path.reach ~from:dir (Path.append t.context.build_dir scope.Scope.root))
| var -> String_map.find var t.vars)
| var ->
let open Action.Var_expansion in
expand_var_no_root t var
|> Option.map ~f:(function
| Paths(p,_) -> let p = List.map p ~f:Path.to_string in
String.concat ~sep:" " p
| Strings(s,_) -> String.concat ~sep:" " s))
let resolve_program t ?hint bin =
Artifacts.binary ?hint t.artifacts bin
@ -163,26 +169,31 @@ let create
| None -> Path.relative context.ocaml_bin "ocamlopt"
| Some p -> p
in
let open Action.Var_expansion in
let open Action.Var_expansion.Concat_or_split in
let make =
match Bin.make with
| None -> "make"
| Some p -> Path.to_string p
| None -> Strings (["make"], Split)
| Some p -> Paths ([p], Split)
in
[ "-verbose" , "" (*"-verbose";*)
; "CPP" , sprintf "%s %s -E" context.c_compiler context.ocamlc_cflags
; "PA_CPP" , sprintf "%s %s -undef -traditional -x c -E" context.c_compiler
context.ocamlc_cflags
; "CC" , sprintf "%s %s" context.c_compiler context.ocamlc_cflags
; "CXX" , String.concat ~sep:" " (context.c_compiler :: cxx_flags)
; "ocaml_bin" , Path.to_string context.ocaml_bin
; "OCAML" , Path.to_string context.ocaml
; "OCAMLC" , Path.to_string context.ocamlc
; "OCAMLOPT" , Path.to_string ocamlopt
; "ocaml_version" , context.version
; "ocaml_where" , Path.to_string context.stdlib_dir
; "ARCH_SIXTYFOUR" , string_of_bool context.arch_sixtyfour
let cflags = String.extract_blank_separated_words context.ocamlc_cflags in
[ "-verbose" , Strings ([] (*"-verbose";*), Concat)
; "CPP" , Strings (context.c_compiler :: cflags @ ["-E"], Split)
; "PA_CPP" , Strings (context.c_compiler :: cflags
@ ["-undef"; "-traditional"; "-x"; "c"; "-E"],
Split)
; "CC" , Strings (context.c_compiler :: cflags, Split)
; "CXX" , Strings (context.c_compiler :: cxx_flags, Split)
; "ocaml_bin" , Paths ([context.ocaml_bin], Split)
; "OCAML" , Paths ([context.ocaml], Split)
; "OCAMLC" , Paths ([context.ocamlc], Split)
; "OCAMLOPT" , Paths ([ocamlopt], Split)
; "ocaml_version" , Strings ([context.version], Concat)
; "ocaml_where" , Paths ([context.stdlib_dir], Concat)
; "ARCH_SIXTYFOUR" , Strings ([string_of_bool context.arch_sixtyfour],
Concat)
; "MAKE" , make
; "null" , Path.to_string Config.dev_null
; "null" , Paths ([Config.dev_null], Concat)
]
|> String_map.of_alist
|> function
@ -467,12 +478,12 @@ module Pkg_version = struct
Build.vpath spec
end
let parse_bang var : Action.Var_expansion.Concat_or_split.t * string =
let parse_bang var : bool * string =
let len = String.length var in
if len > 0 && var.[0] = '!' then
(Split, String.sub var ~pos:1 ~len:(len - 1))
(true, String.sub var ~pos:1 ~len:(len - 1))
else
(Concat, var)
(false, var)
module Action = struct
open Build.O
@ -533,7 +544,10 @@ module Action = struct
let t =
U.partial_expand t ~dir ~map_exe ~f:(fun loc key ->
let open Action.Var_expansion in
let cos, var = parse_bang key in
let has_bang, var = parse_bang key in
if has_bang then
Loc.warn loc "The use of the variable prefix '!' is deprecated, \
simply use '${%s}'@." var;
match String.lsplit2 var ~on:':' with
| Some ("path-no-dep", s) -> Some (path_exp (Path.relative dir s))
| Some ("exe" , s) ->
@ -597,7 +611,7 @@ module Action = struct
let path = Path.relative dir s in
let data =
Build.contents path
>>^ fun s -> Strings ([s], cos)
>>^ fun s -> Strings ([s], Concat)
in
add_ddep acc ~key data
end
@ -605,7 +619,7 @@ module Action = struct
let path = Path.relative dir s in
let data =
Build.lines_of path
>>^ fun l -> Strings (l, cos)
>>^ fun l -> Strings (l, Split)
in
add_ddep acc ~key data
end
@ -613,7 +627,7 @@ module Action = struct
let path = Path.relative dir s in
let data =
Build.strings path
>>^ fun l -> Strings (l, cos)
>>^ fun l -> Strings (l, Split)
in
add_ddep acc ~key data
end
@ -624,32 +638,30 @@ module Action = struct
| "@" -> begin
match targets_written_by_user with
| Infer -> Loc.fail loc "You cannot use ${@} with inferred rules."
| Static l -> Some (Paths (l, cos))
| Static l -> Some (Paths (l, Split))
end
| _ ->
match expand_var_no_root sctx var with
| Some s -> Some (str_exp s)
| None -> None)
| _ -> expand_var_no_root sctx var)
in
(t, acc)
let expand_step2 ~dir ~dynamic_expansions ~deps_written_by_user ~map_exe t =
let open Action.Var_expansion in
U.Partial.expand t ~dir ~map_exe ~f:(fun _loc key ->
U.Partial.expand t ~dir ~map_exe ~f:(fun loc key ->
match String_map.find key dynamic_expansions with
| Some _ as opt -> opt
| None ->
let cos, var = parse_bang key in
let _, var = parse_bang key in
match var with
| "<" ->
Some
(match deps_written_by_user with
| [] ->
(* CR-someday jdimino: this should be an error *)
Strings ([""], cos)
Loc.warn loc "Variable '<' used with no explicit \
dependencies@.";
Strings ([""], Split)
| dep :: _ ->
Paths ([dep], cos))
| "^" -> Some (Paths (deps_written_by_user, cos))
Paths ([dep], Split))
| "^" -> Some (Paths (deps_written_by_user, Split))
| _ -> None)
let run sctx t ~dir ~dep_kind ~targets:targets_written_by_user ~scope

View File

@ -33,7 +33,7 @@
(alias
((name runtest)
(deps (jbuild jbuild-plop))
(action (run diff -u ${!^}))))
(action (run diff -u ${^}))))
;; For some tests in subdirs
@ -56,4 +56,4 @@
(alias
((name runtest)
(deps (pnd-result pnd-expected))
(action (run diff -u ${!^}))))
(action (run diff -u ${^}))))