123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570(**************************************************************************)(* *)(* 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. *)(* *)(**************************************************************************)letlogfmt=OpamConsole.log"XSYS"fmt(* Run commands *)(* Always call this function to run a command, as it handles `dryrun` option *)letrun_command?vars?(discard_err=false)?allow_stdin?verbose?(dryrun=false)cmdargs=letclean_output=ifnotdiscard_errthenfunk->kNoneelsefunk->OpamFilename.with_tmp_dir_job@@fundir->letf=OpamFilename.Op.(dir//"out")inOpamFilename.touchf;k(Some(OpamFilename.to_stringf))inletverbose=OpamStd.Option.defaultOpamCoreConfig.(!r.verbose_level>=3)verboseinletenv=matchvarswith|None->None|Somevars->letenv=OpamStd.Env.list()inletset_vars,kept_vars,env=List.fold_left(fun(n,p,e)(op,(name,contentasvar))->matchOpamStd.List.assoc_optnameenv,opwith|Somec,`addwhenString.compareccontent=0->n,p,e|Some_,`set->var::n,p,(List.remove_assocnameenv)|Some_,_->n,var::p,e|None,_->var::n,p,e)([],[],env)varsinletstr_var(v,c)=Printf.sprintf"%s=%s"vcinifset_vars=[]then((ifkept_vars<>[]thenlog"Won't override %s"(OpamStd.List.to_stringstr_varkept_vars));None)else(log"Adding to env %s"(OpamStd.List.to_stringstr_varset_vars);Some(set_vars@env|>List.rev_mapstr_var|>Array.of_list))inletrun=ifdryrunthenOpamProcess.Job.dry_runelseOpamProcess.Job.runinletopenOpamProcess.Job.Opinrun@@clean_output@@funstdout->OpamSystem.make_command?env?stdout?allow_stdin~verbosecmdargs@@>funr->letcode=r.r_codeinletout=r.r_stdoutinOpamProcess.cleanupr;Done(code,out)letrun_query_command?varscmdargs=letvars=(`set,("LC_ALL","C"))::OpamStd.Option.to_listvarsinletcode,out=run_command~varscmdargsinifcode=0thenoutelse[]letrun_command_exit_code?vars?allow_stdin?verbosecmdargs=letcode,_=run_command?vars?allow_stdin?verbose~dryrun:OpamStateConfig.(!r.dryrun)cmdargsincode(* Please keep this alphabetically ordered, in the type definition, and in
below pattern matching *)typefamilies=|Alpine|Arch|Centos|Debian|Freebsd|Gentoo|Homebrew|Macports|Netbsd|Openbsd|Suse(* System status *)letfamily=letfamily=lazy(matchOpamSysPoll.os_family()with|None->Printf.ksprintffailwith"External dependency unusable, OS family not detected."|Somefamily->matchfamilywith|"alpine"->Alpine|"amzn"|"centos"|"fedora"|"mageia"|"oraclelinux"|"ol"|"rhel"->Centos|"archlinux"|"arch"->Arch|"bsd"->beginmatchOpamSysPoll.os_distribution()with|Some("freebsd"|"dragonfly")->Freebsd|Some"netbsd"->Netbsd|Some"openbsd"->Openbsd|_->Printf.ksprintffailwith"External dependency handling not supported for OS family 'bsd'."end|"debian"|"ubuntu"->Debian|"gentoo"->Gentoo|"homebrew"->Homebrew|"macports"->Macports|"suse"|"opensuse"->Suse|family->Printf.ksprintffailwith"External dependency handling not supported for OS family '%s'."family)infun()->Lazy.forcefamilyletpackages_statuspackages=let(+++)pkgset=OpamSysPkg.Set.add(OpamSysPkg.of_stringpkg)setin(* Some package managers don't permit to request on available packages. In
this case, we consider all non installed packages as [available]. *)letopenOpamSysPkg.Set.Opinletcompute_sets?sys_availablesys_installed=letinstalled=packages%%sys_installedinletavailable,not_found=matchsys_availablewith|Somesys_available->letavailable=(packages--installed)%%sys_availableinletnot_found=packages--installed--availableinavailable,not_found|None->letavailable=packages--installedinavailable,OpamSysPkg.Set.emptyinavailable,not_foundinletnames_re?str_pkgs()=letstr_pkgs=OpamStd.Option.defaultOpamSysPkg.(Set.fold(funpacc->to_stringp::acc)packages[])str_pkgsinletneed_escape=Re.(compile(group(set"+.")))inPrintf.sprintf"^(%s)$"(OpamStd.List.concat_map"|"(Re.replace~all:trueneed_escape~f:(fung->"\\"^Re.Group.getg1))str_pkgs)inletwith_regexp_sglre_pkg=List.fold_left(funpkgsl->tryRe.(Group.get(execre_pkgl)1)+++pkgswithNot_found->pkgs)OpamSysPkg.Set.emptyinletwith_regexp_dbl~re_installed~re_pkg=List.fold_left(fun(inst,avail)l->tryletpkg=Re.(Group.get(execre_pkgl)1)inifRe.execpre_installedlthenpkg+++inst,availelseinst,pkg+++availwithNot_found->inst,avail)OpamSysPkg.Set.(empty,empty)inletpackage_set_of_pkgpathl=List.fold_left(funsetpkg->letshort_name=matchString.rindexpkg'/'with|exceptionNot_found->pkg|idx->String.subpkgidx(String.lengthpkg-idx)inset|>OpamSysPkg.Set.add(OpamSysPkg.of_stringpkg)|>OpamSysPkg.Set.add(OpamSysPkg.of_stringshort_name))OpamSysPkg.Set.emptylinmatchfamily()with|Alpine->letre_installed=Re.(compile(seq[str"[installed]";eol]))inletre_pkg=(* packages form : libpeas-python3-1.22.0-r1 *)Re.(compile@@seq[bol;group@@rep1@@alt[alnum;punct];char'-';rep1digit;repany])inletsys_installed,sys_available=run_query_command"apk"["list";"--available"]|>with_regexp_dbl~re_installed~re_pkgincompute_setssys_installed~sys_available|Arch->(* 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
*)letre_installed=Re.(compile(seq[str"[installed]";eol]))inletre_pkg=Re.(compile@@seq[bol;rep1@@alt[alnum;punct];char'/';group@@rep1@@alt[alnum;punct];space;])inletsys_installed,sys_available=run_query_command"pacman"["-Ss";names_re()]|>with_regexp_dbl~re_installed~re_pkgincompute_setssys_installed~sys_available|Centos->(* XXX /!\ only checked on centos XXX *)letlines=run_query_command"yum"["-q";"-C";"list"]in(* -C to retrieve from cache, no update but still quite long, 1,5 sec *)(* Return a list of installed packages then available ones:
>Installed Packages
>foo.arch version repo
>Available Packages
>bar.arch version repo
*)letsys_installed,sys_available,_=List.fold_left(fun(inst,avail,part)->function(* beware of locales!! *)|"Installed Packages"->inst,avail,`installed|"Available Packages"->inst,avail,`available|l->(matchpart,OpamStd.String.splitl'.'with|`installed,pkg::_->pkg+++inst,avail,part|`available,pkg::_->inst,pkg+++avail,part|_->(* shouldn't happen *)inst,avail,part))OpamSysPkg.Set.(empty,empty,`preamble)linesincompute_setssys_installed~sys_available|Debian->letsys_available,sys_provides,_=letprovides_sep=Re.(compile@@str", ")inletpackage_providedstr=OpamSysPkg.of_string(matchOpamStd.String.cut_atstr' '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)l->ifOpamStd.String.starts_with~prefix:"Package: "lthenletp=String.subl9(String.lengthl-9)inp+++avail,provides,Some(OpamSysPkg.of_stringp)elseifOpamStd.String.starts_with~prefix:"Provides: "lthenletps=List.mappackage_provided(Re.split~pos:10provides_sepl)|>OpamSysPkg.Set.of_listinavail++ps,(matchlatestwith|Somep->OpamSysPkg.Map.addppsprovides|None->provides(* Bad apt-cache output ?? *)),Noneelseavail,provides,latest)(OpamSysPkg.Set.empty,OpamSysPkg.Map.empty,None)inletneed_inst_check=OpamSysPkg.Map.fold(funcpvpsset->ifOpamSysPkg.Set.(is_empty(intervpspackages))thensetelseOpamSysPkg.Set.addcpset)sys_providespackagesinletstr_need_inst_check=OpamSysPkg.(Set.fold(funpacc->to_stringp::acc)need_inst_check[])inletsys_installed=(* ouput:
>ii uim-gtk3 1:1.8.8-6.1 amd64 Universal ...
>ii uim-gtk3-immodule:amd64 1:1.8.8-6.1 amd64 Universal ...
*)letre_pkg=Re.(compile@@seq[bol;str"ii";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_need_inst_check)|>snd|>with_regexp_sglre_pkginletsys_installed=(* Resolve installed "provides" packages;
assumes provides are not recursive *)OpamSysPkg.Set.fold(funpacc->matchOpamSysPkg.Map.find_optpsys_provideswith|None->acc|Someps->OpamSysPkg.Set.unionaccps)sys_installedsys_installedincompute_setssys_installed~sys_available|Freebsd->letsys_installed=run_query_command"pkg"["query";"%n\n%o"]|>List.mapOpamSysPkg.of_string|>OpamSysPkg.Set.of_listincompute_setssys_installed|Gentoo->letsys_installed=letre_pkg=Re.(compile@@seq[group@@rep1@@alt[alnum;punct];char'-';rep@@seq[rep1digit;char'.'];rep1digit;repany;eol])inList.fold_left(funinstdir->List.fold_left(funinstpkg->letto_stringd=OpamFilename.basename_dird|>OpamFilename.Base.to_stringinletpkg=Filename.concat(to_stringdir)(to_stringpkg)intryRe.(Group.get(execre_pkgpkg)1)::instwithNot_found->inst)inst(OpamFilename.dirsdir))[](OpamFilename.dirs(OpamFilename.Dir.of_string"/var/db/pkg"))|>package_set_of_pkgpathincompute_setssys_installed|Homebrew->(* accept 'pkgname' and 'pkgname@version'
exampe output
>openssl@1.1
>bmake
*)letsys_installed=run_query_command"brew"["list";"--formula"]|>List.fold_left(funress->List.fold_left(funresspkg->matchOpamStd.String.cut_atspkg'@'with|Some(n,_v)->n::spkg::res|None->spkg::res)res(OpamStd.String.splits' '))[]|>List.mapOpamSysPkg.of_string|>OpamSysPkg.Set.of_listincompute_setssys_installed|Macports->letstr_pkgs=OpamSysPkg.(Set.fold(funpacc->to_stringp::acc)packages[])inletsys_installed=(* output:
>zlib @1.2.11_0 (active)
*)letre_pkg=Re.(compile@@seq[bol;group@@rep1@@alt[alnum;punct];rep1space;repany;str"(active)";eol])inrun_query_command"port"("installed"::str_pkgs)|>(function_::lines->lines|_->[])|>with_regexp_sglre_pkginletsys_available=(* example output
>diffutils 3.7 sysutils textproc devel GNU diff utilities
>--
>No match for gcc found
*)letre_pkg=Re.(compile@@seq[bol;group@@rep1@@alt[alnum;punct];rep1space;rep1@@alt[digit;punct];])inrun_query_command"port"(["search";"--line";"--exact"]@str_pkgs)|>with_regexp_sglre_pkgincompute_setssys_installed~sys_available|Netbsd->letsys_installed=run_query_command"pkg_info"["-Q";"PKGPATH";"-a"]|>package_set_of_pkgpathincompute_setssys_installed|Openbsd->letsys_installed=run_query_command"pkg_info"["-mqP"]|>package_set_of_pkgpathincompute_setssys_installed|Suse->(* get the second column of the table:
zypper --quiet se -i -t package|grep '^i '|awk -F'|' '{print $2}'|xargs echo
output:
>S | Name | Summary
>--+-----------------------------+-------------
> | go-gosqlite | Trivial SQLi
>i | libqt4-sql-sqlite-32bit | Qt 4 sqlite
*)letre_pkg=Re.(compile@@seq[bol;rep1any;char'|';rep1space;group@@rep1@@alt[alnum;punct];rep1space;char'|';])inletre_installed=Re.(compile@@seq[bol;char'i'])inletsys_installed,sys_available=run_query_command"zypper"["--quiet";"se";"-t";"package"]|>with_regexp_dbl~re_installed~re_pkgincompute_setssys_installed~sys_available(* Install *)letinstall_packages_commands_tsys_packages=letyes?(no=[])yesr=ifOpamStd.Config.env_bool"DEPEXTYES"=Sometruethenyes@relseno@rinletpackages=List.mapOpamSysPkg.to_string(OpamSysPkg.Set.elementssys_packages)inmatchfamily()with|Alpine->["apk","add"::yes~no:["-i"][]packages],None|Arch->["pacman","-S"::yes["--noconfirm"]packages],None|Centos->(* TODO: check if they all declare "rhel" as primary family *)(* 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. *)letepel_release="epel-release"inletinstall_epelrest=ifList.memepel_releasepackagesthen["yum","install"::yes["-y"][epel_release]]@restelserestininstall_epel["yum","install"::yes["-y"](OpamStd.String.Set.of_listpackages|>OpamStd.String.Set.removeepel_release|>OpamStd.String.Set.elements);"rpm","-q"::"--whatprovides"::packages],None|Debian->["apt-get","install"::yes["-qq";"-yy"]packages],None|Freebsd->["pkg","install"::yes["-y"]packages],None|Gentoo->["emerge",yes~no:["-a"][]packages],None|Homebrew->["brew","install"::packages],(* NOTE: Does not have any interactive mode *)Some(["HOMEBREW_NO_AUTO_UPDATE","yes"])|Macports->["port","install"::packages],(* NOTE: Does not have any interactive mode *)None|Netbsd->["pkgin",yes["-y"]("install"::packages)],None|Openbsd->["pkg_add",yes~no:["-i"]["-I"]packages],None|Suse->["zypper",yes["--non-interactive"]("install"::packages)],Noneletinstall_packages_commandssys_packages=fst(install_packages_commands_tsys_packages)letsudo_run_command?varscmdargs=letcmd,args=letnot_root=Unix.getuid()<>0inmatchOpamSysPoll.os(),OpamSysPoll.os_distribution()with|Some"openbsd",_whennot_root->"doas",cmd::args|Some("linux"|"unix"|"freebsd"|"netbsd"|"dragonfly"),_|Some"macos",Some"macports"whennot_root->ifOpamSystem.resolve_command"sudo"=Nonethen"su",["root";"-c";Printf.sprintf"%S"(String.concat" "(cmd::args))]else"sudo",cmd::args|_->cmd,argsinmatchrun_command_exit_code?vars~allow_stdin:true~verbose:truecmdargswith|0->()|code->Printf.ksprintffailwith"failed with exit code %d at command:\n %s"code(String.concat" "(cmd::args))letinstallpackages=ifOpamSysPkg.Set.is_emptypackagesthenlog"Nothing to install"elseletcommands,vars=install_packages_commands_tpackagesinletvars=OpamStd.Option.map(List.map(funx->`add,x))varsinList.iter(fun(cmd,args)->trysudo_run_command?varscmdargswithFailuremsg->failwith("System package install "^msg))commandsletupdate()=letcmd=matchfamily()with|Alpine->Some("apk",["update"])|Arch->Some("pacman",["-Sy"])|Centos->Some("yum",["makecache"])|Debian->Some("apt-get",["update"])|Gentoo->Some("emerge",["--sync"])|Homebrew->Some("brew",["update"])|Macports->Some("port",["sync"])|Suse->Some("zypper",["--non-interactive";"update"])|Freebsd|Netbsd|Openbsd->Noneinmatchcmdwith|None->OpamConsole.warning"Unknown update command for %s, skipping system update"OpamStd.Option.Op.(OpamSysPoll.os_family()+!"unknown")|Some(cmd,args)->trysudo_run_commandcmdargswithFailuremsg->failwith("System package update "^msg)