246 lines
7.4 KiB
OCaml
246 lines
7.4 KiB
OCaml
|
|
(*
|
||
|
|
* Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
|
||
|
|
* Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
|
||
|
|
* Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
|
||
|
|
*
|
||
|
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
|
* copyright notice and this permission notice appear in all copies.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
|
*)
|
||
|
|
|
||
|
|
open Astring
|
||
|
|
open Action.Syntax
|
||
|
|
|
||
|
|
type t = Device.Graph.t
|
||
|
|
|
||
|
|
let if_keys x =
|
||
|
|
Impl.collect
|
||
|
|
(module Key.Set)
|
||
|
|
(function If cond -> Key.deps cond | App | Dev _ -> Key.Set.empty)
|
||
|
|
x
|
||
|
|
|
||
|
|
module Keys = struct
|
||
|
|
type t = Key.Set.t
|
||
|
|
|
||
|
|
let union a b = Key.Set.union a b
|
||
|
|
let empty = Key.Set.empty
|
||
|
|
end
|
||
|
|
|
||
|
|
let keys x =
|
||
|
|
Impl.collect
|
||
|
|
(module Keys)
|
||
|
|
(function
|
||
|
|
| Dev c -> Key.Set.of_list (Device.keys c)
|
||
|
|
| If cond -> Key.deps cond
|
||
|
|
| App -> Keys.empty)
|
||
|
|
x
|
||
|
|
|
||
|
|
module Runtime_args = struct
|
||
|
|
type t = Runtime_arg.Set.t
|
||
|
|
|
||
|
|
let union a b = Runtime_arg.Set.union a b
|
||
|
|
let empty = Runtime_arg.Set.empty
|
||
|
|
end
|
||
|
|
|
||
|
|
let runtime_args x =
|
||
|
|
Impl.collect
|
||
|
|
(module Runtime_args)
|
||
|
|
(function
|
||
|
|
| Dev c -> Runtime_arg.Set.of_list (Device.runtime_args c)
|
||
|
|
| If _ -> Runtime_args.empty
|
||
|
|
| App -> Runtime_args.empty)
|
||
|
|
x
|
||
|
|
|
||
|
|
module Packages = struct
|
||
|
|
type t = Package.Set.t Key.value
|
||
|
|
|
||
|
|
let union x y = Key.(pure Package.Set.union $ x $ y)
|
||
|
|
let empty = Key.pure Package.Set.empty
|
||
|
|
end
|
||
|
|
|
||
|
|
let packages t =
|
||
|
|
let open Impl in
|
||
|
|
let aux = function
|
||
|
|
| Dev c ->
|
||
|
|
let pkgs = Device.packages c in
|
||
|
|
let runtime_args = Device.runtime_args c in
|
||
|
|
let extra_pkgs =
|
||
|
|
List.fold_left
|
||
|
|
(fun acc k ->
|
||
|
|
let pkgs = Runtime_arg.packages k in
|
||
|
|
Package.Set.(union acc (of_list pkgs)))
|
||
|
|
Package.Set.empty runtime_args
|
||
|
|
in
|
||
|
|
let aux x = Package.Set.(union (of_list x) extra_pkgs) in
|
||
|
|
Key.(pure aux $ pkgs)
|
||
|
|
| If _ | App -> Packages.empty
|
||
|
|
in
|
||
|
|
let return x = Package.Set.to_list x in
|
||
|
|
Key.(pure return $ Impl.collect (module Packages) aux t)
|
||
|
|
|
||
|
|
module Installs = struct
|
||
|
|
type t = Install.t Key.value
|
||
|
|
|
||
|
|
let union x y = Key.(pure Install.union $ x $ y)
|
||
|
|
let empty = Key.pure Install.empty
|
||
|
|
end
|
||
|
|
|
||
|
|
let install i x =
|
||
|
|
Impl.collect
|
||
|
|
(module Installs)
|
||
|
|
(function Dev c -> Device.install c i | If _ | App -> Installs.empty)
|
||
|
|
x
|
||
|
|
|
||
|
|
let files info t =
|
||
|
|
Impl.collect
|
||
|
|
(module Fpath.Set)
|
||
|
|
(function Dev c -> Device.files c info | If _ | App -> Fpath.Set.empty)
|
||
|
|
t
|
||
|
|
|
||
|
|
module Dune = struct
|
||
|
|
type t = Dune.stanza list
|
||
|
|
|
||
|
|
let union = ( @ )
|
||
|
|
let empty = []
|
||
|
|
end
|
||
|
|
|
||
|
|
let dune info =
|
||
|
|
Impl.collect (module Dune) @@ function
|
||
|
|
| Dev c -> Device.dune c info
|
||
|
|
| If _ | App -> Dune.empty
|
||
|
|
|
||
|
|
(* [module_expresion tbl c args] returns the module expression of
|
||
|
|
the functor [c] applies to [args]. *)
|
||
|
|
let module_expression fmt (c, args) =
|
||
|
|
Fmt.pf fmt "%s%a" (Device.module_name c)
|
||
|
|
Fmt.(
|
||
|
|
list ~sep:(any "")
|
||
|
|
(any "(" ++ of_to_string Device.Graph.impl_name ++ any ")"))
|
||
|
|
args
|
||
|
|
|
||
|
|
let find_all_devices info g i =
|
||
|
|
let ctx = Info.context info in
|
||
|
|
let id = Impl.with_left_most_device ctx i { f = Device.id } in
|
||
|
|
let f x l =
|
||
|
|
let (Device.Graph.D { dev; _ }) = x in
|
||
|
|
if Device.id dev = id then x :: l else l
|
||
|
|
in
|
||
|
|
Device.Graph.fold f g []
|
||
|
|
|
||
|
|
let iter_actions f t =
|
||
|
|
let f v res =
|
||
|
|
let* () = res in
|
||
|
|
f v
|
||
|
|
in
|
||
|
|
Device.Graph.fold f t (Action.ok ())
|
||
|
|
|
||
|
|
let lines_of_str str =
|
||
|
|
String.fold_left (fun n -> function '\n' -> n + 1 | _ -> n) 0 str
|
||
|
|
|
||
|
|
type main = { dir : Fpath.t; path : Fpath.t; mutable lines : int }
|
||
|
|
|
||
|
|
let main info =
|
||
|
|
let path = Info.main info in
|
||
|
|
let dir = Fpath.(Info.(parent (config_file info) / project_name info)) in
|
||
|
|
let+ str = Action.read_file path in
|
||
|
|
let lines = lines_of_str str in
|
||
|
|
{ dir; path; lines }
|
||
|
|
|
||
|
|
let append_main main msg fmt =
|
||
|
|
let purpose = Fmt.str "Append to main.ml (%s)" msg in
|
||
|
|
Fmt.kstr
|
||
|
|
(fun str ->
|
||
|
|
main.lines <- main.lines + lines_of_str str + 1;
|
||
|
|
Action.with_output ~path:main.path ~append:true ~purpose (fun ppf ->
|
||
|
|
Fmt.pf ppf "%s@." str))
|
||
|
|
fmt
|
||
|
|
|
||
|
|
let pp_pos ppf = function
|
||
|
|
| None -> ()
|
||
|
|
| Some (file, line, _, _) -> Fmt.pf ppf "# %d %S@." line file
|
||
|
|
|
||
|
|
let reset_pos { dir; path; lines } =
|
||
|
|
(* lines are 1-based and the line directive is refering to
|
||
|
|
"next line will be Y", so if we put a directive in the first line of a
|
||
|
|
file, it needs to say "# 2 myfile.ml" since the next line will be the
|
||
|
|
second one. This is the reason for the 2 below. *)
|
||
|
|
let file = Fpath.(dir // path) |> Fpath.normalize |> Fpath.to_string in
|
||
|
|
Some (file, lines + 2, 0, 0)
|
||
|
|
|
||
|
|
let configure info t =
|
||
|
|
let f (v : t) =
|
||
|
|
let* main = main info in
|
||
|
|
let (D { dev; args; _ }) = v in
|
||
|
|
let* () = Device.configure dev info in
|
||
|
|
if args = [] then Action.ok ()
|
||
|
|
else
|
||
|
|
let* () = append_main main "reset" "%a" pp_pos (reset_pos main) in
|
||
|
|
append_main main "configure" "module %s = %a\n" (Device.Graph.impl_name v)
|
||
|
|
module_expression (dev, args)
|
||
|
|
in
|
||
|
|
iter_actions f t
|
||
|
|
|
||
|
|
let meta_init fmt (connect_name, result_name) =
|
||
|
|
Fmt.pf fmt " let _%s = Lazy.force %s in@ " result_name connect_name
|
||
|
|
|
||
|
|
let emit_connect fmt (iname, names, runtime_args, connect_code) =
|
||
|
|
(* We avoid potential collision between double application
|
||
|
|
by prefixing with "_". This also avoid warnings. *)
|
||
|
|
let rnames = List.map (fun x -> "_" ^ x) names in
|
||
|
|
let knames = List.map (fun k -> "_" ^ Runtime_arg.var_name k) runtime_args in
|
||
|
|
let bind ppf name = Fmt.pf ppf " _%s >>= fun %s ->\n" name name in
|
||
|
|
let bind_key ppf k =
|
||
|
|
Fmt.pf ppf " let _%s = %a in\n" (Runtime_arg.var_name k) Runtime_arg.call k
|
||
|
|
in
|
||
|
|
let { Device.pos; code } = connect_code (rnames @ knames) in
|
||
|
|
Fmt.pf fmt "let %s = lazy (\n%a%a%a%a %s@\n);;" iname
|
||
|
|
Fmt.(list ~sep:nop meta_init)
|
||
|
|
(List.combine names rnames)
|
||
|
|
Fmt.(list ~sep:nop bind)
|
||
|
|
rnames
|
||
|
|
Fmt.(list ~sep:nop bind_key)
|
||
|
|
runtime_args pp_pos pos code
|
||
|
|
|
||
|
|
let emit_run main init main_name =
|
||
|
|
(* "exit 1" is ok in this code, since cmdliner will print help. *)
|
||
|
|
let force ppf name = Fmt.pf ppf "Lazy.force %s >>= fun _ ->\n " name in
|
||
|
|
append_main main "emit_run"
|
||
|
|
"let () =\n let t = %aLazy.force %s in\n run t\n;;"
|
||
|
|
Fmt.(list ~sep:nop force)
|
||
|
|
init main_name
|
||
|
|
|
||
|
|
let connect ?(init = []) info t =
|
||
|
|
let* main = main info in
|
||
|
|
let f (v : t) =
|
||
|
|
let (D { dev; args; deps; _ }) = v in
|
||
|
|
let var_name = Device.Graph.var_name v in
|
||
|
|
let impl_name = Device.Graph.impl_name v in
|
||
|
|
let arg_names = List.map Device.Graph.var_name (args @ deps) in
|
||
|
|
let runtime_args = Device.runtime_args dev in
|
||
|
|
let* () =
|
||
|
|
append_main main "connect" "%a" emit_connect
|
||
|
|
(var_name, arg_names, runtime_args, Device.connect dev info impl_name)
|
||
|
|
in
|
||
|
|
append_main main "reset" "%a" pp_pos (reset_pos main)
|
||
|
|
in
|
||
|
|
let* () = iter_actions f t in
|
||
|
|
let main_name = Device.Graph.var_name t in
|
||
|
|
let init_names =
|
||
|
|
List.fold_left
|
||
|
|
(fun acc i ->
|
||
|
|
match find_all_devices info t i with
|
||
|
|
| [] -> assert false
|
||
|
|
| ds -> List.map Device.Graph.var_name ds @ acc)
|
||
|
|
[] init
|
||
|
|
|> List.rev
|
||
|
|
in
|
||
|
|
emit_run main init_names main_name
|