123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506(*
* 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.
*)openAction.SyntaxopenAstringopenDSLmoduleName=Misc.Nameletsrc=Logs.Src.create"functoria"~doc:"functoria library"moduleLog=(valLogs.src_logsrc:Logs.LOG)moduleConfig=structtypet={config_file:Fpath.t;name:string;project_name:string;configure_cmd:string;pre_build_cmd:Fpath.toption->string;lock_location:Fpath.toption->string->string;build_cmd:Fpath.toption->string;packages:packagelistKey.value;if_keys:Key.Set.t;runtime_args:Runtime_arg.Set.t;init:jobimpllist;jobs:Impl.abstract;src:[`Auto|`None|`Someofstring];}typeout={init:jobimpllist;jobs:Impl.abstract;info:Info.t;device_graph:Device.Graph.t;}(* In practice, we get all the keys associated to [if] cases, and
all the keys that have a setter to them. *)letget_if_contextjobs=letall_keys=Engine.keysjobsinletskeys=Engine.if_keysjobsinletfks=iftruethenselseKey.Set.addksinKey.Set.foldfall_keysskeysletv?(config_file=Fpath.v"config.ml")?(init=[])~configure_cmd~pre_build_cmd~lock_location~build_cmd~src~project_namenamejobs=letjobs=Impl.abstractjobsinletif_keys=get_if_contextjobsinletruntime_args=Runtime_arg.Set.emptyin{config_file;if_keys;runtime_args;name;project_name;init;configure_cmd;pre_build_cmd;lock_location;build_cmd;packages=Key.pure[];jobs;src;}leteval~fullcontext{config_file;name=n;project_name;configure_cmd;pre_build_cmd;lock_location;build_cmd;packages;if_keys;runtime_args;jobs;init;src;}=letjobs=Impl.simplify~full~contextjobsinletdevice_graph=Impl.eval~contextjobsinletpackages=Key.(pureList.append$packages$Engine.packagesjobs)inletall_keys=Engine.keysjobsinletall_runtime_args=Engine.runtime_argsjobsinletruntime_args=Runtime_arg.Set.(elements(unionruntime_argsall_runtime_args))inletkeys=Key.Set.(elements(unionif_keysall_keys))inletmkpackages_context=letinfo=Info.v~config_file~packages~keys~runtime_args~context~configure_cmd~pre_build_cmd~lock_location~build_cmd~src~project_namenin{init;jobs;info;device_graph}inKey.(puremk$packages$of_deps(Set.of_listkeys))letif_keyst=t.if_keysletpp_dot=Impl.pp_dotendmoduletypeS=sigvalprelude:Info.t->stringvalpackages:Package.tlistvalname:stringvalversion:stringvalcreate:jobimpllist->jobimplvalname_of_target:Info.t->stringvaldune_project:Dune.stanzalistvaldune_workspace:(?build_dir:Fpath.t->info->Dune.t)optionvalcontext_name:Info.t->stringendmoduleMake(P:S)=structmoduleFilegen=Filegen.Make(P)letdefault_init=[Job.runtime_argsArgv.sys_argv]letbuild_dirargs=Fpath.parentargs.Cli.config_fileletconfig_fileargs=args.Cli.config_fileletmirage_dirargs=Fpath.(build_dirargs/P.name)letartifacts_dirargs=Fpath.(build_dirargs/"dist")letexit_errargs=function|Okv->v|Error(`Msgm)->flush_all();ifm<>""thenFmt.epr"%a\n%!"Fmt.(styled(`Fg`Red)string)m;ifnotargs.Cli.dry_runthenexit1elseFmt.epr"(exit 1)"letget_cmds_=letcommand_line_arguments=Sys.argv|>Array.to_list|>List.tl|>List.filter(funarg->arg<>"configure"&&arg<>"query"&&arg<>"opam")|>String.concat~sep:" "inletopts=ifcommand_line_arguments=""thenNoneelseSomecommand_line_argumentsin(Fmt.str{|%s configure%a --no-extra-repo|}P.nameFmt.(option~none:(any"")(any" "++string))opts,(funsub->Fmt.str{|make %a"lock" "depext-lockfile" "pull"|}Fmt.(option~none:(any"")(any"\"-C"++Fpath.pp++any"\" "))sub),(funsubunikernel->Fmt.str{|%amirage/%s.opam.locked|}Fmt.(option~none:(any"")Fpath.pp)subunikernel),funsub->Fmt.str{|make %a"build"|}Fmt.(option~none:(any"")(any"\"-C"++Fpath.pp++any"\" "))sub)(* STAGE 2 *)letsrc=Logs.Src.create(P.name^"-configure")~doc:"functoria generated"moduleLog=(valLogs.src_logsrc:Logs.LOG)leteval_cached~full~output~cachecontextt=letinfo=Config.eval~fullcontexttinletkeys=Key.depsinfoinletoutput=match(output,Context_cache.peek_outputcache)with|Some_,_->output|_,cache->cacheinletcontext=Key.contextkeysinletcontext=Context_cache.mergecachecontextinletfcontext=letconfig=Key.evalcontextinfocontextinmatchoutputwith|None->config|Someo->{configwithinfo=Info.with_outputconfig.infoo}inCmdliner.Term.(constf$context)(* FIXME: describe init *)letdescribe(t:_Cli.describe_args)=let{Config.jobs;_}=t.args.Cli.contextinletffmt=Fmt.pffmt"%a\n%!"(ift.dotthenConfig.pp_dotelseFmt.nop)jobsinletwith_fmtf=matcht.args.outputwith|Nonewhent.dot->fFormat.str_formatter;letdata=Format.flush_str_formatter()inlet*tmp=Action.tmp_file~mode:0o644"graph%s.dot"inlet*()=Action.write_filetmpdatainAction.run_cmdBos.Cmd.(vt.dotcmd%ptmp)|None->Action.ok(fFmt.stdout)|Some"-"->Action.ok(fFmt.stdout)|Somes->Action.with_output~path:(Fpath.vs)~purpose:"dot file"finwith_fmtfletconfigure_mainiinitjobs=letmain=Info.mainiinletpurpose=Fmt.str"configure: create %a"Fpath.ppmaininLog.info(funm->m"Generating: %a (main file)"Fpath.ppmain);let*()=Action.with_output~path:main~append:false~purpose(funppf->Fmt.pfppf"%a@.@."Fmt.text(P.preludei))inlet*()=Engine.configureijobsinEngine.connecti~initjobsletfilesijobs=letmain=Info.mainiinletfiles=Engine.filesijobsinletfiles=Fpath.Set.addmainfilesinFpath.Set.(elementsfiles)letopam_contents~opam_name~extra_repoargs=let{Config.info;jobs;_}=args.Cli.contextinletinstall=Key.eval(Info.contextinfo)(Engine.installinfojobs)inletname=Misc.Name.Opam.to_stringopam_nameinletopam=Info.opam~install~extra_repo~opam_name:nameinfoinFmt.str"%a"Opam.ppopamletgenerate_opam~opam_name~extra_repoargs=letcontents=opam_contents~opam_name~extra_repoargsinletname=Misc.Name.Opam.to_stringopam_nameinletfile=Fpath.(v(name^".opam"))inLog.info(funm->m"Generating: %a (%a)"Fpath.ppfileCli.pp_query_kind`Opam);Filegen.writefilecontentsletcopy_filesfiles=List.map(funf->matchFpath.split_extfwith|_,(".ml"|".mli")->Dune.stanzaf"(copy_files# %a)"Fpath.ppf|_->Dune.stanzaf"(copy_files %a)"Fpath.ppf)filesletdune_contentsaliasargs=let{Config.info;jobs;_}=args.Cli.contextinletname=P.name_of_targetinfoinletbuild_dir=build_dirargsinmatchaliaswith|`Build->letfiles=filesinfojobsinletfiles=List.map(funp->Fpath.(v"."/P.name//p))filesinletdune=Dune.v(copy_filesfiles@Engine.duneinfojobs)inFmt.str"%a\n"Dune.ppdune|`Project->letdune=Dune.v(Dune.base_project@(Dune.stanzaf"(name %s)"name::P.dune_project))inFmt.str"%a\n"Dune.ppdune|`Workspace->letdune=matchP.dune_workspacewith|None->Dune.base_workspace|Somef->f~build_dirinfoinFmt.str"%a\n"Dune.ppdune|`Dist->letinstall=Key.eval(Info.contextinfo)(Engine.installinfojobs)inFmt.str"%a\n"Dune.pp(Install.dune~context_name_for_bin:(P.context_nameinfo)~context_name_for_etc:"default"install)|`Config->letcwd=Bos.OS.Dir.current()|>Result.get_okinletconfig_ml_file=Fpath.(cwd//args.Cli.config_file)inletdune=Dune.base~config_ml_file~packages:P.packagesinFmt.str"%a\n"Dune.ppduneletgenerate_dunealiasargs=letcontents=dune_contentsaliasargsinletfile=matchaliaswith|`Dist->Fpath.(v"dune")|`Build->Fpath.(v"dune.build")|`Config->Fpath.(v"dune.config")|`Workspace->Fpath.(v"dune-workspace")|`Project->Fpath.(v"dune-project")inLog.info(funm->m"Generating: %a (%a)"Fpath.ppfileCli.pp_query_kind(`Dunealias:>Cli.query_kind));Filegen.writefilecontentsletmakefile_contents~build_dir~depext~extra_repoopam_name=Fmt.to_to_stringMakefile.pp(Makefile.v~build_dir~depext~builder_name:P.name~extra_repoopam_name)letgenerate_makefile~build_dir~depext~extra_repoopam_name=letcontents=makefile_contents~build_dir~depext~extra_repoopam_nameinletfile=Fpath.(v"Makefile")inFilegen.writefilecontentsletquery({args;kind;depext;extra_repo}:_Cli.query_args)=let{Config.jobs;info;_}=args.Cli.contextinletname=P.name_of_targetinfoinletbuild_dir=Fpath.parentargs.config_fileinmatchkindwith|`Name->Fmt.pr"%s\n%!"(Info.nameinfo)|`Packages->letpkgs=Info.packagesinfoinList.iter(Fmt.pr"%a\n%!"(Package.pp~surround:"\""))pkgs|`Opam->letopam_name=Misc.Name.opamifynameinletcontents=opam_contents~opam_name~extra_repoargsinFmt.pr"%s\n%!"contents|`Files->letfiles=filesinfojobsinFmt.pr"%a\n%!"Fmt.(list~sep:(any" ")Fpath.pp)files|`Makefile->letopam_name=Misc.Name.opamifynameinletcontents=makefile_contents~build_dir~depext~extra_repoopam_nameinFmt.pr"%s\n%!"contents|`Dunealias->Fmt.pr"%s%!"(dune_contentsaliasargs)(* Configuration step. *)letclean(args:_Cli.clean_args)=let*()=Action.rmdir(mirage_dirargs)inAction.rmdir(artifacts_dirargs)letconfigure({args;depext;extra_repo;_}:_Cli.configure_args)=let{Config.init;info;device_graph;_}=args.Cli.contextin(* Get application name *)letbuild_dir=build_dirargsinletname=P.name_of_targetinfoinletopam_name=Misc.Name.opamifynameinlet*()=generate_makefile~build_dir~depext~extra_repoopam_nameinlet*_=Action.mkdir(mirage_dirargs)inlet*()=Action.with_dir(mirage_dirargs)(fun()->(* OPAM file *)let*()=generate_opam~opam_name~extra_repoargsin(* Generate application specific-files *)Log.info(funm->m"in dir %a"(Cli.pp_args(fun__->()))args);configure_maininfoinitdevice_graph)inlet*()=Action.with_dirbuild_dir(fun()->let*()=generate_dune`BuildargsinFilegen.writeFpath.(v"dune")"(include dune.build)\n")in(* dune-workspace: defines compilation contexts *)let*()=generate_dune`Workspaceargsin(* dune-project *)let*()=generate_dune`Projectargsin(* Get install spec *)let*_=Action.mkdir(artifacts_dirargs)inAction.with_dir(artifacts_dirargs)(fun()->generate_dune`Distargs)letok()=Action.ok()letexit()=Action.error""letwith_outputargs=matchargs.Cli.outputwith|None->args|Someo->letr=args.Cli.contextinletinfo=Info.with_outputr.Config.infooin{argswithcontext={rwithinfo}}letpp_info(f:('a,Format.formatter,unit)format->'a)levelargs=letverbose=Logs.level()>=levelinf"@[<v>%a@]"(Info.ppverbose)args.Cli.context.Config.infolethandle_parse_args_result=function|`Error_->exit()|`Version|`Help->ok()|`Okaction->(matchactionwith|Cli.Help_->ok()|Cli.Configuret->lett={twithargs=with_outputt.args}inLog.info(funm->pp_infom(SomeLogs.Debug)t.args);configuret|Cli.Queryt->lett={twithargs=with_outputt.args}inLog.info(funm->pp_infom(SomeLogs.Debug)t.args);queryt;ok()|Cli.Describet->lett={twithargs=with_outputt.args}inpp_infoFmt.(pfstdout)(SomeLogs.Info)t.args;describet|Cli.Cleant->lett=with_outputtinLog.info(funm->pp_infom(SomeLogs.Debug)t);cleant)letaction_runargsa=ifnotargs.Cli.dry_runthenAction.runaelseletexeccmd=matchBos.Cmd.to_listcmdwith|["opam";"config";"var";"prefix"]->Some("$prefix","")|_->Action.default_execcmdinletenv=Action.env~files:(`Passtrough(Fpath.v"."))~exec()inletdom=Action.dry_run~envainList.iter(funline->Fmt.epr"%a %s\n%!"Fmt.(styled(`Fg`Cyan)string)"*"line)dom.logs;dom.resultletread_contextargs=matchargs.Cli.context_filewith|None->Action.okContext_cache.empty|Somefile->let*is_file=Action.is_filefileinifis_filethenContext_cache.readfileelseAction.errorf"cannot find file `%a'"Fpath.ppfileletrun_with_argvargvargsconfig=(* whether to fully evaluate the graph *)letfull_eval=Cli.peek_full_evalargvinlet*cache=read_contextargsinletbase_context=(* Consider only the non-required keys. *)letnon_required_term=letif_keys=Config.if_keysconfiginKey.contextif_keysinletcontext=matchCmdliner.Cmd.eval_peek_opts~argvnon_required_termwith|_,Ok(`Okcontext)->context|_->Context.emptyinmatchContext_cache.peekcachenon_required_termwith|None->context|Somedefault->Context.merge~defaultcontextinletoutput=Cli.peek_outputargvin(* 3. Parse the command-line and handle the result. *)letconfigure=eval_cached~full:true~output~cachebase_contextconfiginletdescribe=letfull=matchfull_evalwith|None->not(Context_cache.is_emptycache)|Someb->bineval_cached~full~output~cachebase_contextconfiginletclean=eval_cached~full:true~output~cachebase_contextconfiginletquery=cleaninlethelp=cleaninhandle_parse_args_result(Cli.eval~name:P.name~version:P.version~configure~query~describe~clean~help~mname:P.nameargv)letregister?(init=default_init)?(src=`Auto)namejobs=(* 1. Pre-parse the arguments set the log level, config file
and root directory. *)letargv=Sys.argvin(* TODO: do not are parse the command-line twice *)letargs=(* tool.ml made sure that global arguments are correctly parsed before
running config.exe*)Cli.peek_args~with_setup:true~mname:P.nameargv|>Option.getinletconfig_file=config_fileargsinletrun()=letconfigure_cmd,pre_build_cmd,lock_location,build_cmd=get_cmdsargsinletmain_dev=P.create(init@jobs)inletc=Config.v~config_file~init~configure_cmd~pre_build_cmd~lock_location~build_cmd~src~project_name:P.namenamemain_devinrun_with_argvargvargscinrun()|>action_runargs|>exit_errargsend