A fork of mtelver's day10 project
at main 189 lines 8.0 kB view raw
1type rejection = 2 | UserConstraint of OpamFormula.atom 3 | Unavailable 4 5let ( / ) = Filename.concat 6 7let with_dir path fn = 8 let ch = Unix.opendir path in 9 Fun.protect ~finally:(fun () -> Unix.closedir ch) (fun () -> fn ch) 10 11let list_dir path = 12 let rec aux acc ch = 13 match Unix.readdir ch with 14 | name when name.[0] <> '.' -> aux (name :: acc) ch 15 | _ -> aux acc ch 16 | exception End_of_file -> acc 17 in 18 with_dir path (aux []) 19 20type t = { 21 env : string -> OpamVariable.variable_contents option; 22 packages_dirs : string list; 23 pins : (OpamPackage.Version.t * OpamFile.OPAM.t) OpamPackage.Name.Map.t; 24 constraints : OpamFormula.version_constraint OpamTypes.name_map; (* User-provided constraints *) 25 test : OpamPackage.Name.Set.t; 26 prefer_oldest : bool; 27 doc : bool; (* Whether to filter in {with-doc} deps *) 28 post : bool; (* Whether to filter in {post} deps *) 29} 30 31let load t pkg = 32 let { OpamPackage.name; version = _ } = pkg in 33 match OpamPackage.Name.Map.find_opt name t.pins with 34 | Some (_, opam) -> opam 35 | None -> 36 List.find_map 37 (fun packages_dir -> 38 let opam = packages_dir / OpamPackage.Name.to_string name / OpamPackage.to_string pkg / "opam" in 39 if Sys.file_exists opam then Some opam else None) 40 t.packages_dirs 41 |> Option.get |> OpamFilename.raw |> OpamFile.make |> OpamFile.OPAM.read 42 43let user_restrictions t name = OpamPackage.Name.Map.find_opt name t.constraints 44let dev = OpamPackage.Version.of_string "dev" 45 46let std_env ?(ocaml_native = true) ?sys_ocaml_version ?opam_version ~arch ~os ~os_distribution ~os_family ~os_version () = function 47 | "arch" -> Some (OpamTypes.S arch) 48 | "os" -> Some (OpamTypes.S os) 49 | "os-distribution" -> Some (OpamTypes.S os_distribution) 50 | "os-version" -> Some (OpamTypes.S os_version) 51 | "os-family" -> Some (OpamTypes.S os_family) 52 | "opam-version" -> Some (OpamVariable.S (Option.value ~default:OpamVersion.(to_string current) opam_version)) 53 | "sys-ocaml-version" -> sys_ocaml_version |> Option.map (fun v -> OpamTypes.S v) 54 | "ocaml:native" -> Some (OpamTypes.B ocaml_native) 55 | "enable-ocaml-beta-repository" -> None (* Fake variable? *) 56 | _ -> 57 None 58 59let env t pkg v = 60 if List.mem v OpamPackageVar.predefined_depends_variables then None 61 else 62 match OpamVariable.Full.to_string v with 63 | "version" -> Some (OpamTypes.S (OpamPackage.Version.to_string (OpamPackage.version pkg))) 64 | x -> t.env x 65 66let filter_deps t pkg f = 67 let dev = OpamPackage.Version.compare (OpamPackage.version pkg) dev = 0 in 68 let test = OpamPackage.Name.Set.mem (OpamPackage.name pkg) t.test in 69 f |> OpamFilter.partial_filter_formula (env t pkg) |> OpamFilter.filter_deps ~build:true ~post:t.post ~test ~doc:t.doc ~dev ~dev_setup:false ~default:false 70 71let version_compare t (v1, v1_avoid, _) (v2, v2_avoid, _) = 72 match (v1_avoid, v2_avoid) with 73 | true, true 74 | false, false -> 75 if t.prefer_oldest then OpamPackage.Version.compare v1 v2 else OpamPackage.Version.compare v2 v1 76 | true, false -> 1 77 | false, true -> -1 78 79let candidates t name = 80 match OpamPackage.Name.Map.find_opt name t.pins with 81 | Some (version, opam) -> [ (version, Ok opam) ] 82 | None -> 83 let versions = 84 List.concat_map 85 (fun packages_dir -> 86 try packages_dir / OpamPackage.Name.to_string name |> list_dir with 87 | Unix.Unix_error (Unix.ENOENT, _, _) -> []) 88 t.packages_dirs 89 |> List.sort_uniq compare 90 in 91 let user_constraints = user_restrictions t name in 92 versions 93 |> List.filter_map (fun dir -> 94 match OpamPackage.of_string_opt dir with 95 | Some pkg -> 96 List.find_opt (fun packages_dir -> Sys.file_exists (packages_dir / OpamPackage.Name.to_string name / dir / "opam")) t.packages_dirs 97 |> Option.map (fun _ -> OpamPackage.version pkg) 98 | _ -> None) 99 |> List.filter_map (fun v -> 100 let pkg = OpamPackage.create name v in 101 let opam = load t pkg in 102 let avoid = OpamFile.OPAM.has_flag Pkgflag_AvoidVersion opam in 103 let available = OpamFile.OPAM.available opam in 104 match OpamFilter.eval_to_bool ~default:false (env t pkg) available with 105 | true -> Some (v, avoid, opam) 106 | false -> None) 107 (* https://github.com/ocaml-opam/opam-0install-cudf/issues/5 cf 4.12.1 *) 108 |> (fun l -> if List.for_all (fun (_, avoid, _) -> avoid) l then [] else l) 109 |> List.sort (version_compare t) 110 |> List.map (fun (v, _, opam) -> 111 match user_constraints with 112 | Some test when not (OpamFormula.check_version_formula (OpamFormula.Atom test) v) -> (v, Error (UserConstraint (name, Some test))) 113 | _ -> (v, Ok opam)) 114 115let pp_rejection f = function 116 | UserConstraint x -> Fmt.pf f "Rejected by user-specified constraint %s" (OpamFormula.string_of_atom x) 117 | Unavailable -> Fmt.string f "Availability condition not satisfied" 118 119let create ?(prefer_oldest = false) ?(test = OpamPackage.Name.Set.empty) ?(pins = OpamPackage.Name.Map.empty) ?(doc = false) ?(post = true) ~constraints ~env packages_dirs = 120 { env; packages_dirs; pins; constraints; test; prefer_oldest; doc; post } 121 122(** Create a new context with different doc/post settings. 123 This is used to compute compile vs link deps separately. *) 124let with_doc_post ~doc ~post t = 125 { t with doc; post } 126 127(** Extract x-extra-doc-deps from an opam file. 128 Same implementation as in odoc_gen.ml but needed here to extend packages. 129 Handles both simple package names and package names with constraints. *) 130let get_extra_doc_deps opamfile = 131 let open OpamParserTypes.FullPos in 132 let extensions = OpamFile.OPAM.extensions opamfile in 133 match OpamStd.String.Map.find_opt "x-extra-doc-deps" extensions with 134 | None -> OpamPackage.Name.Set.empty 135 | Some value -> 136 let extract_name item = 137 match item.pelem with 138 | String name -> Some name 139 | Option (inner, _) -> 140 (match inner.pelem with 141 | String name -> Some name 142 | _ -> None) 143 | _ -> None 144 in 145 let extract_names acc v = 146 match v.pelem with 147 | List { pelem = items; _ } -> 148 List.fold_left (fun acc item -> 149 match extract_name item with 150 | Some name -> 151 OpamPackage.Name.Set.add (OpamPackage.Name.of_string name) acc 152 | None -> acc 153 ) acc items 154 | _ -> acc 155 in 156 extract_names OpamPackage.Name.Set.empty value 157 158(** Create an extended context where x-extra-doc-deps are added to each package's 159 regular depends. This is used for doc link solving - x-extra-doc-deps packages 160 need to be in the solution to be available during doc linking. 161 162 The approach: 163 1. For each pinned package, read its opam file to get x-extra-doc-deps 164 2. Create a new opam file with those deps added to the depends formula 165 3. Create a new context with the extended pins *) 166let extend_with_extra_doc_deps t = 167 let new_pins = OpamPackage.Name.Map.mapi (fun _name (version, opam) -> 168 let extra_deps = get_extra_doc_deps opam in 169 if OpamPackage.Name.Set.is_empty extra_deps then 170 (version, opam) 171 else begin 172 (* Add x-extra-doc-deps to the depends formula *) 173 let depends = OpamFile.OPAM.depends opam in 174 let extra_formula = 175 OpamPackage.Name.Set.fold (fun dep_name acc -> 176 (* Add each extra dep as an unconditional dependency *) 177 let atom = OpamFormula.Atom (dep_name, OpamFormula.Empty) in 178 OpamFormula.And (acc, atom) 179 ) extra_deps OpamFormula.Empty 180 in 181 let new_depends = match extra_formula with 182 | OpamFormula.Empty -> depends 183 | _ -> OpamFormula.And (depends, extra_formula) 184 in 185 let new_opam = OpamFile.OPAM.with_depends new_depends opam in 186 (version, new_opam) 187 end 188 ) t.pins in 189 { t with pins = new_pins }