372 lines
11 KiB
OCaml
372 lines
11 KiB
OCaml
open Import
|
|
|
|
module P = Variant
|
|
module Ps = Variant.Set
|
|
|
|
(* An assignment or addition *)
|
|
module Rule = struct
|
|
type t =
|
|
{ preds_required : Ps.t
|
|
; preds_forbidden : Ps.t
|
|
; value : string
|
|
}
|
|
|
|
let pp fmt { preds_required; preds_forbidden; value } =
|
|
Fmt.record fmt
|
|
[ "preds_required", Fmt.const Ps.pp preds_required
|
|
; "preds_forbidden", Fmt.const Ps.pp preds_forbidden
|
|
; "value", Fmt.const (fun fmt -> Format.fprintf fmt "%S") value
|
|
]
|
|
|
|
|
|
let formal_predicates_count t =
|
|
Ps.cardinal t.preds_required + Ps.cardinal t.preds_forbidden
|
|
|
|
let matches t ~preds =
|
|
Ps.is_subset t.preds_required ~of_:preds &&
|
|
Ps.is_empty (Ps.inter preds t.preds_forbidden)
|
|
|
|
let make (rule : Meta.rule) =
|
|
let preds_required, preds_forbidden =
|
|
List.partition_map rule.predicates ~f:(function
|
|
| Pos x -> Left x
|
|
| Neg x -> Right x)
|
|
in
|
|
{ preds_required = Ps.make preds_required
|
|
; preds_forbidden = Ps.make preds_forbidden
|
|
; value = rule.value
|
|
}
|
|
end
|
|
|
|
(* Set of rules for a given variable of a package. Implements the
|
|
algorithm described here:
|
|
|
|
http://projects.camlcity.org/projects/dl/findlib-1.6.3/doc/ref-html/r729.html
|
|
*)
|
|
module Rules = struct
|
|
(* To implement the algorithm, [set_rules] is sorted by decreasing
|
|
number of formal predicates, then according to the order of the
|
|
META file. [add_rules] are in the same order as in the META
|
|
file. *)
|
|
type t =
|
|
{ set_rules : Rule.t list
|
|
; add_rules : Rule.t list
|
|
}
|
|
|
|
let pp fmt { set_rules; add_rules } =
|
|
Fmt.record fmt
|
|
[ "set_rules", (fun fmt () -> Fmt.ocaml_list Rule.pp fmt set_rules)
|
|
; "add_rules", (fun fmt () -> Fmt.ocaml_list Rule.pp fmt add_rules)
|
|
]
|
|
|
|
let interpret t ~preds =
|
|
let rec find_set_rule = function
|
|
| [] -> None
|
|
| rule :: rules ->
|
|
if Rule.matches rule ~preds then
|
|
Some rule.value
|
|
else
|
|
find_set_rule rules
|
|
in
|
|
let v = find_set_rule t.set_rules in
|
|
List.fold_left t.add_rules ~init:v ~f:(fun v rule ->
|
|
if Rule.matches rule ~preds then
|
|
Some ((Option.value ~default:"" v) ^ " " ^ rule.value)
|
|
else
|
|
v)
|
|
|
|
let of_meta_rules (rules : Meta.Simplified.Rules.t) =
|
|
let add_rules = List.map rules.add_rules ~f:Rule.make in
|
|
let set_rules =
|
|
List.map rules.set_rules ~f:Rule.make
|
|
|> List.stable_sort ~compare:(fun a b ->
|
|
compare
|
|
(Rule.formal_predicates_count b)
|
|
(Rule.formal_predicates_count a))
|
|
in
|
|
{ add_rules; set_rules }
|
|
end
|
|
|
|
module Vars = struct
|
|
type t = Rules.t String.Map.t
|
|
|
|
let get (t : t) var preds =
|
|
Option.map (String.Map.find t var) ~f:(fun r ->
|
|
Option.value ~default:"" (Rules.interpret r ~preds))
|
|
|
|
let get_words t var preds =
|
|
match get t var preds with
|
|
| None -> []
|
|
| Some s -> String.extract_comma_space_separated_words s
|
|
end
|
|
|
|
module Config = struct
|
|
type t =
|
|
{ vars : Vars.t
|
|
; preds : Ps.t
|
|
}
|
|
|
|
let pp fmt { vars; preds } =
|
|
Fmt.record fmt
|
|
[ "vars"
|
|
, Fmt.const (Fmt.ocaml_list (Fmt.tuple Format.pp_print_string Rules.pp))
|
|
(String.Map.to_list vars)
|
|
; "preds"
|
|
, Fmt.const Ps.pp preds
|
|
]
|
|
|
|
let load path ~toolchain ~context =
|
|
let path = Path.extend_basename path ~suffix:".d" in
|
|
let conf_file = Path.relative path (toolchain ^ ".conf") in
|
|
if not (Path.exists conf_file) then
|
|
die "@{<error>Error@}: ocamlfind toolchain %s isn't defined in %a \
|
|
(context: %s)" toolchain Path.pp path context;
|
|
let vars = (Meta.load ~name:"" conf_file).vars in
|
|
{ vars = String.Map.map vars ~f:Rules.of_meta_rules
|
|
; preds = Ps.make [toolchain]
|
|
}
|
|
|
|
let get { vars; preds } var =
|
|
Vars.get vars var preds
|
|
|
|
let env t =
|
|
let preds = Ps.add t.preds (P.make "env") in
|
|
String.Map.filter_map ~f:(Rules.interpret ~preds) t.vars
|
|
|> Env.of_string_map
|
|
end
|
|
|
|
module Package = struct
|
|
type t =
|
|
{ meta_file : Path.t
|
|
; name : string
|
|
; dir : Path.t
|
|
; vars : Vars.t
|
|
}
|
|
|
|
let meta_file t = t.meta_file
|
|
let name t = t.name
|
|
let dir t = t.dir
|
|
|
|
let preds = Ps.of_list [P.ppx_driver; P.mt; P.mt_posix]
|
|
|
|
let get_paths t var preds =
|
|
List.map (Vars.get_words t.vars var preds) ~f:(Path.relative t.dir)
|
|
|
|
let make_archives t var preds =
|
|
Mode.Dict.of_func (fun ~mode ->
|
|
get_paths t var (Ps.add preds (Mode.variant mode)))
|
|
|
|
let version t = Vars.get t.vars "version" Ps.empty
|
|
let description t = Vars.get t.vars "description" Ps.empty
|
|
let jsoo_runtime t = get_paths t "jsoo_runtime" Ps.empty
|
|
let requires t = Vars.get_words t.vars "requires" preds
|
|
let ppx_runtime_deps t = Vars.get_words t.vars "ppx_runtime_deps" preds
|
|
|
|
let archives t = make_archives t "archive" preds
|
|
let plugins t =
|
|
Mode.Dict.map2 ~f:(@)
|
|
(make_archives t "archive" (Ps.add preds Variant.plugin))
|
|
(make_archives t "plugin" preds)
|
|
|
|
let dune_file t =
|
|
let fn = Path.relative t.dir (sprintf "%s.dune" t.name) in
|
|
Option.some_if (Path.exists fn) fn
|
|
end
|
|
|
|
module Unavailable_reason = struct
|
|
type t =
|
|
| Not_found
|
|
| Hidden of Package.t
|
|
|
|
let to_string = function
|
|
| Not_found -> "not found"
|
|
| Hidden pkg ->
|
|
sprintf "in %s is hidden (unsatisfied 'exist_if')"
|
|
(Path.to_string_maybe_quoted (Package.dir pkg))
|
|
|
|
let pp ppf t = Format.pp_print_string ppf (to_string t)
|
|
end
|
|
|
|
type t =
|
|
{ stdlib_dir : Path.t
|
|
; path : Path.t list
|
|
; builtins : Meta.Simplified.t String.Map.t
|
|
; packages : (string, (Package.t, Unavailable_reason.t) result) Hashtbl.t
|
|
}
|
|
|
|
let path t = t.path
|
|
|
|
let root_package_name s =
|
|
match String.index s '.' with
|
|
| None -> s
|
|
| Some i -> String.sub s ~pos:0 ~len:i
|
|
|
|
let dummy_package t ~name =
|
|
let dir =
|
|
match t.path with
|
|
| [] -> t.stdlib_dir
|
|
| dir :: _ -> Path.relative dir (root_package_name name)
|
|
in
|
|
{ Package.
|
|
meta_file = Path.relative dir "META"
|
|
; name = name
|
|
; dir = dir
|
|
; vars = String.Map.empty
|
|
}
|
|
|
|
(* Parse a single package from a META file *)
|
|
let parse_package t ~meta_file ~name ~parent_dir ~vars =
|
|
let pkg_dir = Vars.get vars "directory" Ps.empty in
|
|
let dir =
|
|
match pkg_dir with
|
|
| None | Some "" -> parent_dir
|
|
| Some pkg_dir ->
|
|
if pkg_dir.[0] = '+' || pkg_dir.[0] = '^' then
|
|
Path.relative t.stdlib_dir
|
|
(String.sub pkg_dir ~pos:1 ~len:(String.length pkg_dir - 1))
|
|
else if Filename.is_relative pkg_dir then
|
|
Path.relative parent_dir pkg_dir
|
|
else
|
|
Path.of_filename_relative_to_initial_cwd pkg_dir
|
|
in
|
|
let pkg =
|
|
{ Package.
|
|
meta_file
|
|
; name
|
|
; dir
|
|
; vars
|
|
}
|
|
in
|
|
let exists_if = Vars.get_words vars "exists_if" Ps.empty in
|
|
let exists =
|
|
match exists_if with
|
|
| _ :: _ ->
|
|
List.for_all exists_if ~f:(fun fn ->
|
|
Path.exists (Path.relative dir fn))
|
|
| [] ->
|
|
if not (String.Map.mem t.builtins (root_package_name name)) then
|
|
true
|
|
else
|
|
(* The META files for installed packages are sometimes broken,
|
|
i.e. META files for libraries that were not installed by
|
|
the compiler are still present:
|
|
|
|
https://github.com/ocaml/dune/issues/563
|
|
|
|
To workaround this problem, for builtin packages we check
|
|
that at least one of the archive is present. *)
|
|
match Package.archives pkg with
|
|
| { byte = []; native = [] } -> true
|
|
| { byte; native } -> List.exists (byte @ native) ~f:Path.exists
|
|
in
|
|
let res =
|
|
if exists then
|
|
Ok pkg
|
|
else
|
|
Error (Unavailable_reason.Hidden pkg)
|
|
in
|
|
(dir, res)
|
|
|
|
(* Parse all the packages defined in a META file and add them to
|
|
[t.packages] *)
|
|
let parse_and_acknowledge_meta t ~dir ~meta_file (meta : Meta.Simplified.t) =
|
|
let rec loop ~dir ~full_name (meta : Meta.Simplified.t) =
|
|
let vars = String.Map.map meta.vars ~f:Rules.of_meta_rules in
|
|
let dir, res =
|
|
parse_package t ~meta_file ~name:full_name ~parent_dir:dir ~vars
|
|
in
|
|
Hashtbl.add t.packages full_name res;
|
|
List.iter meta.subs ~f:(fun (meta : Meta.Simplified.t) ->
|
|
loop ~dir ~full_name:(sprintf "%s.%s" full_name meta.name) meta)
|
|
in
|
|
loop ~dir ~full_name:meta.name meta
|
|
|
|
(* Search for a <package>/META file in the findlib search path, parse
|
|
it and add its contents to [t.packages] *)
|
|
let find_and_acknowledge_meta t ~fq_name =
|
|
let root_name = root_package_name fq_name in
|
|
let rec loop dirs : (Path.t * Path.t * Meta.Simplified.t) option =
|
|
match dirs with
|
|
| dir :: dirs ->
|
|
let sub_dir = Path.relative dir root_name in
|
|
let fn = Path.relative sub_dir "META" in
|
|
if Path.exists fn then
|
|
Some (sub_dir,
|
|
fn,
|
|
Meta.load ~name:root_name fn)
|
|
else
|
|
(* Alternative layout *)
|
|
let fn = Path.relative dir ("META." ^ root_name) in
|
|
if Path.exists fn then
|
|
Some (dir,
|
|
fn,
|
|
Meta.load fn ~name:root_name)
|
|
else
|
|
loop dirs
|
|
| [] ->
|
|
match String.Map.find t.builtins root_name with
|
|
| Some meta -> Some (t.stdlib_dir, Path.of_string "<internal>", meta)
|
|
| None -> None
|
|
in
|
|
match loop t.path with
|
|
| None ->
|
|
Hashtbl.add t.packages root_name (Error Not_found)
|
|
| Some (dir, meta_file, meta) ->
|
|
parse_and_acknowledge_meta t meta ~meta_file ~dir
|
|
|
|
let find t name =
|
|
match Hashtbl.find t.packages name with
|
|
| Some x -> x
|
|
| None ->
|
|
find_and_acknowledge_meta t ~fq_name:name;
|
|
match Hashtbl.find t.packages name with
|
|
| Some x -> x
|
|
| None ->
|
|
let res = Error Unavailable_reason.Not_found in
|
|
Hashtbl.add t.packages name res;
|
|
res
|
|
|
|
let available t name =
|
|
match find t name with
|
|
| Ok _ -> true
|
|
| Error _ -> false
|
|
|
|
let root_packages t =
|
|
let pkgs =
|
|
List.concat_map t.path ~f:(fun dir ->
|
|
Sys.readdir (Path.to_string dir)
|
|
|> Array.to_list
|
|
|> List.filter ~f:(fun name ->
|
|
Path.exists (Path.relative dir (name ^ "/META"))))
|
|
|> String.Set.of_list
|
|
in
|
|
String.Set.union pkgs
|
|
(String.Set.of_list (String.Map.keys t.builtins))
|
|
|
|
let load_all_packages t =
|
|
String.Set.iter (root_packages t) ~f:(fun pkg ->
|
|
find_and_acknowledge_meta t ~fq_name:pkg)
|
|
|
|
let all_packages t =
|
|
load_all_packages t;
|
|
Hashtbl.fold t.packages ~init:[] ~f:(fun x acc ->
|
|
match x with
|
|
| Ok p -> p :: acc
|
|
| Error _ -> acc)
|
|
|> List.sort ~compare:(fun (a : Package.t) b -> String.compare a.name b.name)
|
|
|
|
let create ~stdlib_dir ~path =
|
|
{ stdlib_dir
|
|
; path
|
|
; builtins = Meta.builtins ~stdlib_dir
|
|
; packages = Hashtbl.create 1024
|
|
}
|
|
|
|
let all_unavailable_packages t =
|
|
load_all_packages t;
|
|
Hashtbl.foldi t.packages ~init:[] ~f:(fun name x acc ->
|
|
match x with
|
|
| Ok _ -> acc
|
|
| Error e -> ((name, e) :: acc))
|
|
|> List.sort ~compare:(fun (a, _) (b, _) -> String.compare a b)
|