Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
opamSysInteract.ml1 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 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296(**************************************************************************) (* *) (* Copyright 2019-2020 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. *) (* *) (**************************************************************************) let log fmt = OpamConsole.log "XSYS" fmt (* Run commands *) (* Always call this function to run a command, as it handles `dryrun` option *) let run_command ?vars ?(discard_err=false) ?allow_stdin ?verbose ?(dryrun=false) cmd args = let clean_output = if not discard_err then fun k -> k None else fun k -> OpamFilename.with_tmp_dir_job @@ fun dir -> let f = OpamFilename.Op.(dir // "out") in OpamFilename.touch f; k (Some (OpamFilename.to_string f)) in let verbose = OpamStd.Option.default OpamCoreConfig.(!r.verbose_level > 3) verbose in let env = match vars with | None -> None | Some vars -> let env = OpamStd.Env.list () in let vars = List.map (fun (c, (n, v)) -> c, (OpamStd.Env.Name.of_string n, v)) vars in let set_vars, kept_vars, env = List.fold_left (fun (n,p,e) (op, (name, content as var)) -> match OpamStd.(List.assoc_opt Env.Name.equal name env), op with | Some c, `add when String.equal c content -> n, p, e | Some _, `set -> var::n, p, (List.filter (fun (k, _) -> not (OpamStd.Env.Name.equal k name)) env) | Some _, _ -> n, var::p, e | None, _ -> var::n, p, e ) ([],[], env) vars in let str_var (v,c) = Printf.sprintf "%s=%s" v c in if set_vars = [] then ((if kept_vars <> [] then log "Won't override %s" (OpamStd.List.to_string str_var (kept_vars :> (string * string) list))); None) else (log "Adding to env %s" (OpamStd.List.to_string str_var (set_vars :> (string * string) list)); Some ((set_vars @ env :> (string * string) list) |> List.rev_map str_var |> Array.of_list)) in let run = if dryrun then OpamProcess.Job.dry_run else OpamProcess.Job.run in let open OpamProcess.Job.Op in run @@ clean_output @@ fun stdout -> OpamSystem.make_command ?env ?stdout ?allow_stdin ~verbose cmd args @@> fun r -> let code = r.r_code in let out = r.r_stdout in OpamProcess.cleanup r; Done (code, out) let run_query_command ?vars cmd args = let vars = (`set, ("LC_ALL","C"))::OpamStd.Option.to_list vars in let code,out = run_command ~vars cmd args in if code = 0 then out else [] let run_command_exit_code ?vars ?allow_stdin ?verbose cmd args = let code,_ = run_command ?vars ?allow_stdin ?verbose ~dryrun:OpamStateConfig.(!r.dryrun) cmd args in code type test_setup = { install: bool; installed: [ `all | `none | `set of OpamSysPkg.Set.t]; available: [ `all | `none | `set of OpamSysPkg.Set.t]; } (* Internal module to get package manager commands defined in global config file *) module Commands = struct let get_cmd_opt config family = OpamStd.String.Map.find_opt family (OpamFile.Config.sys_pkg_manager_cmd config) let cygwin_t = "cygwin" let msys2_t = "msys2" let msys2 config = let override = get_cmd_opt config msys2_t in OpamStd.Option.map_default OpamFilename.to_string "pacman.exe" override let cygcheck config = let override = get_cmd_opt config cygwin_t in OpamStd.Option.map_default OpamFilename.to_string "cygcheck.exe" override end (* Please keep this alphabetically ordered, in the type definition, and in below pattern matching *) type families = | Alpine | Altlinux | Arch | Centos | Cygwin | Debian | Dummy of test_setup | Freebsd | Gentoo | Homebrew | Macports | Msys2 | Netbsd | Nix | Openbsd | Suse (* System status *) let family ~env () = match OpamSysPoll.os_family env with | None -> Printf.ksprintf failwith "External dependency unusable, OS family not detected." | Some family when OpamStd.String.starts_with ~prefix:"dummy-" family -> let error () = OpamConsole.error_and_exit `Bad_arguments "Syntax error on dummy depext test family. Syntax is \ dummy-<success|failure>[:<*|0|pkgslist>:*|0|pkgslist>]" in let install, installed, available = match OpamStd.String.cut_at family ':' with | Some (install, packages) -> let installed, available = match OpamStd.String.cut_at packages ':' with | Some (installed, available) -> Some installed, Some available | None -> error () in install, installed, available | None -> family, None, None in let install = match install with | "dummy-success" -> true | "dummy-failure" -> false | _ -> error() in let parse_packages ~default = function | Some "" | None -> default | Some "*" -> `all | Some "0" -> `none | Some set -> `set (OpamStd.String.split set ',' |> List.map OpamSysPkg.of_string |> OpamSysPkg.Set.of_list) in let installed = parse_packages ~default:`none installed in let available = parse_packages ~default:`all available in Dummy { install; installed; available; } | Some family -> match family with | "alpine" | "wolfi" -> Alpine | "altlinux" -> Altlinux | "amzn" | "centos" | "fedora" | "mageia" | "oraclelinux" | "ol" | "rhel" -> Centos | "archlinux" | "arch" -> Arch | "bsd" -> begin match OpamSysPoll.os_distribution env with | Some ("freebsd" | "dragonfly") -> Freebsd | Some "netbsd" -> Netbsd | Some "openbsd" -> Openbsd | _ -> Printf.ksprintf failwith "External dependency handling not supported for OS family 'bsd'." end | "debian" | "ubuntu" -> Debian | "gentoo" -> Gentoo | "homebrew" -> Homebrew | "macports" -> Macports | "nixos" -> Nix | "macos" -> failwith "External dependency handling for macOS requires either \ MacPorts or Homebrew - neither could be found" | "suse" | "opensuse" -> Suse | "windows" -> (match OpamSysPoll.os_distribution env with | Some "msys2" -> Msys2 | Some "cygwin" -> Cygwin | _ -> failwith "External dependency handling not supported for Windows unless \ MSYS2 or Cygwin is installed. In particular 'os-distribution' \ must be set to 'msys2' or 'cygwin'.") | family -> Printf.ksprintf failwith "External dependency handling not supported for OS family '%s'." family module Cygwin = struct open OpamFilename.Op let url_setupexe = OpamUrl.of_string "https://cygwin.com/setup-x86_64.exe" let url_setupexe_sha512 = OpamUrl.of_string "https://cygwin.com/sha512.sum" let mirror = "https://cygwin.mirror.constant.com/" (* Cygwin setup exe must be stored at Cygwin installation root *) let setupexe = "setup-x86_64.exe" let cygcheckexe = "cygcheck.exe" open OpamStd.Option.Op let cygbin_opt config = Commands.(get_cmd_opt config cygwin_t) >>| OpamFilename.dirname let msys2bin_opt config = Commands.(get_cmd_opt config msys2_t) >>| OpamFilename.dirname let cygroot_opt config = cygbin_opt config >>| OpamFilename.dirname_dir let cygroot config = match cygroot_opt config with | Some c -> c | None -> match OpamSystem.resolve_command "cygcheck.exe" with | Some cygcheck -> OpamFilename.dirname_dir (OpamFilename.Dir.of_string (Filename.dirname cygcheck)) | None -> failwith "Cygwin install not found" let internal_cygwin = let internal = Lazy.from_fun @@ fun () -> (OpamStateConfig.(!r.root_dir) / ".cygwin") in fun () -> Lazy.force internal let internal_cygroot () = internal_cygwin () / "root" let internal_cygcache () = internal_cygwin () / "cache" let cygsetup () = internal_cygwin () // setupexe let is_internal config = OpamStd.Option.equal OpamFilename.Dir.equal (cygroot_opt config) (Some (internal_cygroot ())) let download_setupexe dst = let overwrite = true in let kind = `SHA512 in let dst_exists = OpamFilename.exists dst in let current_checksum = if dst_exists then Some (OpamHash.compute ~kind (OpamFilename.to_string dst)) else None in let open OpamProcess.Job.Op in log "Downloading Cygwin setup checksums"; if OpamConsole.disp_status_line () then if dst_exists then OpamConsole.status_line "Checking if Cygwin setup is up-to-date" else OpamConsole.status_line "Downloading Cygwin setup from cygwin.com"; OpamFilename.with_tmp_dir_job @@ fun dir -> OpamProcess.Job.catch (fun exn -> let backtrace = Printexc.get_raw_backtrace () in if dst_exists then begin OpamConsole.warning "%s failed to update" setupexe; Done () end else Printexc.raise_with_backtrace exn backtrace ) @@ fun () -> OpamDownload.download ~overwrite url_setupexe_sha512 dir @@+ fun file -> let checksum = let content = OpamFilename.read file in let re = (* File content: >SHA512 setup-x86.exe >SHA512 setup-x86_64.exe *) Re.(compile @@ seq [ group @@ repn (alt [ digit ; rg 'A' 'F'; rg 'a' 'f' ]) 128 (Some 128); rep space; str "setup-x86_64.exe" ]) in try Some (OpamHash.sha512 Re.(Group.get (exec re content) 1)) with Not_found -> None in if OpamStd.Option.equal OpamHash.equal current_checksum checksum && dst_exists && OpamStd.Option.equal OpamHash.equal current_checksum (Some (OpamHash.compute ~kind (OpamFilename.to_string dst))) then begin log "Up-to-date"; OpamConsole.clear_status (); Done () end else begin log "Downloading setup-x86_64.exe"; if OpamConsole.disp_status_line () then OpamConsole.status_line "Downloading Cygwin setup from cygwin.com"; OpamDownload.download_as ~overwrite ?checksum url_setupexe dst @@+ fun () -> OpamConsole.clear_status (); Done () end let set_fstab_noacl = let orig = "binary," in let re = Re.compile (Re.str orig) in fun fstab -> let content = OpamFilename.read fstab in let content = Re.replace_string re ~by:("noacl,"^orig) content in OpamFilename.with_open_out_bin_atomic fstab (fun oc -> Stdlib.output_string oc content) let install packages = let open OpamProcess.Job.Op in let cygwin_root = internal_cygroot () in let cygwin_bin = cygwin_root / "bin" in let fstab = cygwin_root / "etc" // "fstab" in let cygcheck = cygwin_bin // cygcheckexe in let local_cygwin_setupexe = cygsetup () in if OpamFilename.exists cygcheck then OpamConsole.warning "Cygwin already installed in root %s" (OpamFilename.Dir.to_string cygwin_root) else (* rjbou: dry run ? there is no dry run on install, from where this function is called *) (OpamProcess.Job.run @@ (* download setup.exe *) download_setupexe local_cygwin_setupexe @@+ fun () -> (* launch install *) let args = [ "--root"; OpamFilename.Dir.to_string cygwin_root; "--arch"; "x86_64"; "--only-site"; "--site"; mirror; "--local-package-dir"; OpamFilename.Dir.to_string (internal_cygcache ()); "--no-admin"; "--no-desktop"; "--no-replaceonreboot"; "--no-shortcuts"; "--no-startmenu"; "--no-write-registry"; "--no-version-check"; "--quiet-mode"; "noinput"; ] @ match packages with | [] -> [] | spkgs -> [ "--packages"; OpamStd.List.concat_map "," OpamSysPkg.to_string spkgs ] in let args = if Unix.has_symlink () then "--symlink-type" :: "native" :: args else args in OpamSystem.make_command (OpamFilename.to_string local_cygwin_setupexe) args @@> fun r -> OpamSystem.raise_on_process_error r; set_fstab_noacl fstab; Done ()) let analysis_cache = Hashtbl.create 17 let analyse_install path = let cygbin = if not (Sys.file_exists path) then Error (path ^ " not found!") else if Filename.remove_extension (Filename.basename path) = "cygcheck" then (* path refers to cygcheck directly *) Ok (Filename.dirname path) else if not (Sys.is_directory path) then Error (Printf.sprintf "%s neither a directory nor cygcheck.exe" path) else (* path is a directory - search path, path\bin and path\usr\bin *) let contains_cygcheck dir = Sys.file_exists (Filename.concat dir "cygcheck.exe") in let tests = [ path; (* e.g. C:\cygwin64\bin / C:\msys64\usr\bin *) Filename.concat path "bin"; (* e.g. C:\cygwin64 *) Filename.concat (Filename.concat path "usr") "bin" (* e.g. C:\msys64 *) ] in match List.filter contains_cygcheck tests with | [] -> Error (Printf.sprintf "cygcheck.exe not found in %s, or subdirectories \ bin and usr\\bin" path) | _::_::_ -> Error (Printf.sprintf "cygcheck.exe found in multiple places in %s which suggests \ it is not a Cygwin/MSYS2 installation" path) | [path] -> Ok path in let identify dir = try Hashtbl.find analysis_cache dir with Not_found -> let result = let cygpath = Filename.concat dir "cygpath.exe" in if not (Sys.file_exists cygpath) then Error (Printf.sprintf "cygcheck.exe found in %s, but cygpath.exe was not" dir) else match OpamStd.Sys.get_windows_executable_variant ~search_in_first:dir cygpath with | `Native | `Tainted _ -> Error (Printf.sprintf "cygcheck.exe found in %s; but it does not appear \ to be part of a Cygwin or MSYS2 installation" dir) | (`Msys2 | `Cygwin) as kind -> (* Check that pacman.exe is present with MSYS2: it is typically not present with a Git-for-Windows Git Bash session, and as these are basically unusable (they don't have all the required tools, and we have no package manager with which to add them), it's better to exclude them). *) if kind = `Msys2 && not (Sys.file_exists (Filename.concat dir "pacman.exe")) then Error (Printf.sprintf "cygcheck.exe found in %s, which appears to be from \ an MSYS2 installation, but pacman.exe was not" dir) else let r = OpamProcess.run (OpamProcess.command ~name:(OpamSystem.temp_file "command") ~allow_stdin:false cygpath ["-w"; "--"; "/"]) in OpamProcess.cleanup ~force:true r; if OpamProcess.is_success r then match r.OpamProcess.r_stdout with | [] -> Error ("Unexpected error translating \"/\" with " ^ cygpath) | l::_ -> Ok (kind, OpamFilename.Dir.of_string l) else Error ("Could not determine the root for " ^ cygpath) in Hashtbl.add analysis_cache dir result; result in Result.bind cygbin identify let bindir_for_root kind root = let open OpamFilename.Op in match kind with | `Msys2 -> root / "usr" / "bin" | `Cygwin -> root / "bin" (* Set setup.exe in the good place, ie in .opam/.cygwin/ *) let check_setup ~update = let dst = cygsetup () in if update || not (OpamFilename.exists dst) then OpamProcess.Job.run @@ download_setupexe dst end let yum_cmd = lazy begin if OpamSystem.resolve_command "yum" <> None then "yum" else if OpamSystem.resolve_command "dnf" <> None then "dnf" else raise (OpamSystem.Command_not_found "yum or dnf") end let packages_status ?(env=OpamVariable.Map.empty) config packages = let (+++) pkg set = OpamSysPkg.Set.add (OpamSysPkg.of_string pkg) set in (* Some package managers don't permit to request on available packages. In this case, we consider all non installed packages as [available]. *) let open OpamSysPkg.Set.Op in let compute_sets ?sys_available sys_installed = let installed = packages %% sys_installed in match sys_available with | Some sys_available -> let s_available = (packages -- installed) %% sys_available in let s_not_found = packages -- installed -- s_available in { OpamSysPkg.s_available; s_not_found } | None -> let s_available = packages -- installed in { OpamSysPkg.status_empty with s_available } in let to_string_list pkgs = OpamSysPkg.(Set.fold (fun p acc -> to_string p :: acc) pkgs []) in let names_re ?str_pkgs () = let str_pkgs = OpamStd.Option.default (to_string_list packages) str_pkgs in let need_escape = Re.(compile (group (set "+."))) in Printf.sprintf "^(%s)$" (OpamStd.List.concat_map "|" (Re.replace ~all:true need_escape ~f:(fun g -> "\\"^Re.Group.get g 1)) str_pkgs) in let with_regexp_sgl re_pkg = List.fold_left (fun pkgs l -> try Re.(Group.get (exec re_pkg l) 1) +++ pkgs with Not_found -> pkgs) OpamSysPkg.Set.empty in let package_set_of_pkgpath l = List.fold_left (fun set pkg -> let short_name = match String.rindex pkg '/' with | exception Not_found -> pkg | idx -> String.sub pkg (idx+1) (String.length pkg - idx - 1) in let no_flavor = match String.index short_name ',' with | exception Not_found -> short_name | idx -> String.sub short_name 0 idx in set |> OpamSysPkg.Set.add (OpamSysPkg.of_string pkg) |> OpamSysPkg.Set.add (OpamSysPkg.of_string short_name) |> OpamSysPkg.Set.add (OpamSysPkg.of_string no_flavor) ) OpamSysPkg.Set.empty l in let compute_sets_with_virtual get_avail_w_virtuals get_installed = let sys_available, sys_provides = get_avail_w_virtuals () in let need_inst_check = OpamSysPkg.Map.fold (fun cp vps set -> if OpamSysPkg.Set.(is_empty (inter vps packages)) then set else OpamSysPkg.Set.add cp set) sys_provides packages in let str_need_inst_check = to_string_list need_inst_check in let sys_installed = get_installed str_need_inst_check in let sys_installed = (* Resolve installed "provides" packages; assumes provides are not recursive *) OpamSysPkg.Set.fold (fun p acc -> match OpamSysPkg.Map.find_opt p sys_provides with | None -> acc | Some ps -> OpamSysPkg.Set.union acc ps) sys_installed sys_installed in compute_sets sys_installed ~sys_available in let compute_sets_for_arch ~pacman = let get_avail_w_virtuals () = let package_provided str = OpamSysPkg.of_string (match OpamStd.String.cut_at str '=' with | None -> str | Some (p, _vc) -> p) in (* Output format: >Repository : core >Name : python >Version : 3.9.6-1 >Description : Next generation of the python high-level scripting language >Architecture : x86_64 >URL : https://www.python.org/ >Licenses : custom >Groups : None >Provides : python3 >Depends On : bzip2 expat gdbm libffi libnsl libxcrypt openssl >Optional Deps : python-setuptools > python-pip >[...] Format partially described in https://archlinux.org/pacman/PKGBUILD.5.html *) (* Discard stderr to not have it pollute output. Plus, exit code is the number of packages not found. *) run_command ~discard_err:true pacman ["-Si"] |> snd |> List.fold_left (fun ((avail, provides, latest) as acc) l -> if OpamStd.String.starts_with ~prefix:"Name" l then match OpamStd.String.split l ' ' with | "Name"::":"::p::_ -> p +++ avail, provides, Some (OpamSysPkg.of_string p) | _ -> acc else if OpamStd.String.starts_with ~prefix:"Provides" l then match OpamStd.String.split l ' ' with | "Provides"::":"::"None"::[] -> acc | "Provides"::":"::pkgs -> let ps = OpamSysPkg.Set.of_list (List.map package_provided pkgs) in let provides = match latest with | Some p -> OpamSysPkg.Map.add p ps provides | None -> provides (* Bad pacman output ?? *) in ps ++ avail, provides, None | _ -> acc else acc) (OpamSysPkg.Set.empty, OpamSysPkg.Map.empty, None) |> (fun (a,p,_) -> a,p) in let get_installed str_pkgs = (* output: >extra/cmake 3.17.1-1 [installed] > A cross-platform open-source make system >extra/cmark 0.29.0-1 > CommonMark parsing and rendering library and program in C *) let re_pkg = Re.(compile @@ seq [ bol; rep1 @@ alt [alnum; punct]; char '/'; group @@ rep1 @@ alt [alnum; punct]; space; ]) in run_query_command pacman ["-Qs" ; names_re ~str_pkgs ()] |> with_regexp_sgl re_pkg in compute_sets_with_virtual get_avail_w_virtuals get_installed in match family ~env () with | Alpine -> (* Output format >capnproto policy: > 0.8.0-r1: > lib/apk/db/installed > @edgecommunity https://dl-cdn.alpinelinux.org/alpine/edge/community >at policy: > 3.2.1-r1: > https://dl-cdn.alpinelinux.org/alpine/v3.13/community >vim policy: > 8.2.2320-r0: > lib/apk/db/installed > https://dl-cdn.alpinelinux.org/alpine/v3.13/main > 8.2.2852-r0: > @edge https://dl-cdn.alpinelinux.org/alpine/edge/main >hwids-udev policy: > 20201207-r0: > https://dl-cdn.alpinelinux.org/alpine/v3.13/main > @edge https://dl-cdn.alpinelinux.org/alpine/v3.13/main > https://dl-cdn.alpinelinux.org/alpine/edge/main > @edge https://dl-cdn.alpinelinux.org/alpine/edge/main *) let sys_installed, sys_available = let pkg_name = Re.(compile @@ seq [ bol; group @@ rep1 @@ alt [ alnum; punct ]; space; str "policy:"; eol ]) in let repo_name = Re.(compile @@ seq [ bol; repn space 4 (Some 4); char '@'; group @@ rep1 @@ alt [ alnum; punct ]; space ]) in let add_pkg pkg repo installed (inst,avail) = let pkg = match repo with Some r -> pkg^"@"^r | None -> pkg in if installed then pkg +++ inst, avail else inst, pkg +++ avail in to_string_list packages |> List.map (fun s -> match OpamStd.String.cut_at s '@' with | Some (pkg, _repo) -> pkg | None -> s) |> (fun l -> run_query_command "apk" ("policy"::l)) |> List.fold_left (fun (pkg, installed, instavail) l -> try (* package name *) Re.(Group.get (exec pkg_name l) 1), false, instavail with Not_found -> if l.[2] <> ' ' then (* only version field is after two spaces *) pkg, false, instavail else if l = " lib/apk/db/installed" then (* from https://git.alpinelinux.org/apk-tools/tree/src/database.c#n58 *) pkg, true, instavail else (* repo (tagged and non-tagged) *) let repo = try Some Re.(Group.get (exec repo_name l) 1) with Not_found -> None in pkg, installed, add_pkg pkg repo installed instavail) ("", false, OpamSysPkg.Set.(empty, empty)) |> (fun (_,_, instavail) -> instavail) in compute_sets sys_installed ~sys_available | Arch -> compute_sets_for_arch ~pacman:"pacman" | Centos | Altlinux | Suse -> (* Output format: >crypto-policies >python3-pip-wheel *) let sys_installed = run_query_command "rpm" ["-qa"; "--qf"; "%{NAME}\\n"] |> List.map OpamSysPkg.of_string |> OpamSysPkg.Set.of_list in compute_sets sys_installed | Cygwin -> (* Output format: >Cygwin Package Information >Package Version >git 2.35.1-1 >binutils 2.37-2 *) let sys_installed = run_query_command (Commands.cygcheck config) ([ "-c"; "-d" ] @ to_string_list packages) |> (function | _::_::l -> l | _ -> []) |> OpamStd.List.filter_map (fun l -> match OpamStd.String.split l ' ' with | pkg::_ -> Some pkg | _ -> None) |> List.map OpamSysPkg.of_string |> OpamSysPkg.Set.of_list in compute_sets sys_installed | Debian -> let get_avail_w_virtuals () = let provides_sep = Re.(compile @@ str ", ") in let package_provided str = OpamSysPkg.of_string (match OpamStd.String.cut_at str ' ' with | None -> str | Some (p, _vc) -> p) in (* Output format: >Package: apt >Version: 2.1.7 >Installed-Size: 4136 >Maintainer: APT Development Team <deity@lists.debian.org> >Architecture: amd64 >Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~) >Provides: apt-transport-https (= 2.1.7) > [...] > The `Provides' field contains provided virtual package(s) by current `Package:'. * manpages.debian.org/buster/apt/apt-cache.8.en.html * www.debian.org/doc/debian-policy/ch-relationships.html#s-virtual *) run_query_command "apt-cache" ["search"; names_re (); "--names-only"; "--full"] |> List.fold_left (fun ((avail, provides, latest) as acc) l -> if OpamStd.String.starts_with ~prefix:"Package: " l then let p = String.sub l 9 (String.length l - 9) in p +++ avail, provides, Some (OpamSysPkg.of_string p) else if OpamStd.String.starts_with ~prefix:"Provides: " l then let ps = List.map package_provided (Re.split ~pos:10 provides_sep l) |> OpamSysPkg.Set.of_list in avail ++ ps, (match latest with | Some p -> OpamSysPkg.Map.add p ps provides | None -> provides (* Bad apt-cache output ?? *)), None else acc) (OpamSysPkg.Set.empty, OpamSysPkg.Map.empty, None) |> (fun (a,p,_) -> a,p) in let get_installed str_pkgs = (* ouput: >ii uim-gtk3 1:1.8.8-6.1 amd64 Universal ... >ri uim-gtk3-immodule:amd64 1:1.8.8-6.1 amd64 Universal ... First column is <desired action><package status> * Desired action: u = Unknown h = Hold p = Purge i = Install r = Remove * Package status: n = Not-installed U = Unpacked t = Triggers-pending c = Config-files F = Half-configured i = Installed H = Half-installed W = Triggers-awaiting We focus on the second element of the column *) let re_pkg = Re.(compile @@ seq [ bol; alpha; char 'i'; rep1 @@ space; group @@ rep1 @@ diff (alt [alnum; punct]) (char ':'); (* pkg:arch convention *) ]) in (* discard stderr as just nagging *) run_command ~discard_err:true "dpkg-query" ("-l" :: str_pkgs) |> snd |> with_regexp_sgl re_pkg in compute_sets_with_virtual get_avail_w_virtuals get_installed | Dummy test -> let sys_installed = match test.installed with | `all -> packages | `none -> OpamSysPkg.Set.empty | `set pkgs -> pkgs %% packages in let sys_available = match test.available with | `all -> packages | `none -> OpamSysPkg.Set.empty | `set pkgs -> pkgs %% packages in compute_sets ~sys_available sys_installed | Freebsd -> let sys_installed = run_query_command "pkg" ["query"; "%n\n%o"] |> List.map OpamSysPkg.of_string |> OpamSysPkg.Set.of_list in compute_sets sys_installed | Gentoo -> let sys_installed = let re_pkg = Re.(compile @@ seq [ group @@ rep1 @@ alt [alnum; punct]; char '-'; rep @@ seq [rep1 digit; char '.']; rep1 digit; rep any; eol ]) in List.fold_left (fun inst dir -> List.fold_left (fun inst pkg -> let to_string d = OpamFilename.basename_dir d |> OpamFilename.Base.to_string in let pkg = Filename.concat (to_string dir) (to_string pkg) in try Re.(Group.get (exec re_pkg pkg) 1) :: inst with Not_found -> inst ) inst (OpamFilename.dirs dir)) [] (OpamFilename.dirs (OpamFilename.Dir.of_string "/var/db/pkg")) |> package_set_of_pkgpath in compute_sets sys_installed | Homebrew -> (* accept 'pkgname' and 'pkgname@version' exampe output >openssl@1.1 >bmake >koekeishiya/formulae/skhd *) let sys_installed = run_query_command "brew" ["list"; "--full-name"] |> List.fold_left (fun res s -> List.fold_left (fun res spkg -> let parse_fullname pkg = match OpamStd.String.rcut_at pkg '/' with | None -> [pkg] | Some (_, simple_name) -> [pkg; simple_name] in match OpamStd.String.cut_at spkg '@' with | Some (n,_v) -> parse_fullname n@parse_fullname spkg@res | None -> parse_fullname spkg@res) res (OpamStd.String.split s ' ')) [] |> List.map OpamSysPkg.of_string |> OpamSysPkg.Set.of_list in compute_sets sys_installed | Macports -> let variants_map, packages = OpamSysPkg.(Set.fold (fun spkg (map, set) -> match OpamStd.String.cut_at (to_string spkg) ' ' with | Some (pkg, variant) -> OpamStd.String.Map.add pkg variant map, pkg +++ set | None -> map, Set.add spkg set) packages (OpamStd.String.Map.empty, Set.empty)) in let str_pkgs = to_string_list packages in let sys_installed = (* output: > zlib @1.2.11_0 (active) > gtk3 @3.24.21_0+quartz (active) *) let re_pkg = Re.(compile @@ seq [ bol; rep space; group @@ rep1 @@ alt [alnum; punct]; rep1 space; char '@'; rep1 @@ diff any (char '+'); opt @@ group @@ rep1 @@ alt [alnum; punct]; rep1 space; str "(active)"; eol ]) in run_query_command "port" ("installed" :: str_pkgs) |> (function _::lines -> lines | _ -> []) |> List.fold_left (fun pkgs l -> try let pkg = Re.(Group.get (exec re_pkg l) 1) in (* variant handling *) match OpamStd.String.Map.find_opt pkg variants_map with | Some variant -> (try if Re.(Group.get (exec re_pkg l) 2) = variant then (pkg ^ " " ^ variant) +++ pkgs else pkgs with Not_found -> pkgs) | None -> pkg +++ pkgs with Not_found -> pkgs) OpamSysPkg.Set.empty in let sys_available = (* example output >diffutils 3.7 sysutils textproc devel GNU diff utilities >-- >No match for gcc found *) let re_pkg = Re.(compile @@ seq [ bol; group @@ rep1 @@ alt [alnum; punct]; rep1 space; rep1 @@ alt [digit; punct]; ]) in let avail = run_query_command "port" [ "search"; "--line"; "--regex"; names_re ~str_pkgs () ] |> with_regexp_sgl re_pkg in (* variants handling *) let variants = OpamStd.String.Map.filter (fun p _ -> OpamSysPkg.Set.mem (OpamSysPkg.of_string p) avail) variants_map |> OpamStd.String.Map.keys in run_query_command "port" ([ "info"; "--name"; "--variants" ] @ variants) |> List.fold_left (fun (prec, avail) l -> match prec, OpamStd.String.split l ' ' with | _, "name:"::pkg::[] -> Some pkg, avail | Some pkg, "variants:"::variants -> None, List.fold_left (fun avail v -> (pkg ^ " +" ^ (OpamStd.String.remove_suffix ~suffix:"," v)) +++ avail) avail variants | _ -> None, avail ) (None, avail) |> snd in compute_sets sys_installed ~sys_available | Msys2 -> compute_sets_for_arch ~pacman:(Commands.msys2 config) | Netbsd -> let sys_installed = run_query_command "pkg_info" ["-Q"; "PKGPATH"; "-a"] |> package_set_of_pkgpath in compute_sets sys_installed | Nix -> (* We say all requested packages are available but uninstalled. We could check that these packages are available in Nixpkgs, but that would involve an expensive Nixpkgs evaluation. Saying no packages are installed results in a warning that conf packages depend on a 'system package that can no longer be found.' But omitting them will mean that they won't be added to the Nix derivation. *) let s_available = packages in let s_not_found = OpamSysPkg.Set.empty in { OpamSysPkg.s_available; s_not_found } | Openbsd -> let sys_installed = run_query_command "pkg_info" ["-qP"] |> package_set_of_pkgpath in compute_sets sys_installed let stateless_install ?(env=OpamVariable.Map.empty) () = match family ~env () with | exception Failure _ -> true (* no depexts *) | Nix -> true | Alpine | Altlinux | Arch | Centos | Cygwin | Debian | Dummy _ | Freebsd | Gentoo | Homebrew | Macports | Msys2 | Netbsd | Openbsd | Suse -> false (* Install *) let package_manager_name_t ?(env=OpamVariable.Map.empty) config = match family ~env () with | Alpine -> `AsAdmin "apk" | Altlinux -> `AsAdmin "apt-get" | Arch -> `AsAdmin "pacman" | Centos -> `AsAdmin (Lazy.force yum_cmd) | Cygwin -> `AsUser (OpamFilename.to_string (Cygwin.cygsetup ())) | Debian -> `AsAdmin "apt-get" | Dummy test -> if test.install then `AsUser "echo" else `AsUser "false" | Freebsd -> `AsAdmin "pkg" | Gentoo -> `AsAdmin "emerge" | Homebrew -> `AsUser "brew" | Macports -> `AsAdmin "port" | Msys2 -> `AsUser (Commands.msys2 config) | Netbsd -> `AsAdmin "pkgin" | Nix -> `AsUser "nix-build" | Openbsd -> `AsAdmin "pkg_add" | Suse -> `AsAdmin "zypper" (* Perform some action for Nix and Cygwin *) let install_packages_commands_t ?(env=OpamVariable.Map.empty) ~to_show st config sys_packages = let unsafe_yes = OpamCoreConfig.answer_is `unsafe_yes in let yes ?(no=[]) yes r = if unsafe_yes then yes @ r else no @ r in let packages = List.map OpamSysPkg.to_string (OpamSysPkg.Set.elements sys_packages.OpamSysPkg.ti_new) in let pm = package_manager_name_t ~env config in match family ~env () with | Alpine -> [pm, "add"::yes ~no:["-i"] [] packages], None | Altlinux -> [pm, "install"::yes ["-qq"; "-yy"] packages], None | Arch -> [pm, "-Su"::yes ["--noconfirm"] packages], None | Centos -> (* TODO: check if they all declare "rhel" as primary family *) (* Kate's answer: no they don't :( (e.g. Fedora, Oraclelinux define Nothing and "fedora" respectively) *) (* When opam-packages specify the epel-release package, usually it means that other dependencies require the EPEL repository to be already setup when yum-install is called. Cf. opam-depext/#70,#76. *) let epel_release = "epel-release" in let install_epel rest = if List.mem epel_release packages then [pm, "install"::yes ["-y"] [epel_release]] @ rest else rest in install_epel [pm, "install"::yes ["-y"] (OpamStd.String.Set.of_list packages |> OpamStd.String.Set.remove epel_release |> OpamStd.String.Set.elements); `AsUser "rpm", "-q"::"--whatprovides"::packages], None | Cygwin -> (* We use setup_x86_64 to install package instead of `cygcheck` that is stored in `sys-pkg-manager-cmd` field *) Cygwin.check_setup ~update:(not to_show); let is_internal = Cygwin.is_internal config in [`AsUser (OpamFilename.to_string (Cygwin.cygsetup ())), [ "--root"; (OpamFilename.Dir.to_string (Cygwin.cygroot config)); "--quiet-mode"; (if is_internal then "noinput" else "unattended"); "--no-shortcuts"; "--no-startmenu"; "--no-desktop"; "--no-admin"; "--no-version-check"; "--no-write-registry"; "--packages"; String.concat "," packages; ] @ (if is_internal then let common = [ "--upgrade-also"; "--only-site"; "--site"; Cygwin.mirror; "--local-package-dir"; OpamFilename.Dir.to_string (Cygwin.internal_cygcache ()); ] in if Unix.has_symlink () then "--symlink-type" :: "native" :: common else common else []) ], None | Debian -> [pm, "install"::yes ["-qq"; "-yy"] packages], (if unsafe_yes then Some ["DEBIAN_FRONTEND", "noninteractive"] else None) | Dummy test -> if test.install then [pm, packages], None else [pm, []], None | Freebsd -> [pm, "install"::yes ["-y"] packages], None | Gentoo -> [pm, yes ~no:["-a"] [] packages], None | Homebrew -> [pm, "install"::packages], (* NOTE: Does not have any interactive mode *) Some (["HOMEBREW_NO_AUTO_UPDATE","yes"]) | Macports -> let packages = (* Separate variants from their packages *) List.map (fun p -> OpamStd.String.split p ' ') packages |> List.flatten in [pm, yes ["-N"] ("install"::packages)], None | Msys2 -> (* NOTE: MSYS2 interactive mode may break (not show output until key pressed) when called from opam. Confer https://www.msys2.org/wiki/Terminals/#mixing-msys2-and-windows. *) [`AsUser (Commands.msys2 config), "-Su"::"--noconfirm"::packages], None | Netbsd -> [pm, yes ["-y"] ("install" :: packages)], None | Nix -> (match st with | None -> log "Nix depext must be passed switch"; [], None | Some (st : _ OpamStateTypes.switch_state) -> let dir = OpamPath.Switch.meta st.switch_global.root st.switch in let drvFile = OpamFilename.create dir (OpamFilename.Base.of_string "env.nix") in let packages = String.concat " " (OpamSysPkg.Set.fold (fun p l -> OpamSysPkg.to_string p :: l) OpamSysPkg.Set.Op.(sys_packages.ti_new ++ sys_packages.ti_required) []) in (* We exclude variables from https://github.com/NixOS/nix/blob/e4bda20918ad2af690c2e938211a7d362548e403/src/nix/develop.cc#L308-L325 append to variables from https://github.com/NixOS/nix/blob/e4bda20918ad2af690c2e938211a7d362548e403/src/nix/develop.cc#L347-L353 and exclude some other regarding the Nix derivation *) let contents = {|{ pkgs ? import <nixpkgs> {} }: with pkgs; stdenv.mkDerivation { name = "opam-nix-env"; nativeBuildInputs = with buildPackages; [ |} ^ packages ^ {| ]; phases = [ "buildPhase" ]; buildPhase = '' while IFS='=' read -r var value; do escaped="''$(echo "$value" | sed -e 's/^$/@/' -e 's/ /\\ /g')" echo "$var = $escaped Nix" >> "$out" done < <(env \ -u BASHOPTS \ -u HOME \ -u NIX_BUILD_TOP \ -u NIX_ENFORCE_PURITY \ -u NIX_LOG_FD \ -u NIX_REMOTE \ -u PPID \ -u SHELLOPTS \ -u SSL_CERT_FILE \ -u TEMP \ -u TEMPDIR \ -u TERM \ -u TMP \ -u TMPDIR \ -u TZ \ -u UID \ -u PATH \ -u XDG_DATA_DIRS \ -u self-referential \ -u excluded_vars \ -u excluded_pattern \ -u phases \ -u buildPhase \ -u outputs) echo "PATH += $PATH Nix" >> "$out" echo "XDG_DATA_DIRS += $XDG_DATA_DIRS Nix" >> "$out" ''; preferLocalBuild = true; } |} in OpamFilename.write drvFile contents; let envFile = OpamPath.Switch.nix_env st.switch_global.root st.switch in [pm, [ OpamFilename.to_string drvFile; "--out-link"; OpamFile.to_string envFile ] ], None) | Openbsd -> [pm, yes ~no:["-i"] ["-I"] packages], None | Suse -> [pm, yes ["--non-interactive"] ("install"::packages)], None let install_packages_commands ?env st config sys_packages = fst (install_packages_commands_t ?env ~to_show:true st config sys_packages) let package_manager_name ?env config = match package_manager_name_t ?env config with | `AsAdmin pkgman | `AsUser pkgman -> pkgman let sudo_run_command ?(env=OpamVariable.Map.empty) ?vars cmd args = let cmd, args = let not_root = Unix.getuid () <> 0 in match cmd, OpamSysPoll.os env with | `AsAdmin cmd, Some "openbsd" when not_root -> (* TODO: alpine is also switching to doas in 3.16 *) "doas", cmd::args | `AsAdmin cmd, Some ("linux" | "unix" | "freebsd" | "netbsd" | "dragonfly" | "macos") when not_root -> if OpamSystem.resolve_command "sudo" = None then "su", ["root"; "-c"; Printf.sprintf "%S" (String.concat " " (cmd::args))] else "sudo", cmd::args | (`AsUser cmd | `AsAdmin cmd), _ -> cmd, args in match run_command_exit_code ?vars ~allow_stdin:true ~verbose:true cmd args with | 0 -> () | code -> Printf.ksprintf failwith "failed with exit code %d at command:\n %s" code (String.concat " " (cmd::args)) let install ?env st config (packages : OpamSysPkg.to_install) = if OpamSysPkg.Set.is_empty packages.ti_new && OpamSysPkg.Set.is_empty packages.ti_required then log "Nothing to install" else let commands, vars = install_packages_commands_t ?env ~to_show:false st config packages in let vars = OpamStd.Option.map (List.map (fun x -> `add, x)) vars in List.iter (fun (cmd, args) -> try sudo_run_command ?env ?vars cmd args with Failure msg -> failwith ("System package install " ^ msg)) commands let update ?(env=OpamVariable.Map.empty) config = let family = family ~env () in let cmd = match family with | Alpine -> Some (`AsAdmin "apk", ["update"]) | Arch -> Some (`AsAdmin "pacman", ["-Sy"]) | Centos -> Some (`AsAdmin (Lazy.force yum_cmd), ["makecache"]) | Cygwin -> None | Debian | Altlinux -> Some (`AsAdmin "apt-get", ["update"]) | Dummy test -> if test.install then None else Some (`AsUser "false", []) | Freebsd -> None | Gentoo -> Some (`AsAdmin "emerge", ["--sync"]) | Homebrew -> Some (`AsUser "brew", ["update"]) | Macports -> Some (`AsAdmin "port", ["sync"]) | Msys2 -> Some (`AsUser (Commands.msys2 config), ["-Sy"]) | Netbsd -> None | Nix -> Some (`AsUser "nix-channel", ["--update"]) | Openbsd -> None | Suse -> Some (`AsAdmin "zypper", ["--non-interactive"; "refresh"]) in match cmd with | None -> (* Cygwin doesn't have an update database per se, but one is supposed to use the most current setup program when downloading setup.ini (which is the package database (cf. the --no-version-check option). Also, when #5839 is addressed, we'll need to cache setup.ini, and that will want to be updated here too. *) if family = Cygwin then Cygwin.check_setup ~update:true else OpamConsole.warning "Unknown update command for %s, skipping system update" OpamStd.Option.Op.(OpamSysPoll.os_family env +! "unknown") | Some (cmd, args) -> try sudo_run_command ~env cmd args with Failure msg -> failwith ("System package update " ^ msg) let repo_enablers ?(env=OpamVariable.Map.empty) config = if family ~env () <> Centos then None else let status = packages_status ~env config (OpamSysPkg.raw_set (OpamStd.String.Set.singleton "epel-release")) in if OpamSysPkg.Set.is_empty status.s_available then None else Some "On CentOS/RHEL, many packages may assume that the Extra Packages for \ Enterprise Linux (EPEL) repository has been enabled. \ This is typically done by installing the 'epel-release' package. \ Please see https://fedoraproject.org/wiki/EPEL for more information"