123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590(*
* Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
*
* Permission to use, copy, modify, and 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.
*)letsetup_logstyle_rendererlevel=Fmt_tty.setup_std_outputs?style_renderer();Logs.set_levellevel;Logs.set_reporter(Logs_fmt.reporter())openCmdlinerletcommon_section="COMMON OPTIONS"letconfiguration_section="CONFIGURE OPTIONS"letquery_section="QUERY OPTIONS"letdescription_section="DESCRIBE OPTIONS"typequery_kind=[`Name|`Packages|`Opam|`Files|`Duneof[`Config|`Build|`Project|`Workspace|`Dist]|`Makefile]letquery_kinds:(string*query_kind)list=[("name",`Name);("packages",`Packages);("opam",`Opam);("files",`Files);("Makefile",`Makefile);("dune.config",`Dune`Config);("dune.build",`Dune`Build);("dune-project",`Dune`Project);("dune-workspace",`Dune`Workspace);("dune.dist",`Dune`Dist);]letsetup~with_setup=Term.(const(ifwith_setupthensetup_logelsefun__->())$Fmt_cli.style_renderer~docs:common_section()$Logs_cli.level~docs:common_section())letconfig_file=letdoc=Arg.info~docs:configuration_section~docv:"FILE"~doc:"The configuration file to use."["f";"file";"config-file"]inTerm.(constFpath.v$Arg.(value&optstring"config.ml"&doc))letmap_default~defaultfx=ifx==defaultthenNoneelseSome(fx)letcontext_filemname=letdoc=Arg.info~docs:configuration_section~docv:"FILE"~doc:"The context file to use."["context-file"]inletdefault=mname^".context"inTerm.(const(map_default~defaultFpath.v)$Arg.(value&optstringdefault&doc))letextra_reposdoc_section=letkey=letparserstr=matchAstring.String.cut~sep:":"strwith|Some(name,repository)->Ok(name,repository)|None->Rresult.R.error_msgf"Invalid extra repository argument (expected <name>:<repository>)"inletppppf(name,repository)=Fmt.pfppf"%s:%s"namerepositoryinArg.conv(parser,pp)inletenv=Cmd.Env.info"MIRAGE_EXTRA_REPOS"inletdoc=Arg.info~docs:doc_section~docv:"NAME1:URL1,NAME2:URL2,..."~env~doc:"Additional opam-repositories to use when using `opam monorepo lock' \
to gather local sources. Default: \
https://github.com/dune-universe/opam-overlays.git & \
https://github.com/dune-universe/mirage-opam-overlays.git."["extra-repos"]inArg.(value&opt(listkey)[("opam-overlays","https://github.com/dune-universe/opam-overlays.git");("mirage-overlays","https://github.com/dune-universe/mirage-opam-overlays.git");]&doc)letno_extra_repodoc_section=letdoc=Arg.info~docs:doc_section~doc:"Disable the use of any overlay repository."["no-extra-repo"]inArg.(value&flag&doc)letextra_reposdoc_section=letex=extra_reposdoc_sectioninletno_ex=no_extra_repodoc_sectioninTerm.(const(funexno_ex->ifno_exthen[]elseex)$ex$no_ex)letdry_run=letdoc=Arg.info~docs:configuration_section~doc:"Display I/O actions instead of executing them."["dry-run"]inArg.(value&flagdoc)(** * Argument specifications *)(** Argument specification for --depext *)letdepextsection=letdepext_doc=Arg.info~docs:section["depext"]~doc:"Enable call to `opam depext' in the project Makefile."inletno_depext_doc=Arg.info~docs:section["no-depext"]~doc:"Disable call to `opam depext' in the project Makefile."inleteval_opts=[(true,depext_doc);(false,no_depext_doc)]inArg.(value&vflagtrueeval_opts)(** Argument specification for --eval *)letfull_eval=leteval_doc=Arg.info~docs:description_section["eval"]~doc:"Fully evaluate the graph before showing it. The default when the \
unikernel has already been configured."inletno_eval_doc=Arg.info~docs:description_section["no-eval"]~doc:"Do not evaluate the graph before showing it. See $(b,--eval). The \
default when the unikernel has not been configured."inleteval_opts=[(Sometrue,eval_doc);(Somefalse,no_eval_doc)]inArg.(value&vflagNoneeval_opts)(** Argument specification for --dot *)letdot=letdoc=Arg.info~docs:description_section["dot"]~doc:"Output a dot description. If no output file is given, it will display \
the dot file using the command given to $(b,--dot-command). Use in \
combination with $(b,--output=-) (short version: $(b,-o-)) to display \
the dot file on stdout."inArg.(value&flagdoc)(** Argument specification for --dot-command=COMMAND *)letdotcmd=letdoc=Arg.info~docs:description_section~docv:"COMMAND"["dot-command"]~doc:"Command used to show a dot file. This command should accept a dot \
file on its standard input."inArg.(value&optstring"xdot"&doc)(** Argument specification for -o FILE or --output=FILE *)letoutput=letdoc=Arg.info~docs:configuration_section~docv:"FILE"["o";"output"]~doc:"Name of the output file."inArg.(value&opt(somestring)None&doc)letkind=letenums=Arg.doc_alts_enum~quoted:truequery_kindsinletdoc=Arg.info~docs:configuration_section~docv:"INFO"[]~doc:(Fmt.str"The information to query. $(docv) must be %s"enums)inArg.(value&pos0(enumquery_kinds)`Packages&doc)type'aargs={context:'a;config_file:Fpath.t;context_file:Fpath.toption;output:stringoption;dry_run:bool;}letdefault_args={context=();config_file=Fpath.v"dummy";context_file=None;output=None;dry_run=false;}type'aconfigure_args={args:'aargs;depext:bool;extra_repo:(string*string)list;}type'abuild_args='aargstype'aclean_args='aargstype'ahelp_args='aargstype'adescribe_args={args:'aargs;dotcmd:string;dot:bool;eval:booloption;}type'aquery_args={args:'aargs;kind:query_kind;depext:bool;extra_repo:(string*string)list;}type'aaction=|Configureof'aconfigure_args|Queryof'aquery_args|Describeof'adescribe_args|Cleanof'aclean_args|Helpof'ahelp_args(*
* Pretty-printing
*)letpp_argspp_a=letopenFmt.Dumpinrecord[field"context"(fun(t:'aargs)->t.context)pp_a;field"config_file"(funt->t.config_file)Fpath.pp;field"output"(funt->t.output)(optionstring);field"dry_run"(funt->t.dry_run)Fmt.bool;]letpp_configurepp_a=letopenFmt.Dumpinrecord[field"args"(fun(t:'aconfigure_args)->t.args)(pp_argspp_a);field"depext"(fun(t:'aconfigure_args)->t.depext)Fmt.bool;]letpp_clean=pp_argsletpp_help=pp_argsletpp_query_kindppf(q:query_kind)=letrecaux=function|[]->invalid_arg"missing query kind!"|(a,b)::t->ifb=qthenFmt.stringppfaelseauxtinauxquery_kindsletpp_querypp_a=letopenFmt.Dumpinrecord[field"args"(fun(t:'aquery_args)->t.args)(pp_argspp_a);field"kind"(funt->t.kind)pp_query_kind;field"depext"(funt->t.depext)Fmt.bool;]letpp_describepp_a=letopenFmt.Dumpinrecord[field"args"(fun(t:'adescribe_args)->t.args)(pp_argspp_a);field"dotcmd"(funt->t.dotcmd)string;field"dot"(funt->t.dot)Fmt.bool;field"eval"(funt->t.eval)(optionFmt.bool);]letpp_actionpp_appf=function|Configurec->Fmt.pfppf"@[configure:@ @[<2>%a@]@]"(pp_configurepp_a)c|Queryq->Fmt.pfppf"@[query:@ @[<2>%a@]@]"(pp_querypp_a)q|Described->Fmt.pfppf"@[describe:@ @[<2>%a@]@]"(pp_describepp_a)d|Cleanc->Fmt.pfppf"@[clean:@ @[<2>%a@]@]"(pp_cleanpp_a)c|Helph->Fmt.pfppf"@[help:@ @[<2>%a@]@]"(pp_helppp_a)h(*
* Subcommand specifications
*)moduleSubcommands=structtype'at={with_setup:bool;mname:string;context:'aTerm.t}moduleT=structletargs{with_setup;context;mname}=Term.(const(fun()config_filecontext_filedry_runoutputcontext->{config_file;context_file;dry_run;output;context})$setup~with_setup$config_file$context_filemname$dry_run$output$context)end(** The 'configure' subcommand *)letconfiguret=(Term.(const(funargsdepextextra_repo->Configure{args;depext;extra_repo})$T.argst$depextconfiguration_section$extra_reposconfiguration_section),Cmd.info"configure"~doc:"Configure a $(mname) application."~man:[`S"DESCRIPTION";`P"The $(b,configure) command initializes a fresh $(mname) \
application.";])letqueryt=(Term.(const(funkindargsdepextextra_repo->Query{kind;args;depext;extra_repo})$kind$T.argst$depextquery_section$extra_reposquery_section),Cmd.info"query"~doc:"Query information about the $(mname) application."~man:[`S"DESCRIPTION";`P"The $(b,query) command queries information about the $(mname) \
application.";])(** The 'describe' subcommand *)letdescribet=(Term.(const(funargsevaldotcmddot->Describe{args;eval;dotcmd;dot})$T.argst$full_eval$dotcmd$dot),Cmd.info"describe"~doc:"Describe a $(mname) application."~man:[`S"DESCRIPTION";`P"The $(b,describe) command describes the configuration of a \
$(mname) application.";`P"The dot output contains the following elements:";`Noblank;`I("If vertices","Represented as circles. Branches are dotted, and the default \
branch is in bold.");`Noblank;`I("Configurables","Represented as rectangles. The order of the output arrows is \
the order of the functor arguments.");`Noblank;`I("Data dependencies","Represented as dashed arrows.");`Noblank;`I("App vertices","Represented as diamonds. The bold arrow is the functor part.");])(** The 'clean' subcommand *)letcleant=letdoc="Clean the files produced by $(mname) for a given application."in(Term.(const(funargs->Cleanargs)$T.argst),Cmd.info"clean"~doc~man:[`S"DESCRIPTION";`Pdoc])(** The 'help' subcommand *)lethelpt=lettopic=letdoc=Arg.info[]~docv:"TOPIC"~doc:"The topic to get help on."inArg.(value&pos0(somestring)None&doc)inlethelpman_formatcmdstopic=matchtopicwith|None->`Help(man_format,None)|Sometopic->(letparser,_=Arg.enum(List.rev_map(funs->(s,s))("topics"::cmds))inmatchparsertopicwith|`Errore->`Error(false,e)|`Oktwhent="topics"->List.iterprint_endlinecmds;`Ok()|`Okt->`Help(man_format,Somet))in(Term.(const(funargs___()->Helpargs)$T.argst$depextconfiguration_section$extra_reposconfiguration_section$full_eval$ret(consthelp$Arg.man_format$Term.choice_names$topic)),Cmd.info"help"~doc:"Display help about $(mname) commands."~man:[`S"DESCRIPTION";`P"Prints help.";`P"Use `$(mname) help topics' to get the full list of help topics.";])letdefault~with_setup~name~version=letusage=`Help(`Plain,None)in(Term.(ret(constusage)$setup~with_setup),Cmd.infoname~version~doc:"The $(mname) application builder"~man:[`S"DESCRIPTION";`P"The $(mname) application builder. It glues together a set of \
libraries and configuration (e.g. network and storage) into a \
standalone unikernel or UNIX binary.";`P"Use $(mname) $(b,help <command>) for more information on a \
specific command.";])end(*
* Functions for extracting particular flags from the command line.
*)letpeek_full_evalargv=matchCmd.eval_peek_opts~argvfull_evalwith_,Ok(`Okb)->b|_->Noneletpeek_outputargv=matchCmd.eval_peek_opts~argvoutputwith_,Ok(`Okb)->b|_->Noneletpeek_args?(with_setup=false)~mnameargv=letargs=Subcommands.T.args{with_setup;mname;context=Term.const()}inmatchCmd.eval_peek_opts~argvargswith|_,Ok(`Okb)|Someb,_->Someb|_->Noneleteval?(with_setup=true)?help_ppf?err_ppf~name~version~configure~query~describe~clean~help~mnameargv=letdefault,info=Subcommands.default~with_setup~name~versioninletargscontext={Subcommands.with_setup;mname;context}inletgroup=Cmd.group~defaultinfo(List.map(fun(term,info)->Cmd.vinfoterm)[Subcommands.configure(argsconfigure);Subcommands.describe(argsdescribe);Subcommands.query(argsquery);Subcommands.clean(argsclean);Subcommands.help(argshelp);])inmatchCmd.eval_value?help:help_ppf?err:err_ppf~argv~catch:falsegroupwith|Ok(#Cmd.eval_okasv)->v|Error(#Cmd.eval_errorase)->`Erroreletargs=function|Configure{args;_}->args|Cleanx|Helpx->x|Query{args;_}->args|Describe{args;_}->argsletchoices=[("configure",`Configure);("clean",`Clean);("query",`Query);("describe",`Describe);("help",`Help);]letfind_choicess=List.find_all(fun(k,_)->Astring.String.is_prefix~affix:sk)choicesletfind_kinds=List.find_all(fun(k,_)->Astring.String.is_prefix~affix:sk)query_kindsletnext_pos_argargvi=letrecauxi=ifi>=Array.lengthargvthenNoneelseifargv.(i)=""thenaux(i+1)elseifargv.(i).[0]='-'thenaux(i+1)elseSomeiinauxiletremove_argvargvi=leta=Array.subargv0iinletb=Array.subargv(i+1)(Array.lengthargv-i-1)inArray.appendabletrecfind_next_kindargvi=matchnext_pos_argargviwith|None->(None,argv)|Somei->(matchfind_kindargv.(i)with|[]->find_next_kindargv(i+1)|_::_::_ascs->Fmt.invalid_arg"ambiguous sub-command: %a\n%!"Fmt.Dump.(liststring)(List.mapfstcs)|[(_,k)]->(Somek,remove_argvargvi))letrecfind_next_choiceargvi=matchnext_pos_argargviwith|None->(None,argv)|Somei->(matchfind_choicesargv.(i)with|[]->find_next_choiceargv(i+1)|_::_::_ascs->Fmt.invalid_arg"ambiguous sub-command: %a\n%!"Fmt.Dump.(liststring)(List.mapfstcs)|[(_,a)]->(matchawith|(`Configure|`Clean|`Describe|`Help)asc->(Somec,remove_argvargvi)|`Query->letk,argv=find_next_kindargv(i+1)in(Some(`Queryk),remove_argvargvi)))letpeek_choiceargv=trymatchfind_next_choiceargv1withSomec,_->`Okc|_->`DefaultwithInvalid_argument_->`Error`Parsetype'aresult=[`Okof'aaction|`Errorof'aargsoption*[`Exn|`Parse|`Term]|`Version]letpeek?(with_setup=false)~mnameargv:unitresult=letniet=Term.const()inletpeekt=matchCmd.eval_peek_opts~argv~version_opt:true(fstt)with|_,Ok`Version->`Version|_,Errore->`Error(peek_args~mnameargv,e)|_,Ok`Help->(letargs=peek_args~with_setup:false~mnameargvinmatchargswith|Someargs->`Ok(Helpargs)|_->`Error(None,`Parse))|Somev,_|_,Ok(`Okv)->`Okvinletpeek_cmdf=letargs={Subcommands.with_setup;mname;context=niet}inpeek(fargs)inmatchpeek_choiceargvwith|`Ok`Configure->peek_cmdSubcommands.configure|`Ok`Clean->peek_cmdSubcommands.clean|`Ok(`Query_)->peek_cmdSubcommands.query|`Ok`Describe->peek_cmdSubcommands.describe|`Ok`Help->peek_cmdSubcommands.help|`Default->peek(Subcommands.default~with_setup~name:"<name>"~version:"<version>")|`Errore->`Error(peek_args~mnameargv,e)