dune/src/lib.ml

1216 lines
36 KiB
OCaml

open Import
open Result.O
(* +-----------------------------------------------------------------+
| Raw library information |
+-----------------------------------------------------------------+ *)
module Status = struct
type t =
| Installed
| Public of Package.t
| Private of Dune_project.Name.t
let pp ppf t =
Format.pp_print_string ppf
(match t with
| Installed -> "installed"
| Public _ -> "public"
| Private name ->
sprintf "private (%s)" (Dune_project.Name.to_string_hum name))
let is_private = function
| Private _ -> true
| Installed | Public _ -> false
end
module Info = struct
module Deps = struct
type t =
| Simple of (Loc.t * string) list
| Complex of Jbuild.Lib_dep.t list
let of_lib_deps deps =
let rec loop acc (deps : Jbuild.Lib_dep.t list) =
match deps with
| [] -> Some (List.rev acc)
| Direct x :: deps -> loop (x :: acc) deps
| Select _ :: _ -> None
in
match loop [] deps with
| Some l -> Simple l
| None -> Complex deps
let to_lib_deps = function
| Simple l -> List.map l ~f:Jbuild.Lib_dep.direct
| Complex l -> l
end
type t =
{ loc : Loc.t
; kind : Jbuild.Library.Kind.t
; status : Status.t
; src_dir : Path.t
; obj_dir : Path.t
; version : string option
; synopsis : string option
; archives : Path.t list Mode.Dict.t
; plugins : Path.t list Mode.Dict.t
; foreign_archives : Path.t list Mode.Dict.t
; jsoo_runtime : Path.t list
; requires : Deps.t
; ppx_runtime_deps : (Loc.t * string) list
; pps : (Loc.t * Jbuild.Pp.t) list
; optional : bool
; virtual_deps : (Loc.t * string) list
; sub_systems : Jbuild.Sub_system_info.t Sub_system_name.Map.t
}
let user_written_deps t =
List.fold_left (t.virtual_deps @ t.ppx_runtime_deps)
~init:(Deps.to_lib_deps t.requires)
~f:(fun acc s -> Jbuild.Lib_dep.Direct s :: acc)
let of_library_stanza ~dir (conf : Jbuild.Library.t) =
let archive_file ext = Path.relative dir (conf.name ^ ext) in
let archive_files ~f_ext =
Mode.Dict.of_func (fun ~mode -> [archive_file (f_ext mode)])
in
let stubs =
if Jbuild.Library.has_stubs conf then
[Jbuild.Library.stubs_archive conf ~dir ~ext_lib:""]
else
[]
in
let jsoo_runtime =
List.map conf.buildable.js_of_ocaml.javascript_files
~f:(Path.relative dir)
in
let status =
match conf.public with
| None -> Status.Private conf.project.name
| Some p -> Public p.package
in
let foreign_archives =
{ Mode.Dict.
byte = stubs
; native = Path.relative dir conf.name :: stubs
}
in
{ loc = conf.buildable.loc
; kind = conf.kind
; src_dir = dir
; obj_dir = Utils.library_object_directory ~dir conf.name
; version = None
; synopsis = conf.synopsis
; archives = archive_files ~f_ext:Mode.compiled_lib_ext
; plugins = archive_files ~f_ext:Mode.plugin_ext
; optional = conf.optional
; foreign_archives
; jsoo_runtime
; status
; virtual_deps = conf.virtual_deps
; requires = Deps.of_lib_deps conf.buildable.libraries
; ppx_runtime_deps = conf.ppx_runtime_libraries
; pps = Jbuild.Preprocess_map.pps conf.buildable.preprocess
; sub_systems = conf.sub_systems
}
let of_findlib_package pkg =
let module P = Findlib.Package in
let loc = Loc.in_file (Path.to_string (P.meta_file pkg)) in
let add_loc x = (loc, x) in
let sub_systems =
match P.dune_file pkg with
| None -> Sub_system_name.Map.empty
| Some fn -> Installed_dune_file.load fn
in
{ loc = loc
; kind = Normal
; src_dir = P.dir pkg
; obj_dir = P.dir pkg
; version = P.version pkg
; synopsis = P.description pkg
; archives = P.archives pkg
; plugins = P.plugins pkg
; jsoo_runtime = P.jsoo_runtime pkg
; requires = Simple (List.map (P.requires pkg) ~f:add_loc)
; ppx_runtime_deps = List.map (P.ppx_runtime_deps pkg) ~f:add_loc
; pps = []
; virtual_deps = []
; optional = false
; status = Installed
; (* We don't know how these are named for external libraries *)
foreign_archives = Mode.Dict.make_both []
; sub_systems = sub_systems
}
end
(* +-----------------------------------------------------------------+
| Types |
+-----------------------------------------------------------------+ *)
module Error0 = struct
module Library_not_available = struct
module Reason = struct
module Hidden = struct
type t =
{ name : string
; path : Path.t
; reason : string
}
end
type t =
| Not_found
| Hidden of Hidden.t
let to_string = function
| Not_found -> "not found"
| Hidden { path; reason; _ } ->
sprintf "in %s is hidden (%s)"
(Path.to_string_maybe_quoted path) reason
let pp ppf t = Format.pp_print_string ppf (to_string t)
end
type t =
{ loc : Loc.t
; name : string
; reason : Reason.t
}
end
module No_solution_found_for_select = struct
type t = { loc : Loc.t }
end
end
module Resolved_select = struct
type t =
{ src_fn : (string, Error0.No_solution_found_for_select.t) result
; dst_fn : string
}
end
type sub_system = ..
module Sub_system0 = struct
module type S = sig
type t
type sub_system += T of t
val to_sexp : (t -> Syntax.Version.t * Sexp.t) option
end
type 'a s = (module S with type t = 'a)
module Instance = struct
type t = T : 'a s * 'a -> t
end
end
module Id = struct
type t =
{ unique_id : int
; path : Path.t
; name : string
}
end
type t =
{ loc : Loc.t
; name : string
; unique_id : int
; kind : Jbuild.Library.Kind.t
; status : Status.t
; src_dir : Path.t
; obj_dir : Path.t
; version : string option
; synopsis : string option
; archives : Path.t list Mode.Dict.t
; plugins : Path.t list Mode.Dict.t
; foreign_archives : Path.t list Mode.Dict.t
; jsoo_runtime : Path.t list
; requires : t list Or_exn.t
; ppx_runtime_deps : t list Or_exn.t
; pps : t list Or_exn.t
; resolved_selects : Resolved_select.t list
; optional : bool
; user_written_deps : Jbuild.Lib_deps.t
; (* This is mutable to avoid this error:
{[
This kind of expression is not allowed as right-hand side of `let rec'
}]
*)
mutable sub_systems : Sub_system0.Instance.t Lazy.t Sub_system_name.Map.t
}
and db =
{ parent : db option
; resolve : string -> resolve_result
; table : (string, status) Hashtbl.t
; all : string list Lazy.t
}
and status =
| St_initializing of Id.t (* To detect cycles *)
| St_found of t
| St_not_found
| St_hidden of t * Error0.Library_not_available.Reason.Hidden.t
and error =
| Library_not_available of Error0.Library_not_available.t
| No_solution_found_for_select of Error0.No_solution_found_for_select.t
| Dependency_cycle of (Path.t * string) list
| Conflict of conflict
| Overlap of overlap
| Private_deps_not_allowed of private_deps_not_allowed
and resolve_result =
| Not_found
| Found of Info.t
| Hidden of Info.t * string
| Redirect of db option * string
and conflict =
{ lib1 : t * Dep_path.Entry.t list
; lib2 : t * Dep_path.Entry.t list
}
and overlap =
{ in_workspace : t
; installed : t * Dep_path.Entry.t list
}
and private_deps_not_allowed =
{ private_dep : t
; pd_loc : Loc.t
}
type lib = t
module Error = struct
include Error0
module Conflict = struct
type nonrec t = conflict =
{ lib1 : t * Dep_path.Entry.t list
; lib2 : t * Dep_path.Entry.t list
}
end
module Overlap = struct
type nonrec t = overlap =
{ in_workspace : t
; installed : t * Dep_path.Entry.t list
}
end
module Private_deps_not_allowed = struct
type nonrec t = private_deps_not_allowed =
{ private_dep : t
; pd_loc : Loc.t
}
end
type t = error =
| Library_not_available of Library_not_available.t
| No_solution_found_for_select of No_solution_found_for_select.t
| Dependency_cycle of (Path.t * string) list
| Conflict of Conflict.t
| Overlap of Overlap.t
| Private_deps_not_allowed of Private_deps_not_allowed.t
end
exception Error of Error.t
let not_available ~loc reason fmt =
Errors.kerrf fmt ~f:(fun s ->
Loc.fail loc "%s %a" s
Error.Library_not_available.Reason.pp reason)
(* +-----------------------------------------------------------------+
| Generals |
+-----------------------------------------------------------------+ *)
let name t = t.name
let kind t = t.kind
let synopsis t = t.synopsis
let archives t = t.archives
let plugins t = t.plugins
let jsoo_runtime t = t.jsoo_runtime
let unique_id t = t.unique_id
let src_dir t = t.src_dir
let obj_dir t = t.obj_dir
let is_local t = Path.is_managed t.obj_dir
let status t = t.status
let package t =
match t.status with
| Installed ->
Some (Findlib.root_package_name t.name
|> Package.Name.of_string)
| Public p -> Some p.name
| Private _ ->
None
let to_id t : Id.t =
{ unique_id = t.unique_id
; path = t.src_dir
; name = t.name
}
module Set = Set.Make(
struct
type nonrec t = t
let compare x y = compare x.unique_id y.unique_id
end)
module Map = Map.Make(
struct
type nonrec t = t
let compare x y = compare x.unique_id y.unique_id
end)
module L = struct
type nonrec t = t list
let to_iflags dirs =
Arg_spec.S
(Path.Set.fold dirs ~init:[] ~f:(fun dir acc ->
Arg_spec.Path dir :: A "-I" :: acc)
|> List.rev)
let include_paths ts ~stdlib_dir =
let dirs =
List.fold_left ts ~init:Path.Set.empty ~f:(fun acc t ->
Path.Set.add acc (obj_dir t))
in
Path.Set.remove dirs stdlib_dir
let include_flags ts ~stdlib_dir =
to_iflags (include_paths ts ~stdlib_dir)
let c_include_paths ts ~stdlib_dir =
let dirs =
List.fold_left ts ~init:Path.Set.empty ~f:(fun acc t ->
Path.Set.add acc t.src_dir)
in
Path.Set.remove dirs stdlib_dir
let c_include_flags ts ~stdlib_dir =
to_iflags (c_include_paths ts ~stdlib_dir)
let link_flags ts ~mode ~stdlib_dir =
Arg_spec.S
(c_include_flags ts ~stdlib_dir ::
List.map ts ~f:(fun t -> Arg_spec.Deps (Mode.Dict.get t.archives mode)))
let compile_and_link_flags ~compile ~link ~mode ~stdlib_dir =
let dirs =
Path.Set.union
( include_paths compile ~stdlib_dir)
(c_include_paths link ~stdlib_dir)
in
Arg_spec.S
(to_iflags dirs ::
List.map link ~f:(fun t ->
Arg_spec.Deps (Mode.Dict.get t.archives mode)))
let jsoo_runtime_files ts =
List.concat_map ts ~f:(fun t -> t.jsoo_runtime)
let archive_files ts ~mode ~ext_lib =
List.concat_map ts ~f:(fun t ->
Mode.Dict.get t.archives mode @
List.map (Mode.Dict.get t.foreign_archives mode)
~f:(Path.extend_basename ~suffix:ext_lib))
let remove_dups l =
let rec loop acc l seen =
match l with
| [] -> acc
| x :: l ->
if Int.Set.mem seen x.unique_id then
loop acc l seen
else
loop (x :: acc) l (Int.Set.add seen x.unique_id)
in
loop [] l Int.Set.empty
end
(* +-----------------------------------------------------------------+
| Sub-systems |
+-----------------------------------------------------------------+ *)
module Sub_system = struct
type t = sub_system = ..
module type S = sig
module Info : Jbuild.Sub_system_info.S
type t
type sub_system += T of t
val instantiate
: resolve:(Loc.t * string -> lib Or_exn.t)
-> get:(loc:Loc.t -> lib -> t option)
-> lib
-> Info.t
-> t
val to_sexp : (t -> Syntax.Version.t * Sexp.t) option
end
module type S' = sig
include S
val for_instance : t Sub_system0.s
val get : lib -> t option
end
let all = Sub_system_name.Table.create ~default_value:None
module Register(M : S) = struct
let get lib =
Option.map (Sub_system_name.Map.find lib.sub_systems M.Info.name)
~f:(fun (lazy (Sub_system0.Instance.T ((module X), t))) ->
match X.T t with
| M.T t -> t
| _ -> assert false)
let () =
let module M = struct
include M
let for_instance = (module M : Sub_system0.S with type t = t)
let get = get
end in
Sub_system_name.Table.set all ~key:M.Info.name
~data:(Some (module M : S'))
end
let instantiate name info lib ~resolve =
let impl = Option.value_exn (Sub_system_name.Table.get all name) in
let (module M : S') = impl in
match info with
| M.Info.T info ->
let get ~loc lib' =
if lib.unique_id = lib'.unique_id then
Loc.fail loc "Library %S depends on itself" lib.name
else
M.get lib'
in
Sub_system0.Instance.T
(M.for_instance, M.instantiate ~resolve ~get lib info)
| _ -> assert false
let dump_config lib =
Sub_system_name.Map.filter_map lib.sub_systems ~f:(fun (lazy inst) ->
let (Sub_system0.Instance.T ((module M), t)) = inst in
match M.to_sexp with
| None -> None
| Some f -> Some (f t))
end
(* +-----------------------------------------------------------------+
| Library name resolution and transitive closure |
+-----------------------------------------------------------------+ *)
let gen_unique_id =
let next = ref 0 in
fun () ->
let n = !next in
next := n + 1;
n
(* Dependency stack used while resolving the dependencies of a library
that was just returned by the [resolve] callback *)
module Dep_stack = struct
type t =
{ stack : Id.t list
; seen : Int.Set.t
}
let empty =
{ stack = []
; seen = Int.Set.empty
}
let to_required_by t ~stop_at =
let stop_at = stop_at.stack in
let rec loop acc l =
if l == stop_at then
List.rev acc
else
match l with
| [] -> assert false
| { Id.path; name; _ } :: l ->
loop (Dep_path.Entry.Library (path, name) :: acc) l
in
loop [] t.stack
let dependency_cycle t (last : Id.t) =
assert (Int.Set.mem t.seen last.unique_id);
let rec build_loop acc stack =
match stack with
| [] -> assert false
| (x : Id.t) :: stack ->
let acc = (x.path, x.name) :: acc in
if x.unique_id = last.unique_id then
acc
else
build_loop acc stack
in
let loop = build_loop [(last.path, last.name)] t.stack in
Error (Dependency_cycle loop)
let create_and_push t name path =
let unique_id = gen_unique_id () in
let init = { Id. unique_id; name; path } in
(init,
{ stack = init :: t.stack
; seen = Int.Set.add t.seen unique_id
})
let push t (x : Id.t) : (_, _) result =
if Int.Set.mem t.seen x.unique_id then
Error (dependency_cycle t x)
else
Ok { stack = x :: t.stack
; seen = Int.Set.add t.seen x.unique_id
}
end
let check_private_deps ~(lib : lib) ~loc ~allow_private_deps =
if (not allow_private_deps) && Status.is_private lib.status then
Result.Error (Error (
Private_deps_not_allowed { private_dep = lib ; pd_loc = loc }))
else
Ok lib
let already_in_table (info : Info.t) name x =
let to_sexp = Sexp.To_sexp.(pair Path.sexp_of_t string) in
let sexp =
match x with
| St_initializing x ->
Sexp.List [Sexp.unsafe_atom_of_string "Initializing";
Path.sexp_of_t x.path]
| St_found t ->
List [Sexp.unsafe_atom_of_string "Found";
Path.sexp_of_t t.src_dir]
| St_not_found ->
Sexp.unsafe_atom_of_string "Not_found"
| St_hidden (_, { path; reason; _ }) ->
List [Sexp.unsafe_atom_of_string "Hidden";
Path.sexp_of_t path; Sexp.atom reason]
in
Exn.code_error
"Lib_db.DB: resolver returned name that's already in the table"
[ "name" , Sexp.atom name
; "returned_lib" , to_sexp (info.src_dir, name)
; "conflicting_with", sexp
]
let result_of_resolve_status = function
| St_initializing _ -> assert false
| St_found x -> Ok x
| St_not_found -> Error Error.Library_not_available.Reason.Not_found
| St_hidden (_, hidden) -> Error (Hidden hidden)
let rec instantiate db name (info : Info.t) ~stack ~hidden =
let id, stack =
Dep_stack.create_and_push stack name info.src_dir
in
Option.iter (Hashtbl.find db.table name) ~f:(fun x ->
already_in_table info name x);
(* Add [id] to the table, to detect loops *)
Hashtbl.add db.table name (St_initializing id);
let allow_private_deps = Status.is_private info.status in
let requires, pps, resolved_selects =
resolve_user_deps db info.requires ~allow_private_deps ~pps:info.pps ~stack
in
let ppx_runtime_deps =
resolve_simple_deps db info.ppx_runtime_deps ~allow_private_deps ~stack
in
let map_error x =
Result.map_error x ~f:(fun e ->
Dep_path.prepend_exn e (Library (info.src_dir, name)))
in
let requires = map_error requires in
let ppx_runtime_deps = map_error ppx_runtime_deps in
let resolve (loc, name) =
resolve_dep db name ~allow_private_deps ~loc ~stack in
let t =
{ loc = info.loc
; name = name
; unique_id = id.unique_id
; kind = info.kind
; status = info.status
; src_dir = info.src_dir
; obj_dir = info.obj_dir
; version = info.version
; synopsis = info.synopsis
; archives = info.archives
; plugins = info.plugins
; foreign_archives = info.foreign_archives
; jsoo_runtime = info.jsoo_runtime
; requires = requires
; ppx_runtime_deps = ppx_runtime_deps
; pps = pps
; resolved_selects = resolved_selects
; optional = info.optional
; user_written_deps = Info.user_written_deps info
; sub_systems = Sub_system_name.Map.empty
}
in
t.sub_systems <-
Sub_system_name.Map.mapi info.sub_systems ~f:(fun name info ->
lazy (Sub_system.instantiate name info t ~resolve));
let res =
let hidden =
match hidden with
| None ->
Option.some_if
(info.optional &&
not (Result.is_ok t.requires && Result.is_ok t.ppx_runtime_deps))
"optional with unavailable dependencies"
| Some _ -> hidden
in
match hidden with
| None -> St_found t
| Some reason ->
St_hidden (t, { name; path = t.src_dir; reason })
in
Hashtbl.replace db.table ~key:name ~data:res;
res
and find db name : (t, Error.Library_not_available.Reason.t) result =
result_of_resolve_status (find_internal db name ~stack:Dep_stack.empty)
and find_even_when_hidden db name =
match find_internal db name ~stack:Dep_stack.empty with
| St_initializing _ -> assert false
| St_found t -> Some t
| St_not_found -> None
| St_hidden (t, _) -> Some t
and find_internal db name ~stack : status =
match Hashtbl.find db.table name with
| Some x -> x
| None -> resolve_name db name ~stack
and resolve_dep db name ~allow_private_deps ~loc ~stack : t Or_exn.t =
match find_internal db name ~stack with
| St_initializing id ->
Error (Dep_stack.dependency_cycle stack id)
| St_found lib -> check_private_deps ~lib ~loc ~allow_private_deps
| St_not_found ->
Error (Error (Library_not_available { loc; name; reason = Not_found }))
| St_hidden (_, hidden) ->
Error (Error (Library_not_available { loc; name; reason = Hidden hidden }))
and resolve_name db name ~stack =
match db.resolve name with
| Redirect (db', name') -> begin
let db' = Option.value db' ~default:db in
match find_internal db' name' ~stack with
| St_initializing _ as x -> x
| x ->
Hashtbl.add db.table name x;
x
end
| Found info ->
instantiate db name info ~stack ~hidden:None
| Not_found ->
let res =
match db.parent with
| None -> St_not_found
| Some db -> find_internal db name ~stack
in
Hashtbl.add db.table name res;
res
| Hidden (info, hidden) ->
match
match db.parent with
| None -> St_not_found
| Some db -> find_internal db name ~stack
with
| St_found _ as x ->
Hashtbl.add db.table name x;
x
| _ ->
instantiate db name info ~stack ~hidden:(Some hidden)
and available_internal db name ~stack =
match resolve_dep db name ~allow_private_deps:true ~loc:Loc.none ~stack with
| Ok _ -> true
| Error _ -> false
and resolve_simple_deps db names ~allow_private_deps ~stack =
let rec loop acc = function
| [] -> Ok (List.rev acc)
| (loc, name) :: names ->
resolve_dep db name ~allow_private_deps ~loc ~stack >>= fun x ->
loop (x :: acc) names
in
loop [] names
and resolve_complex_deps db deps ~allow_private_deps ~stack =
let res, resolved_selects =
List.fold_left deps ~init:(Ok [], []) ~f:(fun (acc_res, acc_selects) dep ->
let res, acc_selects =
match (dep : Jbuild.Lib_dep.t) with
| Direct (loc, name) ->
let res =
resolve_dep db name ~allow_private_deps ~loc ~stack >>| fun x -> [x]
in
(res, acc_selects)
| Select { result_fn; choices; loc } ->
let res, src_fn =
match
List.find_map choices ~f:(fun { required; forbidden; file } ->
if String.Set.exists forbidden
~f:(available_internal db ~stack) then
None
else
match
let deps =
String.Set.fold required ~init:[] ~f:(fun x acc ->
(Loc.none, x) :: acc)
in
resolve_simple_deps ~allow_private_deps db deps ~stack
with
| Ok ts -> Some (ts, file)
| Error _ -> None)
with
| Some (ts, file) ->
(Ok ts, Ok file)
| None ->
let e = { Error.No_solution_found_for_select.loc } in
(Error (Error (No_solution_found_for_select e)),
Error e)
in
(res, { Resolved_select. src_fn; dst_fn = result_fn } :: acc_selects)
in
let res =
match res, acc_res with
| Ok l, Ok acc -> Ok (List.rev_append l acc)
| (Error _ as res), _
| _, (Error _ as res) -> res
in
(res, acc_selects))
in
let res =
match res with
| Ok l -> Ok (List.rev l)
| Error _ -> res
in
(res, resolved_selects)
and resolve_deps db deps ~allow_private_deps ~stack =
match (deps : Info.Deps.t) with
| Simple names ->
(resolve_simple_deps db names ~allow_private_deps ~stack, [])
| Complex names ->
resolve_complex_deps ~allow_private_deps db names ~stack
and resolve_user_deps db deps ~allow_private_deps ~pps ~stack =
let deps, resolved_selects =
resolve_deps db deps ~allow_private_deps ~stack in
let deps, pps =
match pps with
| [] -> (deps, Ok [])
| first :: others as pps ->
(* Location of the list of ppx rewriters *)
let loc =
let last = Option.value (List.last others) ~default:first in
{ (fst first) with stop = (fst last).stop }
in
let pps =
let pps = (pps : (Loc.t * Jbuild.Pp.t) list :> (Loc.t * string) list) in
resolve_simple_deps db pps ~allow_private_deps:true ~stack
>>= fun pps ->
closure_with_overlap_checks None pps ~stack
in
let deps =
let rec check_runtime_deps acc pps = function
| [] -> loop acc pps
| lib :: ppx_rts ->
check_private_deps ~lib ~loc ~allow_private_deps >>= fun rt ->
check_runtime_deps (rt :: acc) pps ppx_rts
and loop acc = function
| [] -> Ok acc
| pp :: pps ->
pp.ppx_runtime_deps >>= fun rt_deps ->
check_runtime_deps acc pps rt_deps
in
deps >>= fun deps ->
pps >>= fun pps ->
loop deps pps
in
(deps, pps)
in
(deps, pps, resolved_selects)
and closure_with_overlap_checks db ts ~stack =
let visited = ref String.Map.empty in
let res = ref [] in
let orig_stack = stack in
let rec loop t ~stack =
match String.Map.find !visited t.name with
| Some (t', stack') ->
if t.unique_id = t'.unique_id then
Ok ()
else
let req_by = Dep_stack.to_required_by ~stop_at:orig_stack in
Error
(Error (Conflict { lib1 = (t', req_by stack')
; lib2 = (t , req_by stack )
}))
| None ->
visited := String.Map.add !visited t.name (t, stack);
(match db with
| None -> Ok ()
| Some db ->
match find_internal db t.name ~stack with
| St_found t' ->
if t.unique_id = t'.unique_id then
Ok ()
else begin
let req_by = Dep_stack.to_required_by stack ~stop_at:orig_stack in
Error
(Error (Overlap
{ in_workspace = t'
; installed = (t, req_by)
}))
end
| _ -> assert false)
>>= fun () ->
Dep_stack.push stack (to_id t) >>= fun stack ->
t.requires >>= fun deps ->
iter deps ~stack >>| fun () ->
res := t :: !res
and iter ts ~stack =
match ts with
| [] -> Ok ()
| t :: ts ->
loop t ~stack >>= fun () ->
iter ts ~stack
in
iter ts ~stack >>| fun () ->
List.rev !res
let closure_with_overlap_checks db l =
closure_with_overlap_checks db l ~stack:Dep_stack.empty
let closure l = closure_with_overlap_checks None l
let to_exn res =
match res with
| Ok x -> x
| Error e -> raise e
let requires_exn t = to_exn t.requires
let ppx_runtime_deps_exn t = to_exn t.ppx_runtime_deps
let closure_exn l = to_exn (closure l)
module Compile = struct
module Resolved_select = Resolved_select
type nonrec t =
{ direct_requires : t list Or_exn.t
; requires : t list Or_exn.t
; pps : t list Or_exn.t
; resolved_selects : Resolved_select.t list
; optional : bool
; user_written_deps : Jbuild.Lib_deps.t
; sub_systems : Sub_system0.Instance.t Lazy.t Sub_system_name.Map.t
}
let for_lib db (t : lib) =
{ direct_requires = t.requires
; requires = t.requires >>= closure_with_overlap_checks db
; resolved_selects = t.resolved_selects
; pps = t.pps
; optional = t.optional
; user_written_deps = t.user_written_deps
; sub_systems = t.sub_systems
}
let direct_requires t = t.direct_requires
let requires t = t.requires
let resolved_selects t = t.resolved_selects
let pps t = t.pps
let optional t = t.optional
let user_written_deps t = t.user_written_deps
let sub_systems t =
Sub_system_name.Map.values t.sub_systems
|> List.map ~f:(fun (lazy (Sub_system0.Instance.T ((module M), t))) ->
M.T t)
end
(* +-----------------------------------------------------------------+
| Databases |
+-----------------------------------------------------------------+ *)
module DB = struct
module Resolve_result = struct
type t = resolve_result =
| Not_found
| Found of Info.t
| Hidden of Info.t * string
| Redirect of db option * string
end
type t = db
let create ?parent ~resolve ~all () =
{ parent
; resolve
; table = Hashtbl.create 1024
; all = Lazy.from_fun all
}
let create_from_library_stanzas ?parent stanzas =
let map =
List.concat_map stanzas ~f:(fun (dir, (conf : Jbuild.Library.t)) ->
let info = Info.of_library_stanza ~dir conf in
match conf.public with
| None ->
[(conf.name, Resolve_result.Found info)]
| Some p ->
if p.name = conf.name then
[(p.name, Found info)]
else
[ p.name , Found info
; conf.name, Redirect (None, p.name)
])
|> String.Map.of_list
|> function
| Ok x -> x
| Error (name, _, _) ->
match
List.filter_map stanzas ~f:(fun (_, (conf : Jbuild.Library.t)) ->
if name = conf.name ||
match conf.public with
| None -> false
| Some p -> name = p.name
then Some conf.buildable.loc
else None)
with
| [] | [_] -> assert false
| loc1 :: loc2 :: _ ->
die "Library %S is defined twice:\n\
- %s\n\
- %s"
name
(Loc.to_file_colon_line loc1)
(Loc.to_file_colon_line loc2)
in
create () ?parent
~resolve:(fun name ->
match String.Map.find map name with
| None -> Not_found
| Some x -> x)
~all:(fun () -> String.Map.keys map)
let create_from_findlib ?(external_lib_deps_mode=false) findlib =
create ()
~resolve:(fun name ->
match Findlib.find findlib name with
| Ok pkg -> Found (Info.of_findlib_package pkg)
| Error e ->
match e with
| Not_found ->
if external_lib_deps_mode then
Found
(Info.of_findlib_package
(Findlib.dummy_package findlib ~name))
else
Not_found
| Hidden pkg ->
Hidden (Info.of_findlib_package pkg,
"unsatisfied 'exist_if'"))
~all:(fun () ->
Findlib.all_packages findlib
|> List.map ~f:Findlib.Package.name)
let find = find
let find_even_when_hidden = find_even_when_hidden
let resolve t (loc, name) =
match find t name with
| Ok _ as res -> res
| Error reason ->
Error (Error (Library_not_available
{ loc
; name
; reason
}))
let find_many =
let rec loop t acc = function
| [] -> Ok (List.rev acc)
| name :: names ->
resolve t (Loc.none, name) >>= fun lib ->
loop t (lib ::acc) names
in
fun t names -> loop t [] names
let available t name = available_internal t name ~stack:Dep_stack.empty
let get_compile_info t ?(allow_overlaps=false) name =
match find_even_when_hidden t name with
| None ->
Exn.code_error "Lib.DB.get_compile_info got library that doesn't exist"
[ "name", Sexp.To_sexp.string name ]
| Some lib ->
let t = Option.some_if (not allow_overlaps) t in
Compile.for_lib t lib
let resolve_user_written_deps t ?(allow_overlaps=false) deps ~pps =
let res, pps, resolved_selects =
resolve_user_deps t (Info.Deps.of_lib_deps deps) ~pps
~stack:Dep_stack.empty ~allow_private_deps:true
in
let requires =
res
>>=
closure_with_overlap_checks (Option.some_if (not allow_overlaps) t)
in
{ Compile.
direct_requires = res
; requires
; pps
; resolved_selects
; optional = false
; user_written_deps = deps
; sub_systems = Sub_system_name.Map.empty
}
let resolve_pps t pps =
resolve_simple_deps t ~allow_private_deps:true
(pps : (Loc.t *Jbuild.Pp.t) list :> (Loc.t * string) list)
~stack:Dep_stack.empty
let rec all ?(recursive=false) t =
let l =
List.fold_left (Lazy.force t.all) ~f:(fun libs name ->
match find t name with
| Ok x -> Set.add libs x
| Error _ -> libs) ~init:Set.empty
in
match recursive, t.parent with
| true, Some t -> Set.union (all ~recursive t) l
| _ -> l
end
(* +-----------------------------------------------------------------+
| META files |
+-----------------------------------------------------------------+ *)
module Meta = struct
let to_names ts =
List.fold_left ts ~init:String.Set.empty ~f:(fun acc t ->
String.Set.add acc t.name)
(* For the deprecated method, we need to put all the runtime
dependencies of the transitive closure.
We need to do this because [ocamlfind ocamlc -package ppx_foo]
will not look for the transitive dependencies of [foo], and the
runtime dependencies might be attached to a dependency of [foo]
rather than [foo] itself.
Sigh... *)
let ppx_runtime_deps_for_deprecated_method t =
closure_exn [t]
|> List.concat_map ~f:ppx_runtime_deps_exn
|> to_names
let requires t = to_names (requires_exn t)
let ppx_runtime_deps t = to_names (ppx_runtime_deps_exn t)
end
(* +-----------------------------------------------------------------+
| Error reporting |
+-----------------------------------------------------------------+ *)
let report_lib_error ppf (e : Error.t) =
match e with
| Library_not_available { loc = _; name; reason } ->
Format.fprintf ppf
"@{<error>Error@}: Library %S %a.@\n"
name
Error.Library_not_available.Reason.pp reason
| Conflict { lib1 = (lib1, rb1); lib2 = (lib2, rb2) } ->
Format.fprintf ppf
"@[<v>@{<error>Error@}: Conflict between the following libraries:@,\
- %S in %s@,\
\ %a@,\
- %S in %s@,\
\ %a@,\
This cannot work.@\n"
lib1.name (Path.to_string_maybe_quoted lib1.src_dir)
Dep_path.Entries.pp rb1
lib2.name (Path.to_string_maybe_quoted lib2.src_dir)
Dep_path.Entries.pp rb2
| Overlap { in_workspace = lib1; installed = (lib2, rb2) } ->
Format.fprintf ppf
"@[<v>@{<error>Error@}: Conflict between the following libraries:@,\
- %S in %s@,\
- %S in %s@,\
\ %a@,\
This is not allowed.@\n"
lib1.name (Path.to_string_maybe_quoted lib1.src_dir)
lib2.name (Path.to_string_maybe_quoted lib2.src_dir)
Dep_path.Entries.pp rb2
| No_solution_found_for_select { loc } ->
Format.fprintf ppf
"%a@{<error>Error@}: No solution found for this select form.\n"
Loc.print loc
| Dependency_cycle cycle ->
Format.fprintf ppf
"@{<error>Error@}: Dependency cycle detected between the \
following libraries:@\n\
@[<v>%a@]\n"
(Format.pp_print_list (fun ppf (path, name) ->
Format.fprintf ppf "-> %S in %s"
name (Path.to_string_maybe_quoted path)))
cycle
| Private_deps_not_allowed t ->
Format.fprintf ppf
"@{<error>Error@}: Library %S is private, it cannot be a dependency of \
a public library.\nYou need to give %S a public name.\n"
t.private_dep.name
t.private_dep.name
let () =
Report_error.register (fun exn ->
match exn with
| Error e ->
let loc, hint =
match e with
| Library_not_available { loc; _ } ->
(Some loc,
match !Clflags.external_lib_deps_hint with
| [] -> (* during bootstrap *) None
| l ->
Some (List.map l ~f:quote_for_shell |> String.concat ~sep:" "))
| Private_deps_not_allowed t ->
(Some t.pd_loc, None)
| _ -> (None, None)
in
Some
{ Report_error.
loc
; hint
; pp = (fun ppf -> report_lib_error ppf e)
; backtrace = false
}
| _ -> None)