Remove (provides ...) and uses (install ...) instead

This commit is contained in:
Jeremie Dimino 2017-03-01 11:04:32 +00:00
parent 60be8f7734
commit 6e25a7dfe5
19 changed files with 300 additions and 187 deletions

View File

@ -513,44 +513,6 @@ The typical use of the =alias= stanza is to define tests:
See the [[runtest][section about running tests]] for details.
**** provides
The =provides= stanza allows you to globally name a file, either a
source file or a target. This is especially important for build tools;
by using the =provides= mechanism, you don't need to know whether the
binary is in the workspace or installed.
The syntax is as follow:
#+begin_src scheme
(provides (<name> (file <filename>)))
#+end_src
=<name>= is the name under which the artifact can be referred and
=<filename>= is the file it resolves to. When =<filename>= can be
guessed from the =<name>=, you can use the following shorter syntax:
#+begin_src scheme
(provides <name>)
#+end_src
In this case, the file name is guessed as follow:
- if =<name>= contains a =:=, the file name is anything that comes
after the first =:=
- otherwise it is the same as =<name>=
Once you have written a =provides= stanza, you can refer to the file
in points to using the special forms =${bin:<name>}= or
=${findlib:<library>:<file>}= inside =(action ...)= fields. See the
[[Variables expansion][section about variables expansion]] for details.
Note that any file referred by a =provides= stanza should probably be
installed as well, using an [[install]] stanza. If the file is meant to be
installed in a library directory, then its name should be of the form
=<public-library-name>:<file>=. If it is meant to be installed in the
=bin= directory, then its name should be the program name.
**** install
The =install= stanza is what lets you describe what Jbuilder should
@ -650,20 +612,24 @@ In addition, =(action ...)= fields support the following special variables:
- =@= expands to the list of target, separated by spaces
- =<= expands to the first dependency, or the empty string if there are no dependencies
- =^= expands to the list of dependencies, separated by spaces
- =exe:<path>= expands to =<path>=, except when cross-compiling, in
- =path:<path>= expands to =<path>=
- =exe:<path>= is the same as =<path>=, except when cross-compiling, in
which case it will expand to =<path>= from the host build context
- =bin:<program>= expands to a path to =program=. If =program= is
provided by a jbuild in the workspace (see [[provides][provides stanzas]]), the
installed by a package in the workspace (see [[install][install stanzas]]), the
locally built binary will be used, otherwise it will be searched in
the =PATH= of the current build context
- =findlib:<public-library-name>:<file>= expands to a path to file
- =lib:<public-library-name>:<file>= expands to a path to file
=<file>= of library =<public-library-name>=. If
=<public-library-name>= is available in the current workspace, the
local file will be used, otherwise the one from the installed world
will be used
- =libexec:<public-library-name>:<file>= is the same as =lib:...=
except when cross-compiling, in which case it will expand to the
file from the host build context
The last two forms of variable are what allows you to write custom
rules that work transparently whether things are installed or not.
The =${<kind>:...}= forms are what allows you to write custom rules
that work transparently whether things are installed or not.
***** Alternative dependencies
@ -832,9 +798,8 @@ of these two forms:
In both case, all atoms in the argument of this field supports
[[Variables
expansion][variables expansion]]. Moreover, you don't need to specify dependencies
explicitly for the special =${exe:...}=, =${bin:...}= or
=${findlib:...}= forms, these are recognized automatically by
Jbuilder.
explicitly for the special =${<kind>:...}= forms, these are recognized
automatically handled by Jbuilder.
The DSL is preferable in general as it will make your package more
portable. It is currently quite limited, so the recommendation is to

83
src/artifacts.ml Normal file
View File

