12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238(*---------------------------------------------------------------------------
Copyright (c) 2019 The b0 programmers. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
---------------------------------------------------------------------------*)openB0_stdopenB0_std.Fut.SyntaxopenB00letadd_ifcvl=ifcthenv::lelselmoduleTool=struct(* Compilers *)letcomp_env_vars=["CAMLLIB";"CAMLSIGPIPE";"CAML_DEBUG_FILE";"CAML_DEBUG_SOCKET";"CAML_LD_LIBRARY_PATH";"BUILD_PATH_PREFIX_MAP";"OCAMLDEBUG";"OCAMLLIB";"OCAMLPROF_DUMP";"OCAMLRUNPARAM";"OCAML_COLOR";"OCAML_FLEXLINK";"OCAML_INSTR_FILE";"OCAML_INSTR_START";"OCAML_INSTR_STOP";"OCAML_SPACETIME_INTERVAL";"OCAML_SPACETIME_SNAPSHOT_DIR";"PATH";"TERM";"__AFL_SHM_ID";(* XXX For cc for now we add them in bulk but we could make
them depend on the conffiguration. *)"LD_LIBRARY_PATH";"LIBRARY_PATH";"C_INCLUDE_PATH";(* XXX These are Windows specific and needed by cl.exe *)"SystemRoot";"INCLUDE";"LIB";]letocamlc=Tool.by_name~vars:comp_env_vars"ocamlc"letocamlopt=Tool.by_name~vars:comp_env_vars"ocamlopt"letocamldep=Tool.by_name~vars:comp_env_vars"ocamldep"letocamlmklib=Tool.by_name~vars:("OCAML_FLEXLINK"::comp_env_vars)"ocamlmklib"letocamlobjinfo=Tool.by_name~vars:comp_env_vars"ocamlobjinfo"(* Toplevels *)lettop_env_vars=["CAML_LD_LIBRARY_PATH";"CAMLRUNPARAM";"OCAMLTOP_INCLUDE_PATH";"HOME";"OCAMLLIB";"OCAMLRUN_PARAM";"OCAMLTOP_UTF_8";"PATH";"TERM";]letocaml=Tool.by_name~vars:top_env_vars"ocaml"letocamlnat=Tool.by_name~vars:top_env_vars"ocamlnat"endmoduleConf=structtypecode=[`Byte|`Native]typet={fields:stringString.Map.t;version:int*int*int*stringoption;where:Fpath.t;asm_ext:string;dll_ext:string;exe_ext:string;lib_ext:string;obj_ext:string;has_dynlink:bool;}letfindkc=String.Map.find_optkc.fieldsletversionc=c.versionletwherec=c.whereletasm_extc=c.asm_extletexe_extc=c.exe_extletdll_extc=c.dll_extletlib_extc=c.lib_extletobj_extc=c.obj_extlethas_dynlinkc=c.has_dynlinkletto_string_mapc=c.fieldsletof_string_mapfields=tryleterr=Fmt.failwithinleterr_keyk=err"key %a not found."Fmt.(codestring)kinletfindkfs=matchString.Map.find_optkfswith|None->err_keyk|Somev->vinletversion=letv=find"version"fieldsinmatchString.to_versionvwith|None->err"could not parse version string %S"v|Somev->vinletwhere=Fpath.of_string(find"standard_library"fields)inletwhere=where|>Result.to_failureinletasm_ext=find"ext_asm"fieldsinletdll_ext=find"ext_dll"fieldsinletexe_ext=find"ext_exe"fieldsinletlib_ext=find"ext_lib"fieldsinletobj_ext=find"ext_obj"fieldsinlethas_dynlink=letk="supports_shared_libraries"inlets=findkfieldsinmatchbool_of_string_optswith|None->err"key %a cound not parse bool from %S"Fmt.(codestring)ks|Someb->binOk{fields;version;where;asm_ext;dll_ext;exe_ext;lib_ext;obj_ext;has_dynlink;}with|Failuree->Errore(* IO *)letof_string?files=letparse_lineilacc=matchString.cut_left~sep:":"lwith|None->acc|Some(k,v)->String.Map.add(String.trimk)(String.trimv)accinlets=String.trimsinResult.bind(B00_lines.fold?filesparse_lineString.Map.empty)@@funfields->matchof_string_mapfieldswith|Okv->Okv|Errore->B00_lines.err_file?file(Fmt.str"OCaml config: %s"e)letwritem~comp~o=letcomp=Memo.toolmcompinMemo.spawnm~writes:[o]~stdout:(`Fileo)@@comp(Cmd.atom"-config")letreadmfile=let*s=Memo.readmfileinFut.return(of_string~files|>Memo.fail_if_errorm)endmoduleMod=structmoduleName=structtypet=stringletof_filenamef=String.Ascii.capitalize(Fpath.basename~no_ext:truef)letvn=String.Ascii.capitalizenletequal=String.equalletcompare=String.compareletpp=Fmt.tty_string[`Bold]moduleSet=String.SetmoduleMap=String.Map(* Filename mangling *)letof_mangled_filenames=letrem_ocaml_exts=matchString.cut_right~sep:"."swith|None->s|Some(s,("ml"|".mli"))->s|Some_->sinletmangles=letchar_len=function|'-'|'.'|'a'..'z'|'A'..'Z'|'0'..'9'|'_'|'\''->1|_->2inletset_charbic=matchcwith|'.'|'-'->Bytes.setbi'_';i+1|'a'..'z'|'A'..'Z'|'0'..'9'|'_'|'\''asc->Bytes.setbic;i+1|c->letc=Char.codecinBytes.setb(i)(Char.Ascii.upper_hex_digit(clsr4));Bytes.setb(i+1)(Char.Ascii.upper_hex_digit(c));i+2inString.byte_replacerchar_lenset_charsinlets=mangle(rem_ocaml_exts)inlets=matchString.headswith|SomecwhenChar.Ascii.is_letterc->s|None|Some_->"M"^sinString.Ascii.capitalizesendmoduleRef=structtypet=Name.t*Digest.tletvnd=(String.Ascii.capitalizen,d)letname=fstletdigest=sndletequal(_,d0)(_,d1)=Digest.equald0d1letcompare(n0,d0)(n1,d1)=matchName.comparen0n1with|0->Digest.compared0d1|c->cletppppf(n,d)=Fmt.pfppf"@[%s %a@]"(Digest.to_hexd)Name.ppnmoduleT=structtypenonrect=tletcompare=compareendmoduleSet=structincludeSet.Make(T)letdumpppfrs=Fmt.pfppf"@[<1>{%a}@]"(Fmt.iter~sep:Fmt.commaiterpp)rsletpp?seppp_elt=Fmt.iter?sepiterpp_eltendmoduleMap=structincludeMap.Make(T)letdomm=fold(funk_acc->Set.addkacc)mSet.emptyletof_listbs=List.fold_left(funm(k,v)->addkvm)emptybsletadd_to_listkvm=matchfindkmwith|exceptionNot_found->addk[v]m|l->addk(v::l)mletadd_to_set(typeset)(typeelt)(moduleS:B0_std.Stdlib_set.Swithtypeelt=eltandtypet=set)kvm=matchfindkmwith|exceptionNot_found->addk(S.singletonv)m|set->addk(S.addvset)mletdumppp_vppfm=letpp_bindingppf(k,v)=Fmt.pfppf"@[<1>(@[%a@],@ @[%a@])@]"ppkpp_vvinFmt.pfppf"@[<1>{%a}@]"(Fmt.iter_bindings~sep:Fmt.spiterpp_binding)mletpp?seppp_binding=Fmt.iter_bindings?sepiterpp_bindingendendmoduleSrc=structmoduleDeps=structletof_string?(file=Fpath.dash)?src_rootdata=(* Parse ocamldep's [-slash -modules], a bit annoying to parse.
ocamldep shows its Makefile legacy. *)letparse_pathnp=(* ocamldep escapes spaces as "\ ",
a bit annoying *)letchar_len_atsi=matchs.[i]with|'\\'wheni+1<String.lengths&&s.[i+1]=' '->2|_->1inletset_charbksi=matchchar_len_atsiwith|2->Bytes.setbk' ';2|1->Bytes.setbks.[i];1|_->assertfalseinmatchString.byte_unescaperchar_len_atset_charpwith|Errorj->B00_lines.errn"%d: illegal escape"j|Okp->matchFpath.of_stringpwith|Errore->B00_lines.errn"%s"e|Okp->pinletparse_line~src_rootnlineacc=ifline=""thenaccelsematchString.cut_right(* right, windows drives *)~sep:":"linewith|None->B00_lines.errn"cannot parse line: %S"line|Some(file,mods)->letfile=parse_pathnfileinletfile=matchsrc_rootwith|None->file|Somesrc_root->Fpath.(src_root//file)inletadd_modaccm=Name.Set.addmaccinletmods=String.cuts_left~drop_empty:true~sep:" "modsinletstart=Name.Set.singleton"Stdlib"inletmods=List.fold_leftadd_modstartmodsinFpath.Map.addfilemodsaccinB00_lines.fold~filedata(parse_line~src_root)Fpath.Map.emptyletwrite?src_rootm~srcs~o=letocamldep=Memo.toolmTool.ocamldepinletsrcs',cwd=matchsrc_rootwith|None->srcs,None|Someroot->(* XXX unfortunately this doesn't report parse error
at the right place. So we don't do anything for now
the output thus depends on the path location and can't
be cached across machines.
let rem_prefix src = Fpath.rem_prefix root src |> Option.get in
List.map rem_prefix srcs, Some root
*)srcs,NoneinMemo.spawnm?cwd~reads:srcs~writes:[o]~stdout:(`Fileo)@@ocamldepCmd.(atom"-slash"%"-modules"%%pathssrcs')letread?src_rootmfile=let*s=Memo.readmfileinFut.return(of_string?src_root~files|>Memo.fail_if_errorm)endtypet={mod_name:Name.t;opaque:bool;mli:Fpath.toption;mli_deps:Name.Set.t;ml:Fpath.toption;ml_deps:Name.Set.t;build_dir:Fpath.t;build_base:Fpath.t}letv~mod_name~opaque~mli~mli_deps~ml~ml_deps~build_dir=letbuild_base=Fpath.(build_dir/String.Ascii.uncapitalizemod_name)in{mod_name;opaque;mli;mli_deps;ml;ml_deps;build_dir;build_base}letmod_namem=m.mod_nameletopaquem=m.opaqueletmlim=m.mliletmli_depsm=m.mli_depsletmlm=m.mlletml_depsm=m.ml_depsletbuild_dirm=m.build_dirletbuilt_filem~ext=Fpath.(m.build_base+ext)letcmi_filem=built_filem~ext:".cmi"letcmo_filem=matchmlmwith|None->None|Some_->Some(built_filem~ext:".cmo")letcmx_filem=matchmlmwith|None->None|Some_->Some(built_filem~ext:".cmx")letpp=letpath_option=Fmt.option~none:Fmt.noneFpath.pp_unquotedinletdeps=Name.Set.pp~sep:Fmt.spFmt.stringinFmt.recordFmt.[field"mod-name"mod_nameName.pp;field"opaque"opaquebool;field"mli"mlipath_option;field"mli-deps"mli_depsdeps;field"ml"mlpath_option;field"ml-deps"ml_depsdeps;field"build-dir"build_dirFpath.pp_unquoted]letimpl_file~codem=letfile=matchcodewith`Byte->cmo_file|`Native->cmx_fileinfilemletas_intf_dep_files?(init=[])m=cmi_filem::initletas_impl_dep_files?(init=[])~codem=matchcodewith|`Byte->cmi_filem::init|`Native->matchmlmwith|None->cmi_filem::init|Some_whenm.opaque->cmi_filem::init|Some_->cmi_filem::Option.get(cmx_filem)::initletmod_name_mapm~kindfiles=letaddaccf=letmname=Name.of_filenamefinmatchName.Map.find_optmnameaccwith|None->Name.Map.addmnamefacc|Somef'->Memo.notifym`Warn"@[<v>%a:@,File ignored. %a's module %s defined by file:@,%a:@]"Fpath.ppfName.ppmnamekindFpath.ppf';accinList.fold_leftaddName.Map.emptyfilesletmap_of_srcsm~build_dir~srcs~src_deps=letget_src_deps=function|None->Name.Set.empty|Somefile->matchFpath.Map.findfilesrc_depswith|exceptionNot_found->Name.Set.empty|deps->depsinletmlis,mls=List.partition(Fpath.has_ext".mli")srcsinletmlis=mod_name_mapm~kind:"interface"mlisinletmls=mod_name_mapm~kind:"implementation"mlsinletmod'mod_namemliml=letmli_deps=get_src_depsmliinletml_deps=get_src_depsmlinSome(v~mod_name~opaque:false~mli~mli_deps~ml~ml_deps~build_dir)inName.Map.mergemod'mlismlsletsort?stable~depsname_map=(* FIXME do something better, on cycles this lead to link failure
we should detect it. *)letrecloopseenacc=function|[]->seen,acc|src::srcs->ifName.Set.memsrc.mod_nameseenthenloopseenaccsrcselseletseen=Name.Set.addsrc.mod_nameseeninletadd_src_depnacc=matchName.Set.memnseenwith|true->acc|false->matchName.Map.find_optnname_mapwith|None->acc|Somesrc->src::accinletdeps=Name.Set.foldadd_src_dep(depssrc)[]inletseen,acc=loopseenaccdepsinloopseen(src::acc)srcsinletadd_src_srcacc=src::accinletstable=Option.value~default:[]stableinlettodo=stable@Name.Map.foldadd_srcname_map[]inlet_,acc=loopName.Set.empty[]todoinList.revaccletfindnsmap=letrecloopresremaindeps=matchName.Set.choosedepswith|exceptionNot_found->res,remain|dep->letdeps=Name.Set.removedepdepsinmatchName.Map.finddepmapwith|m->loop(m::res)remaindeps|exceptionNot_found->loopres(Name.Set.adddepremain)depsinloop[]Name.Set.emptynsletmap_of_files?(only_mlis=false)m~build_dir~src_root~srcs=letexts=B00_fexts.v(".mli"::ifonly_mlisthen[]else[".ml"])inletsrcs=B00_fexts.find_filesextssrcsinleto=Fpath.(build_dir/"ocaml-srcs.deps")inDeps.writem~src_root~srcs~o;let*src_deps=Deps.readm~src_rootoinFut.return(map_of_srcsm~build_dir~srcs~src_deps)endendmoduleCobj=structletarchive_ext_of_code=function`Byte->".cma"|`Native->".cmxa"letobject_ext_of_code=function`Byte->".cmo"|`Native->".cmx"typet={file:Fpath.t;defs:Mod.Ref.Set.t;deps:Mod.Ref.Set.t;link_deps:Mod.Ref.Set.t;(* deps whose name appear in required
globals/implementations imported *)}letfilec=c.fileletdefsc=c.defsletdepsc=c.depsletlink_depsc=c.link_depsletequalc0c1=Fpath.equalc0.filec1.fileletcomparec0c1=Fpath.comparec0.filec1.fileletpp=Fmt.record@@[Fmt.field"file"fileFpath.pp_quoted;Fmt.field"defs"defsMod.Ref.Set.dump;Fmt.field"deps"depsMod.Ref.Set.dump;Fmt.field"link-deps"link_depsMod.Ref.Set.dump;]moduleT=structtypenonrect=tletcompare=compareendmoduleSet=Set.Make(T)moduleMap=Map.Make(T)letsort?(deps=link_deps)cobjs=letrecloopcobjs_defsseenext_depscobjs=function|(c::csasl)::todo->beginmatchMod.Ref.Set.subset(defsc)seenwith|true->loopcobjs_defsseenext_depscobjs(cs::todo)|false->letseen=Mod.Ref.Set.union(defsc)seeninletadd_depd(local_deps,ext_depsasacc)=ifMod.Ref.Set.memdseenthenaccelsematchMod.Ref.Map.finddcobjs_defswith|exceptionNot_found->local_deps,Mod.Ref.Set.adddext_deps|dep_cobj->dep_cobj::local_deps,ext_depsinletstart=[],ext_depsinletlocal_deps,ext_deps=Mod.Ref.Set.foldadd_dep(depsc)startinmatchlocal_depswith|[]->loopcobjs_defsseenext_deps(c::cobjs)(cs::todo)|deps->loopcobjs_defsseenext_depscobjs(deps::l::todo)end|[]::(c::cs)::todo->loopcobjs_defsseenext_deps(c::cobjs)(cs::todo)|[]::([]::todo)->loopcobjs_defsseenext_depscobjstodo|[]::[]->letsorted=List.revcobjsinsorted,ext_deps|[]->assertfalseinletadd_defcdacc=Mod.Ref.Map.adddcaccinletadd_defsaccc=Mod.Ref.Set.fold(add_defc)(defsc)accinletcobjs_defs=List.fold_leftadd_defsMod.Ref.Map.emptycobjsinloopcobjs_defsMod.Ref.Set.emptyMod.Ref.Set.empty[](cobjs::[])(* ocamlobjinfo output parsing, could be easier... *)letmake_cobjfiledefsdepsldeps=letdeps=Mod.Ref.Set.diffdepsdefsinletlink_deps=letkeepm=String.Set.mem(Mod.Ref.namem)ldepsinMod.Ref.Set.filterkeepdepsin{file;defs;deps;link_deps;}letfile_prefix="File "letparse_file_pathnline=letlen=String.lengthfile_prefixinmatchFpath.of_string(String.drop_leftlenline)with|Okfile->file|Errore->B00_lines.errn"%s"eletrecparse_ldepsaccfiledefsdepsldepsnamen=function|[]->make_cobjfiledefsdepsldeps::acc|(l::ls)asdata->matchString.cut_right~sep:"\t"lwith|None->parse_fileaccfiledefsdepsldepsndata|Some(_,ldep)->letldeps=String.Set.add(String.trimldep)ldepsinparse_ldepsaccfiledefsdepsldepsname(n+1)lsandparse_depsaccfiledefsdepsldepsnamen=function|[]->make_cobjfiledefsdepsldeps::acc|(l::ls)asdata->matchString.cut_right~sep:"\t"lwith|None->beginmatchlwith|lwhenString.starts_with~prefix:"Implementations imported:"l||String.starts_with~prefix:"Required globals:"l->parse_ldepsaccfiledefsdepsldepsname(n+1)ls|_->parse_fileaccfiledefsdepsldepsndataend|Some(dhex,dname)->letdhex=String.trimdhexinletdname=String.trimdnameinmatchDigest.from_hexdhexwith|digest->letmref=Mod.Ref.vdnamedigestinletdefs,deps=matchString.equaldnamenamewith|true->Mod.Ref.Set.addmrefdefs,deps|false->defs,Mod.Ref.Set.addmrefdepsinparse_depsaccfiledefsdepsldepsname(n+1)ls|exceptionInvalid_argument_->(* skip undigested deps *)matchdhex<>""&&dhex.[0]='-'with|true->parse_depsaccfiledefsdepsldepsname(n+1)ls|false->B00_lines.errn"%S: could not parse digest"dhexandparse_unitaccfiledefsdepsldepsnamen=function|[]->B00_lines.errn"unexpected end of input"|line::restwhenString.starts_with~prefix:"Interfaces imported:"line->parse_depsaccfiledefsdepsldepsname(n+1)rest|_::rest->parse_unitaccfiledefsdepsldepsname(n+1)restandparse_fileaccfiledefsdepsldepsn=function|[]->make_cobjfiledefsdepsldeps::acc|l::lswhenString.starts_with~prefix:"Unit name"l||String.starts_with~prefix:"Name"l->beginmatchString.cut_left~sep:":"lwith|None->assertfalse|Some(_,name)->parse_unitaccfiledefsdepsldeps(String.trimname)(n+1)lsend|l::lswhenString.starts_with~prefix:file_prefixl->letacc=make_cobjfiledefsdepsldeps::accinletfile=parse_file_pathnlinparse_fileaccfileMod.Ref.Set.emptyMod.Ref.Set.emptyString.Set.empty(n+1)ls|_::ls->parse_fileaccfiledefsdepsldeps(n+1)lsandparse_filesaccn=function|[]->acc|l::lswhenString.starts_with~prefix:file_prefixl->letfile=parse_file_pathnlinparse_fileaccfileMod.Ref.Set.emptyMod.Ref.Set.emptyString.Set.empty(n+1)ls|l::ls->parse_filesacc(n+1)lsletof_string?filedata=tryOk(parse_files[]1(B00_lines.of_stringdata))with|Failuree->B00_lines.err_file?fileeletwritem~cobjs~o=(* FIXME add [src_root] so that we can properly unstamp. *)letocamlobjinfo=Memo.toolmTool.ocamlobjinfoinMemo.spawnm~reads:cobjs~writes:[o]~stdout:(`Fileo)@@ocamlobjinfoCmd.(atom"-no-approx"%"-no-code"%%pathscobjs)letreadmfile=let*s=B00.Memo.readmfileinFut.return(of_string~files|>Memo.fail_if_errorm)end(* Libraries *)moduleLib=struct(* Library names. *)moduleName=structletfpath_to_name?(sep='.')s=letb=Bytes.of_string(Fpath.to_strings)infori=0toBytes.lengthb-1doifBytes.getbi=Fpath.dir_sep_charthenBytes.setbisep;done;Bytes.unsafe_to_stringbletname_to_fpaths=leterrsexp=Fmt.error"%S: not a library name, %s"sexpinleterr_starts=errs"expected a starting lowercase ASCII letter"inletb=Bytes.of_stringsinletmax=String.lengths-1inletrecloopi~id_start=matchi>maxwith|true->ifid_startthenerr_startselseOk(Fpath.v(Bytes.unsafe_to_stringb))|falsewhenid_start->beginmatchBytes.getbiwith|'a'..'z'->loop(i+1)~id_start:false|_->err_startsend|false->beginmatchBytes.getbiwith|'a'..'z'|'A'..'Z'|'0'..'9'|'_'|'-'->loop(i+1)~id_start:false|'.'->Bytes.setbiFpath.dir_sep_char;loop(i+1)~id_start:true|c->errs(Fmt.str"illegal character %C"c)endinloop0~id_start:truetypet=Fpath.t(* dots are Fpath.dir_sep_char *)letfirstn=letn=Fpath.to_stringninmatchString.cut_right~sep:Fpath.dir_sepnwith|None->n|Some(_,n)->nletlastn=letn=Fpath.to_stringninmatchString.cut_left~sep:Fpath.dir_sepnwith|None->n|Some(n,_)->nletto_archive_namen=fpath_to_name~sep:'_'nletundot~repn=fpath_to_name~sep:repnletof_strings=Result.bind(name_to_fpaths)@@funname->Oknameletto_stringn=fpath_to_namenletto_fpathn=nletvs=of_strings|>Result.to_invalid_argletequal=Fpath.equalletcompare=Fpath.compareletpp=Fmt.usingto_string(Fmt.codeFmt.string)moduleT=structtypenonrect=tletcompare=compareendmoduleSet=Set.Make(T)moduleMap=Map.Make(T)end(* Libraries *)typet={name:Name.t;requires:Name.tlist;dir:Fpath.t;cmis:Fpath.tlist;cmxs:Fpath.tlist;cma:Fpath.toption;cmxa:Fpath.toption;c_archive:Fpath.toption;c_stubs:Fpath.tlist;js_stubs:Fpath.tlist;}letv~name~requires~dir~cmis~cmxs~cma~cmxa~c_archive~c_stubs~js_stubs={name;requires;dir;cmis;cmxs;cma;cmxa;c_archive;c_stubs;js_stubs}letof_dirm~clib_ext~name~requires~dir~archive~js_stubs=Fut.return@@Result.map_error(fune->Fmt.str"library %a: %s"Name.ppnamee)@@Result.bind(Os.Dir.existsdir)@@function|false->Memo.notifym`Warn"library %a: no directory %a"Name.ppnameFpath.ppdir;Ok(v~name~requires~dir~cmis:[]~cmxs:[]~cma:None~cmxa:None~c_archive:None~c_stubs:[]~js_stubs:[])|true->Result.bind(Os.Dir.fold_files~recurse:falseOs.Dir.path_listdir[])@@funfs->letjs_stubs=List.map(funf->Fpath.(dir//f))js_stubsinlet()=List.iter(Memo.file_readym)js_stubsinletrecloopcmiscmxscmacmxac_archivec_stubs=function|[]->v~name~requires~dir~cmis~cmxs~cma~cmxa~c_archive~c_stubs~js_stubs|f::fs->letis_lib_archivef=matcharchivewith|None->false|Somea->String.equal(Fpath.basename~no_ext:truef)ainmatchFpath.get_extfwith|".cmi"->Memo.file_readymf;loop(f::cmis)cmxscmacmxac_archivec_stubsfs|".cmx"->Memo.file_readymf;loopcmis(f::cmxs)cmacmxac_archivec_stubsfs|".cma"->letcma=matchis_lib_archivefwith|true->Memo.file_readymf;Somef|false->cmainloopcmiscmxscmacmxac_archivec_stubsfs|".cmxa"->letcmxa=matchis_lib_archivefwith|true->Memo.file_readymf;Somef|false->cmxainloopcmiscmxscmacmxac_archivec_stubsfs|extwhenString.equalextclib_ext->Memo.file_readymf;letc_archive,c_stubs=matchis_lib_archivefwith|true->Somef,c_stubs|false->c_archive,(f::c_stubs)inloopcmiscmxscmacmxac_archivec_stubsfs|_->loopcmiscmxscmacmxac_archivec_stubsfsinOk(loop[][]NoneNoneNone[]fs)letnamel=l.nameletrequiresl=l.requiresletdirl=l.dirletcmisl=l.cmisletcmxsl=l.cmxsletcmal=l.cmaletcmxal=l.cmxaletc_archivel=l.c_archiveletc_stubsl=l.c_stubsletjs_stubsl=l.js_stubs(* Resolvers *)moduleResolver=struct(* FIXME rework erroring, for now we are not using the mecanisms
and they likely need to be tweaked. *)typelib=t(* Resolution scopes *)typescope_find=Conf.t->B00.Memo.t->Name.t->liboptionFut.ttypescope_suggest=Conf.t->B00.Memo.t->Name.t->stringoptionFut.ttypescope={name:string;find:scope_find;suggest:scope_suggest;}letscope_names=s.nameletscope_finds=s.findletscope_suggests=s.suggestletscope~name~find~suggest={name;find;suggest}moduleOcamlpath=struct(* Stubbed at the moment *)letfind~cache_dir~ocamlpathconfmn=Fut.returnNoneletsuggestconfmn=Fut.returnNoneletscope~cache_dir~ocamlpath=letfind=find~cache_dir~ocamlpathin{name="OCAMLPATH";find;suggest}endletocamlpath=Ocamlpath.scopemoduleOcamlfind=structlettool=B00.Tool.by_name"ocamlfind"letparse_infom?(file=Fpath.dash)~names=letparse_requiresrequires=letto_libnames=Result.to_failure@@Result.map_error(Fmt.str"required library: %s")@@Name.of_stringsinifrequires=""then[]else(* ocamlfind does not normalize *)letskip_ws=String.lose_leftChar.Ascii.is_whiteinletget_tok=String.span_left(Fun.negateChar.Ascii.is_white)inletrecrev_toksaccs=lets=(skip_wss)inmatchget_tokswith|"",rest->ifrest=""thenaccelserest::acc(* will err *)|tok,rest->rev_toks(tok::acc)restinList.rev_mapto_libname(rev_toks[]requires)inletparse_archivea=ifa=""thenNoneelsematchString.cut_right~sep:"."awith|None->Somea|Some(a,_ext)->Someainletparse_js_stubsjs_stubs=letstubs=String.cuts_left~drop_empty:true~sep:","js_stubsinletto_paths=Result.to_failure@@Result.map_error(Fmt.str"js stubs: %s")@@Fpath.of_stringsinList.mapto_pathstubsintrymatchString.split_on_char':'(String.trims)with|[meta;dir;archive;requires;js_stubs]->letrequires=parse_requiresrequiresinletarchive=parse_archivearchiveinletdir=Result.to_failure@@Result.map_error(Fmt.str"library directory: %s")@@Fpath.of_stringdirinletjs_stubs=parse_js_stubsjs_stubsinOk(meta,requires,dir,archive,js_stubs)|_->Fmt.failwith"could not parse %S"swith|Failuree->Fmt.error"@[<v>%a: %s@]"Fpath.ppfilee(* FIXME need to solve the META file read.
FIXME post exec is still super messy, check if we can make it
to use Memo.t *)letwrite_infomn~o=(* FIXME better [n] not found error *)letocamlfind=Memo.toolmtoolinletlib,predicates=matchName.to_stringnwith|"ocaml.threads"|"threads"|"threads.posix"->"threads.posix","byte,native,mt,mt_posix"|n->n,"byte,native"inletpost_execop=matchB000.Op.statusopwith|B000.Op.Done->beginmatchOption.get(B000.Op.Spawn.exit(B000.Op.Spawn.getop))with|`Exited2->(* FIXME checktypo *)leterr=Fmt.str"OCaml library %a not found"Name.ppninB000.Op.set_statusop(B000.Op.(Failed(Exec(Someerr))))|_->()end|_->()inletsuccess_exits=[0;2(* not found *)]inletinfo=(* We use %A otherwise whith %a we get a blank line if there's
no archive. Technically though we only support single library
archives *)"%m:%d:%A:%(requires):%(jsoo_runtime)"inletstdout=`FileoinMemo.spawnm~success_exits~reads:[]~writes:[o]~stdout~post_exec@@ocamlfindCmd.(atom"query"%lib%"-predicates"%predicates%"-format"%info)letread_infomclib_extnamefile=let*s=Memo.readmfileinmatchparse_info~filem~nameswith|Error_ase->Memo.fail_if_errorme|Ok(_meta,requires,dir,archive,js_stubs)->let*lib=of_dirm~clib_ext~name~requires~dir~archive~js_stubsinFut.return(Some(Memo.fail_if_errormlib))letfind~cache_dir~ocamlpathconfmn=(* This never returns None we should factor error reporting
in *)letclib_ext=Conf.lib_extconfinletfname=Fmt.str"ocamlfind.%s"(Name.to_stringn)inleto=Fpath.(cache_dir/fname)inwrite_infomn~o;read_infomclib_extnoletsuggestconfmn=Fut.returnNoneletscope~cache_dir=letfind=find~cache_dir~ocamlpathin{name="ocamlfind";find;suggest}endletocamlfind=Ocamlfind.scopetypet={memo:Memo.t;conf:Conf.t;scopes:scopelist;mutablelibs:liboptionFut.tName.Map.t;}letcreatememoconfscopes=letmemo=B00.Memo.with_markmemo"ocamlib"in{memo;conf;scopes;libs=Name.Map.empty}letocaml_confr=r.confletfindrn=matchName.Map.find_optnr.libswith|Somev->v|None->letreclooprn=function|[]->Fut.returnNone|s::ss->letfut=scope_findsr.confr.memoninlet*l=futinmatchlwith|None->looprnss|Some_->r.libs<-Name.Map.addnfutr.libs;futinlooprnr.scopesletgetrn=Fut.bind(findrn)@@function|None->Memo.failr.memo"No OCaml library %a found"Name.ppn|Somelib->Fut.returnlibletget_listrns=Fut.of_list(List.map(getr)ns)letget_list_and_depsrns=letrecloopseenacc=function|[]->Fut.return(seen,acc)|l::ls->ifName.Set.memlseenthenloopseenacclselseletseen=Name.Set.addlseeninlet*lib=getrlinletnot_seenn=not(Name.Set.memnseen)inletdeps=List.filternot_seen(requireslib)inlet*seen,acc=loopseenaccdepsinloopseen(lib::acc)lsinlet*_,libs=loopName.Set.empty[]nsinFut.return(List.revlibs)endend(* FIXME likely remove that *)moduleOcamlpath=structletget_varparsevarm=(* FIXME move that to Memo.env ? *)letenv=Env.env(Memo.envm)inmatchString.Map.find_optvarenvwith|None|Some""->None|Somev->matchparsevwith|Errore->Memo.failm"parsing %a: %s"Fmt.(codestring)varv|Okv->Somevletgetmps=matchpswith|Someps->Fut.returnps|None->matchget_varFpath.list_of_search_path"OCAMLPATH"mwith|Someps->Fut.returnps|None->matchget_varFpath.of_string"OPAM_SWITCH_PREFIX"mwith|Somep->Fut.return[Fpath.(p/"lib")]|None->Memo.failm"Could not determine an %a in the build environment."Fmt.(codestring)"OCAMLPATH"endmoduleCompile=struct(* XXX We should properly investigate how to use BUILD_PATH_PREFIX_MAP.
However for some reasons that were never not really answered by @gasche in
https://github.com/ocaml/ocaml/pull/1515, the map does not affect
absolute paths which severly limits its applicability.
XXX At some point we would had -o OBJ src [-I inc...] this worked
at least in 4.07 but not in 4.03, where apparently the order mattered.
XXX thread conf/version at that level ? E.g. if `-inc` becomes a
reality. We'd like to slip `-inc` in incs_of_file. *)typecode=[`Byte|`Native]letincs_of_filesfiles=Cmd.paths~slip:"-I"@@Fpath.distinct@@List.mapFpath.parentfilesletc_to_o?post_exec?km~comp~opts~reads~c~o=letcwd=Fpath.parento(* We can't use `-c` and `-o` on C files see
https://github.com/ocaml/ocaml/issues/7677 so we cwd to the
output directory to perform the spawn. *)inletincs=incs_of_filesreadsinMemo.spawnm?post_exec?k~reads:(c::reads)~writes:[o]~cwd@@(Memo.toolmcomp)Cmd.(atom"-c"%%opts%%unstamp(incs%%pathc))letmli_to_cmi?post_exec?k~and_cmtim~comp~opts~reads~mli~o=letbase=Fpath.strip_extoinletstamp=Fpath.basenamebaseinletreads=mli::readsinletwrites=o::ifand_cmtithen[Fpath.(base+".cmti")]else[]inletincs=incs_of_filesreadsinletbin_annot=Cmd.if'and_cmti(Cmd.atom"-bin-annot")inletio=Cmd.(unstamp(patho%%incs%%pathmli))inMemo.spawnm?post_exec?k~stamp~reads~writes@@(Memo.toolmcomp)Cmd.(atom"-c"%%bin_annot%%opts%"-o"%%io)letml_to_cmo?post_exec?k~and_cmtm~opts~reads~has_cmi~ml~o=letocamlc=Memo.toolmTool.ocamlcinletbase=Fpath.strip_extoinletstamp=Fpath.basenamebase(* output depends on mod name *)inletreads=ml::readsinletwrites=o::(add_ifand_cmtFpath.(base+".cmt")@@add_if(nothas_cmi)Fpath.(base+".cmi")[])inletincs=incs_of_filesreadsinletbin_annot=Cmd.if'and_cmt(Cmd.atom"-bin-annot")inletio=Cmd.(unstamp(patho%%incs%%pathml))inMemo.spawnm?post_exec?k~stamp~reads~writes@@ocamlcCmd.(atom"-c"%%bin_annot%%opts%"-o"%%io)letml_to_cmx?post_exec?k~and_cmtm~opts~reads~has_cmi~ml~o=letocamlopt=Memo.toolmTool.ocamloptinletbase=Fpath.strip_extoinletstamp=Fpath.basenamebase(* output depends on mod name *)inletreads=ml::readsinletwrites=o::Fpath.(base+".o")::(add_ifand_cmtFpath.(base+".cmt")@@add_if(nothas_cmi)Fpath.(base+".cmi")[])inletincs=incs_of_filesreadsinletbin_annot=Cmd.if'and_cmt(Cmd.atom"-bin-annot")inletio=Cmd.(unstamp(patho%%incs%%pathml))inMemo.spawnm?post_exec?k~stamp~reads~writes@@ocamloptCmd.(atom"-c"%%bin_annot%%opts%"-o"%%io)letml_to_impl?post_exec?km~code~opts~reads~has_cmi~ml~o~and_cmt=letml_to_obj=matchcodewith`Byte->ml_to_cmo|`Native->ml_to_cmxinml_to_obj?post_exec?km~opts~reads~has_cmi~ml~o~and_cmt(* Mod.Src convenience *)letmod_src_intf~and_cmtim~comp~opts~requires~mod_srcssrc=matchMod.Src.mlisrcwith|None->()|Somemli->leto=Mod.Src.cmi_filesrcinletdeps=Mod.Src.mli_depssrcinletsrc_deps,_remain=Mod.Src.finddepsmod_srcsinletadd_src_dep_objsaccdep=Mod.Src.as_intf_dep_files~init:accdepinletsrc_deps_objs=List.fold_leftadd_src_dep_objs[]src_depsinletext_objs=(* XXX could be more precise with [_remain] *)letadd_libaccl=List.rev_append(Lib.cmisl)accinList.fold_leftadd_lib[]requiresinletreads=List.rev_appendsrc_deps_objsext_objsinmli_to_cmi~and_cmtim~comp~opts~reads~mli~oletmod_src_impl~and_cmtm~code~opts~requires~mod_srcssrc=matchMod.Src.mlsrcwith|None->()|Someml->leto=Option.get(Mod.Src.impl_file~codesrc)inletdeps=Mod.Src.ml_depssrcinletsrc_deps,_remain=Mod.Src.finddepsmod_srcsinletadd_src_dep_objsaccdep=Mod.Src.as_impl_dep_files~code~init:accdepinletsrc_deps_objs=List.fold_leftadd_src_dep_objs[]src_depsinletext_objs=letadd_libaccl=matchcodewith|`Native->List.rev_append(Lib.cmxsl)@@List.rev_append(Lib.cmisl)acc|`Byte->List.rev_append(Lib.cmisl)accinList.fold_leftadd_lib[]requiresinlethas_cmi,src_deps_objs=matchMod.Src.mlisrcwith|None->false,src_deps_objs|Some_->true,Mod.Src.cmi_filesrc::src_deps_objsinletreads=List.rev_appendext_objssrc_deps_objsinml_to_impl~and_cmtm~code~opts~reads~has_cmi~ml~oletintfs~and_cmtim~comp~opts~requires~mod_srcs=letcompile_src=mod_src_intf~and_cmtim~comp~mod_srcs~requires~optssrcinString.Map.itercompilemod_srcsletimpls~and_cmtm~code~opts~requires~mod_srcs=letcompile_src=mod_src_impl~and_cmtm~code~opts~mod_srcs~requiressrcinString.Map.itercompilemod_srcsendmoduleArchive=structletcstubs_namename=Fmt.str"%s_stubs"nameletcstubs_clibnameext_lib=Fmt.str"lib%s_stubs%s"nameext_libletcstubs_dllnameext_dll=Fmt.str"dll%s_stubs%s"nameext_dllletcstubs?post_exec?km~conf~opts~c_objs~odir~oname=letlib_ext=Conf.lib_extconfinletdll_ext=Conf.dll_extconfinletocamlmklib=Memo.toolmTool.ocamlmklibinleto=Fpath.(odir/cstubs_nameoname)inletwrites=Fpath.(odir/cstubs_clibonamelib_ext)::add_if(Conf.has_dynlinkconf)Fpath.(odir/cstubs_dllonamedll_ext)[]inMemo.spawn?post_exec?km~reads:c_objs~writes@@ocamlmklibCmd.(atom"-o"%%unstamp(patho)%%opts%%unstamp(pathsc_objs))letbyte?post_exec?km~conf~opts~has_cstubs~cobjs~odir~oname=letocamlc=Memo.toolmTool.ocamlcinletcstubs_opts=ifnothas_cstubsthenCmd.emptyelseletlib=Fmt.str"-l%s"(cstubs_nameoname)inCmd.(atom"-cclib"%lib%%if'(Conf.has_dynlinkconf)(atom"-dllib"%lib))inletcma=Fpath.(odir/Fmt.str"%s.cma"oname)inMemo.spawnm~reads:cobjs~writes:[cma]@@ocamlcCmd.(atom"-a"%"-o"%%unstamp(pathcma)%%opts%%cstubs_opts%%unstamp(pathscobjs))letnative?post_exec?km~conf~opts~has_cstubs~cobjs~odir~oname=letocamlopt=Memo.toolmTool.ocamloptinletlib_ext=Conf.lib_extconfinletobj_ext=Conf.obj_extconfinletcstubs_opts=ifnothas_cstubsthenCmd.emptyelseCmd.(atom"-cclib"%Fmt.str"-l%s"(cstubs_nameoname))inletcmxa_clib=ifcobjs=[]&&Conf.versionconf>=(4,13,0,None)then[]else[Fpath.(odir/Fmt.str"%s%s"onamelib_ext)]inletcmxa=Fpath.(odir/Fmt.str"%s.cmxa"oname)inletwrites=cmxa::cmxa_clibinletc_objs=List.rev_map(Fpath.set_extobj_ext)cobjsinletreads=List.rev_appendc_objscobjsinMemo.spawnm?post_exec?k~reads~writes@@ocamloptCmd.(atom"-a"%"-o"%%unstamp(pathcmxa)%%opts%%cstubs_opts%%unstamp(pathscobjs))letcode?post_exec?km~conf~opts~code~has_cstubs~cobjs~odir~oname=letarchive=matchcodewith`Byte->byte|`Native->nativeinarchive?post_exec?km~conf~opts~has_cstubs~cobjs~odir~onameletnative_dynlink?post_exec?km~conf~opts~has_cstubs~cmxa~o=letlib_ext=Conf.lib_extconfinletocamlopt=Memo.toolmTool.ocamloptinletcmxa_clib=Fpath.(cmxa-+lib_ext)inletcstubs_opts,reads=ifnothas_cstubsthenCmd.empty,[cmxa;cmxa_clib]else(* Fixme do this on a cstubs path *)letoname=Fpath.basename~no_ext:truecmxainletcstubs_dir=Fpath.(parentcmxa)inletcstubs=Fpath.(cstubs_dir/cstubs_clibonamelib_ext)inletinc=Cmd.(atom"-I"%%unstamp(pathcstubs_dir))inCmd.(inc%%unstamp(pathcstubs)),[cstubs;cmxa;cmxa_clib]inMemo.spawnm?post_exec?k~reads~writes:[o]@@ocamloptCmd.(atom"-shared"%"-linkall"%"-o"%%unstamp(patho)%%opts%%cstubs_opts%%unstamp(pathcmxa))endmoduleLink=struct(* FIXME Add cstubs archives of cm[x]a to [reads] ? Do we need it ?
that would entail an ocamlobjinfo + C library lookup *)letcstubs_incsobjs=letadd_incaccobj=Fpath.Set.add(Fpath.parentobj)accinletincs=List.fold_leftadd_incFpath.Set.emptyobjsinCmd.paths~slip:"-I"(Fpath.Set.elementsincs)letbyte?post_exec?km~conf~opts~c_objs~cobjs~o=letocamlc=Memo.toolmTool.ocamlcinletreads=List.rev_appendcobjsc_objsinletincs=cstubs_incscobjsinMemo.spawnm?post_exec?k~reads~writes:[o]@@ocamlcCmd.(atom"-o"%%unstamp(patho)%%opts%%unstamp(incs%%pathsc_objs%%pathscobjs))letnative?post_exec?km~conf~opts~c_objs~cobjs~o=letocamlopt=Memo.toolmTool.ocamloptinletobj_ext=Conf.obj_extconfinletincs=cstubs_incscobjsinletreads,cobjs=letreclooprsidesrcobjs=function|[]->List.rev_appendrcobjs(List.rev_appendrsidesc_objs),List.revrcobjs|cobj::cobjs->matchFpath.has_ext".cmx"cobjwith|true->(* Add the side `.o` C object to read files. *)letrsides=Fpath.set_extobj_extcobj::rsidesinlooprsides(cobj::rcobjs)cobjs|false->matchFpath.has_ext".cmxa"cobjwith|true->looprsides(cobj::rcobjs)cobjs|false->(* This should be the `cmxa`s C library archives *)loop(cobj::rsides)rcobjscobjsinloop[][]cobjsinMemo.spawnm?post_exec?k~reads~writes:[o]@@ocamloptCmd.(atom"-o"%%unstamp(patho)%%opts%%unstamp(incs%%pathsc_objs%%pathscobjs))letcode?post_exec?km~conf~opts~code~c_objs~cobjs~o=letlinker=matchcodewith`Byte->byte|`Native->nativeinlinker?post_exec?km~conf~opts~c_objs~cobjs~oendmoduleCrunch=structletstring_to_string~id~data:s=letlen=String.lengthsinletlen=len*4+(len/18)*(3+2)inletb=Buffer.create(len+String.lengthid+3)inletadds=Buffer.add_stringinaddsb"let ";addsbid;addsb" =\n \"";fori=0toString.lengths-1doifimod18=0&&i<>0thenaddsb"\\\n ";letc=Char.code(String.getsi)inaddsb"\\x";Buffer.add_charb(Char.Ascii.lower_hex_digit((clsr4)land0xF));Buffer.add_charb(Char.Ascii.lower_hex_digit(cland0xF))done;addsb"\"\n";Buffer.contentsbend(*---------------------------------------------------------------------------
Copyright (c) 2019 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.
---------------------------------------------------------------------------*)