123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245(*
* 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.
*)openAstringopenAction.Syntaxtypet=Device.Graph.tletif_keysx=Impl.collect(moduleKey.Set)(functionIfcond->Key.depscond|App|Dev_->Key.Set.empty)xmoduleKeys=structtypet=Key.Set.tletunionab=Key.Set.unionabletempty=Key.Set.emptyendletkeysx=Impl.collect(moduleKeys)(function|Devc->Key.Set.of_list(Device.keysc)|Ifcond->Key.depscond|App->Keys.empty)xmoduleRuntime_args=structtypet=Runtime_arg.Set.tletunionab=Runtime_arg.Set.unionabletempty=Runtime_arg.Set.emptyendletruntime_argsx=Impl.collect(moduleRuntime_args)(function|Devc->Runtime_arg.Set.of_list(Device.runtime_argsc)|If_->Runtime_args.empty|App->Runtime_args.empty)xmodulePackages=structtypet=Package.Set.tKey.valueletunionxy=Key.(purePackage.Set.union$x$y)letempty=Key.purePackage.Set.emptyendletpackagest=letopenImplinletaux=function|Devc->letpkgs=Device.packagescinletruntime_args=Device.runtime_argscinletextra_pkgs=List.fold_left(funacck->letpkgs=Runtime_arg.packageskinPackage.Set.(unionacc(of_listpkgs)))Package.Set.emptyruntime_argsinletauxx=Package.Set.(union(of_listx)extra_pkgs)inKey.(pureaux$pkgs)|If_|App->Packages.emptyinletreturnx=Package.Set.to_listxinKey.(purereturn$Impl.collect(modulePackages)auxt)moduleInstalls=structtypet=Install.tKey.valueletunionxy=Key.(pureInstall.union$x$y)letempty=Key.pureInstall.emptyendletinstallix=Impl.collect(moduleInstalls)(functionDevc->Device.installci|If_|App->Installs.empty)xletfilesinfot=Impl.collect(moduleFpath.Set)(functionDevc->Device.filescinfo|If_|App->Fpath.Set.empty)tmoduleDune=structtypet=Dune.stanzalistletunion=(@)letempty=[]endletduneinfo=Impl.collect(moduleDune)@@function|Devc->Device.dunecinfo|If_|App->Dune.empty(* [module_expresion tbl c args] returns the module expression of
the functor [c] applies to [args]. *)letmodule_expressionfmt(c,args)=Fmt.pffmt"%s%a"(Device.module_namec)Fmt.(list~sep:(any"")(any"("++of_to_stringDevice.Graph.impl_name++any")"))argsletfind_all_devicesinfogi=letctx=Info.contextinfoinletid=Impl.with_left_most_devicectxi{f=Device.id}inletfxl=let(Device.Graph.D{dev;_})=xinifDevice.iddev=idthenx::lelselinDevice.Graph.foldfg[]letiter_actionsft=letfvres=let*()=resinfvinDevice.Graph.foldft(Action.ok())letlines_of_strstr=String.fold_left(funn->function'\n'->n+1|_->n)0strtypemain={dir:Fpath.t;path:Fpath.t;mutablelines:int}letmaininfo=letpath=Info.maininfoinletdir=Fpath.(Info.(parent(config_fileinfo)/project_nameinfo))inlet+str=Action.read_filepathinletlines=lines_of_strstrin{dir;path;lines}letappend_mainmainmsgfmt=letpurpose=Fmt.str"Append to main.ml (%s)"msginFmt.kstr(funstr->main.lines<-main.lines+lines_of_strstr+1;Action.with_output~path:main.path~append:true~purpose(funppf->Fmt.pfppf"%s@."str))fmtletpp_posppf=function|None->()|Some(file,line,_,_)->Fmt.pfppf"# %d %S@."linefileletreset_pos{dir;path;lines}=(* lines are 1-based and the line directive is refering to
"next line will be Y", so if we put a directive in the first line of a
file, it needs to say "# 2 myfile.ml" since the next line will be the
second one. This is the reason for the 2 below. *)letfile=Fpath.(dir//path)|>Fpath.normalize|>Fpath.to_stringinSome(file,lines+2,0,0)letconfigureinfot=letf(v:t)=let*main=maininfoinlet(D{dev;args;_})=vinlet*()=Device.configuredevinfoinifargs=[]thenAction.ok()elselet*()=append_mainmain"reset""%a"pp_pos(reset_posmain)inappend_mainmain"configure""module %s = %a\n"(Device.Graph.impl_namev)module_expression(dev,args)initer_actionsftletmeta_initfmt(connect_name,result_name)=Fmt.pffmt" let _%s = Lazy.force %s in@ "result_nameconnect_nameletemit_connectfmt(iname,names,runtime_args,connect_code)=(* We avoid potential collision between double application
by prefixing with "_". This also avoid warnings. *)letrnames=List.map(funx->"_"^x)namesinletknames=List.map(funk->"_"^Runtime_arg.var_namek)runtime_argsinletbindppfname=Fmt.pfppf" _%s >>= fun %s ->\n"namenameinletbind_keyppfk=Fmt.pfppf" let _%s = %a in\n"(Runtime_arg.var_namek)Runtime_arg.callkinlet{Device.pos;code}=connect_code(rnames@knames)inFmt.pffmt"let %s = lazy (\n%a%a%a%a %s@\n);;"inameFmt.(list~sep:nopmeta_init)(List.combinenamesrnames)Fmt.(list~sep:nopbind)rnamesFmt.(list~sep:nopbind_key)runtime_argspp_posposcodeletemit_runmaininitmain_name=(* "exit 1" is ok in this code, since cmdliner will print help. *)letforceppfname=Fmt.pfppf"Lazy.force %s >>= fun _ ->\n "nameinappend_mainmain"emit_run""let () =\n let t = %aLazy.force %s in\n run t\n;;"Fmt.(list~sep:nopforce)initmain_nameletconnect?(init=[])infot=let*main=maininfoinletf(v:t)=let(D{dev;args;deps;_})=vinletvar_name=Device.Graph.var_namevinletimpl_name=Device.Graph.impl_namevinletarg_names=List.mapDevice.Graph.var_name(args@deps)inletruntime_args=Device.runtime_argsdevinlet*()=append_mainmain"connect""%a"emit_connect(var_name,arg_names,runtime_args,Device.connectdevinfoimpl_name)inappend_mainmain"reset""%a"pp_pos(reset_posmain)inlet*()=iter_actionsftinletmain_name=Device.Graph.var_nametinletinit_names=List.fold_left(funacci->matchfind_all_devicesinfotiwith|[]->assertfalse|ds->List.mapDevice.Graph.var_nameds@acc)[]init|>List.revinemit_runmaininit_namesmain_name