@ -0,0 +1,83 @@
open Import
open Jbuild_types
type t =
{ context : Context.t
; provides : Path.t String_map.t
; local_bins : String_set.t
; local_libs : String_set.t
}
let create context stanzas =
let local_bins, local_libs, provides =
List.fold_left stanzas ~init:(String_set.empty, String_set.empty, String_map.empty)
~f:(fun acc (dir, stanzas) ->
List.fold_left stanzas ~init:acc
~f:(fun (local_bins, local_libs, provides) stanza ->
match (stanza : Stanza.t) with
| Provides { name; file } ->
(local_bins,
local_libs,
String_map.add provides ~key:name ~data:(Path.relative dir file))
| Install { section = Bin; files; _ } ->
(List.fold_left files ~init:local_bins ~f:(fun acc { Install_conf. src; dst } ->
let name =
match dst with
| Some s -> s
| None -> Filename.basename src
in
String_set.add name acc),
local_libs,
provides)
| Library { public_name = Some name; _ } ->
(local_bins,
String_set.add name local_libs,
provides)
| _ ->
(local_bins, local_libs, provides)))
in
{ context
; provides
; local_bins
; local_libs
}
let binary t name =
if String_set.mem name t.local_bins then
Ok (Path.relative (Config.local_install_dir ~context:t.context.name) name)
else
match String_map.find name t.provides with
| Some p -> Ok p
| None ->
match Context.which t.context name with
| Some p -> Ok p
| None ->
Error
{ fail = fun () ->
die "Program %s not found in the tree or in the PATH" name
}
let file_of_lib ?(use_provides=false) t ~lib ~file =
if String_set.mem lib t.local_libs then
let lib_install_dir =
Path.relative (Config.local_install_dir ~context:t.context.name)
(Findlib.root_package_name lib)
in
Ok (Path.relative lib_install_dir file)
else
match Findlib.find t.context.findlib lib with
| Some pkg ->
Ok (Path.relative pkg.dir file)
| None ->
match
if use_provides then
String_map.find (sprintf "%s:%s" lib file) t.provides
else
None
with
| Some p -> Ok p
| None ->
Error
{ fail = fun () ->
die "Library %s not found in the tree or in the PATH" lib
}

21
src/artifacts.mli Normal file
View File

@ -0,0 +1,21 @@
(** [Named_artifact] provides a way to reference artifacts in jbuild rules without having
to hardcode their exact locations. These named artifacts will be looked up
appropriately (in the tree, or for the public release, possibly in the PATH or in
findlib). *)
open! Import
type t
val create : Context.t -> (Path.t * Jbuild_types.Stanza.t list) list -> t
(** A named artifact that is looked up in the PATH if not found in the tree *)
val binary : t -> string -> (Path.t, fail) result
(** A named artifact that is looked up in the given library. *)
val file_of_lib
: ?use_provides:bool
-> t
-> lib:string
-> file:string
-> (Path.t, fail) result

View File

