package opam-client
Source file opamLockCommand.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307(**************************************************************************) (* *) (* Copyright 2019 OCamlPro *) (* *) (* All rights reserved. This file is distributed under the terms of the *) (* GNU Lesser General Public License version 2.1, with the special *) (* exception on linking described in the file LICENSE. *) (* *) (**************************************************************************) open OpamTypes let select_packages atom_locs st = let st, atoms = OpamAuxCommands.simulate_autopin ~quiet:true ~for_view:true st atom_locs in let packages = OpamFormula.packages_of_atoms OpamPackage.Set.Op.(st.packages ++ st.installed) atoms in if OpamPackage.Set.is_empty packages then OpamConsole.error_and_exit `Not_found "No package matching %s" (OpamFormula.string_of_atoms atoms) else (let names = OpamPackage.names_of_packages packages in let missing = OpamStd.List.filter_map (fun (n,vc) -> if OpamPackage.Name.Set.mem n names then None else Some (n,vc)) atoms in if missing <> [] then OpamConsole.error "No package matching %s" (OpamFormula.string_of_atoms missing); (* we keep only one version of each package, the pinned or installed one, the latest version otherwise ; and the one that have their dependencies \ installed *) let packages = OpamPackage.Name.Set.fold (fun name acc -> let pkgs = OpamPackage.packages_of_name packages name in let pkg, is_pinned = let open OpamPackage.Set.Op in let pinned = pkgs %% st.pinned in if OpamPackage.Set.is_empty pinned then pkgs %% st.installed, false else pinned, true in let nv = match OpamPackage.Set.elements pkg with | [nv] -> nv | _ -> (let nv = OpamPackage.Set.max_elt pkgs in OpamConsole.note "Package %s is not installed nor pinned, generating lock \ file for its latest version %s" (OpamConsole.colorise `underline (OpamPackage.Name.to_string name)) (OpamConsole.colorise `underline (OpamPackage.version_to_string nv)); nv) in let opam = if is_pinned then let open OpamStd.Option.Op in match OpamSwitchState.opam st nv |> OpamFile.OPAM.url >>| OpamFile.URL.url >>= OpamUrl.local_dir >>= OpamPinned.find_opam_file_in_source name >>| fst >>| OpamFile.OPAM.read with | Some opam -> (* we add the name/version because of an [OpamFile.OPAM.package] in all depends *) let opam = if OpamFile.OPAM.name_opt opam = None then OpamFile.OPAM.with_name name opam else opam in if OpamFile.OPAM.version_opt opam = None then OpamFile.OPAM.with_version (OpamPackage.version nv) opam else opam | None -> OpamSwitchState.opam st nv else OpamSwitchState.opam st nv in let atoms = OpamFormula.atoms (OpamPackageVar.all_depends ~depopts:false st opam) in let missing = List.filter (fun (n,vc) -> let pkgs = OpamPackage.packages_of_name (OpamPackage.Set.union st.installed packages) n in OpamPackage.Set.is_empty pkgs || not (OpamPackage.Set.exists (OpamFormula.check (n,vc)) pkgs)) atoms in if missing <> [] then (OpamConsole.error "Skipping %s, dependencies are not satisfied in this switch, \ not installed packages are:\n%s" (OpamPackage.to_string nv) (OpamStd.Format.itemize OpamFormula.string_of_atom missing); acc) else OpamPackage.Set.add nv acc) names OpamPackage.Set.empty in st, packages) let get_git_url url nv dir = let module VCS = (val OpamRepository.find_backend_by_kind url.OpamUrl.backend) in let open OpamProcess.Job.Op in OpamProcess.Job.run @@ VCS.get_remote_url ?hash:url.OpamUrl.hash dir @@| function | Some u -> (if u.OpamUrl.hash = None then OpamConsole.warning "Referenced git branch for %s is not available in remote: %s, \ use default branch instead." (OpamConsole.colorise `underline (OpamPackage.to_string nv)) (OpamUrl.to_string u); Some u) | _ -> (OpamConsole.error "Can't retrieve remote informations for %s" (OpamPackage.to_string nv); None) let lock_opam ?(only_direct=false) st opam = let nv = OpamFile.OPAM.package opam in (* Depends *) let all_depends = OpamSwitchState.dependencies ~depopts:true ~build:true ~post:true ~installed:true st (OpamPackage.Set.singleton nv) |> OpamPackage.Set.remove nv in let depends = if only_direct then let names = OpamFilter.filter_formula ~default:true (fun _ -> None) (OpamFile.OPAM.depends opam) |> OpamFormula.fold_left (fun acc (n,_) -> OpamPackage.Name.Set.add n acc) OpamPackage.Name.Set.empty in OpamPackage.packages_of_names all_depends names else all_depends in let map_of_set x set = OpamPackage.Map.of_list (List.map (fun nv -> nv, x) (OpamPackage.Set.elements set)) in let depends_map = map_of_set `version depends in (* others: dev, test, doc, dev-setup *) let open OpamPackage.Set.Op in let select ?(build=false) ?(test=false) ?(doc=false) ?(dev_setup=false) ?(dev=false) ?(default=false) ?(post=false) () = OpamFormula.packages st.packages (OpamFilter.filter_deps ~build ~test ~doc ~dev ~dev_setup ~default ~post (OpamFile.OPAM.depends opam)) in let default = select () in let select_depends typ selection = let depends = selection -- default in let installed = depends %% st.installed in let uninstalled = OpamPackage.(Name.Set.diff (names_of_packages depends) (names_of_packages installed)) in if OpamPackage.Name.Set.is_empty uninstalled then let depends_map = map_of_set `other installed in if only_direct then depends_map else (OpamSwitchState.dependencies ~depopts:false ~build:true ~post:true ~installed:true st installed -- all_depends) |> map_of_set (`other_dep typ) |> OpamPackage.Map.union (fun _v _o -> `other_dep typ) depends_map else (OpamConsole.msg "Not all dependencies of %s are satisfied, not \ including these: %s\n" (OpamPackage.to_string nv) (OpamStd.List.concat_map ", " OpamPackage.Name.to_string (OpamPackage.Name.Set.elements uninstalled)); OpamPackage.Map.empty) in (* variables are set here as a string *) let dev_depends_map = select_depends "dev" (select ~dev:true ~build:true () -- select ~build:true ()) in let test_depends_map = select_depends "with-test" (select ~test:true ()) in let doc_depends_map = select_depends "with-doc" (select ~doc:true ()) in let dev_setup_depends_map = select_depends "with-dev-setup" (select ~dev_setup:true ()) in let depends = let f a b = match a,b with | _, (`other_dep _ as ot) | (`other_dep _ as ot), _ -> ot | _, `other | `other, _ -> `other | `version, `version -> `version in OpamPackage.Map.( depends_map |> union f dev_depends_map |> union f test_depends_map |> union f doc_depends_map |> union f dev_setup_depends_map ) in (* formulas *) let filters = OpamFormula.fold_left (fun acc form -> let n, vc = form in OpamPackage.Name.Map.add n vc acc) OpamPackage.Name.Map.empty (OpamFile.OPAM.depends opam) in let depends_formula = OpamFormula.ands (List.rev (OpamPackage.Map.fold (fun nv cst acc -> let filter = let cst_v = Atom ( Constraint (`Eq, FString (OpamPackage.version_to_string nv))) in match cst with | `version -> cst_v | `other_dep typ -> And (cst_v, Atom (Filter (FIdent ([], OpamVariable.of_string typ, None)))) | `other -> let orig_deps_formula = OpamPackage.Name.Map.find (OpamPackage.name nv) filters in let new_formula = OpamFormula.map (function | Constraint _ -> cst_v | Filter _ as f -> Atom f ) orig_deps_formula in if new_formula = orig_deps_formula then And (cst_v, new_formula) else new_formula in Atom (nv.name, filter)::acc) depends [])) in (* keep installed depopts in depends and set as conflicting uninstalled ones *) let all_depopts = OpamFormula.packages st.packages (OpamFilter.filter_deps ~build:true ~test:true ~doc:true ~dev_setup:true ~default:true ~post:false (OpamFile.OPAM.depopts opam)) in let installed_depopts = OpamPackage.Set.inter all_depopts st.installed in let uninstalled_depopts = OpamPackage.(Name.Set.diff (names_of_packages all_depopts) (names_of_packages installed_depopts)) in let conflicts = OpamFormula.ors (OpamFile.OPAM.conflicts opam :: List.map (fun n -> Atom (n, Empty)) (OpamPackage.Name.Set.elements uninstalled_depopts)) in let pin_depends = OpamPackage.Set.fold (fun nv acc -> if not (OpamSwitchState.is_pinned st nv.name) then acc else match OpamSwitchState.primary_url st nv with | None -> acc | Some u -> match OpamUrl.local_dir u with | Some d -> let local_warn () = OpamConsole.warning "Dependency %s is pinned to local target %s" (OpamPackage.to_string nv) (OpamUrl.to_string u); acc in (match u.OpamUrl.backend with | #OpamUrl.version_control -> (match get_git_url u nv d with | Some resolved_u -> OpamConsole.note "Local pin %s resolved to %s" (OpamUrl.to_string u) (OpamUrl.to_string resolved_u); (nv, resolved_u) :: acc | None -> local_warn ()) | _ -> local_warn ()) | None -> (nv, u) :: acc) all_depends [] |> List.rev in opam |> OpamFile.OPAM.with_depopts OpamFormula.Empty |> OpamFile.OPAM.with_depends depends_formula |> OpamFile.OPAM.with_conflicts conflicts |> OpamFile.OPAM.with_pin_depends pin_depends |> OpamFile.OPAM.with_url_opt None