dune/src/build.ml

288 lines
7.4 KiB
OCaml

open Import
module Vspec = struct
type 'a t = T : Path.t * 'a Vfile_kind.t -> 'a t
end
type lib_dep_kind =
| Optional
| Required
type lib_deps = lib_dep_kind String.Map.t
let merge_lib_dep_kind a b =
match a, b with
| Optional, Optional -> Optional
| _ -> Required
module Repr = struct
type ('a, 'b) t =
| Arr : ('a -> 'b) -> ('a, 'b) t
| Targets : Path.t list -> ('a, 'a) t
| Store_vfile : 'a Vspec.t -> ('a, Action.t) t
| Compose : ('a, 'b) t * ('b, 'c) t -> ('a, 'c) t
| First : ('a, 'b) t -> ('a * 'c, 'b * 'c) t
| Second : ('a, 'b) t -> ('c * 'a, 'c * 'b) t
| Split : ('a, 'b) t * ('c, 'd) t -> ('a * 'c, 'b * 'd) t
| Fanout : ('a, 'b) t * ('a, 'c) t -> ('a, 'b * 'c) t
| Paths : Path.Set.t -> ('a, 'a) t
| Paths_for_rule : Path.Set.t -> ('a, 'a) t
| Paths_glob : glob_state ref -> ('a, Path.Set.t) t
(* The reference gets decided in Build_interpret.deps *)
| If_file_exists : Path.t * ('a, 'b) if_file_exists_state ref -> ('a, 'b) t
| Contents : Path.t -> ('a, string) t
| Lines_of : Path.t -> ('a, string list) t
| Vpath : 'a Vspec.t -> (unit, 'a) t
| Dyn_paths : ('a, Path.Set.t) t -> ('a, 'a) t
| Record_lib_deps : lib_deps -> ('a, 'a) t
| Fail : fail -> (_, _) t
| Memo : 'a memo -> (unit, 'a) t
| Catch : ('a, 'b) t * (exn -> 'b) -> ('a, 'b) t
| Lazy_no_targets : ('a, 'b) t Lazy.t -> ('a, 'b) t
and 'a memo =
{ name : string
; t : (unit, 'a) t
; mutable state : 'a memo_state
}
and 'a memo_state =
| Unevaluated
| Evaluating
| Evaluated of 'a * Path.Set.t
and ('a, 'b) if_file_exists_state =
| Undecided of ('a, 'b) t * ('a, 'b) t
| Decided of bool * ('a, 'b) t
and glob_state =
| G_unevaluated of Loc.t * Path.t * Re.re
| G_evaluated of Path.Set.t
let get_if_file_exists_exn state =
match !state with
| Decided (_, t) -> t
| Undecided _ ->
Exn.code_error "Build.get_if_file_exists_exn: got undecided" []
let get_glob_result_exn state =
match !state with
| G_evaluated l -> l
| G_unevaluated (loc, path, _) ->
Exn.code_error "Build.get_glob_result_exn: got unevaluated"
[ "loc", Loc.sexp_of_t loc
; "path", Path.sexp_of_t path ]
end
include Repr
let repr t = t
let merge_lib_deps a b =
String.Map.merge a b ~f:(fun _ a b ->
match a, b with
| None, None -> None
| x, None | None, x -> x
| Some a, Some b -> Some (merge_lib_dep_kind a b))
let arr f = Arr f
let return x = Arr (fun () -> x)
let record_lib_deps_simple lib_deps =
Record_lib_deps lib_deps
let record_lib_deps ~kind lib_deps =
Record_lib_deps
(List.concat_map lib_deps ~f:(function
| Jbuild.Lib_dep.Direct (_, s) -> [(s, kind)]
| Select { choices; _ } ->
List.concat_map choices ~f:(fun c ->
String.Set.to_list c.Jbuild.Lib_dep.required
|> List.map ~f:(fun d -> (d, Optional))))
|> String.Map.of_list_reduce ~f:merge_lib_dep_kind)
module O = struct
let ( >>> ) a b =
match a, b with
| Arr a, Arr b -> Arr (fun x -> (b (a x)))
| _ -> Compose (a, b)
let ( >>^ ) t f = t >>> arr f
let ( ^>> ) f t = arr f >>> t
let ( *** ) a b = Split (a, b)
let ( &&& ) a b = Fanout (a, b)
end
open O
let first t = First t
let second t = Second t
let fanout a b = Fanout (a, b)
let fanout3 a b c =
let open O in
(a &&& (b &&& c))
>>>
arr (fun (a, (b, c)) -> (a, b, c))
let fanout4 a b c d =
let open O in
(a &&& (b &&& (c &&& d)))
>>>
arr (fun (a, (b, (c, d))) -> (a, b, c, d))
let rec all = function
| [] -> arr (fun _ -> [])
| t :: ts ->
t &&& all ts
>>>
arr (fun (x, y) -> x :: y)
let lazy_no_targets t = Lazy_no_targets t
let path p = Paths (Path.Set.singleton p)
let paths ps = Paths (Path.Set.of_list ps)
let path_set ps = Paths ps
let paths_glob ~loc ~dir re = Paths_glob (ref (G_unevaluated (loc, dir, re)))
let vpath vp = Vpath vp
let dyn_paths t = Dyn_paths (t >>^ Path.Set.of_list)
let dyn_path_set t = Dyn_paths t
let paths_for_rule ps = Paths_for_rule ps
let catch t ~on_error = Catch (t, on_error)
let contents p = Contents p
let lines_of p = Lines_of p
let strings p =
lines_of p
>>^ fun l ->
List.map l ~f:Scanf.unescaped
let read_sexp p syntax =
contents p
>>^ fun s ->
Usexp.parse_string s
~lexer:(File_tree.Dune_file.Kind.lexer syntax)
~fname:(Path.to_string p) ~mode:Single
let if_file_exists p ~then_ ~else_ =
If_file_exists (p, ref (Undecided (then_, else_)))
let file_exists p =
if_file_exists p
~then_:(arr (fun _ -> true))
~else_:(arr (fun _ -> false))
let file_exists_opt p t =
if_file_exists p
~then_:(t >>^ fun x -> Some x)
~else_:(arr (fun _ -> None))
let fail ?targets x =
match targets with
| None -> Fail x
| Some l -> Targets l >>> Fail x
let of_result ?targets = function
| Ok x -> x
| Error e -> fail ?targets { fail = fun () -> raise e }
let of_result_map ?targets res ~f =
match res with
| Ok x -> f x
| Error e -> fail ?targets { fail = fun () -> raise e }
let memoize name t =
Memo { name; t; state = Unevaluated }
let source_tree ~dir ~file_tree =
let prefix_with, dir =
match Path.extract_build_context_dir dir with
| None -> (Path.root, dir)
| Some (ctx_dir, src_dir) -> (ctx_dir, src_dir)
in
let paths = File_tree.files_recursively_in file_tree dir ~prefix_with in
path_set paths >>^ fun _ -> paths
let store_vfile spec = Store_vfile spec
let get_prog = function
| Ok p -> path p >>> arr (fun _ -> Ok p)
| Error f ->
arr (fun _ -> Error f)
>>> dyn_paths (arr (function Error _ -> [] | Ok x -> [x]))
let prog_and_args ?(dir=Path.root) prog args =
Paths (Arg_spec.add_deps args Path.Set.empty)
>>>
(get_prog prog &&&
(arr (Arg_spec.expand ~dir args)
>>>
dyn_path_set (arr (fun (_args, deps) -> deps))
>>>
arr fst))
let run ~context ?(dir=context.Context.build_dir) ?stdout_to prog args =
let targets = Arg_spec.add_targets args (Option.to_list stdout_to) in
prog_and_args ~dir prog args
>>>
Targets targets
>>^ (fun (prog, args) ->
let action : Action.t = Run (prog, args) in
let action =
match stdout_to with
| None -> action
| Some path -> Redirect (Stdout, path, action)
in
Action.Chdir (dir, action))
let action ?dir ~targets action =
Targets targets
>>^ fun _ ->
match dir with
| None -> action
| Some dir -> Action.Chdir (dir, action)
let action_dyn ?dir ~targets () =
Targets targets
>>^ fun action ->
match dir with
| None -> action
| Some dir -> Action.Chdir (dir, action)
let write_file fn s =
action ~targets:[fn] (Write_file (fn, s))
let write_file_dyn fn =
Targets [fn]
>>^ fun s ->
Action.Write_file (fn, s)
let copy ~src ~dst =
path src >>>
action ~targets:[dst] (Copy (src, dst))
let copy_and_add_line_directive ~src ~dst =
path src >>>
action ~targets:[dst]
(Copy_and_add_line_directive (src, dst))
let symlink ~src ~dst =
path src >>>
action ~targets:[dst] (Symlink (src, dst))
let create_file fn =
action ~targets:[fn] (Redirect (Stdout, fn, Progn []))
let remove_tree dir =
arr (fun _ -> Action.Remove_tree dir)
let mkdir dir =
arr (fun _ -> Action.Mkdir dir)
let progn ts =
all ts >>^ fun actions ->
Action.Progn actions
let merge_files_dyn ~target =
dyn_paths (arr fst)
>>^ (fun (sources, extras) ->
Action.Merge_files_into (sources, extras, target))
>>> action_dyn ~targets:[target] ()