@ -270,8 +270,8 @@ module Shexp = struct
exec t ~dir ~env ~env_extra:String_map.empty ~stdout_to:None ~tail:true ~f
end
let action action ~dir ~env ~targets =
prim ~targets (fun f ->
let action action ~dir ~env ~targets ~expand:f =
prim ~targets (fun () ->
match (action : _ Action.t) with
| Bash cmd ->
Future.run Strict ~dir:(Path.to_string dir) ~env
@ -288,6 +288,14 @@ let copy ~src ~dst =
create_file ~target:dst (fun () ->
copy_file ~src:(Path.to_string src) ~dst:(Path.to_string dst))
let symlink ~src ~dst =
if Sys.win32 then
copy ~src ~dst
else
path src >>>
create_file ~target:dst (fun () ->
Unix.symlink (Path.to_string src) (Path.to_string dst))
let touch target =
create_file ~target (fun _ ->
Unix.close

View File

@ -83,13 +83,16 @@ val action
-> dir:Path.t
-> env:string array
-> targets:Path.t list
-> (dir:Path.t -> 'a -> string, unit) t
-> expand:(dir:Path.t -> 'a -> string)
-> (unit, unit) t
(** Create a file with the given contents. *)
val echo : Path.t -> (string, unit) t
val copy : src:Path.t -> dst:Path.t -> (unit, unit) t
val symlink : src:Path.t -> dst:Path.t -> (unit, unit) t
val touch : Path.t -> (unit, unit) t
type lib_dep_kind =

5
src/config.ml Normal file
View File

@ -0,0 +1,5 @@
open! Import
let local_install_dir =
let dir = Path.(relative root) "install" in
fun ~context -> Path.relative dir context

6
src/config.mli Normal file
View File

@ -0,0 +1,6 @@
(** Configuration parameters *)
open! Import
(** Local installation directory *)
val local_install_dir : context:string -> Path.t

View File

@ -63,6 +63,7 @@ type t =
; ast_intf_magic_number : string
; cmxs_magic_number : string
; cmt_magic_number : string
; which_cache : (string, Path.t option) Hashtbl.t
}
let compare a b = compare a.name b.name
@ -102,6 +103,9 @@ let get_env env var =
in
loop 0
let which ~cache ~path x =
Hashtbl.find_or_add cache x ~f:(Bin.which ~path)
let create ~(kind : Kind.t) ~path ~env ~name ~merlin =
let opam_var_cache = Hashtbl.create 128 in
(match kind with
@ -111,7 +115,8 @@ let create ~(kind : Kind.t) ~path ~env ~name ~merlin =
let prog_not_found_in_path prog =
die "Program %s not found in PATH (context: %s)" prog name
in
let which x = Bin.which ~path x in
let which_cache = Hashtbl.create 128 in
let which x = which ~cache:which_cache ~path x in
let ocamlc =
match which "ocamlc" with
| None -> prog_not_found_in_path "ocamlc"
@ -248,6 +253,8 @@ let create ~(kind : Kind.t) ~path ~env ~name ~merlin =
; ast_intf_magic_number = get "ast_intf_magic_number"
; cmxs_magic_number = get "cmxs_magic_number"
; cmt_magic_number = get "cmt_magic_number"
; which_cache
}
let opam_config_var t var = opam_config_var ~env:t.env ~cache:t.opam_var_cache var
@ -310,7 +317,7 @@ let create_for_opam ?root ~switch ~name ?(merlin=false) () =
create ~kind:(Opam { root; switch }) ~path ~env:(extend_env ~vars ~env)
~name ~merlin
let which t s = Bin.which ~path:t.path s
let which t s = which ~cache:t.which_cache ~path:t.path s
let install_prefix t =
opam_config_var t "prefix" >>| function

View File

@ -103,6 +103,8 @@ type t =
; ast_intf_magic_number : string
; cmxs_magic_number : string
; cmt_magic_number : string
; which_cache : (string, Path.t option) Hashtbl.t
}
(** Compare the context names *)

View File

@ -392,6 +392,11 @@ let find_exn t name =
Hashtbl.add t.packages ~key:name ~data:Absent;
raise (Package_not_found name)
let find t name =
match find_exn t name with
| exception (Package_not_found _) -> None
| x -> Some x
let available t name =
match find_exn t name with
| _ -> true

View File

@ -22,6 +22,7 @@ type package =
; has_headers : bool
}
val find : t -> string -> package option
val find_exn : t -> string -> package
val available : t -> string -> bool

View File

@ -240,21 +240,21 @@ module Gen(P : Params) = struct
let _ = t
end
module Named_artifacts = struct
open Named_artifacts
module Artifacts = struct
open Artifacts
let t = create ~path:ctx.path findlib (List.map P.stanzas ~f:(fun d -> (d.ctx_dir, d.stanzas)))
let t = create ctx (List.map P.stanzas ~f:(fun d -> (d.ctx_dir, d.stanzas)))
let binary name = Build.arr (fun _ -> binary t name)
let in_findlib ~dir ~dep_kind name =
let pkg =
let binary name = binary t name
let file_of_lib ?use_provides ~dir name =
let lib, file =
match String.lsplit2 name ~on:':' with
| None -> invalid_arg "Named_artifacts.in_findlib"
| Some (pkg, _) -> pkg
| None ->
Loc.fail (Loc.in_file (Path.to_string (Path.relative dir "jbuild")))
"invalid ${lib:...} form: %s" name
| Some x -> x
in
Build.record_lib_deps ~dir ~kind:dep_kind [Direct pkg]
>>>
(Build.arr (fun () -> in_findlib t name))
(lib, file_of_lib t ~lib ~file ?use_provides)
(* Hides [t] so that we don't resolve things statically *)
let t = ()
@ -1270,53 +1270,78 @@ module Gen(P : Params) = struct
+-----------------------------------------------------------------+ *)
module Action_interpret : sig
type expander
val expand
val run
: Action.Unexpanded.t
-> dir:Path.t
-> dep_kind:Build.lib_dep_kind
-> targets:Path.t list
-> deps:Dep_conf.t list
-> (unit, expander) Build.t
val run
: Action.Unexpanded.t
-> dir:Path.t
-> targets:Path.t list
-> (expander, unit) Build.t
-> (unit, unit) Build.t
end = struct
module U = Action.Unexpanded
type expander = dir:Path.t -> String_with_vars.t -> string
type resolved_forms =
{ (* Mapping from ${...} forms to their resolutions *)
artifacts : Path.t String_map.t
; (* Failed resolutions *)
failures : fail list
; (* All "name" for ${lib:name:...} forms *)
lib_deps : String_set.t
}
type artefact =
| Direct of Path.t
| Dyn of (unit, Path.t) Build.t
let add_artifact ?lib_dep acc ~var result =
let lib_deps =
match lib_dep with
| None -> acc.lib_deps
| Some lib -> String_set.add lib acc.lib_deps
in
match result with
| Ok path ->
{ acc with
artifacts = String_map.add acc.artifacts ~key:var ~data:path
; lib_deps
}
| Error fail ->
{ acc with
failures = fail :: acc.failures
; lib_deps
}
let extract_artifacts ~dir ~dep_kind t =
U.fold t ~init:String_map.empty ~f:(fun acc var ->
let module N = Named_artifacts in
let extract_artifacts ~dir t =
let init =
{ artifacts = String_map.empty
; failures = []
; lib_deps = String_set.empty
}
in
U.fold t ~init ~f:(fun acc var ->
let module A = Artifacts in
match String.lsplit2 var ~on:':' with
(* CR-someday jdimino: map the exe to the host exe here *)
| Some ("exe", s) ->
String_map.add acc ~key:var ~data:(Direct (Path.relative dir s))
| Some ("bin", s) -> String_map.add acc ~key:var ~data:(Dyn (N.binary s))
| Some ("exe" , s) -> add_artifact acc ~var (Ok (Path.relative dir s))
| Some ("path" , s) -> add_artifact acc ~var (Ok (Path.relative dir s))
| Some ("bin" , s) -> add_artifact acc ~var (A.binary s)
| Some ("lib" , s)
| Some ("libexec" , s) ->
let lib_dep, res = A.file_of_lib ~dir s in
add_artifact acc ~var ~lib_dep res
(* CR-someday jdimino: allow this only for (jbuild_version jane_street) *)
| Some ("findlib" , s) ->
String_map.add acc ~key:var ~data:(Dyn (N.in_findlib ~dir ~dep_kind s))
let lib_dep, res = A.file_of_lib ~dir s ~use_provides:true in
add_artifact acc ~var ~lib_dep res
| _ -> acc)
let expand_string_with_vars ~artifact_map ~targets ~deps : expander =
let expand_string_with_vars ~artifacts ~targets ~deps =
let dep_exn ~dir name = function
| Some dep -> Path.reach ~from:dir dep
| None -> die "cannot use ${%s} with files_recursively_in" name
in
let lookup ~dir var_name =
match String_map.find var_name artifact_map with
match String_map.find var_name artifacts with
| Some path -> Some (Path.reach ~from:dir path)
| None ->
match var_name with
| "@" -> Some (String.concat ~sep:" " (List.map targets ~f:(Path.reach ~from:dir)))
| "@" -> Some (String.concat ~sep:" "
(List.map targets ~f:(Path.reach ~from:dir)))
| "<" -> Some (match deps with [] -> "" | dep1::_ -> dep_exn ~dir var_name dep1)
| "^" ->
let deps = List.map deps ~f:(dep_exn ~dir var_name) in
@ -1326,42 +1351,26 @@ module Gen(P : Params) = struct
fun ~dir str ->
String_with_vars.expand str ~f:(lookup ~dir)
let expand t ~dir ~dep_kind ~targets ~deps =
let run t ~dir ~dep_kind ~targets ~deps =
let deps =
List.map deps ~f:(fun dep ->
Option.map (Dep_conf_interpret.only_plain_file ~dir dep)
~f:(Path.relative dir))
in
let needed_artifacts = extract_artifacts ~dir ~dep_kind t in
if String_map.is_empty needed_artifacts then
let expand = expand_string_with_vars ~artifact_map:String_map.empty ~targets ~deps in
Build.return expand
else begin
let directs, dyns =
String_map.bindings needed_artifacts
|> List.partition_map ~f:(function
| (name, Direct x) -> Inl (name, x)
| (name, Dyn x) -> Inr (name, x))
in
Build.fanout
(Build.paths (List.map directs ~f:snd))
(Build.all (List.map dyns ~f:(fun (name, artifact) ->
artifact
>>>
Build.arr (fun path -> (name, path)))))
>>^ snd
let forms = extract_artifacts ~dir t in
let build =
Build.record_lib_deps ~dir ~kind:dep_kind
(String_set.elements forms.lib_deps
|> List.map ~f:(fun s -> Lib_dep.Direct s))
>>>
Build.dyn_paths (Build.arr (List.map ~f:snd))
Build.paths (String_map.values forms.artifacts)
>>>
Build.arr (fun artifacts ->
let artifact_map =
String_map.of_alist_exn (List.rev_append directs artifacts)
in
expand_string_with_vars ~artifact_map ~targets ~deps)
end
let run action ~dir ~targets =
Build.action action ~dir ~env:ctx.env ~targets
Build.action t ~dir ~env:ctx.env ~targets
~expand:(expand_string_with_vars ~artifacts:forms.artifacts ~targets ~deps)
in
match forms.failures with
| [] -> build
| fail :: _ -> Build.fail fail >>> build
end
(* +-----------------------------------------------------------------+
@ -1373,17 +1382,12 @@ module Gen(P : Params) = struct
add_rule
(Dep_conf_interpret.dep_of_list ~dir rule.deps
>>>
Action_interpret.expand
Action_interpret.run
rule.action
~dir
~dep_kind:Required
~targets
~deps:rule.deps
>>>
Action_interpret.run
rule.action
~dir
~targets)
~deps:rule.deps)
let alias_rules (alias_conf : Alias_conf.t) ~dir =
let digest =
@ -1407,13 +1411,13 @@ module Gen(P : Params) = struct
| None -> deps
| Some action ->
deps
>>> Action_interpret.expand
>>> Action_interpret.run
action
~dir
~dep_kind:Required
~targets:[]
~deps:alias_conf.deps
>>> Action_interpret.run action ~dir ~targets:[] in
in
add_rule (deps >>> dummy)
(* +-----------------------------------------------------------------+
@ -1708,6 +1712,13 @@ module Gen(P : Params) = struct
List.exists [ "README"; "LICENSE"; "CHANGE"; "HISTORY"]
~f:(fun prefix -> String.is_prefix fn ~prefix)
let local_install_rules (entries : Install.Entry.t list) ~package =
let install_dir = Config.local_install_dir ~context:ctx.name in
List.iter entries ~f:(fun entry ->
let dst = Install.Entry.relative_installed_path entry ~package in
add_rule
(Build.symlink ~src:entry.src ~dst:(Path.append install_dir dst)))
let install_file package_path package =
let entries =
List.concat_map stanzas_to_consider_for_install ~f:(fun (dir, stanza) ->
@ -1749,6 +1760,7 @@ module Gen(P : Params) = struct
let fn =
Path.relative (Path.append ctx.build_dir package_path) (package ^ ".install")
in
local_install_rules entries ~package;
add_rule
(Build.path_set (Install.files entries) >>>
Build.create_file ~target:fn (fun () ->

View File

@ -97,7 +97,7 @@ module Hashtbl = struct
match find t key with
| Some x -> x
| None ->
let x = f () in
let x = f key in
add t ~key ~data:x;
x
end
@ -227,6 +227,14 @@ module String = struct
(sub s ~pos:0 ~len:i,
sub s ~pos:(i + 1) ~len:(String.length s - i - 1))
let rsplit2 s ~on =
match rindex s on with
| exception Not_found -> None
| i ->
Some
(sub s ~pos:0 ~len:i,
sub s ~pos:(i + 1) ~len:(String.length s - i - 1))
let index s ch =
match index s ch with
| i -> Some i

View File

@ -61,6 +61,51 @@ module Entry = struct
; dst
; section
}
module Paths = struct
let lib = Path.(relative root) "lib"
let libexec = Path.(relative root) "libexec"
let bin = Path.(relative root) "bin"
let sbin = Path.(relative root) "sbin"
let toplevel = Path.(relative root) "lib/toplevel"
let share = Path.(relative root) "share"
let share_root = Path.(relative root) "share_root"
let etc = Path.(relative root) "etc"
let doc = Path.(relative root) "doc"
let stublibs = Path.(relative root) "lib/stublibs"
let man = Path.(relative root) "man"
end
let relative_installed_path t ~package =
let main_dir =
match t.section with
| Bin -> Paths.bin
| Sbin -> Paths.sbin
| Toplevel -> Paths.toplevel
| Share_root -> Paths.share_root
| Stublibs -> Paths.stublibs
| Man -> Paths.man
| Lib -> Path.relative Paths.lib package
| Libexec -> Path.relative Paths.libexec package
| Share -> Path.relative Paths.share package
| Etc -> Path.relative Paths.etc package
| Doc -> Path.relative Paths.doc package
| Misc -> invalid_arg "Install.Entry.relative_installed_path"
in
let dst =
match t.dst with
| Some x -> x
| None ->
let dst = Path.basename t.src in
match t.section with
| Man -> begin
match String.rsplit2 dst ~on:'.' with
| None -> dst
| Some (_, sec) -> sprintf "man%s/%s" sec dst
end
| _ -> dst
in
Path.relative main_dir dst
end
module SMap = Map.Make(Section)

View File

@ -26,6 +26,8 @@ module Entry : sig
}
val make : Section.t -> ?dst:string -> Path.t -> t
val relative_installed_path : t -> package:string -> Path.t
end
val files : Entry.t list -> Path.Set.t

View File

@ -701,7 +701,6 @@ module Stanza = struct
; cstr "rule" (Rule.v1 @> nil) (fun x -> [Rule x])
; cstr "ocamllex" (list string @> nil) (fun x -> rules (Rule.ocamllex_v1 x))
; cstr "ocamlyacc" (list string @> nil) (fun x -> rules (Rule.ocamlyacc_v1 x))
; cstr "provides" (Provides.v1 @> nil) (fun x -> [Provides x])
; cstr "install" (Install_conf.v1 @> nil) (fun x -> [Install x])
; cstr "alias" (Alias_conf.v1 @> nil) (fun x -> [Alias x])
(* Just for validation and error messages *)

View File

@ -1,40 +0,0 @@
open Import
open Jbuild_types
type t =
{ path : Path.t list
; findlib : Findlib.t
; artifacts : (string, Path.t) Hashtbl.t
}
let create ~path findlib stanzas =
let artifacts : (string, Path.t) Hashtbl.t = Hashtbl.create 1024 in
List.iter stanzas ~f:(fun (dir, stanzas) ->
List.iter stanzas ~f:(fun stanza ->
match (stanza : Stanza.t) with
| Provides { name; file } ->
Hashtbl.add artifacts ~key:name ~data:(Path.relative dir file)
| _ -> ()));
{ path; findlib; artifacts }
let binary t name =
match Hashtbl.find t.artifacts name with
| Some p -> p
| None ->
match Bin.which ~path:t.path name with
| Some p ->
Hashtbl.add t.artifacts ~key:name ~data:p;
p
| None ->
die "Program %s not found in the tree or in the PATH" name
let in_findlib t name =
match Hashtbl.find t.artifacts name with
| Some p -> p
| None ->
match String.lsplit2 name ~on:':' with
| None -> invalid_arg "Named_artifacts.in_findlib"
| Some (pkg, file) ->
let p = Path.relative (Findlib.find_exn t.findlib pkg).dir file in
Hashtbl.add t.artifacts ~key:name ~data:p;
p

View File

@ -1,20 +0,0 @@
(** [Named_artifact] provides a way to reference artifacts in jbuild rules without having
to hardcode their exact locations. These named artifacts will be looked up
appropriately (in the tree, or for the public release, possibly in the PATH or in
findlib). *)
open! Import
type t
val create : path:Path.t list -> Findlib.t -> (Path.t * Jbuild_types.Stanza.t list) list -> t
(** In the three following functions, the string argument matches the first argument of
the [(provides ...)] stanza in the jbuild. *)
(** A named artifact that is looked up in the PATH if not found in the tree *)
val binary : t -> string -> Path.t
(** A named artifact that is looked up in the given findlib package if not found in the
tree. Syntax is: ["<findlib_package>:<filename>"]. *)
val in_findlib : t -> string -> Path.t

View File

@ -51,6 +51,7 @@ let t sexps =
if name = "" ||
String.is_prefix name ~prefix:"." ||
name = "log" ||
name = "install" ||
String.contains name '/' ||
String.contains name '\\' then
of_sexp_errorf sexp "%S is not allowed as a build context name" name;