123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332(* Copyright (c) 2016 David Kaloper Meršinjak. All rights reserved.
See LICENSE.md. *)openOcamlbuild_pluginopenOcamlbuild_packopenOutcomeopenAstringlet(>>=)ab=matchawithSomex->bx|_->Noneleterror_msgffmt=Format.ksprintf(funstr->raise(Failurestr))("Ocb_stubblr: "^^fmt)leterror_exit_msgffmt=letkstr=Format.printf"%s\n%!"str;exit1inFormat.ksprintfk("Ocb_stubblr: "^^fmt)letchomps=letdrop~revs=String.drop~rev~sat:Char.Ascii.is_whitesindrop~rev:false(drop~rev:trues)letrun_and_readcmd=run_and_readcmd|>chompletmemof=lett=Hashtbl.create13infunx->tryHashtbl.findtxwithNot_found->lety=fxinHashtbl.addtxy;ymodulePkg_config=struct(* XXX Would be nice to move pkg-config results to a build artefact. *)letopam_prefix_cmd=letcmd="opam config var prefix"inlazy(tryrun_and_readcmdwithFailure_->error_msgf"error running opam")letopam_prefix=lazy(trySys.getenv"OPAM_SWITCH_PREFIX"withNot_found->Lazy.forceopam_prefix_cmd)letvar="PKG_CONFIG_PATH"letpath()=letopam=Lazy.forceopam_prefixandrest=try[Sys.getenvvar]withNot_found->[]inopam/"lib"/"pkgconfig"::opam/"share"/"pkgconfig"::rest|>String.concat~sep:":"letrun~flagspackage=letcmd=strf"%s=%s pkg-config %s %s 2>/dev/null"var(path())package(String.concat~sep:" "flags)intry`Res(run_and_readcmd)withFailure_->`Nonexistentendletskip_discovered_dirx=x="_build"||x.[0]='.'letpath_fold?(elements=`Any)?(traverse=`Any)fsroots=letrecgoaccpath=letdir=Pathname.is_directorypathinletacc=match(dir,elements)with|(_,`Any)->faccpath|(false,`Files)|(true,`Dirs)->faccpath|(_,`Satp)whenppath->faccpath|_->accinletvisit=dir&&matchtraversewith|`Any->true|`Satp->ppath|_->falseinifvisitthenletauxaccname=ifskip_discovered_dirnamethenaccelsegoacc(path/name)inArray.fold_leftauxacc(Pathname.readdirpath)elseaccinList.fold_leftgosrootsletfind_extextpaths=letfxsx=ifPathname.check_extensionxextthenx::xselsexsinpath_fold~elements:`Filesf[]paths|>List.rev(* random exportables *)typepath=Pathname.ttypeocb_hook=Ocamlbuild_plugin.hook->unitletafter_rulesf=functionAfter_rules->f()|_->()letinclude_include_dirs=after_rules@@fun()->letinc=SList.(!Options.include_dirs|>map(fundir->[A"-I";Adir])|>concat)inflag["ocaml";"link";"program"]inc;flag["ocaml";"link";"extension:cmxs"]incletocaml_libs?(mllibs=["."])=after_rules@@fun()->find_ext"mllib"mllibs|>List.iter@@funmllib->ocaml_lib(Pathname.remove_extensionmllib)letccopt?(tags=[])opts=after_rules@@fun()->flag(["compile";"c"]@tags)(S[A"-ccopt";Aopts])letcclib?(tags=[])opts=after_rules@@fun()->flag(["link";"c"]@tags)(S[A"-cclib";Aopts])letldopt?(tags=[])opts=after_rules@@fun()->flag(["c";"ocamlmklib"]@tags)(S[A"-ldopt";Aopts])letdispatchvhooks=dispatch@@funhook->List.iter(funf->fhook)hookslet(&)fgh=fh;gh(* os/machine detection *)typeos=[`Linux|`Hurd|`Darwin|`FreeBSD|`OpenBSD|`NetBSD|`DragonFly|`KFreeBSD|`Haiku|`HP_UX|`AIX|`Interix|`Minix|`QNX|`SunOS|`Cygwinofstring|`Mingwofstring|`Uwinofstring|`UNKNOWNofstring]letos()=matchrun_and_read"uname -s"with|"Linux"->`Linux|"GNU"->`Hurd|"Darwin"->`Darwin|"FreeBSD"->`FreeBSD|"OpenBSD"->`OpenBSD|"NetBSD"->`NetBSD|"DragonFly"->`DragonFly|"GNU/kFreeBSD"->`KFreeBSD|"Haiku"->`Haiku|"HP-UX"->`HP_UX|"AIX"->`AIX|"Interix"->`Interix|"Minix"->`Minix|"QNX"->`QNX|"SunOS"->`SunOS|resp->matchString.cut~sep:"-"respwith|Some("CYGWIN_NT",v)->`Cygwinv|Some("MINGW32_NT",v)->`Mingwv|Some("Uwin",v)->`Uwinv|_->`UNKNOWNresptypemachine=[`x86_64|`x86|`ARMv6|`ARMv7|`UNKNOWNofstring]letmachine()=matchrun_and_read"uname -m"with|"x86_64"|"amd64"|"i686-64"->`x86_64|"i386"|"i686"->`x86|"armv6l"->`ARMv6|"armv7l"->`ARMv7|resp->`UNKNOWNresp(* RULES RULES RULES *)(* link_stubs(path/to/clib) *)letlink_flag()=lettag="link_stubs"inletlibargswitchclib=letname=Pathname.(remove_extensionclib|>basename)inletname=String.(ifis_prefix~affix:"lib"namethendrop~max:3nameelsename)inS[Aswitch;A("-l"^name)]anddepflag=Pathname.([remove_extensionflag-.-"a"])inpflag["link";"ocaml";"library";"byte"]tag(libarg"-dllib");pflag["link";"ocaml";"library"]tag(libarg"-cclib");pdep["link";"ocaml"]tagdep;pdep["compile";"ocaml"]tagdep(* XXX sneak in '-I' for compile;ocaml;program ?? *)(* *.c depends on *.h *)(* Source dir is caught as `cwd` at module-init time. *)letroot=Unix.getcwd()letcdepscdepsenv_=letc=envcanddeps=envdepsinletto_liststr=List.filter((<>)"\\")@@String.fields~empty:false~is_sep:Char.Ascii.is_whitestrinletcmd=Cmd(S[A"cd";Proot;Sh"&&";A"cc";T(tags_of_pathnamec);A"-MM";A"-MG";Pc])inletheaders=matchCommand.to_stringcmd|>run_and_read|>to_listwith|_::_::xs->List.map(funp->Pathname.normalizep^"\n")xs|_->error_exit_msgf"%s: depends: unrecognized format"cin(* XXX Prepend dirname to unresolved headers? *)Echo(headers,deps)letcc_rules()=rule"ocaml C stubs: c -> c.depends"~dep:"%.c"~prod:"%.c.depends"(cdeps"%.c""%.c.depends");letx_o="%"-.-(!Options.ext_obj)in(* XXX
* The original OCamlbuild action for building [c -> o]. Keep in sync with
* their [src/ocaml_specific.ml].
* *)letdefault_actionenv_build=letc=env"%.c"inleto=envx_oinletcomp=ifTags.mem"native"(tags_of_pathnamec)then!Options.ocamloptelse!Options.ocamlcinletcc=Cmd(S[comp;T(tags_of_pathnamec++"c"++"compile");A"-c";Pxc])inifPathname.dirnameo=Pathname.current_dir_namethenccelseSeq[cc;mv(Pathname.basenameo)o]inletactionenvbuild=letdeps=string_list_of_file(env"%.c.depends")andcheck_outcome=function|Good_->()|Bad_->()in(* XXX We ignore errors here because we can't tell the difference
* between external and internal includes. *)(* | Bad exn -> error_msgf "building %s: %s" c (Printexc.to_string exn) in *)build(List.map(funp->[p])deps)|>List.itercheck_outcome;default_actionenvbuildinrule"ocaml C stubs: c & c.depends -> o"~prod:x_o~deps:["%.c";"%.c.depends"](* XXX [~doc] was introduced in OCaml 4.02. *)(* ~doc:"The OCaml compiler can be passed .c files and will call \ *)
(* the underlying C toolchain to produce corresponding .o files. \ *)
(* ocamlc or ocamlopt will be used depending on whether \ *)
(* the 'native' flag is set on the .c file.\ *)
(* (Extended version, taking #includes into account.)" *)action(* pkg-config(package[,relax[,param...]]) *)letrun_pkgconf=memo@@funpackage->letrunflags=matchPkg_config.run~flagspackagewith|`Resx->Somex|_->Noneinrun["--cflags"]>>=funcflags->run["--libs"]>>=funlibs->run["--libs";"--static"]>>=funstatic->Some(cflags,libs,static)letpkgconf_argsargstr=matchString.cuts~empty:false~sep:" "argstrwith|x::xs->let(relax,flags)=List.partition((=)"relax")xsin(x,List.mem"relax"relax,flags)|_->error_msgf"pkg-config(%s): malformed arguments"argstrletget_pkgconfargstring=let(package,relax,flags)=pkgconf_argsargstringinletflagx=List.memxflagsandsome=function""->None|fl->Someflinmatchrun_pkgconfpackagewith|Nonewhenrelax->None|None->error_msgf"pkg-config: package %s not found"package|Some(cf,lb,lb_st)->letcf=ifflag"cflags"||flags=[]thensomecfelseNoneandlb=ifflag"static"thensomelb_stelseifflag"libs"||flags=[]thensomelbelseNoneinSome(cf,lb)letpkg_conf_flag()=lettag="pkg-config"inletpkgconfpfargs=matchget_pkgconfargs>>=pwithSomex->fx|_->S[]inpflag["c";"compile"]tag(pkgconffst(funcflags->S[A"-ccopt";Acflags]));pflag["ocaml";"link"]tag(pkgconfsnd(funlibs->S[A"-cclib";Alibs]));pflag["c";"ocamlmklib"]tag(pkgconfsnd(funlibs->Alibs))(* let () = *)(* rule "get pkg-config" ~prod:"%(package).pkg-config" *)(* (fun env _ -> *)(* let pkg = env "%(package)" *)(* and dst = env "%(package).pkg-config" in *)(* let cmd = S [A "pkg-config"; A pkg] in *)(* Seq [Cmd (S [cmd; A "--cflags"; Sh ">"; Px dst]); *)(* Cmd (S [cmd; A "--libs"; Sh ">>"; Px dst]); *)(* Cmd (S [cmd; A "--static"; Sh ">>"; Px dst])]) *)(* multi-lib *)letx_cdepssrcdsttargetenv_=lettarget=envtargetandpaths=string_list_of_file(envsrc)inEcho(List.map(funp->"X"/target/p^"\n")paths,envdst)letx_rules()=rule"multi-lib: derive .c.depends"~dep:"%(path).c.depends"~prod:"X/%(target)/%(path).c.depends"(x_cdeps"%(path).c.depends""X/%(target)/%(path).c.depends""%(target)");copy_rule"multi-lib: cp .c""%(path).c""X/%(target)/%(path).c";copy_rule"multi-lib: cp .h""%(path).h""X/%(target)/%(path).h";copy_rule"multi-lib: cp .clib""%(path).clib""X/%(target)/%(path)+%(target).clib"letmirage_rules()=letopenConfigurationin(* Mirage itself takes care of the linkage. *)parse_string"<X/mirage-xen/**>: pkg-config(mirage-xen-ocaml relax cflags)";parse_string"<X/mirage-freestanding/**>: pkg-config(ocaml-freestanding relax cflags)"(* back-ports of 0.9.3 flags *)letbackported_c_flags()=letvs=["4.01";"4.02"]inifList.exists(funaffix->String.is_prefix~affixSys.ocaml_version)vs(* Inject flags if the OCaml version is known to have been shipped with
bundled ocamlbuild. We can't detect the three stand-alone versions of
ocamlbuild that lack them, 0.9.0, 0.9.1 and 0.9.2. *)thenbeginpflag["c";"compile"]"ccopt"(funparam->S[A"-ccopt";Aparam]);pflag["c";"link"]"ccopt"(funparam->S[A"-ccopt";Aparam]);pflag["c";"compile"]"cclib"(funparam->S[A"-cclib";Aparam]);pflag["c";"link"]"cclib"(funparam->S[A"-cclib";Aparam]);end(* activate *)letrules=function|Before_rules->link_flag();pkg_conf_flag();backported_c_flags();x_rules();(* multi-lib c.depends takes precedence *)cc_rules();mirage_rules();|_->()letignore_=()letinit?(incdirs=true)?mllibs=rules&ocaml_libs?mllibs&(ifincdirstheninclude_include_dirselseignore)