Fix jbuilder rule in case of missing binaries (#292)

Make jbuilder rules work even when binaries are missing

* Proper error messages for missing binaries

* Unify Prog_spec and Maybe_prog

both can simply be unified into a path type that has a hint for the error

* Remove scarcely useful in_the_tree parameter

It's always true except for the C compiler. In which case, there's no harm in
making it true.

* Make Artifacts return Action.Prog

The old return value was simply converted to this anyway. It's simpler to just
return the proper error straight up.

* Remove remains of in_the_tree
This commit is contained in:
Rudi Grinberg 2017-11-07 21:42:55 +08:00 committed by GitHub
parent 9c8ecc9fbc
commit a57c488dd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 99 additions and 91 deletions

View File

@ -128,14 +128,35 @@ module Make_mapper
| Digest_files x -> Digest_files (List.map x ~f:f_path) | Digest_files x -> Digest_files (List.map x ~f:f_path)
end end
module Prog = struct
module Not_found = struct
type t =
{ context : string
; program : string
; hint : string option
}
let raise { context ; program ; hint } =
Utils.program_not_found ?hint ~context program
end
type t = (Path.t, Not_found.t) result
let t sexp = Ok (Path.t sexp)
let sexp_of_t = function
| Ok s -> Path.sexp_of_t s
| Error (e : Not_found.t) -> Sexp.To_sexp.string e.program
end
module type Ast = Action_intf.Ast module type Ast = Action_intf.Ast
with type program = Path.t with type program = Prog.t
with type path = Path.t with type path = Path.t
with type string = String.t with type string = String.t
module rec Ast : Ast = Ast module rec Ast : Ast = Ast
include Make_ast include Make_ast
(Path) (Prog)
(Path) (Path)
(struct (struct
type t = string type t = string
@ -169,8 +190,8 @@ module Unresolved = struct
let resolve t ~f = let resolve t ~f =
map t ~f_path:(fun x -> x) ~f_string:(fun x -> x) map t ~f_path:(fun x -> x) ~f_string:(fun x -> x)
~f_program:(function ~f_program:(function
| This p -> p | This p -> Ok p
| Search s -> f s) | Search s -> Ok (f s))
end end
module Var_expansion = struct module Var_expansion = struct
@ -519,7 +540,9 @@ let exec_echo stdout_to str =
let rec exec t ~ectx ~dir ~env_extra ~stdout_to ~stderr_to = let rec exec t ~ectx ~dir ~env_extra ~stdout_to ~stderr_to =
match t with match t with
| Run (prog, args) -> | Run (Error e, _) ->
Prog.Not_found.raise e
| Run (Ok prog, args) ->
run ~ectx ~dir ~env_extra ~stdout_to ~stderr_to prog args run ~ectx ~dir ~env_extra ~stdout_to ~stderr_to prog args
| Chdir (dir, t) -> | Chdir (dir, t) ->
exec t ~ectx ~dir ~env_extra ~stdout_to ~stderr_to exec t ~ectx ~dir ~env_extra ~stdout_to ~stderr_to
@ -657,7 +680,10 @@ let sandbox t ~sandboxed ~deps ~targets =
Some (Ast.Symlink (path, sandboxed path)) Some (Ast.Symlink (path, sandboxed path))
else else
None)) None))
; map t ~f_string:(fun x -> x) ~f_path:sandboxed ~f_program:sandboxed ; map t ~f_string:(fun x -> x) ~f_path:sandboxed
~f_program:(function
| Ok p -> Ok (sandboxed p)
| Error _ as e -> e)
; Progn (List.filter_map targets ~f:(fun path -> ; Progn (List.filter_map targets ~f:(fun path ->
if Path.is_local path then if Path.is_local path then
Some (Ast.Rename (sandboxed path, path)) Some (Ast.Rename (sandboxed path, path))
@ -680,7 +706,8 @@ module Infer = struct
let rec infer acc t = let rec infer acc t =
match t with match t with
| Run (prog, _) -> acc +< prog | Run (Ok prog, _) -> acc +< prog
| Run (Error _, _) -> acc
| Redirect (_, fn, t) -> infer (acc +@ fn) t | Redirect (_, fn, t) -> infer (acc +@ fn) t
| Cat fn -> acc +< fn | Cat fn -> acc +< fn
| Write_file (fn, _) -> acc +@ fn | Write_file (fn, _) -> acc +@ fn

View File

@ -14,8 +14,24 @@ end
module Outputs : module type of struct include Action_intf.Outputs end module Outputs : module type of struct include Action_intf.Outputs end
(** result of the lookup of a program, the path to it or information about the
failure and possibly a hint how to fix it *)
module Prog : sig
module Not_found : sig
type t =
{ context : string
; program : string
; hint : string option
}
val raise : t -> _
end
type t = (Path.t, Not_found.t) result
end
include Action_intf.Ast include Action_intf.Ast
with type program := Path.t with type program := Prog.t
with type path := Path.t with type path := Path.t
with type string := string with type string := string

View File

@ -57,10 +57,10 @@ let create (context : Context.t) l ~f =
; local_libs ; local_libs
} }
let binary t ?hint ?(in_the_tree=true) name = let binary t ?hint name =
if not (Filename.is_relative name) then if not (Filename.is_relative name) then
Ok (Path.absolute name) Ok (Path.absolute name)
else if in_the_tree then begin else
match String_map.find name t.local_bins with match String_map.find name t.local_bins with
| Some path -> Ok path | Some path -> Ok path
| None -> | None ->
@ -68,24 +68,11 @@ let binary t ?hint ?(in_the_tree=true) name =
| Some p -> Ok p | Some p -> Ok p
| None -> | None ->
Error Error
{ fail = fun () -> { Action.Prog.Not_found.
Utils.program_not_found name program = name
~context:t.context.name ; hint
?hint ; context = t.context.Context.name
~in_the_tree:true
} }
end else begin
match Context.which t.context name with
| Some p -> Ok p
| None ->
Error
{ fail = fun () ->
Utils.program_not_found name
~context:t.context.name
?hint
~in_the_tree:false
}
end
let file_of_lib t ~from ~lib ~file = let file_of_lib t ~from ~lib ~file =
match String_map.find lib t.local_libs with match String_map.find lib t.local_libs with

View File

@ -8,17 +8,15 @@ val create
-> f:('a -> Jbuild.Stanza.t list) -> f:('a -> Jbuild.Stanza.t list)
-> t -> t
(** A named artifact that is looked up in the PATH if not found in the tree or (** A named artifact that is looked up in the PATH if not found in the tree
[in_the_tree] is [false].
If the name is an absolute path, it is used as it. If the name is an absolute path, it is used as it.
*) *)
val binary val binary
: t : t
-> ?hint:string -> ?hint:string
-> ?in_the_tree:bool (* default true *)
-> string -> string
-> (Path.t, fail) result -> Action.Prog.t
(** [file_of_lib t ~from name] a named artifact that is looked up in the given library. (** [file_of_lib t ~from name] a named artifact that is looked up in the given library.

View File

@ -6,12 +6,6 @@ module Vspec = struct
type 'a t = T : Path.t * 'a Vfile_kind.t -> 'a t type 'a t = T : Path.t * 'a Vfile_kind.t -> 'a t
end end
module Prog_spec = struct
type 'a t =
| Dep of Path.t
| Dyn of ('a -> Path.t)
end
type lib_dep_kind = type lib_dep_kind =
| Optional | Optional
| Required | Required
@ -194,10 +188,11 @@ let files_recursively_in ~dir ~file_tree =
let store_vfile spec = Store_vfile spec let store_vfile spec = Store_vfile spec
let get_prog (prog : _ Prog_spec.t) = let get_prog = function
match prog with | Ok p -> path p >>> arr (fun _ -> Ok p)
| Dep p -> path p >>> arr (fun _ -> p) | Error f ->
| Dyn f -> arr f >>> dyn_paths (arr (fun x -> [x])) arr (fun _ -> Error f)
>>> dyn_paths (arr (function Error _ -> [] | Ok x -> [x]))
let prog_and_args ?(dir=Path.root) prog args = let prog_and_args ?(dir=Path.root) prog args =
Paths (Arg_spec.add_deps args Pset.empty) Paths (Arg_spec.add_deps args Pset.empty)

View File

@ -76,18 +76,12 @@ val fail : ?targets:Path.t list -> fail -> (_, _) t
result is computed only once. *) result is computed only once. *)
val memoize : string -> (unit, 'a) t -> (unit, 'a) t val memoize : string -> (unit, 'a) t -> (unit, 'a) t
module Prog_spec : sig
type 'a t =
| Dep of Path.t
| Dyn of ('a -> Path.t)
end
val run val run
: context:Context.t : context:Context.t
-> ?dir:Path.t (* default: [context.build_dir] *) -> ?dir:Path.t (* default: [context.build_dir] *)
-> ?stdout_to:Path.t -> ?stdout_to:Path.t
-> ?extra_targets:Path.t list -> ?extra_targets:Path.t list
-> 'a Prog_spec.t -> Action.Prog.t
-> 'a Arg_spec.t list -> 'a Arg_spec.t list
-> ('a, Action.t) t -> ('a, Action.t) t

View File

@ -108,7 +108,7 @@ module Gen(P : Params) = struct
>>> >>>
Build.dyn_paths (Build.arr objs) Build.dyn_paths (Build.arr objs)
>>> >>>
Build.run ~context:ctx (Dep compiler) Build.run ~context:ctx (Ok compiler)
~extra_targets:( ~extra_targets:(
match mode with match mode with
| Byte -> [] | Byte -> []
@ -138,7 +138,7 @@ module Gen(P : Params) = struct
(* We have to execute the rule in the library directory as the .o is produced in (* We have to execute the rule in the library directory as the .o is produced in
the current directory *) the current directory *)
~dir ~dir
(Dep ctx.ocamlc) (Ok ctx.ocamlc)
[ As (Utils.g ()) [ As (Utils.g ())
; Dyn (fun (c_flags, libs) -> ; Dyn (fun (c_flags, libs) ->
S [ Lib.c_include_flags libs S [ Lib.c_include_flags libs
@ -163,9 +163,7 @@ module Gen(P : Params) = struct
(* We have to execute the rule in the library directory as the .o is produced in (* We have to execute the rule in the library directory as the .o is produced in
the current directory *) the current directory *)
~dir ~dir
(SC.resolve_program sctx ctx.c_compiler (SC.resolve_program sctx ctx.c_compiler)
(* The C compiler surely is not in the tree *)
~in_the_tree:false)
[ S [A "-I"; Path ctx.stdlib_dir] [ S [A "-I"; Path ctx.stdlib_dir]
; As (SC.cxx_flags sctx) ; As (SC.cxx_flags sctx)
; Dyn (fun (cxx_flags, libs) -> ; Dyn (fun (cxx_flags, libs) ->
@ -328,7 +326,7 @@ module Gen(P : Params) = struct
>>> >>>
Build.run ~context:ctx Build.run ~context:ctx
~extra_targets:targets ~extra_targets:targets
(Dep ctx.ocamlmklib) (Ok ctx.ocamlmklib)
[ As (Utils.g ()) [ As (Utils.g ())
; if custom then A "-custom" else As [] ; if custom then A "-custom" else As []
; A "-o" ; A "-o"
@ -390,7 +388,7 @@ module Gen(P : Params) = struct
Ocaml_flags.get flags Native Ocaml_flags.get flags Native
>>> >>>
Build.run ~context:ctx Build.run ~context:ctx
(Dep ocamlopt) (Ok ocamlopt)
[ Dyn (fun flags -> As flags) [ Dyn (fun flags -> As flags)
; A "-shared"; A "-linkall" ; A "-shared"; A "-linkall"
; A "-I"; Path dir ; A "-I"; Path dir
@ -473,7 +471,7 @@ module Gen(P : Params) = struct
(SC.expand_and_eval_set sctx ~scope ~dir link_flags ~standard:[]) (SC.expand_and_eval_set sctx ~scope ~dir link_flags ~standard:[])
>>> >>>
Build.run ~context:ctx Build.run ~context:ctx
(Dep compiler) (Ok compiler)
[ Dyn (fun (_, (flags,_)) -> As flags) [ Dyn (fun (_, (flags,_)) -> As flags)
; A "-o"; Target exe ; A "-o"; Target exe
; Dyn (fun (_, (_, link_flags)) -> As (link_custom @ link_flags)) ; Dyn (fun (_, (_, link_flags)) -> As (link_custom @ link_flags))

View File

@ -63,7 +63,7 @@ let build_cm sctx ?sandbox ~dynlink ~flags ~cm_kind ~(dep_graph:Ocamldep.dep_gra
other_cm_files >>> other_cm_files >>>
requires &&& requires &&&
Ocaml_flags.get_for_cm flags ~cm_kind >>> Ocaml_flags.get_for_cm flags ~cm_kind >>>
Build.run ~context:ctx (Dep compiler) Build.run ~context:ctx (Ok compiler)
~extra_targets ~extra_targets
[ Dyn (fun (_, ocaml_flags) -> As ocaml_flags) [ Dyn (fun (_, ocaml_flags) -> As ocaml_flags)
; cmt_args ; cmt_args

View File

@ -76,7 +76,7 @@ let rules sctx ~ml_kind ~dir ~item ~modules ~alias_module ~lib_interface_module
in in
let ctx = SC.context sctx in let ctx = SC.context sctx in
SC.add_rule sctx SC.add_rule sctx
(Build.run ~context:ctx (Dep ctx.ocamldep) [A "-modules"; S files] (Build.run ~context:ctx (Ok ctx.ocamldep) [A "-modules"; S files]
~stdout_to:ocamldep_output); ~stdout_to:ocamldep_output);
Build.memoize (Path.to_string ocamldep_output) Build.memoize (Path.to_string ocamldep_output)
(Build.lines_of ocamldep_output (Build.lines_of ocamldep_output

View File

@ -82,13 +82,8 @@ let expand_vars t ~scope ~dir s =
Some (Path.reach ~from:dir (Path.append t.context.build_dir scope.Scope.root)) Some (Path.reach ~from:dir (Path.append t.context.build_dir scope.Scope.root))
| var -> String_map.find var t.vars) | var -> String_map.find var t.vars)
let resolve_program_internal t ?hint ?(in_the_tree=true) bin = let resolve_program t ?hint bin =
Artifacts.binary t.artifacts ?hint ~in_the_tree bin Artifacts.binary ?hint t.artifacts bin
let resolve_program t ?hint ?in_the_tree bin =
match resolve_program_internal t ?hint ?in_the_tree bin with
| Error fail -> Build.Prog_spec.Dyn (fun _ -> fail.fail ())
| Ok path -> Build.Prog_spec.Dep path
let create let create
~(context:Context.t) ~(context:Context.t)
@ -519,7 +514,6 @@ module Action = struct
in in
let t = let t =
U.partial_expand dir t ~f:(fun loc key -> U.partial_expand dir t ~f:(fun loc key ->
let module A = Artifacts in
let open Action.Var_expansion in let open Action.Var_expansion in
let cos, var = parse_bang key in let cos, var = parse_bang key in
match String.lsplit2 var ~on:':' with match String.lsplit2 var ~on:':' with
@ -527,21 +521,25 @@ module Action = struct
| Some ("exe" , s) -> static_dep_exp acc (Path.relative dir s) | Some ("exe" , s) -> static_dep_exp acc (Path.relative dir s)
| Some ("path" , s) -> static_dep_exp acc (Path.relative dir s) | Some ("path" , s) -> static_dep_exp acc (Path.relative dir s)
| Some ("bin" , s) -> begin | Some ("bin" , s) -> begin
match A.binary (artifacts sctx) s with match Artifacts.binary (artifacts sctx) s with
| Ok path -> static_dep_exp acc path | Ok path ->
| Error fail -> add_fail acc fail static_dep_exp acc path
| Error e ->
add_fail acc ({ fail = fun () -> Action.Prog.Not_found.raise e })
end end
(* "findlib" for compatibility with Jane Street packages which are not yet updated (* "findlib" for compatibility with Jane Street packages which are not yet updated
to convert "findlib" to "lib" *) to convert "findlib" to "lib" *)
| Some (("lib"|"findlib"), s) -> begin | Some (("lib"|"findlib"), s) -> begin
let lib_dep, res = A.file_of_lib (artifacts sctx) ~loc ~from:dir s in let lib_dep, res =
Artifacts.file_of_lib (artifacts sctx) ~loc ~from:dir s in
add_lib_dep acc lib_dep dep_kind; add_lib_dep acc lib_dep dep_kind;
match res with match res with
| Ok path -> static_dep_exp acc path | Ok path -> static_dep_exp acc path
| Error fail -> add_fail acc fail | Error fail -> add_fail acc fail
end end
| Some ("libexec" , s) -> begin | Some ("libexec" , s) -> begin
let lib_dep, res = A.file_of_lib (artifacts sctx) ~loc ~from:dir s in let lib_dep, res =
Artifacts.file_of_lib (artifacts sctx) ~loc ~from:dir s in
add_lib_dep acc lib_dep dep_kind; add_lib_dep acc lib_dep dep_kind;
match res with match res with
| Error fail -> add_fail acc fail | Error fail -> add_fail acc fail
@ -693,9 +691,9 @@ module Action = struct
expand_step2 t ~dir ~dynamic_expansions ~deps_written_by_user expand_step2 t ~dir ~dynamic_expansions ~deps_written_by_user
in in
Action.Unresolved.resolve unresolved ~f:(fun prog -> Action.Unresolved.resolve unresolved ~f:(fun prog ->
match resolve_program_internal sctx prog with match Artifacts.binary sctx.artifacts prog with
| Ok path -> path | Ok path -> path
| Error fail -> fail.fail ())) | Error fail -> Action.Prog.Not_found.raise fail))
>>> >>>
Build.dyn_paths (Build.arr (fun action -> Build.dyn_paths (Build.arr (fun action ->
let { Action.Infer.Outcome.deps; targets = _ } = let { Action.Infer.Outcome.deps; targets = _ } =
@ -795,7 +793,7 @@ module PP = struct
>>> >>>
Build.dyn_paths (Build.arr (Lib.archive_files ~mode ~ext_lib:ctx.ext_lib)) Build.dyn_paths (Build.arr (Lib.archive_files ~mode ~ext_lib:ctx.ext_lib))
>>> >>>
Build.run ~context:ctx (Dep compiler) Build.run ~context:ctx (Ok compiler)
[ A "-o" ; Target target [ A "-o" ; Target target
; Dyn (Lib.link_flags ~mode) ; Dyn (Lib.link_flags ~mode)
]) ])
@ -833,7 +831,8 @@ module PP = struct
a new module with only OCaml sources *) a new module with only OCaml sources *)
let setup_reason_rules sctx ~dir (m : Module.t) = let setup_reason_rules sctx ~dir (m : Module.t) =
let ctx = sctx.context in let ctx = sctx.context in
let refmt = resolve_program sctx "refmt" ~hint:"opam install reason" in let refmt =
Artifacts.binary sctx.artifacts "refmt" ~hint:"opam install reason" in
let rule src target = let rule src target =
let src_path = Path.relative dir src in let src_path = Path.relative dir src in
Build.run ~context:ctx refmt Build.run ~context:ctx refmt
@ -895,7 +894,7 @@ module PP = struct
(preprocessor_deps (preprocessor_deps
>>> >>>
Build.run ~context:sctx.context Build.run ~context:sctx.context
(Dep ppx_exe) (Ok ppx_exe)
[ As flags [ As flags
; A "--dump-ast" ; A "--dump-ast"
; As (cookie_library_name lib_name) ; As (cookie_library_name lib_name)

View File

@ -58,19 +58,18 @@ val rules : t -> Build_interpret.Rule.t list
val sources_and_targets_known_so_far : t -> src_path:Path.t -> String_set.t val sources_and_targets_known_so_far : t -> src_path:Path.t -> String_set.t
(** [prog_spec t ?hint ?in_the_tree name] resolve a program. If [in_the_tree] is [true] (** [prog_spec t ?hint name] resolve a program. [name] is looked up in the
(the default), [name] is looked up in the workspace. Otherwise, or if it is not found workspace, if it is not found in the tree is is looked up in the PATH. If it
in the tree is is looked up in the PATH. If it is not found at all, the resulting is not found at all, the resulting [Prog_spec.t] will either return the
[Prog_spec.t] will fail when evaluated. resolved path or a record with details about the error and possibly a hint.
[hint] should tell the user what to install when the program is not found. [hint] should tell the user what to install when the program is not found.
*) *)
val resolve_program val resolve_program
: t : t
-> ?hint:string -> ?hint:string
-> ?in_the_tree:bool (* default true *)
-> string -> string
-> _ Build.Prog_spec.t -> Action.Prog.t
module Libs : sig module Libs : sig
val find : t -> from:Path.t -> string -> Lib.t option val find : t -> from:Path.t -> string -> Lib.t option

View File

@ -88,12 +88,9 @@ let describe_target fn =
| _ -> | _ ->
Path.to_string_maybe_quoted fn Path.to_string_maybe_quoted fn
let program_not_found ?context ?(in_the_tree=false) ?hint prog = let program_not_found ?context ?hint prog =
die "@{<error>Error@}: Program %s not found in%s PATH%s%a" (maybe_quoted prog) die "@{<error>Error@}: Program %s not found in the tree or in PATH%s%a"
(if in_the_tree then (maybe_quoted prog)
" the tree or in"
else
"")
(match context with (match context with
| None -> "" | None -> ""
| Some name -> sprintf " (context: %s)" name) | Some name -> sprintf " (context: %s)" name)

View File

@ -18,11 +18,9 @@ val jbuild_name_in : dir:Path.t -> string
(** Nice description of a target *) (** Nice description of a target *)
val describe_target : Path.t -> string val describe_target : Path.t -> string
(** Raise an error about a program not found in the PATH. If [in_the_tree] is [true], then (** Raise an error about a program not found in the PATH or in the tree *)
assume that the program was looked up in the tree as well. *)
val program_not_found val program_not_found
: ?context:string : ?context:string
-> ?in_the_tree:bool (* default: false *)
-> ?hint:string -> ?hint:string
-> string -> string
-> _ -> _