123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991(*---------------------------------------------------------------------------
Copyright (c) 2018 The b0 programmers. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
---------------------------------------------------------------------------*)openB0_std(* Exit *)moduleExit=structopenCmdlinerletok=Os.Exit.Code0letno_such_name=Os.Exit.Code122letsome_error=Os.Exit.CodeCmd.Exit.some_errorletcli_error=Os.Exit.CodeCmd.Exit.cli_errorletinternal_error=Os.Exit.CodeCmd.Exit.internal_errorletecdoc=Cmd.Exit.info(Os.Exit.get_codec)~docletinfos=eno_such_name"if a specified name does not exist."::Cmd.Exit.defaultsletof_eval_result?(term_error=cli_error)=function|Ok(`Oke)->e|Ok_->ok|Error`Term->term_error|Error`Parse->cli_error|Error`Exn->internal_errorletrecexit~exec_errore=exit~exec_error(Log.if_error~use:exec_error(Os.Exit.exite))end(* Argument converters *)leterr_msgof_strings=Result.map_error(fune->`Msge)(of_strings)letcmd=Cmdliner.Arg.conv~docv:"CMD"(err_msgCmd.of_string,Cmd.pp_dump)letfpath=Cmdliner.Arg.conv~docv:"PATH"(err_msgFpath.of_string,Fpath.pp_quoted)lets_output_format_options="OUTPUT FORMAT OPTIONS"moduleArg=structopenCmdliner(* Specifying output detail *)typeoutput_format=[`Normal|`Short|`Long]letoutput_format?(docs=s_output_format_options)?(short_opts=["s";"short"])?(long_opts=["l";"long"])()=letshort=letdoc="Short output. Line based output with only relevant data."inArg.infoshort_opts~doc~docsinletlong=letdoc="Long output. Outputs as much information as possible."inArg.infolong_opts~doc~docsinArg.(value&vflag`Normal[`Short,short;`Long,long])end(* B0_std setup *)moduleB0_std=structopenCmdlinerletget_tty_capc=matchOption.joincwith|Somec->c|None->Tty.(cap(of_fdUnix.stdout))letget_log_levellevel=Option.value~default:Log.Warninglevelletsetupcaplevel~log_spawns=Fmt.set_tty_cap~cap();Log.set_levellevel;iflevel>=log_spawnsthenOs.Cmd.set_spawn_tracer(Log.spawn_tracerlog_spawns)(* Cli argumements *)lettty_cap_of_strings=matchString.trimswith|""|"auto"->OkNone|"always"->Ok(Some`Ansi)|"never"->Ok(Some`None)|e->letpp_cap=Fmt.(codestring)inletkind=Fmt.any"color behaviour"inletdom=["auto";"always";"never"]inFmt.error"%a"Fmt.(unknown'~kindpp_cap~hint:must_be)(e,dom)lettty_cap?(docs=Manpage.s_common_options)?env()=letparses=Result.map_error(fune->`Msge)(tty_cap_of_strings)inletppppfc=Fmt.stringppf@@matchcwith|None->"auto"|Some`Ansi->"always"|Some`None->"never"inletcolor=Arg.conv~docv:"WHEN"(parse,pp)inletdoc="Colorize the output. $(docv) must be $(b,auto), $(b,always) \
or $(b,never)."inletdocv="WHEN"andnone=NoneinArg.(value&opt(some'~nonecolor)None&info["color"]?env~doc~docv~docs)letlog_level?(none=Log.Warning)?(docs=Manpage.s_common_options)?env()=letvopts=letdoc="Increase verbosity. Repeatable, but more than twice does not bring \
more. Takes over $(b,--verbosity)."(* The reason for taking over verbosity is due to cmdliner
limitation: we cannot distinguish in choose below if verbosity
was set via an env var. And cli args should always take over env
var. So verbosity set through the env var would take over -v
otherwise. *)inArg.(value&flag_all&info["v";"verbose"]~doc~docs)inletverbosity=letparses=Result.map_error(fune->`Msge)(Log.level_of_strings)inletlevel=Arg.conv~docv:"LEVEL"(parse,Log.pp_level)inletdoc="Be more or less verbose. $(docv) must be $(b,quiet), $(b,app), \
$(b,error), $(b,warning), $(b,info) or $(b,debug)."inArg.(value&opt(some~none:"warning"level)None&info["verbosity"]?env~docv:"LEVEL"~doc~docs)inletquiet=letdoc="Be quiet. Takes over $(b,-v) and $(b,--verbosity)."inArg.(value&flag&info["q";"quiet"]~doc~docs)inletchoosequietverbosityvopts=ifquietthenSomeLog.Quietelsematchvoptswith|(_::[])->SomeLog.Info|(_::_::_)->SomeLog.Debug|[]->verbosityinTerm.(constchoose$quiet$verbosity$vopts)endmoduleFile_cache=struct(* Cache stats *)typekey_stats={keys_count:int;keys_file_count:int;keys_byte_size:int}letpp_key_statsppfs=Fmt.pfppf" %5d %5d %a"s.keys_counts.keys_file_count(Fmt.codeFmt.byte_size)s.keys_byte_sizeletpp_statsppf(total,used)=letrow=Fmt.tty_string[`Fg`Yellow]inletcol=Fmt.tty_string[`Italic]inletpp_colsppf()=Fmt.pfppf" %a %a"col"keys"col"files"inFmt.pfppf"@[<v>%a%a@,%a%a@,%a@]"row"total"pp_key_statstotalrow"used "pp_key_statsusedpp_cols()letpp_statsppf(total,used)=letrow=Fmt.tty_string[`Fg`Yellow]inletcol=Fmt.tty_string[`Italic]inletpp_sizeppfs=Fmt.pfppf"%6s"(Fmt.str"%a"Fmt.byte_sizes)inletpp_colsppf()=Fmt.pfppf" %a %a"col"total"col"used"inFmt.pfppf"@[<v>%a@,%a %6d %6d@,%a %6d %6d@,%a %a %a@]"pp_cols()row"keys "total.keys_countused.keys_countrow"files"total.keys_file_countused.keys_file_countrow"size "(Fmt.codepp_size)total.keys_byte_size(Fmt.codepp_size)used.keys_byte_sizeletstats_of_cachec~used=letreclooptktftbukufub=function|[]->lett={keys_count=tk;keys_file_count=tf;keys_byte_size=tb}inletu={keys_count=uk;keys_file_count=uf;keys_byte_size=ub}int,u|k::ks->letkf,kb,_=B000.File_cache.key_statsck|>Result.to_failureinmatchString.Set.memkusedwith|true->loop(tk+1)(tf+kf)(tb+kb)(uk+1)(uf+kf)(ub+kb)ks|false->loop(tk+1)(tf+kf)(tb+kb)ukufubksintryletkeys=B000.File_cache.keysc|>Result.to_failureinOk(loop000000keys)with|Failuree->Error(Fmt.str"cache stats: %s"e)(* High-level commands *)letkeys_of_done_opsops=letadd_opacco=leth=B000.Op.hashoinmatchnot(Hash.is_nilh)&&B000.Op.statuso=B000.Op.Donewith|true->String.Set.add(Hash.to_hexh)acc|false->accinList.fold_leftadd_opString.Set.emptyopsletdelete~dirkeys=Result.bind(Os.Dir.existsdir)@@function|false->Okfalse|true->matchkeyswith|`All->(* Delete and recreate dir *)Result.bind(Os.Path.delete~recurse:truedir)@@fun_->Result.bind(Os.Dir.create~make_path:truedir)@@fun_->Oktrue|`Keyskeys->Result.bind(B000.File_cache.createdir)@@func->letdeleteck=Log.if_error~use:()@@Result.bind(B000.File_cache.remck)@@function|true->Ok()|false->Log.warnbeginfunm->m"No key %a in cache, ignored."Fmt.(codestring)kend;Ok()inList.iter(deletec)keys;Oktrueletgc~dir~used=Result.bind(Os.Dir.existsdir)@@function|false->Okfalse|true->Result.bind(B000.File_cache.createdir)@@func->Result.bind(B000.File_cache.keysc)@@funkeys->letunusedk=not(String.Set.memkused)inletunused=List.filterunusedkeysinletdeleteck=ignore(B000.File_cache.remck|>Log.if_error~use:false)inList.iter(deletec)unused;Oktrueletkeys~dir=Result.bind(Os.Dir.existsdir)@@function|false->Okfalse|true->Result.bind(B000.File_cache.createdir)@@func->Result.bind(B000.File_cache.keysc)@@funkeys->Log.app(funm->m"@[<v>%a@]"Fmt.(liststring)keys);Oktrueletstats~dir~used=Result.bind(Os.Dir.existsdir)@@function|false->Okfalse|true->Result.bind(B000.File_cache.createdir)@@func->Result.bind(stats_of_cachec~used)@@funstats->Log.app(funm->m"@[<v>%a@]"pp_statsstats);Oktruelettrim~dir~used~max_byte_size~pct=Result.bind(Os.Dir.existsdir)@@function|false->Okfalse|true->letis_unusedk=not(String.Set.memkused)inResult.bind(B000.File_cache.createdir)@@func->Result.bind(B000.File_cache.trim_sizec~is_unused~max_byte_size~pct)@@func->Oktrue(* Cli fragments *)openCmdlinerletkey_arg=letof_strings=matchFpath.is_segswith|true->Oks|false->Error("Not a valid key (not a path segment)")inArg.conv'(of_string,String.pp)~docv:"KEY"letkeys_none_is_all?(pos_right=-1)()=letdoc="Select $(docv) (repeatable). If unspecified selects all keys."inletkeys=Arg.(value&pos_right0key_arg[]&info[]~doc~docv:"KEY")inTerm.(const(function[]->`All|ks->`Keysks)$keys)lettrim_cli?(mb_opts=["to-mb"])?(pct_opts=["to-pct"])?docs()=lettrim_to_mb=letdoc="Trim the cache to at most $(docv) megabytes."inletdocv="MB"inArg.(value&opt(someint)None&infomb_opts~doc?docs~docv)inlettrim_to_pct=letdoc="Trim the cache to at most $(docv)% of the current size."inletdocv="PCT"inArg.(value&opt(someint)None&info["to-pct"]~doc~docv)inlettrimtrim_to_mbtrim_to_pct=matchtrim_to_mb,trim_to_pctwith|None,None->max_int,50|None,Somepct->max_int,pct|Somemb,None->mb*1000*1000,100|Somemb,Somepct->mb*1000*1000,pctinTerm.(consttrim$trim_to_mb$trim_to_pct)endmoduleOp=structopenB000(* Finding dependencies *)letfind_deps?(acc=Op.Set.empty)~recursiveindexdepsops=letadd_directindexoacc=letadd_index_opsindexaccp=matchFpath.Map.findpindexwith|exceptionNot_found->acc|ops->Op.Set.unionopsaccinList.fold_left(add_index_opsindex)acc(depso)inifnotrecursivethen(Op.Set.fold(add_directindex)opsacc)elseletrecloopindexaccseentodo=matchOp.Set.choosetodowith|exceptionNot_found->acc|o->letseen=Op.Set.addoseeninlettodo=Op.Set.removeotodoinletdeps=add_directindexoOp.Set.emptyinlettodo=Op.Set.(uniontodo(diffdepsseen))inletacc=Op.Set.unionaccdepsinloopindexaccseentodoinloopindexaccOp.Set.emptyopsletfind_needs?acc~recursive~writesops=find_deps?acc~recursivewritesOp.readsopsletfind_enables?acc~recursive~readsops=find_deps?acc~recursivereadsOp.writesops(* Queries *)typequery=B000.Op.tlist->B000.Op.tlistletselect~reads~writes~ids~hashes~marks=letall=reads=[]&&writes=[]&&ids=[]&&hashes=[]&&marks=[]inifallthenfun_->trueelseletreads=Fpath.Set.of_listreadsinletmem_readsf=Fpath.Set.memfreadsinletwrites=Fpath.Set.of_listwritesinletmem_writesf=Fpath.Set.memfwritesinlethashes=String.Set.of_list(List.rev_mapHash.to_byteshashes)inletmem_hashh=String.Set.memhhashesinletmarks=String.Set.of_listmarksinletmem_markm=String.Set.memmmarksinfuno->List.exists((=)(Op.ido))ids||mem_hash(Hash.to_bytes(Op.hasho))||List.existsmem_reads(Op.readso)||List.existsmem_writes(Op.writeso)||mem_mark(Op.marko)letselect_deps~needs~enables~recursive~domops=ifnotneeds&¬enablesthenopselseletreads,writes=B000.Op.read_write_mapsdominletops=Op.Set.of_listopsinletacc=Op.Set.emptyinletacc=ifneedsthenfind_needs~recursive~writes~accopselseaccinletacc=ifenablesthenfind_enables~recursive~reads~accopselseaccinOp.Set.elementsaccletop_kind_enumo=matchOp.kindowith|Op.Copy_->`Copy|Op.Delete_->`Delete|Op.Notify_->`Notify|Op.Mkdir_->`Mkdir|Op.Read_->`Read|Op.Spawn_->`Spawn|Op.Wait_files_->`Wait_files|Op.Write_->`Writeletop_status_enumo=matchOp.statusowith|Op.Aborted->`Aborted|Op.Done->`Done|Op.Failed_->`Failed|Op.Waiting->`Waitingletfilter~revived~statuses~kinds=letrevived_filter=matchrevivedwith|None->fun_->true|Somerevived->funo->Op.revivedo=revived&¬(Hash.equalHash.nil(Op.hasho))inletstatus_filter=matchstatuseswith|[]->fun_->true|statuses->funo->List.mem(op_status_enumo)statusesinletkind_filter=matchkindswith|[]->fun_->true|kinds->funo->List.mem(op_kind_enumo)kindsinfuno->revived_filtero&&status_filtero&&kind_filteroletorder~byops=letorder_by_fieldcmpfo0o1=cmp(fo0)(fo1)inletorder=matchbywith|`Create->order_by_fieldMtime.Span.compareOp.time_created|`Start->order_by_fieldMtime.Span.compareOp.time_started|`Wait->order_by_fieldMtime.Span.compareOp.waited|`Dur->letrev_comparet0t1=Mtime.Span.comparet1t0inorder_by_fieldrev_compareOp.durationinList.sortorderopsletquery~select~select_deps~filter~orderops=letsel=List.filterselectopsinletsel=select_deps~dom:opsselinletsel=List.filterfilterselinordersel(* Command line *)openCmdlinerlethash=letof_strings=leterr_=Fmt.str"Could not parse hash from %S"sinResult.map_errorerr(Hash.of_hexs)inArg.conv'~docv:"HASH"(of_string,Hash.pp)letmarks?(opts=["m";"mark"])?docs?(doc="Select operations marked by $(docv). Repeatable.")?(docv="MARK")()=letdocv="MARK"inArg.(value&opt_allstring[]&info["m";"mark"]~doc?docs~docv)letselect_cli?docs?(marks=marks()?docs)()=letreads=letdoc="Select operations that read file $(docv). Repeatable."inArg.(value&opt_allfpath[]&info["r";"read"]~doc?docs~docv:"FILE")inletwrites=letdoc="Select operations that wrote file $(docv). Repeatable."inArg.(value&opt_allfpath[]&info["w";"write"]~doc?docs~docv:"FILE")inletids=letdoc="Select operation with identifier $(docv). Repeatable."inArg.(value&opt_allint[]&info["id"]~doc?docs~docv:"ID")inlethashes=letdoc="Select operation with hash $(docv). Repeatable."inArg.(value&opt_allhash[]&info["hash"]~doc?docs~docv:"HASH")inletselectreadswritesidshashesmarks=select~reads~writes~ids~hashes~marksinTerm.(constselect$reads$writes$ids$hashes$marks)letselect_deps_cli?docs()=letneeds=letdoc="Once operations have been selected, replace them with all direct \
operations needed by these before filtering. Use with option \
$(b,--rec) to get the recursive operations."inArg.(value&flag&info["needs"]~doc?docs)inletenables=letdoc="Once operations have been selected, replace them with all direct \
operations enabled by these before filtering. Use with option \
$(b,--rec) to get the recursive operations."inArg.(value&flag&info["enables"]~doc?docs)inletrecursive=letdoc="Make $(b,--needs) or $(b,--enables) recursive."inArg.(value&flag&info["rec"]~doc?docs)inletselect_depsneedsenablesrecursive=select_deps~needs~enables~recursiveinTerm.(constselect_deps$needs$enables$recursive)letfilter_cli?docs()=letrevived=letrevived=letdoc="Keep only revivable operations that were revived."inSometrue,Arg.info["revived"]~doc?docsinletunrevived=letdoc="Keep only revivable operations that were not revived."inSomefalse,Arg.info["u";"unrevived"]~doc?docsinArg.(value&vflagNone[revived;unrevived])inletstatuses=letstatuses=letstatus_enum=["aborted",`Aborted;"done",`Done;"failed",`Failed;"waiting",`Waiting]inletstatus=Arg.enumstatus_enuminletstatuses=Arg.liststatusanddocv="STATUS,..."inletdoc=Fmt.str"Keep only operations that have their status in $(i,STATUS). \
$(i,STATUS) must be %s"(Arg.doc_alts_enumstatus_enum)inArg.(value&optstatuses[]&info["status"]~doc?docs~docv)inleterrors=letdoc="Keep only failed operations (errors). Equivalent
to add $(b,failed) to the $(b,--status) option."inArg.(value&flag&info["e";"errors"]~doc?docs)inletstsstatuseserrs=iferrsthen`Failed::statuseselsestatusesinTerm.(conststs$statuses$errors)inletkinds=letkind_enum=["copy",`Copy;"delete",`Delete;"notify",`Notify;"mkdir",`Mkdir;"read",`Read;"spawn",`Spawn;"wait",`Wait_files;"write",`Write]inletkind=Arg.enumkind_enuminletkinds=Arg.listkindinletdoc=Fmt.str"Keep only operations that have their kind in $(i,KIND). \
$(i,KIND) must be %s."(Arg.doc_alts_enumkind_enum)inArg.(value&optkinds[]&info["kind"]~doc?docs~docv:"KIND,...")inletfilterrevivedstatuseskinds=filter~revived~statuses~kindsinTerm.(constfilter$revived$statuses$kinds)letorder_cli?docs()=letorder_by=letorder=["create",`Create;"start",`Start;"wait",`Wait;"dur",`Dur;]inletdoc=Fmt.str"Order by $(docv). $(docv) must be %s time."(Arg.doc_alts_enumorder)inletorder=Arg.enumorderanddocv="ORDER"inArg.(value&optorder`Start&info["order-by"]~doc?docs~docv)inletby_dur=letdoc="Order by decreasing duration. Takes over $(b,--order-by)."inArg.(value&flag&info["d"]~doc?docs)inletorderorder_byby_dur=letby=ifby_durthen`Durelseorder_byinorder~byinTerm.(constorder$order_by$by_dur)lets_selection_options="BUILD OPERATION SELECTION OPTIONS"letquery_cli?(docs=s_selection_options)()=letopenCmdlinerinletqueryselectselect_depsfilterorder=query~select~select_deps~filter~orderinTerm.(constquery$select_cli~docs()$select_deps_cli~docs()$filter_cli~docs()$order_cli~docs())letquery_man=[`P"Options are provided to select and filter operations. \
Any operation that satifies one of the selectors and all of the \
filters is included in the result. If no selector is specified all \
operations are selected. If no filter is specified all selected \
operations are returned. By default the result is sorted by \
execution start time, this can be changed with the $(b,--order-by) \
or $(b,-d) option."]endmoduleMemo=struct(* Memo feedback *)letop_howtoppfo=Fmt.pfppf"b00-log --id %d"(B000.Op.ido)letpp_leveled_feedback?(sep=Fmt.flush_nl)?(op_howto=op_howto)~show_op~show_ui~levelppff=letopenB000iniflevel=Log.Quietthen()elsematchfwith|`Exec_start(_,_)->()(* we have B0_std.Os spawn tracer on debug *)|`Op_completeo->iflevel>=show_op||level=Log.Debugthen(B000_conv.Op.pp_line_and_uippfo;sepppf())elseiflevel>=show_uithen(B000_conv.Op.pp_ui~sep~op_howtoppfo)|`Miss_tool(t,e)whenlevel>=Log.Error->Fmt.pfppf"@[<v>Missing tool:@,%s@]%a"esep()|_->()openCmdliner(* B0 directory *)letb0_dir_env="B0_DIR"letb0_dir_name="_b0"letb0_dir?(opts=["b0-dir"])?(docs=Manpage.s_common_options)?(doc="Use $(docv) for the b0 directory.")?doc_none:(absent="$(b,_b0) in root directory")?(env=Cmdliner.Cmd.Env.infob0_dir_env)()=Arg.(value&opt(somefpath)None&infoopts~env~absent~doc~docs~docv:"DIR")letget_b0_dir~cwd~root~b0_dir=matchb0_dirwith|None->Fpath.(root/b0_dir_name)|Somed->Fpath.(cwd//d)letget_b0_dir_path~cwd~b0_dirdefaultp=matchpwith|None->Fpath.(b0_dir/default)|Somep->Fpath.(cwd//p)letfind_dir_with_b0_dir~start=letrecloopp=matchFpath.is_rootpwith|true->None|false->matchOs.Dir.existsFpath.(p/b0_dir_name)with|Error_|Okfalse->loop(Fpath.parentp)|Oktrue->SomepinifFpath.is_relstartthenNoneelse(loopstart)(* File cache directory *)letcache_dir_env="B0_CACHE_DIR"letcache_dir_name=".cache"letcache_dir?(opts=["cache-dir"])?(docs=Manpage.s_common_options)?(doc="Use $(docv) for the build cache directory.")?doc_none:(absent="$(b,.cache) in b0 directory")?(env=Cmdliner.Cmd.Env.infocache_dir_env)()=Arg.(value&opt(somefpath)None&infoopts~env~absent~doc~docs~docv:"DIR")letget_cache_dir~cwd~b0_dir~cache_dir=get_b0_dir_path~cwd~b0_dircache_dir_namecache_dir(* Trash directory *)lettrash_dir_name=".trash"letget_trash_dir~cwd~b0_dir~trash_dir=get_b0_dir_path~cwd~b0_dirtrash_dir_nametrash_dir(* Log file *)letlog_file_name=".log"letlog_file_env="B0_LOG_FILE"letlog_file?(opts=["log-file"])?docs?(doc="Use $(docv) for the build log file.")?doc_none:(absent="$(b,.log) in b0 directory")?(env=Cmdliner.Cmd.Env.infolog_file_env)()=Arg.(value&opt(somefpath)None&infoopts~absent~env~doc?docs~docv:"LOG_FILE")letget_log_file~cwd~b0_dir~log_file=get_b0_dir_path~cwd~b0_dirlog_file_namelog_file(* Jobs *)letjobs_env="B0_JOBS"letjobs?(opts=["j";"jobs"])?docs?(doc="Maximal number of commands to spawn concurrently.")?doc_none:(absent="Number of CPUs available")?(env=Cmdliner.Cmd.Env.infojobs_env)()=Arg.(value&opt(someint)None&infoopts~env~absent~doc?docs~docv:"COUNT")letget_jobs~jobs=matchjobswith|Somemax->max|None->Os.Cpu.logical_count()(* Hash fun *)lethash_fun=letof_strings=Result.map_error(funm->`Msgm)@@Hash.get_funsinletppppf(moduleH:Hash.T)=Fmt.stringppfH.idinArg.conv~docv:"HASHFUN"(of_string,pp)lethash_fun_env="B0_HASH_FUN"lethash_fun?(opts=["hash-fun"])?docs?doc?(doc_none=Hash.Xxh3_64.id)?(env=Cmdliner.Cmd.Env.infohash_fun_env)()=letdoc=matchdocwith|Somedoc->doc|None->letids=List.map(fun(moduleH:Hash.T)->H.id)(Hash.funs())inFmt.str"Hash function to use for caching. %a"Fmt.(must_be(Fmt.codestring))idsinArg.(value&opt(some~none:doc_nonehash_fun)None&infoopts~env~doc?docs~docv:"HASHFUN")letget_hash_fun~hash_fun=matchhash_funwith|Somem->m|None->(moduleHash.Xxh3_64:Hash.T)(* Logs *)moduleLog=struct(* XXX at the moment we are not serializing Memo.t.ready_roots.
This means we can't use the log with [B000.Op.find_aggregate_error]
we might want to change this but it seems log writing is already
not so fast. *)(* Logs *)typet={hash_fun:string;file_hashes:Hash.tFpath.Map.t;hash_dur:Mtime.span;total_dur:Mtime.span;cpu_dur:Os.Cpu.Time.span;jobs:int;ops:B000.Op.tlist;}letof_memom=letr=B00.Memo.reviverminletmoduleH=(val(B000.Reviver.hash_funr))inletfile_hashes=B000.Reviver.file_hashesrinlethash_dur=B000.Reviver.file_hash_durrinlettotal_dur=Os.Mtime.count(B00.Memo.clockm)inletcpu_dur=Os.Cpu.Time.count(B00.Memo.cpu_clockm)inletjobs=B000.Exec.jobs(B00.Memo.execm)inletops=B00.Memo.opsmin{hash_fun=H.id;hash_dur;file_hashes;total_dur;cpu_dur;jobs;ops}lethash_funl=l.hash_funletfile_hashesl=l.file_hasheslethash_durl=l.hash_durlettotal_durl=l.total_durletcpu_durl=l.cpu_durletjobsl=l.jobsletopsl=l.ops(* IO *)letenc_file_hashesbhs=letenc_file_hashbfh=Bincode.enc_fpathbf;Bincode.enc_hashbhinletcount=Fpath.Map.cardinalhsinBincode.enc_intbcount;Fpath.Map.iter(enc_file_hashb)hsletdec_file_hashessi=letrecloopacccountsi=ifcount=0theni,accelseleti,file=Bincode.dec_fpathsiinleti,hash=Bincode.dec_hashsiinloop(Fpath.Map.addfilehashacc)(count-1)siinleti,count=Bincode.dec_intsiinloopFpath.Map.emptycountsiletmagic="b\x00\x00\x00log"letencbl=Bincode.enc_magicmagicb();Bincode.enc_stringbl.hash_fun;enc_file_hashesbl.file_hashes;Bincode.enc_time_spanbl.hash_dur;Bincode.enc_time_spanbl.total_dur;Bincode.enc_cpu_time_spanbl.cpu_dur;Bincode.enc_intbl.jobs;Bincode.enc_list(Bincode.encB000_conv.Op.bincode)bl.opsletdecsi=leti,()=Bincode.dec_magicmagicsiinleti,hash_fun=Bincode.dec_stringsiinleti,file_hashes=i,Fpath.Map.emptyinleti,file_hashes=dec_file_hashessiinleti,hash_dur=Bincode.dec_time_spansiinleti,total_dur=Bincode.dec_time_spansiinleti,cpu_dur=Bincode.dec_cpu_time_spansiinleti,jobs=Bincode.dec_intsiinleti,ops=Bincode.dec_list(Bincode.dec(B000_conv.Op.bincode))siini,{hash_fun;file_hashes;hash_dur;total_dur;cpu_dur;jobs;ops;}letbincode=Bincode.vencdecletwritefilel=letdata=Log.time(fun_msg->msg"generating log")@@fun()->letbuf=Buffer.create(1024*1024)inBincode.to_string~bufbincodelinLog.time(fun_msg->msg"writing log")@@fun()->Os.File.write~force:true~make_path:truefiledataletreadfile=Result.bind(Os.File.readfile)@@fundata->Bincode.of_string~filebincodedata(* Log formatters *)lethashed_byte_sizefile_hashes=letadd_filef_acc=matchUnix.stat(Fpath.to_stringf)with|exceptionUnix.Unix_error(_,_,_)->0|s->acc+s.Unix.st_sizeinFpath.Map.foldadd_filefile_hashes0letpp_stats~hashed_sizeselppfl=letsc,st,sd,wc,wt,wd,cc,ct,cd,rt,rd,ot,od=let(++)=Mtime.Span.addinletrecloopscstsdwcwtwdccctcdrtrdotod=function|[]->sc,st,sd,wc,wt,wd,cc,ct,cd,rt,rd,ot,od|o::os->letrevived=B000.Op.revivedoandd=B000.Op.durationoinletot=ot+1andod=od++dinmatchB000.Op.kindowith|B000.Op.Spawn_->letsc=ifrevivedthensc+1elsescinloopsc(st+1)(sd++d)wcwtwdccctcdrtrdotodos|B000.Op.Write_->letwc=ifrevivedthenwc+1elsewcinloopscstsdwc(wt+1)(wd++d)ccctcdrtrdotodos|B000.Op.Copy_->letcc=ifrevivedthencc+1elseccinloopscstsdwcwtwdcc(ct+1)(cd++d)rtrdotodos|B000.Op.Read_->loopscstsdwcwtwdccctcd(rt+1)(rd++d)otodos|_->loopscstsdwcwtwdccctcdrtrdotodosinloop00Mtime.Span.zero00Mtime.Span.zero00Mtime.Span.zero0Mtime.Span.zero0Mtime.Span.zero(sell.ops)inletpp_totalsppf(ot,od)=Fmt.pfppf"%a %d"Mtime.Span.ppodotinletpp_hashesppfl=lethc,hd=Fpath.Map.cardinall.file_hashes,l.hash_durinleths=ifnothashed_sizethen0elsehashed_byte_sizel.file_hashesinletpp_hashed_sizeppfs=letlabel=Fmt.tty_string[`Italic]inmatchhashed_sizewith|true->Fmt.field~label"size"(func->c)Fmt.byte_sizeppfs|false->()inFmt.pfppf"%a %a"pp_totals(hc,hd)pp_hashed_sizehsinletpp_xtimeppf(self,children)=letlabel=Fmt.tty_string[`Italic]inFmt.pfppf"%a %a"Mtime.Span.ppself(Fmt.field~label"children"(func->c)Mtime.Span.pp)childreninletpp_stimeppfl=lett=Os.Cpu.Time.(stimel.cpu_dur,children_stimel.cpu_dur)inpp_xtimeppftinletpp_utimeppfl=lett=Os.Cpu.Time.(utimel.cpu_dur,children_utimel.cpu_dur)inpp_xtimeppftinletpp_opppf(oc,ot,od)=Fmt.pfppf"%a %d (%d revived)"Mtime.Span.ppodotocinletpp_op_no_cacheppf(ot,od)=Fmt.pfppf"%a %d"Mtime.Span.ppodotinletpp_secsppf_=Fmt.tty_string[`Bold]ppfsin(Fmt.record@@[pp_sec"selected operations";Fmt.field"spawns"(fun_->(sc,st,sd))pp_op;Fmt.field"writes"(fun_->(wc,wt,wd))pp_op;Fmt.field"copies"(fun_->(cc,ct,cd))pp_op;Fmt.field"reads"(fun_->(rt,rd))pp_op_no_cache;Fmt.field"all"(fun_->(ot,od))pp_totals;pp_sec"global timings";Fmt.field"jobs"jobsFmt.int;Fmt.field"hashes"Fmt.idpp_hashes;Fmt.field"utime"Fmt.idpp_utime;Fmt.field"stime"Fmt.idpp_stime;Fmt.field"real"(fun_->l.total_dur)Mtime.Span.pp])ppfltypeout_format=[`Hashed_files|`Op_hashes|`Ops|`Path|`Stats|`Root_hashed_files|`Trace_event]letpp_op=function|`Short->B000_conv.Op.pp_line|`Normal->B000_conv.Op.pp_line_and_ui|`Long->B000_conv.Op.ppletpp_op_hash=function|`Short|`Normal->Fmt.usingB000.Op.hashHash.pp|`Long->funppfo->Fmt.intppf(B000.Op.ido);Fmt.spppf();Hash.ppppf(B000.Op.hasho)letpp_hashed_file=function|`Short->Fmt.usingfstFpath.pp_unquoted|`Normal|`Long->funppf(f,h)->Hash.ppppfh;Fmt.charppf' ';Fpath.pp_unquotedppffletoutppfformatdetailsquery~pathl=matchformatwith|`Path->Fmt.pfppf"@[%a@]@."Fpath.pp_unquotedpath|`Ops->letops=queryl.opsinifops=[]then()elseFmt.pfppf"@[<v>%a@]@."(Fmt.list(pp_opdetails))ops|`Stats->lethashed_size=details<>`Short(* do it by default for now *)inFmt.pfppf"@[%a@]@."(pp_stats~hashed_sizequery)l|`Trace_event->letops=queryl.opsinifops=[]then()elselett=B00_trace.Trace_event.of_opsopsinFmt.pfppf"@[%s@]@."(B00_serialk_json.Jsong.to_stringt)|`Op_hashes->lethas_hasho=not(Hash.is_nil(B000.Op.hasho))inletops=List.filterhas_hash(queryl.ops)inifops=[]then()elseFmt.pfppf"@[<v>%a@]@."(Fmt.list(pp_op_hashdetails))ops|`Root_hashed_files->letwrites=letadd_writeaccf=Fpath.Set.addfaccinletadd_opacco=List.fold_leftadd_writeacc(B000.Op.writeso)inList.fold_leftadd_opFpath.Set.emptyl.opsinletadd_filewritesfhacc=ifFpath.Set.memfwritesthenaccelse(f,h)::accinletroots=Fpath.Map.fold(add_filewrites)l.file_hashes[]inifroots=[]then()elseFmt.pfppf"@[<v>%a@]@."(Fmt.list(pp_hashed_filedetails))roots|`Hashed_files->letpp_hashed_files=Fmt.iter_bindingsFpath.Map.iter(pp_hashed_filedetails)inifFpath.Map.is_emptyl.file_hashesthen()elseFmt.pfppf"@[<v>%a@]@."pp_hashed_filesl.file_hashesletout_format_cli?(docs=s_output_format_options)()=letaoptdoc=Cmdliner.Arg.info[opt]~doc~docsinletfmts=[`Hashed_files,a"hashed-files""Output the path of every hashed file.";`Op_hashes,a"op-hashes""Output the hashes (cache keys) of selected operations.";`Ops,a"ops""Output selected operations (default).";`Path,a"path""Output the path to the log file.";`Root_hashed_files,a"root-hashed-files""Output the path of hashed files that are not written by any \
of the build operations in the log.";`Stats,a"stats""Output statistics about the build and selected operations.";`Trace_event,a"trace-event""Output selected operations in Trace Event format."]inCmdliner.Arg.(value&vflag`Opsfmts)endend(*---------------------------------------------------------------------------
Copyright (c) 2018 The b0 programmers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)