123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956(**************************************************************************)(* *)(* Copyright 2012-2020 OCamlPro *)(* Copyright 2012 INRIA *)(* *)(* 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. *)(* *)(**************************************************************************)openOpamCompatletlog?levelfmt=OpamConsole.log"PROC"?levelfmtletcygwin_create_process_envprogargsenvfd1fd2fd3=(*
* Unix.create_process_env correctly converts arguments to a command line for
* native Windows execution, but it does not correctly handle Cygwin's quoting
* requirements.
*
* The process followed here is based on an analysis of the sources for the
* Cygwin DLL (git://sourceware.org/git/newlib-cygwin.git,
* winsup/cygwin/dcrt0.cc) and a lack of refutation on the Cygwin mailing list
* in May 2016!
*
* In case this seems terminally stupid, it's worth noting that Cygwin's
* implementation of the exec system calls do not pass argv using the Windows
* command line, these weird and wonderful rules exist for corner cases and,
* as here, for invocations from native Windows processes.
*
* There are two forms of escaping which can apply, controlled by the CYGWIN
* environment variable option noglob.
*
* If none of the strings in argv contains the double-quote character, then
* the process should be invoked with the noglob option (to ensure that no
* characters are unexpectedly expanded). In this mode of escaping, it is
* necessary to protect any whitespace characters (\r, \n, \t, and space
* itself) by surrounding sequences of them with double-quotes). Additionally,
* if any string in argv begins with the @ sign, this should be double-quoted.
*
* If any one of the strings in argv does contain a double-quote character,
* then the process should be invoked with the glob option (this is the
* default). Every string in argv should have double-quotes put around it. Any
* double-quote characters within the string should be translated to "'"'".
*
* The reason for supporting both mechanisms is that the noglob method has
* shorter command lines and Windows has an upper limit of 32767 characters
* for the command line.
*
* COMBAK If the command line does exceed 32767 characters, then Cygwin allows
* a parameter beginning @ to refer to a file from which to read the
* rest of the command line. This support is not implemented at this
* time in OPAM.
*
* [This stray " is here to terminate a previous part of the comment!]
*)letmake_argsargv=letb=Buffer.create128inletgen_quote~quote~pre?(post=pre)s=log~level:3"gen_quote: %S"s;Buffer.clearb;letl=String.lengthsinletrecfi=letj=tryOpamStd.String.find_from(func->tryString.indexquotec>=0withNot_found->false)s(succi)withNot_found->linBuffer.add_stringb(String.subsi(j-i));ifj<lthenbeginBuffer.add_stringbpre;leti=jinletj=tryOpamStd.String.find_from(func->tryString.indexquotec<0withNot_found->true)s(succi)withNot_found->linBuffer.add_stringb(String.subsi(j-i));Buffer.add_stringbpost;ifj<lthenfjelseBuffer.contentsbendelseBuffer.contentsbinletr=ifs=""then"\"\""elsef0inlog~level:3"result: %S"r;rin(* Setting noglob is causing some problems for ocamlbuild invoking Cygwin's
find. The reason for using it is to try to keep command line lengths
below the maximum, but for now disable the use of noglob. *)iftrue||List.exists(funs->tryString.indexs'"'>=0withNot_found->false)argvthen("\""^String.concat"\" \""(List.map(gen_quote~quote:"\""~pre:"\"'"~post:"'\"")argv)^"\"",false)else(String.concat" "(List.map(gen_quote~quote:"\b\r\n "~pre:"\"")argv),true)inlet(command_line,no_glob)=make_args(Array.to_listargs)inlog"cygvoke(%sglob): %s"(ifno_globthen"no"else"")command_line;letenv=Array.to_listenvinletset=reffalseinletfitem=let(key,value)=matchOpamStd.String.cut_atitem'='withSomepair->pair|None->(item,"")inmatchString.lowercase_asciikeywith|"cygwin"->let()=ifkey="CYGWIN"thenset:=trueinletsettings=OpamStd.String.splitvalue' 'inletset=reffalseinletfsetting=letsetting=String.trimsettinginletsetting=matchOpamStd.String.cut_atsetting':'withSome(setting,_)->setting|None->settinginmatchsettingwith"glob"->ifno_globthenbeginlog~level:2"Removing glob from %s"key;falseendelsebeginlog~level:2"Leaving glob in %s"key;set:=true;trueend|"noglob"->ifno_globthenbeginlog~level:2"Leaving noglob in %s"key;set:=true;trueendelsebeginlog~level:2"Removing noglob from %s"key;falseend|_->trueinletsettings=List.filterfsettingsinletsettings=ifnot!set&&no_globthenbeginlog~level:2"Setting noglob in %s"key;"noglob"::settingsendelsesettingsinifsettings=[]thenbeginlog~level:2"Removing %s completely"key;NoneendelseSome(key^"="^String.concat" "settings)|"path"->letpath_dirs=OpamStd.Sys.split_path_variableiteminletwinsys=Filename.concat(OpamStd.Sys.system())"."|>String.lowercase_asciiinletrecfprefixsuffix=function|dir::dirs->letcontains_cygpath=Sys.file_exists(Filename.concatdir"cygpath.exe")inifsuffix=[]thenifString.lowercase_ascii(Filename.concatdir".")=winsysthenfprefix[dir]dirselseifcontains_cygpaththenpath_dirselsef(dir::prefix)[]dirselseifcontains_cygpaththenbeginlog~level:2"Moving %s to after %s in PATH"dir(List.hdprefix);List.rev_appendprefix(dir::(List.rev_appendsuffixdirs))endelsefprefix(dir::suffix)dirs|[]->assertfalseinSome(String.concat";"(f[][]path_dirs))|_->Someiteminletenv=OpamStd.List.filter_mapfenvinletenv=if!setthenenvelseifno_globthenbeginlog~level:2"Adding CYGWIN=noglob";"CYGWIN=noglob"::envendelseenvinOpamStubs.win_create_processprogcommand_line(Some(String.concat"\000"env^"\000"))fd1fd2fd3(** Shell commands *)typecommand={cmd:string;args:stringlist;cmd_text:stringoption;cmd_dir:stringoption;cmd_env:stringarrayoption;cmd_stdin:booloption;cmd_stdout:stringoption;cmd_verbose:booloption;cmd_name:stringoption;cmd_metadata:(string*string)listoption;}letstring_of_commandc=String.concat" "(c.cmd::c.args)lettext_of_commandc=c.cmd_textletdefault_verbose()=OpamCoreConfig.(!r.verbose_level)>=2letis_verbose_commandc=OpamStd.Option.default(default_verbose())c.cmd_verboseletmake_command_text?(color=`green)str?(args=[])cmd=letsummary=matchList.filter(funs->String.lengths>0&&s.[0]<>'-'&¬(String.containss'/')&¬(String.containss'='))argswith|hd::_->String.concat" "[cmd;hd]|[]->cmdinPrintf.sprintf"[%s: %s]"(OpamConsole.colorisecolorstr)summaryletcommand?env?verbose?name?metadata?dir?allow_stdin?stdout?textcmdargs={cmd;args;cmd_env=env;cmd_verbose=verbose;cmd_name=name;cmd_metadata=metadata;cmd_dir=dir;cmd_stdin=allow_stdin;cmd_stdout=stdout;cmd_text=text;}(** Running processes *)typet={p_name:string;p_args:stringlist;p_pid:int;p_cwd:string;p_time:float;p_stdout:stringoption;p_stderr:stringoption;p_env:stringoption;p_info:stringoption;p_metadata:(string*string)list;p_verbose:bool;p_tmp_files:stringlist;}letopen_flags=[Unix.O_WRONLY;Unix.O_CREAT;Unix.O_APPEND]letoutput_linesoclines=List.iter(funline->output_stringocline;output_stringoc"\n";flushoc;)lines;output_stringoc"\n";flushocletmake_info?code?signal~cmd~args~cwd~env_file~stdout_file~stderr_file~metadata()=letb=ref[]inlethome=OpamStd.Sys.home()inletprintnamestr=letstr=ifOpamStd.String.starts_with~prefix:homestrthen"~"^OpamStd.String.remove_prefix~prefix:homestrelsestrinb:=(name,str)::!binletprint_optname=function|None->()|Somes->printnamesinList.iter(fun(k,v)->printkv)metadata;print"path"cwd;print"command"(String.concat" "(cmd::args));print_opt"exit-code"(OpamStd.Option.mapstring_of_intcode);print_opt"signalled"(OpamStd.Option.mapstring_of_intsignal);print_opt"env-file"env_file;ifstderr_file=stdout_filethenprint_opt"output-file"stdout_fileelse(print_opt"stdout-file"stdout_file;print_opt"stderr-file"stderr_file;);List.rev!bletstring_of_info?(color=`yellow)info=letb=Buffer.create1024inList.iter(fun(k,v)->Printf.bprintfb"%s %-20s %s\n"(OpamConsole.colorisecolor"#")(OpamConsole.colorisecolork)v)info;Buffer.contentsbletresolve_command_fn=ref(fun?env:_?dir:__->None)letset_resolve_command=letcalled=reffalseinfunresolve_command->if!calledtheninvalid_arg"Just what do you think you're doing, Dave?";called:=true;resolve_command_fn:=resolve_commandletresolve_commandcmd=!resolve_command_fncmd(** [create cmd args] create a new process to execute the command
[cmd] with arguments [args]. If [stdout_file] or [stderr_file] are
set, the channels are redirected to the corresponding files. The
outputs are discarded is [verbose] is set to false. The current
environment can also be overridden if [env] is set. The environment
which is used to run the process is recorded into [env_file] (if
set). *)letcreate?info_file?env_file?(allow_stdin=true)?stdout_file?stderr_file?env?(metadata=[])?dir~verbose~tmp_filescmdargs=letnothing()=()inletteef=letfd=Unix.openfilefopen_flags0o644inletclose_fd()=Unix.closefdinfd,close_fdinletoldcwd=Sys.getcwd()inletcwd=OpamStd.Option.defaultoldcwddirinOpamStd.Option.iterUnix.chdirdir;letstdin_fd,close_stdin=ifallow_stdinthenUnix.stdin,nothingelseletfd,outfd=Unix.pipe()inletclose_stdin()=Unix.closefdinUnix.closeoutfd;fd,close_stdininletstdout_fd,close_stdout=matchstdout_filewith|None->Unix.stdout,nothing|Somef->teefinletstderr_fd,close_stderr=matchstderr_filewith|None->Unix.stderr,nothing|Somef->ifstdout_file=Somefthenstdout_fd,nothingelseteefinletenv=matchenvwith|None->Unix.environment()|Somee->einlettime=Unix.gettimeofday()inlet()=(* write the env file before running the command*)matchenv_filewith|None->()|Somef->letchan=open_outfinletenv=Array.to_listenvin(* Remove dubious variables *)letenv=List.filter(funline->not(OpamStd.String.contains_charline'$'))envinoutput_lineschanenv;close_outchaninlet()=(* write the info file *)matchinfo_filewith|None->()|Somef->letchan=open_outfinletinfo=make_info~cmd~args~cwd~env_file~stdout_file~stderr_file~metadata()inoutput_stringchan(string_of_infoinfo);close_outchaninletpid=letcmd,args=ifSys.win32thentryletactual_command=ifSys.file_existscmdthencmdelseifSys.file_exists(cmd^".exe")thencmd^".exe"elseraiseExitinletactual_image,args=letc=open_inactual_commandinset_binary_mode_inctrue;tryifreally_input_stringc2="#!"thenbegin(* The input_line will only fail for a 2-byte file consisting of exactly #! (with no \n), which is acceptable! *)letl=String.trim(input_linec)inletcmd,arg=tryleti=String.indexl' 'inletcmd=Filename.basename(String.trim(String.subl0i))inletarg=String.trim(String.subli(String.lengthl-i))inifcmd="env"thenarg,Noneelsecmd,SomeargwithNot_found->Filename.basenamel,Noneinclose_inc;tryletcmd=OpamStd.Option.defaultcmd(resolve_commandcmd)in(*Printf.eprintf "Deduced %s => %s to be executed via %s\n%!" cmd actual_command cmd;*)letargs=actual_command::argsincmd,OpamStd.Option.map_default(funarg->arg::args)argsargwithNot_found->(* Script interpreter isn't available - fall back *)raiseExitendelsebeginclose_inc;actual_command,argsendwithEnd_of_file->close_inc;(* A two-byte image can't be executable! *)raiseExitin(*Printf.eprintf "Final deduction: %s -> %s\n%!" cmd actual_image;*)actual_image,argswithExit->(* Fall back to default behaviour if anything went wrong - almost certainly means a broken package *)cmd,argselsecmd,argsinletcreate_process,cmd,args=ifSys.win32thenifOpamStd.Sys.is_cygwin_variantcmd=`Cygwinthencygwin_create_process_env,cmd,argselseUnix.create_process_env,cmd,argselseUnix.create_process_env,cmd,argsintrycreate_processcmd(Array.of_list(cmd::args))envstdin_fdstdout_fdstderr_fdwithe->close_stdin();close_stdout();close_stderr();raiseeinclose_stdin();close_stdout();close_stderr();Unix.chdiroldcwd;{p_name=cmd;p_args=args;p_pid=pid;p_cwd=cwd;p_time=time;p_stdout=stdout_file;p_stderr=stderr_file;p_env=env_file;p_info=info_file;p_metadata=metadata;p_verbose=verbose;p_tmp_files=tmp_files;}typeresult={r_code:int;r_signal:intoption;r_duration:float;r_info:(string*string)list;r_stdout:stringlist;r_stderr:stringlist;r_cleanup:stringlist;}letempty_result={r_code=0;r_signal=None;r_duration=0.;r_info=[];r_stdout=[];r_stderr=[];r_cleanup=[];}(* XXX: the function might block for ever for some channels kinds *)letread_linesf=tryletic=open_infinletlines=ref[]inbegintrywhiletruedoletline=input_lineicinlines:=line::!lines;donewithEnd_of_file|Sys_error_->()end;close_inic;List.rev!lineswithSys_error_->[](* Compat function (Windows) *)letinterruptp=ifSys.win32thenUnix.killp.p_pidSys.sigkillelseUnix.killp.p_pidSys.sigintletrun_backgroundcommand=let{cmd;args;cmd_env=env;cmd_verbose=_;cmd_name=name;cmd_text=_;cmd_metadata=metadata;cmd_dir=dir;cmd_stdin=allow_stdin;cmd_stdout}=commandinletverbose=is_verbose_commandcommandinletallow_stdin=OpamStd.Option.defaultfalseallow_stdininletenv=matchenvwithSomee->e|None->Unix.environment()inletfileext=matchnamewith|None->None|Somen->letd=ifFilename.is_relativenthenmatchdirwith|Somed->d|None->OpamCoreConfig.(!r.log_dir)else""inSome(Filename.concatd(Printf.sprintf"%s.%s"next))inletstdout_file=OpamStd.Option.Op.(cmd_stdout>>+fun()->file"out")inletstderr_file=ifOpamCoreConfig.(!r.merged_output)thenfile"out"elsefile"err"inletenv_file=file"env"inletinfo_file=file"info"inlettmp_files=OpamStd.List.filter_some[info_file;env_file;stderr_file;ifcmd_stdout<>None||stderr_file=stdout_filethenNoneelsestdout_file;]increate~env?info_file?env_file?stdout_file?stderr_file~verbose?metadata~allow_stdin?dir~tmp_filescmdargsletdry_run_backgroundc={p_name=c.cmd;p_args=c.args;p_pid=-1;p_cwd=OpamStd.Option.default(Sys.getcwd())c.cmd_dir;p_time=Unix.gettimeofday();p_stdout=None;p_stderr=None;p_env=None;p_info=None;p_metadata=OpamStd.Option.default[]c.cmd_metadata;p_verbose=is_verbose_commandc;p_tmp_files=[];}letverbose_print_cmdp=OpamConsole.msg"%s %s %s%s\n"(OpamConsole.colorise`yellow"+")p.p_name(OpamStd.List.concat_map" "(Printf.sprintf"%S")p.p_args)(ifp.p_cwd=Sys.getcwd()then""elsePrintf.sprintf" (CWD=%s)"p.p_cwd)letverbose_print_out=letpfx=lazy(OpamConsole.colorise`yellow"- ")infuns->OpamConsole.msg"%s%s\n"(Lazy.forcepfx)s(** Semi-synchronous printing of the output of a command *)letset_verbose_f,print_verbose_f,isset_verbose_f,stop_verbose_f=letverbose_f=refNoneinletstop()=match!verbose_fwith|None->()|Some(ics,_)->List.iterclose_in_noerrics;verbose_f:=Noneinletsetfiles=stop();(* implem relies on sigalrm, not implemented on win32.
This will fall back to buffered output. *)ifSys.win32then()elseletfiles=OpamStd.List.sort_nodupcomparefilesinletics=List.map(open_in_gen[Open_nonblock;Open_rdonly;Open_text;Open_creat]0o600)filesinletf()=List.iter(funic->trywhiletruedoverbose_print_out(input_lineic)donewithEnd_of_file->flushstdout)icsinverbose_f:=Some(ics,f)inletprint()=match!verbose_fwith|Some(_,f)->f()|None->()inletisset()=!verbose_f<>Noneinletflush_and_stop()=print();stop()inset,print,isset,flush_and_stopletset_verbose_processp=ifp.p_verbosethenletfs=OpamStd.List.filter_some[p.p_stdout;p.p_stderr]iniffs<>[]then(verbose_print_cmdp;set_verbose_ffs)letexit_statuspreturn=letduration=Unix.gettimeofday()-.p.p_timeinletstdout=OpamStd.Option.default[](OpamStd.Option.mapread_linesp.p_stdout)inletstderr=OpamStd.Option.default[](OpamStd.Option.mapread_linesp.p_stderr)inletcleanup=p.p_tmp_filesinletcode,signal=matchreturnwith|Unix.WEXITEDr->Somer,None|Unix.WSIGNALEDs|Unix.WSTOPPEDs->None,Somesinifisset_verbose_f()thenstop_verbose_f()elseifp.p_verbosethen(verbose_print_cmdp;List.iterverbose_print_outstdout;List.iterverbose_print_outstderr;flushStdlib.stdout);letinfo=make_info?code?signal~cmd:p.p_name~args:p.p_args~cwd:p.p_cwd~metadata:p.p_metadata~env_file:p.p_env~stdout_file:p.p_stdout~stderr_file:p.p_stderr()in{r_code=OpamStd.Option.default256code;r_signal=signal;r_duration=duration;r_info=info;r_stdout=stdout;r_stderr=stderr;r_cleanup=cleanup;}letsafe_waitfallback_pidfx=letsh=ifisset_verbose_f()thenlethndl_=print_verbose_f()inSome(Sys.signalSys.sigalrm(Sys.Signal_handlehndl))elseNoneinletcleanup()=matchshwith|Somesh->ignore(Unix.alarm0);(* cancels the alarm *)Sys.set_signalSys.sigalrmsh|None->()inletrecaux()=ifsh<>Nonethenignore(Unix.alarm1);matchtryfxwith|Unix.Unix_error(Unix.EINTR,_,_)->aux()(* handled signal *)|Unix.Unix_error(Unix.ECHILD,_,_)->log"Warn: no child to wait for %d"fallback_pid;fallback_pid,Unix.WEXITED256with|_,Unix.WSTOPPED_->(* shouldn't happen as we don't use WUNTRACED *)aux()|r->rintryletr=aux()incleanup();rwithe->cleanup();raiseeletwaitp=set_verbose_processp;let_,return=safe_waitp.p_pid(Unix.waitpid[])p.p_pidinexit_statuspreturnletdontwaitp=matchsafe_waitp.p_pid(Unix.waitpid[Unix.WNOHANG])p.p_pidwith|0,_->None|_,return->Some(exit_statuspreturn)letdead_childs=Hashtbl.create13letwait_oneprocesses=ifprocesses=[]thenraise(Invalid_argument"wait_one");tryletp=List.find(funp->Hashtbl.memdead_childsp.p_pid)processesinletreturn=Hashtbl.finddead_childsp.p_pidinHashtbl.removedead_childsp.p_pid;p,exit_statuspreturnwithNot_found->letrecaux()=letpid,return=ifSys.win32then(* No Unix.wait on Windows, so use a stub wrapping
WaitForMultipleObjects *)letpids,len=letf(l,n)t=(t.p_pid::l,succn)inList.fold_leftf([],0)processesinOpamStubs.waitpidspidslenelsesafe_wait(List.hdprocesses).p_pidUnix.wait()intryletp=List.find(funp->p.p_pid=pid)processesinp,exit_statuspreturnwithNot_found->Hashtbl.adddead_childspidreturn;aux()inaux()letdry_wait_one=function|{p_pid=-1;_}asp::_->ifp.p_verbosethen(verbose_print_cmdp;flushstdout);p,empty_result|_->raise(Invalid_argument"dry_wait_one")letruncommand=letcommand={commandwithcmd_stdin=OpamStd.Option.Op.(command.cmd_stdin++Sometrue)}inletp=run_backgroundcommandintrywaitpwithe->match(trydontwaitpwith_->raisee)with|None->(* still running *)(tryinterruptpwithUnix.Unix_error_->());raisee|_->raiseeletis_failurer=r.r_code<>0||r.r_signal<>Noneletis_successr=not(is_failurer)letsafe_unlinkf=trylog~level:2"safe_unlink: %s"f;Unix.unlinkfwithUnix.Unix_error_->log~level:2"safe_unlink: %s (FAILED)"fletcleanup?(force=false)r=ifforce||(not(OpamConsole.debug())&&is_successr)thenList.itersafe_unlinkr.r_cleanupletcheck_success_and_cleanupr=List.itersafe_unlinkr.r_cleanup;is_successrletlog_line_limit=5*80lettruncate_str="[...]"(* Truncate long lines *)lettruncate_linestr=ifString.lengthstr<=log_line_limitthenstrelseString.substr0(log_line_limit-String.lengthtruncate_str)^truncate_str(* Take the last [n] elements of [l] (trying to keep an unindented header line
for context, like diff) *)lettruncatel=letunindenteds=String.lengths>0&&s.[0]<>' '&&s.[0]<>'\t'inletreccutnacc=function|[]->acc|[x]whenn=0->truncate_linex::acc|_whenn=0->truncate_str::acc|x::lwhenn=1->(ifunindentedxthentruncate_str::truncate_linex::accelsetrytruncate_line(List.findunindentedl)::truncate_str::accwithNot_found->truncate_str::truncate_linex::acc)|x::r->cut(n-1)(truncate_linex::acc)rinletlen=OpamCoreConfig.(!r.errlog_length)iniflen<=0thenlelsecutlen[](List.revl)letstring_of_result?(color=`yellow)r=letb=Buffer.create2048inletprint=Buffer.add_stringbinletprintlnstr=printstr;Buffer.add_charb'\n'inprint(string_of_info~colorr.r_info);ifr.r_stdout<>[]thenifr.r_stderr=r.r_stdoutthenprint(OpamConsole.colorisecolor"### output ###\n")elseprint(OpamConsole.colorisecolor"### stdout ###\n");List.iter(funs->print(OpamConsole.colorisecolor"# ");printlns)(truncater.r_stdout);ifr.r_stderr<>[]&&r.r_stderr<>r.r_stdoutthen(print(OpamConsole.colorisecolor"### stderr ###\n");List.iter(funs->print(OpamConsole.colorisecolor"# ");printlns)(truncater.r_stderr));Buffer.contentsbletresult_summaryr=Printf.sprintf"%S exited with code %d%s"(tryList.assoc"command"r.r_infowithNot_found->"command")r.r_code(ifr.r_code=0then""elsematchr.r_stderr,r.r_stdoutwith|[e],_|[],[e]->Printf.sprintf" \"%s\""e|[],es|es,_->tryPrintf.sprintf" \"%s\""(List.findRe.(execp(compile(seq[bos;rep(diffanyalpha);no_case(str"error")])))(List.reves))withNot_found->""|_->"")(* Higher-level interface to allow parallelism *)moduleJob=structmoduleOp=structtype'ajob=(* Open the variant type *)|Doneof'a|Runofcommand*(result->'ajob)(* Parallelise shell commands *)let(@@>)commandf=Run(command,f)(* Sequentialise jobs *)letrec(@@+)job1fjob2=matchjob1with|Donex->fjob2x|Run(cmd,cont)->Run(cmd,funr->contr@@+fjob2)let(@@|)jobf=job@@+funx->Done(fx)endopenOpletrun=letrecaux=function|Donex->x|Run(cmd,cont)->OpamStd.Option.iter(ifOpamConsole.disp_status_line()thenOpamConsole.status_line"Processing: %s"elseifOpamConsole.verbose()thenOpamConsole.msg"%s\n"elsefun_->())(text_of_commandcmd);letr=runcmdinletk=trycontrwithe->cleanupr;OpamConsole.clear_status();raiseeincleanupr;OpamConsole.clear_status();auxkinauxletrecdry_run=function|Donex->x|Run(_command,cont)->dry_run(contempty_result)letreccatchhandlerfjob=trymatchfjob()with|Donex->Donex|Run(cmd,cont)->Run(cmd,funr->catchhandler(fun()->contr))withe->handlereletignore_errors~default?messagejob=catch(fune->OpamStd.Exn.fatale;OpamStd.Option.iter(OpamConsole.error"%s")message;Donedefault)jobletrecfinallyfinfjob=trymatchfjob()with|Donex->fin();Donex|Run(cmd,cont)->Run(cmd,funr->finallyfin(fun()->contr))withe->fin();raiseeletof_list?(keep_going=false)l=letrecauxerr=function|[]->Doneerr|cmd::commands->letcont=funr->ifis_successrthenauxerrcommandselseifkeep_goingthenauxOpamStd.Option.Op.(err++Some(cmd,r))commandselseDone(Some(cmd,r))inRun(cmd,cont)inauxNonelletof_fun_list?(keep_going=false)l=letrecauxerr=function|[]->Doneerr|cmdf::commands->letcmd=cmdf()inletcont=funr->ifis_successrthenauxerrcommandselseifkeep_goingthenauxOpamStd.Option.Op.(err++Some(cmd,r))commandselseDone(Some(cmd,r))inRun(cmd,cont)inauxNonelletseqjobstart=List.fold_left(@@+)(Donestart)jobletseq_mapfl=List.fold_left(funjobx->job@@+funacc->fx@@|funy->y::acc)(Done[])l@@|List.revletrecwith_texttext=function|Done_asj->j|Run(cmd,cont)->Run({cmdwithcmd_text=Sometext},funr->with_texttext(contr))endtype'ajob='aJob.Op.job