├── .gitignore ├── .merlin ├── .ocamlinit ├── .ocp-indent ├── .travis.yml ├── CHANGES.md ├── LICENSE.md ├── README.md ├── _tags ├── doc ├── api.odocl ├── dev.odocl └── style.css ├── opam ├── pkg ├── META └── pkg.ml ├── src-topkg ├── .merlin ├── ocb-stubblr-topkg.mllib ├── ocb_stubblr_topkg.ml └── ocb_stubblr_topkg.mli └── src ├── .merlin ├── ocb-stubblr.mllib ├── ocb_stubblr.ml └── ocb_stubblr.mli /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | tmp 3 | *~ 4 | \.\#* 5 | \#*# 6 | *.install 7 | *.native 8 | *.byte -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | PKG bytes 2 | S src 3 | S test 4 | B _build/** 5 | -------------------------------------------------------------------------------- /.ocamlinit: -------------------------------------------------------------------------------- 1 | #require "ocamlbuild,astring" 2 | #directory "_build/src" 3 | #load "ocb-stubblr.cma" 4 | 5 | open Ocb_stubblr 6 | -------------------------------------------------------------------------------- /.ocp-indent: -------------------------------------------------------------------------------- 1 | strict_with=always,match_clause=4,strict_else=never 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-opam.sh 3 | script: bash -ex .travis-opam.sh 4 | sudo: required 5 | env: 6 | global: 7 | - PACKAGE="ocb-stubblr" 8 | matrix: 9 | - OCAML_VERSION=4.01 10 | - OCAML_VERSION=4.02 11 | - OCAML_VERSION=4.03 12 | - OCAML_VERSION=4.04 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## v0.1.1 2017-01-30 2 | 3 | * Include both `lib` and `share` in pkg-config path. 4 | * Use `mirage-xen-ocaml` for Xen pkg-config. 5 | * Add include dirs to `cmxs` linking. 6 | 7 | ## v0.1.0 2016-11-03 8 | 9 | * Fix header discovery and its interaction with multi-lib. 10 | * `pkg-config()` can query a subset of `--cflags`, `--libs` or `--static`. 11 | * Rename `ccopt_flags`/`cclib_flags` to `ccopt`/`cclib`; add `ldopt`. 12 | * Detect old `ocamlbuild` and add 0.9.3-compatible `ccopt`/`cclib` flags. 13 | * `mirage` combinator returns a single `install`, not a list. 14 | * Topkg 0.8.x. 15 | 16 | ## v0.0.2 2016-10-27 17 | 18 | * Fix the wrong dependencies in META. 19 | 20 | ## v0.0.1 2016-10-26 21 | 22 | First release. 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 David Kaloper Meršinjak 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ocb-stubblr — OCamlbuild plugin for C stubs 2 | ------------------------------------------------------------------------------- 3 | %%VERSION%% 4 | 5 | Do you get excited by C stubs? Do they sometimes make you swoon, and even faint, 6 | and in the end no `cmxa`s get properly linked -- not to mention correct 7 | multi-lib support? 8 | 9 | Do you wish that the things that excite you the most, would excite you just a 10 | little less? Then ocb-stubblr is just the library for you. 11 | 12 | ocb-stubblr is about ten lines of code that you need to repeat over, over, over 13 | and over again if you are using `ocamlbuild` to build OCaml projects that 14 | contain C stubs -- now with 100% more lib! 15 | 16 | It does what everyone wants to do with `.clib` files in their project 17 | directories. It can also clone the `.clib` and arrange for multiple compilations 18 | with different sets of discovered `cflags`. 19 | 20 | ocb-stubblr is distributed under the ISC license. 21 | 22 | ## Set it up 23 | 24 | `pkg/pkg.ml`: 25 | 26 | #require "ocb-stubblr.topkg" 27 | open Ocb_stubblr_topkg 28 | 29 | let () = 30 | Pkg.describe ~build:(Pkg.build ~cmd ()) ... 31 | 32 | `myocamlbuild.ml`: 33 | 34 | open Ocamlbuild_plugin 35 | 36 | let () = dispatch Ocb_stubblr.init 37 | 38 | `opam`: 39 | 40 | depends: [ 41 | "ocamlfind" {build} 42 | "ocamlbuild" {build} 43 | "topkg" {build} 44 | "ocb-stubblr" {build} 45 | ... 46 | ] 47 | 48 | ## Documentation 49 | 50 | Interfaces are documented. [Online][doc] too. 51 | 52 | [doc]: https://pqwy.github.io/ocb-stubblr/doc 53 | 54 | ## Development 55 | 56 | Feel free to pitch in with ideas, especially if you have work-flows that are 57 | not, but could *almost* be supported. 58 | 59 | [![Build Status](https://travis-ci.org/pqwy/ocb-stubblr.svg?branch=master)](https://travis-ci.org/pqwy/ocb-stubblr) 60 | -------------------------------------------------------------------------------- /_tags: -------------------------------------------------------------------------------- 1 | true: color(always) 2 | true: bin_annot, safe_string 3 | true: warn(A-4-29-33-40-41-42-43-34-44-48) 4 | true: package(bytes) 5 | 6 | : include 7 | : package(ocamlbuild), package(astring) 8 | 9 | : include 10 | : package(topkg) 11 | -------------------------------------------------------------------------------- /doc/api.odocl: -------------------------------------------------------------------------------- 1 | Ocb_stubblr 2 | Ocb_stubblr_topkg 3 | -------------------------------------------------------------------------------- /doc/dev.odocl: -------------------------------------------------------------------------------- 1 | Ocb_stubblr 2 | Ocb_stubblr_topkg 3 | -------------------------------------------------------------------------------- /doc/style.css: -------------------------------------------------------------------------------- 1 | /* A style for ocamldoc. Daniel C. Buenzli. Frills by hamsters. */ 2 | 3 | /* Reset a few things. */ 4 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 5 | a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp, 6 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset, 7 | form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td 8 | { margin: 0; padding: 0; border: 0 none; outline: 0; font-size: 100%; 9 | font-weight: inherit; font-style:inherit; font-family:inherit; 10 | line-height: inherit; vertical-align: baseline; text-align:inherit; 11 | color:inherit; background: transparent; } 12 | 13 | table { border-collapse: collapse; border-spacing: 0; } 14 | 15 | /* Basic page layout */ 16 | 17 | body { font: normal 10pt/1.375em helvetica, arial, sans-serif; text-align:left; 18 | margin: 1.375em 10%; min-width: 40ex; max-width: 72ex; 19 | color: black; background: white /* url(line-height-22.gif) */; } 20 | 21 | b { font-weight: bold } 22 | em { font-style: italic } 23 | 24 | tt, code, pre { font-family: WorkAroundWebKitAndMozilla, monospace; 25 | font-size: 1em; } 26 | pre code { font-size : inherit; } 27 | .codepre { margin-bottom:1.375em /* after code example we introduce space. */ } 28 | 29 | pre { padding: 5px; } 30 | code.code, pre.codepre, pre.verbatim { background-color: #f7f7f7; border-radius: 3px; } 31 | code.code { font-size: 95%; padding: 0.1em 0.2em; } 32 | pre.codepre code.code { padding: 0; } 33 | 34 | 35 | .superscript,.subscript 36 | { font-size : 0.813em; line-height:0; margin-left:0.4ex;} 37 | .superscript { vertical-align: super; } 38 | .subscript { vertical-align: sub; } 39 | 40 | /* ocamldoc markup workaround hacks */ 41 | 42 | 43 | 44 | hr, hr + br, div + br, center + br, span + br, ul + br, ol + br, pre + br 45 | { display: none } /* annoying */ 46 | 47 | div.info + br { display:block} 48 | 49 | .codepre br + br { display: none } 50 | h1 + pre { margin-bottom:1.375em} /* Toplevel module description */ 51 | 52 | /* Sections and document divisions */ 53 | 54 | /* .navbar { margin-bottom: -1.375em } */ 55 | h1 { font-weight: bold; font-size: 1.5em; /* margin-top:1.833em; */ 56 | margin-top:0.917em; padding-top:0.875em; 57 | border-top-style:solid; border-width:1px; border-color:#AAA; } 58 | h2 { font-weight: bold; font-size: 1.313em; margin-top: 1.048em } 59 | h3 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 60 | h3 { font-weight: bold; font-size: 1em; margin-top: 1.375em} 61 | h4 { font-style: italic; } 62 | 63 | /* Used by OCaml's own library documentation. */ 64 | h6 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 65 | .h7 { font-weight: bold; font-size: 1em; margin-top: 1.375em } 66 | 67 | p { margin-top: 1.375em } 68 | pre { margin-top: 1.375em } 69 | .info { margin: 0.458em 0em -0.458em 2em;}/* Description of types values etc. */ 70 | td .info { margin:0; padding:0; margin-left: 2em;} /* Description in indexes */ 71 | 72 | ul, ol { margin-top:0.688em; padding-bottom:0.687em; 73 | list-style-position:outside} 74 | ul + p, ol + p { margin-top: 0em } 75 | ul { list-style-type: square } 76 | 77 | 78 | /* h2 + ul, h3 + ul, p + ul { } */ 79 | ul > li { margin-left: 1.375em; } 80 | ol > li { margin-left: 1.7em; } 81 | /* Links */ 82 | 83 | a, a:* { text-decoration: underline } 84 | *:target {background-color: #FFFF99;} /* anchor highlight */ 85 | 86 | /* Code */ 87 | 88 | .keyword { font-weight: bold; } 89 | .comment { color : red } 90 | .constructor { color : green } 91 | .string { color : #957; } 92 | .warning { color : red ; font-weight : bold } 93 | 94 | /* Functors */ 95 | 96 | .paramstable { border-style : hidden ; padding-bottom:1.375em} 97 | .paramstable code { margin-left: 1ex; margin-right: 1ex } 98 | .sig_block {margin-left: 1em} 99 | 100 | /* Images */ 101 | 102 | img { margin-top: 1.375em; display:block } 103 | li img { margin-top: 0em; } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | maintainer: "David Kaloper Meršinjak " 3 | authors: ["David Kaloper Meršinjak "] 4 | homepage: "https://github.com/pqwy/ocb-stubblr" 5 | doc: "https://pqwy.github.io/ocb-stubblr/doc" 6 | license: "ISC" 7 | dev-repo: "https://github.com/pqwy/ocb-stubblr.git" 8 | bug-reports: "https://github.com/pqwy/ocb-stubblr/issues" 9 | tags: ["ocamlbuild"] 10 | available: [ ocaml-version >= "4.01.0" ] 11 | depends: [ 12 | "ocamlfind" {build} 13 | "ocamlbuild" {>="0.9.3" | <"0.9.0"} 14 | "topkg" {>= "0.8.1"} 15 | "astring" ] 16 | depopts: [] 17 | build: [ "ocaml" "pkg/pkg.ml" "build" "--pinned" "%{pinned}%" "--tests" "false" ] 18 | -------------------------------------------------------------------------------- /pkg/META: -------------------------------------------------------------------------------- 1 | description = "OCamlbuild plugin for C stubs" 2 | version = "%%VERSION_NUM%%" 3 | requires = "astring ocamlbuild" 4 | archive(byte) = "ocb-stubblr.cma" 5 | archive(native) = "ocb-stubblr.cmxa" 6 | plugin(byte) = "ocb-stubblr.cma" 7 | plugin(native) = "ocb-stubblr.cmxs" 8 | 9 | package "topkg" ( 10 | description = "OCamlbuild plugin for C stubs" 11 | version = "%%VERSION_NUM%%" 12 | requires = "topkg" 13 | archive(byte) = "ocb-stubblr-topkg.cma" 14 | archive(native) = "ocb-stubblr-topkg.cmxa" 15 | plugin(byte) = "ocb-stubblr-topkg.cma" 16 | plugin(native) = "ocb-stubblr-topkg.cmxs" 17 | exists_if = "ocb-stubblr-topkg.cma" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/pkg.ml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ocaml 2 | #use "topfind" 3 | #require "topkg" 4 | open Topkg 5 | 6 | let () = 7 | Pkg.describe "ocb-stubblr" @@ fun c -> 8 | Ok [ Pkg.mllib "src/ocb-stubblr.mllib"; 9 | Pkg.mllib "src-topkg/ocb-stubblr-topkg.mllib"; 10 | ] 11 | -------------------------------------------------------------------------------- /src-topkg/.merlin: -------------------------------------------------------------------------------- 1 | PKG topkg 2 | REC 3 | -------------------------------------------------------------------------------- /src-topkg/ocb-stubblr-topkg.mllib: -------------------------------------------------------------------------------- 1 | Ocb_stubblr_topkg 2 | -------------------------------------------------------------------------------- /src-topkg/ocb_stubblr_topkg.ml: -------------------------------------------------------------------------------- 1 | (* Copyright (c) 2016 David Kaloper Meršinjak. All rights reserved. 2 | See LICENSE.md. *) 3 | 4 | open Topkg 5 | 6 | let build_arg = Cmd.(v "-plugin-tag" % "package(ocb-stubblr)") 7 | let build_cmd c os = Cmd.(Pkg.build_cmd c os %% build_arg) 8 | let cmd c os files = 9 | OS.Cmd.run @@ Cmd.(%%) (build_cmd c os) (Cmd.of_list files) 10 | 11 | (* Vs Pkg.clib: 12 | - .clib file can be generated; 13 | - installs only the static part (libFOO.a); 14 | - no ~lib_dst_dir; 15 | - no source debugging support. *) 16 | let clib_static ?(cond = false) clib = 17 | if not cond then Pkg.nothing else 18 | let name = Fpath.(rem_ext clib |> basename) in 19 | Log.on_error_msg ~use:(fun () -> Pkg.nothing) @@ 20 | if Fpath.get_ext clib = ".clib" && String.is_prefix ~affix:"lib" name then 21 | let base = String.with_index_range ~first:3 name 22 | and dir = Fpath.dirname clib in 23 | Ok (Pkg.lib ~exts:Exts.c_library Fpath.(dir // "lib" ^ base)) 24 | else R.error_msgf "%s: OCamlbuild .clib must be lib.clib" clib 25 | 26 | let mirage ?(xen=false) ?(fs=false) path = 27 | let tpath target = 28 | let (dir, base) = Fpath.(dirname path, basename path) in 29 | let (name, ext) = Fpath.(rem_ext base, get_ext base) in 30 | Fpath.("X"//target//dir//name^"+"^target^ext) in 31 | Pkg.flatten [ 32 | clib_static ~cond:xen (tpath "mirage-xen"); 33 | clib_static ~cond:fs (tpath "mirage-freestanding"); ] 34 | -------------------------------------------------------------------------------- /src-topkg/ocb_stubblr_topkg.mli: -------------------------------------------------------------------------------- 1 | (* Copyright (c) 2016 David Kaloper Meršinjak. All rights reserved. 2 | See LICENSE.md. *) 3 | 4 | (** Hook up {!Ocb_stubblr} with [topkg]. *) 5 | 6 | open Topkg 7 | 8 | (** {1 Activating [ocb-stubblr]} *) 9 | 10 | val build_arg : Cmd.t 11 | (** [build_arg] is the command-line parameter that needs to be passed to 12 | [ocamlbuild] in order to access {!Ocb_stubblr} inside [myocamlbuild.ml]. *) 13 | 14 | val build_cmd : Conf.t -> Conf.os -> Cmd.t 15 | (** [build_cmd c os] is the default {!Topkg.build_cmd} extended with 16 | {{!build_arg}[build_arg]}. *) 17 | 18 | val cmd : Conf.t -> Conf.os -> fpath list -> unit result 19 | (** [cmd c os files] is a full invocation of {{!build_cmd}[build_cmd]}. *) 20 | 21 | (** {1 Install helpers} *) 22 | 23 | val mirage : ?xen:bool -> ?fs:bool -> fpath -> Pkg.install 24 | (** [mirage ?xen ?fs clib] installs Mirage-specific 25 | {{!Ocb_stubblr.multilib}multi-lib} variants of the given [.clib] file. 26 | 27 | [xen] enables [mirage-xen] target. Defaults to [false]. 28 | 29 | [fs] enables [mirage-freestanding] target. Defaults to [false]. *) 30 | 31 | (** {1 Usage} 32 | 33 | In [pkg.ml]: 34 | 35 | {2 Making [ocb-stubblr] available from [myocamlbuild.ml]} 36 | 37 | {[#require "ocb-stubblr.topkg" 38 | 39 | let build = Pkg.build ~cmd:Ocb_stubblr_topkg.cmd () 40 | 41 | let () = Pkg.describe ~build ...]} 42 | 43 | {2 Mirage} 44 | 45 | {[Pkg.describe ... @@ fun c -> 46 | ... 47 | Ok [ ...; mirage ~xen ~fs "path/to/libstubs.clib"]]} 48 | 49 | *) 50 | -------------------------------------------------------------------------------- /src/.merlin: -------------------------------------------------------------------------------- 1 | PKG astring ocamlbuild 2 | REC 3 | -------------------------------------------------------------------------------- /src/ocb-stubblr.mllib: -------------------------------------------------------------------------------- 1 | Ocb_stubblr 2 | -------------------------------------------------------------------------------- /src/ocb_stubblr.ml: -------------------------------------------------------------------------------- 1 | (* Copyright (c) 2016 David Kaloper Meršinjak. All rights reserved. 2 | See LICENSE.md. *) 3 | 4 | open Ocamlbuild_plugin 5 | open Ocamlbuild_pack 6 | open Outcome 7 | open Astring 8 | 9 | 10 | let (>>=) a b = match a with Some x -> b x | _ -> None 11 | 12 | let error_msgf fmt = 13 | Format.ksprintf (fun str -> raise (Failure str)) ("Ocb_stubblr: " ^^ fmt) 14 | 15 | let error_exit_msgf fmt = 16 | let k str = Format.printf "%s\n%!" str; exit 1 in 17 | Format.ksprintf k ("Ocb_stubblr: " ^^ fmt) 18 | 19 | let chomp s = 20 | let drop ~rev s = String.drop ~rev ~sat:Char.Ascii.is_white s in 21 | drop ~rev:false (drop ~rev:true s) 22 | 23 | let run_and_read cmd = run_and_read cmd |> chomp 24 | 25 | let memo f = 26 | let t = Hashtbl.create 13 in fun x -> 27 | try Hashtbl.find t x with Not_found -> 28 | let y = f x in Hashtbl.add t x y; y 29 | 30 | module Pkg_config = struct 31 | 32 | (* XXX Would be nice to move pkg-config results to a build artefact. *) 33 | 34 | let opam_prefix = 35 | let cmd = "opam config var prefix" in 36 | lazy ( try run_and_read cmd with Failure _ -> 37 | error_msgf "error running opam") 38 | 39 | let var = "PKG_CONFIG_PATH" 40 | 41 | let path () = 42 | let opam = Lazy.force opam_prefix 43 | and rest = try [Sys.getenv var] with Not_found -> [] in 44 | opam/"lib"/"pkgconfig" :: opam/"share"/"pkgconfig" :: rest 45 | |> String.concat ~sep:":" 46 | 47 | let run ~flags package = 48 | let cmd = strf "%s=%s pkg-config %s %s 2>/dev/null" 49 | var (path ()) package (String.concat ~sep:" " flags) in 50 | try `Res (run_and_read cmd) with Failure _ -> `Nonexistent 51 | end 52 | 53 | let skip_discovered_dir x = x = "_build" || x.[0] = '.' 54 | 55 | let path_fold ?(elements = `Any) ?(traverse = `Any) f s roots = 56 | let rec go acc path = 57 | let dir = Pathname.is_directory path in 58 | let acc = match (dir, elements) with 59 | | (_, `Any) -> f acc path 60 | | (false, `Files) | (true, `Dirs) -> f acc path 61 | | (_, `Sat p) when p path -> f acc path 62 | | _ -> acc in 63 | let visit = dir && match traverse with 64 | | `Any -> true | `Sat p -> p path | _ -> false in 65 | if visit then 66 | let aux acc name = 67 | if skip_discovered_dir name then acc else go acc (path/name) in 68 | Array.fold_left aux acc (Pathname.readdir path) 69 | else acc in 70 | List.fold_left go s roots 71 | 72 | let find_ext ext paths = 73 | let f xs x = if Pathname.check_extension x ext then x::xs else xs in 74 | path_fold ~elements:`Files f [] paths |> List.rev 75 | 76 | (* random exportables *) 77 | 78 | type path = Pathname.t 79 | 80 | type ocb_hook = Ocamlbuild_plugin.hook -> unit 81 | 82 | let after_rules f = function After_rules -> f () | _ -> () 83 | 84 | let include_include_dirs = after_rules @@ fun () -> 85 | let inc = S List.(!Options.include_dirs 86 | |> map (fun dir -> [A "-I"; A dir]) |> concat) in 87 | flag ["ocaml"; "link"; "program"] inc; 88 | flag ["ocaml"; "link"; "extension:cmxs"] inc 89 | 90 | let ocaml_libs ?(mllibs = ["."]) = 91 | after_rules @@ fun () -> 92 | find_ext "mllib" mllibs |> List.iter @@ fun mllib -> 93 | ocaml_lib (Pathname.remove_extension mllib) 94 | 95 | let ccopt ?(tags = []) opts = after_rules @@ fun () -> 96 | flag (["compile"; "c"] @ tags) (S [A "-ccopt"; A opts]) 97 | 98 | let cclib ?(tags = []) opts = after_rules @@ fun () -> 99 | flag (["link"; "c"] @ tags) (S [A "-cclib"; A opts]) 100 | 101 | let ldopt ?(tags = []) opts = after_rules @@ fun () -> 102 | flag (["c"; "ocamlmklib"] @ tags) (S [A "-ldopt"; A opts]) 103 | 104 | let dispatchv hooks = dispatch @@ fun hook -> List.iter (fun f -> f hook) hooks 105 | 106 | let (&) f g h = f h; g h 107 | 108 | (* os/machine detection *) 109 | 110 | type os = [ 111 | `Linux | `Hurd | `Darwin | `FreeBSD | `OpenBSD | `NetBSD 112 | | `DragonFly | `KFreeBSD | `Haiku | `HP_UX | `AIX | `Interix 113 | | `Minix | `QNX | `SunOS 114 | | `Cygwin of string | `Mingw of string | `Uwin of string | `UNKNOWN of string 115 | ] 116 | 117 | let os () = 118 | match run_and_read "uname -s" with 119 | | "Linux" -> `Linux 120 | | "GNU" -> `Hurd 121 | | "Darwin" -> `Darwin 122 | | "FreeBSD" -> `FreeBSD 123 | | "OpenBSD" -> `OpenBSD 124 | | "NetBSD" -> `NetBSD 125 | | "DragonFly" -> `DragonFly 126 | | "GNU/kFreeBSD" -> `KFreeBSD 127 | | "Haiku" -> `Haiku 128 | | "HP-UX" -> `HP_UX 129 | | "AIX" -> `AIX 130 | | "Interix" -> `Interix 131 | | "Minix" -> `Minix 132 | | "QNX" -> `QNX 133 | | "SunOS" -> `SunOS 134 | | resp -> 135 | match String.cut ~sep:"-" resp with 136 | | Some ("CYGWIN_NT", v) -> `Cygwin v 137 | | Some ("MINGW32_NT", v) -> `Mingw v 138 | | Some ("Uwin", v) -> `Uwin v 139 | | _ -> `UNKNOWN resp 140 | 141 | type machine = [ `x86_64 | `x86 | `ARMv6 | `ARMv7 | `UNKNOWN of string ] 142 | 143 | let machine () = 144 | match run_and_read "uname -m" with 145 | | "x86_64" | "amd64" | "i686-64" -> `x86_64 146 | | "i386" | "i686" -> `x86 147 | | "armv6l" -> `ARMv6 148 | | "armv7l" -> `ARMv7 149 | | resp -> `UNKNOWN resp 150 | 151 | (* RULES RULES RULES *) 152 | 153 | (* link_stubs(path/to/clib) *) 154 | 155 | let link_flag () = 156 | let tag = "link_stubs" in 157 | let libarg switch clib = 158 | let name = Pathname.(remove_extension clib |> basename) in 159 | let name = String.(if is_prefix ~affix:"lib" name then drop ~max:3 name else name) in 160 | S [A switch; A ("-l"^name)] 161 | and dep flag = Pathname.([remove_extension flag -.- "a"]) in 162 | pflag ["link"; "ocaml"; "library"; "byte"] tag (libarg "-dllib"); 163 | pflag ["link"; "ocaml"; "library"; "native"] tag (libarg "-cclib"); 164 | pdep ["link"; "ocaml"] tag dep; 165 | pdep ["compile"; "ocaml"] tag dep 166 | (* XXX sneak in '-I' for compile;ocaml;program ?? *) 167 | 168 | (* *.c depends on *.h *) 169 | 170 | (* Source dir is caught as `cwd` at module-init time. *) 171 | let root = Unix.getcwd () 172 | 173 | let cdeps c deps env _ = 174 | let c = env c and deps = env deps in 175 | let to_list str = List.filter ((<>) "\\") @@ 176 | String.fields ~empty:false ~is_sep:Char.Ascii.is_white str in 177 | let cmd = Cmd ( 178 | S [ A "cd"; P root; Sh "&&"; 179 | A "cc"; T (tags_of_pathname c); A "-MM"; A "-MG"; P c ]) in 180 | let headers = match Command.to_string cmd |> run_and_read |> to_list with 181 | | _::_::xs -> List.map (fun p -> Pathname.normalize p ^ "\n") xs 182 | | _ -> error_exit_msgf "%s: depends: unrecognized format" c in 183 | (* XXX Prepend dirname to unresolved headers? *) 184 | Echo (headers, deps) 185 | 186 | let cc_rules () = 187 | 188 | rule "ocaml C stubs: c -> c.depends" 189 | ~dep:"%.c" ~prod:"%.c.depends" (cdeps "%.c" "%.c.depends"); 190 | 191 | let x_o = "%"-.-(!Options.ext_obj) in 192 | (* XXX 193 | * The original OCamlbuild action for building [c -> o]. Keep in sync with 194 | * their [src/ocaml_specific.ml]. 195 | * *) 196 | let default_action env _build = 197 | let c = env "%.c" in 198 | let o = env x_o in 199 | let comp = if Tags.mem "native" (tags_of_pathname c) then !Options.ocamlopt else !Options.ocamlc in 200 | let cc = Cmd(S[comp; T(tags_of_pathname c++"c"++"compile"); A"-c"; Px c]) in 201 | if Pathname.dirname o = Pathname.current_dir_name then cc 202 | else Seq[cc; mv (Pathname.basename o) o] 203 | in 204 | let action env build = 205 | let deps = string_list_of_file (env "%.c.depends") 206 | and check_outcome = function 207 | | Good _ -> () 208 | | Bad _ -> () in 209 | (* XXX We ignore errors here because we can't tell the difference 210 | * between external and internal includes. *) 211 | (* | Bad exn -> error_msgf "building %s: %s" c (Printexc.to_string exn) in *) 212 | build (List.map (fun p -> [p]) deps) |> List.iter check_outcome; 213 | default_action env build in 214 | rule "ocaml C stubs: c & c.depends -> o" 215 | ~prod:x_o ~deps:["%.c"; "%.c.depends"] 216 | (* XXX [~doc] was introduced in OCaml 4.02. *) 217 | (* ~doc:"The OCaml compiler can be passed .c files and will call \ *) 218 | (* the underlying C toolchain to produce corresponding .o files. \ *) 219 | (* ocamlc or ocamlopt will be used depending on whether \ *) 220 | (* the 'native' flag is set on the .c file.\ *) 221 | (* (Extended version, taking #includes into account.)" *) 222 | action 223 | 224 | (* pkg-config(package[,relax[,param...]]) *) 225 | 226 | let run_pkgconf = memo @@ fun package -> 227 | let run flags = match Pkg_config.run ~flags package with 228 | | `Res x -> Some x | _ -> None in 229 | run ["--cflags"] >>= fun cflags -> 230 | run ["--libs"] >>= fun libs -> 231 | run ["--libs"; "--static"] >>= fun static -> 232 | Some (cflags, libs, static) 233 | 234 | let pkgconf_args argstr = 235 | match String.cuts ~empty:false ~sep:" " argstr with 236 | | x::xs -> 237 | let (relax, flags) = List.partition ((=) "relax") xs in 238 | (x, List.mem "relax" relax, flags) 239 | | _ -> error_msgf "pkg-config(%s): malformed arguments" argstr 240 | 241 | let get_pkgconf argstring = 242 | let (package, relax, flags) = pkgconf_args argstring in 243 | let flag x = List.mem x flags 244 | and some = function "" -> None | fl -> Some fl in 245 | match run_pkgconf package with 246 | | None when relax -> None 247 | | None -> error_msgf "pkg-config: package %s not found" package 248 | | Some (cf, lb, lb_st) -> 249 | let cf = if flag "cflags" || flags = [] then some cf else None 250 | and lb = 251 | if flag "static" then some lb_st else 252 | if flag "libs" || flags = [] then some lb else None in 253 | Some (cf, lb) 254 | 255 | let pkg_conf_flag () = 256 | let tag = "pkg-config" in 257 | let pkgconf p f args = 258 | match get_pkgconf args >>= p with Some x -> f x | _ -> S [] in 259 | pflag ["c"; "compile"] tag 260 | (pkgconf fst (fun cflags -> S [A "-ccopt"; A cflags])); 261 | pflag ["ocaml"; "link"] tag 262 | (pkgconf snd (fun libs -> S [A "-cclib"; A libs])); 263 | pflag ["c"; "ocamlmklib"] tag 264 | (pkgconf snd (fun libs -> A libs)) 265 | 266 | (* let () = *) 267 | (* rule "get pkg-config" ~prod:"%(package).pkg-config" *) 268 | (* (fun env _ -> *) 269 | (* let pkg = env "%(package)" *) 270 | (* and dst = env "%(package).pkg-config" in *) 271 | (* let cmd = S [A "pkg-config"; A pkg] in *) 272 | (* Seq [Cmd (S [cmd; A "--cflags"; Sh ">"; Px dst]); *) 273 | (* Cmd (S [cmd; A "--libs"; Sh ">>"; Px dst]); *) 274 | (* Cmd (S [cmd; A "--static"; Sh ">>"; Px dst])]) *) 275 | 276 | (* multi-lib *) 277 | 278 | let x_cdeps src dst target env _ = 279 | let target = env target 280 | and paths = string_list_of_file (env src) in 281 | Echo (List.map (fun p -> "X"/target/p^"\n") paths, env dst) 282 | 283 | let x_rules () = 284 | rule "multi-lib: derive .c.depends" 285 | ~dep:"%(path).c.depends" ~prod:"X/%(target)/%(path).c.depends" 286 | (x_cdeps "%(path).c.depends" "X/%(target)/%(path).c.depends" "%(target)"); 287 | copy_rule "multi-lib: cp .c" "%(path).c" "X/%(target)/%(path).c"; 288 | copy_rule "multi-lib: cp .h" "%(path).h" "X/%(target)/%(path).h"; 289 | copy_rule "multi-lib: cp .clib" "%(path).clib" "X/%(target)/%(path)+%(target).clib" 290 | 291 | let mirage_rules () = let open Configuration in 292 | (* Mirage itself takes care of the linkage. *) 293 | parse_string ": pkg-config(mirage-xen-ocaml relax cflags)"; 294 | parse_string ": pkg-config(ocaml-freestanding relax cflags)" 295 | 296 | (* back-ports of 0.9.3 flags *) 297 | 298 | let backported_c_flags () = 299 | let vs = ["4.01"; "4.02"] in 300 | if List.exists (fun affix -> String.is_prefix ~affix Sys.ocaml_version) vs 301 | (* Inject flags if the OCaml version is known to have been shipped with 302 | bundled ocamlbuild. We can't detect the three stand-alone versions of 303 | ocamlbuild that lack them, 0.9.0, 0.9.1 and 0.9.2. *) 304 | then begin 305 | pflag ["c"; "compile"] "ccopt" (fun param -> S [A "-ccopt"; A param]); 306 | pflag ["c"; "link"] "ccopt" (fun param -> S [A "-ccopt"; A param]); 307 | pflag ["c"; "compile"] "cclib" (fun param -> S [A "-cclib"; A param]); 308 | pflag ["c"; "link"] "cclib" (fun param -> S [A "-cclib"; A param]); 309 | end 310 | 311 | (* activate *) 312 | 313 | let rules = function 314 | | Before_rules -> 315 | link_flag (); 316 | pkg_conf_flag (); 317 | backported_c_flags (); 318 | x_rules (); (* multi-lib c.depends takes precedence *) 319 | cc_rules (); 320 | mirage_rules (); 321 | | _ -> () 322 | 323 | let ignore _ = () 324 | 325 | let init ?(incdirs=true) ?mllibs = 326 | rules 327 | & ocaml_libs ?mllibs 328 | & (if incdirs then include_include_dirs else ignore) 329 | -------------------------------------------------------------------------------- /src/ocb_stubblr.mli: -------------------------------------------------------------------------------- 1 | (* Copyright (c) 2016 David Kaloper Meršinjak. All rights reserved. 2 | See LICENSE.md. *) 3 | 4 | (** OCamlbuild plugin for C stubs 5 | 6 | See the {{!intro}Intro}, {{!interface}Interface} or {{!examples}Examples}. *) 7 | 8 | 9 | (** {1:intro Intro} 10 | 11 | [ocb-stubblr] helps dealing with C libraries that are built as part of an 12 | OCaml project. It is especially useful for libraries of OCaml primitives 13 | (stubs). 14 | 15 | Most of the plugin consists of new tags that can be applied to files, and 16 | new rules that are activated when OCamlbuild tries to build certain targets. 17 | In order to enable these, {!init} needs to be called from 18 | {!Ocamlbuild_plugin.dispatch}: 19 | 20 | {[let () = Ocamlbuild_plugin.dispatch Ocb_stubblr.init]} 21 | 22 | The plugin helps with three aspects of building C stubs: 23 | 24 | {2 1. Using [.clib] files} 25 | 26 | [stubblr] modifies [.clib] build rules to automatically search for, and 27 | require, project-local headers that the C source files [#include]. 28 | 29 | Furthermore, it provides the tag [link_stubs()]. This tag acts on OCaml 30 | archives, and records the link flags needed for linking with the given C 31 | library. The parameter is assumed to be a [.clib] file in the same project. 32 | 33 | For example, adding 34 | {[: link_stubs(path/libbar)]} 35 | records the link flag [-lbar] in [foo.cm{,x}a]. Assuming that the C 36 | libraries described by [path/libbar.clib] are installed, this causes any 37 | final executables that use the archive [foo.cmxa] (resp. [foo.cma]) to link 38 | to [libbar.a] (resp [dllbar.so]). This is useful if [Foo] provides the 39 | interface to the C primitives in [libbar]. 40 | 41 | Another feature is the automatic addition of [use_] tags for every 42 | [.mllib]. OCaml sources tagged with this tag are built against the 43 | archive [.cm{,x}a], instead of using its constituent [cm{o,x}] through 44 | [include] tags. This means that the in-tree executables inherit the link 45 | flags (as introduced, for example, above), and are correctly linked against 46 | the in-tree C libraries. 47 | 48 | Finally, the tags [ccopt] and [cclib], which were introduced in OCamlbuild 49 | 0.9.3, are added if the plugin is used with an older version. This allows 50 | setting these options directly from [_tags] with any OCamlbuild version. 51 | 52 | {2 2. [pkg-config]} 53 | 54 | [stubblr] provides the tag [pkg-config()]. 55 | 56 | Tagging objects with [pkg-config(package)] will query [pkg-config] for the 57 | [package], and: 58 | 59 | {ol 60 | {- add the C flags ([pkg-config --cflags]) to the compilation of tagged C 61 | sources;} 62 | {- add the link flags ([pkg-config --libs]) to the linking step of tagged C 63 | libraries; and} 64 | {- record those link flags in the tagged OCaml archives.}} 65 | 66 | For example 67 | {[: pkg-config(sdl2)]} 68 | will add the flags needed to compile against SDL2 when compiling C sources, 69 | and record the flags needed to link the final executables against [libSDL2.so] 70 | to the native and bytecode archives. 71 | 72 | The full syntax of the tag is [pkg-config(package[ relax][ ])] where 73 | [] is a space-separated combination of any of: [cflags], [libs], 74 | [static]. 75 | 76 | [relax] will ignore the package if it is not found. Otherwise, the build 77 | will abort. 78 | 79 | [cflags] will query [--cflags]. 80 | 81 | [libs] will query [--libs]. 82 | 83 | [static] will query [--libs --static] (and has precedence over [libs]). 84 | 85 | If none of [cflags], [libs] and [static] are present, [cflags libs] is 86 | assumed. Thus [pkg-config(pkg relax)] will query [--cflags] and [--libs], 87 | and ignore the error if [pkg] is not installed; [pkg-config(pkg cflags)] 88 | will query only the [--cflags]; while [pkg-config(pkg static)] will query 89 | only [--libs --static]. 90 | 91 | {b Note} [.pc] files in the current Opam switch take precedence; see 92 | {{!Pkg_config}[Pkg_config]}. 93 | 94 | {2:multilib 3. Multi-lib} 95 | 96 | Sometimes it can be desirable to compile a C library in several ways, e.g. 97 | with different compilation options, and install all of the versions. 98 | 99 | 100 | For any file [path/libstub.clib], [stubblr] introduces the rules to build 101 | [X//path/libstub+.clib], where [] is an arbitrary 102 | name. The new library is built from the same sources, but it doesn't inherit 103 | any of the tags directly applied to the original [.clib] and its products. 104 | Instead, the files [X//**/*] can be marked with a separate set of 105 | tags, causing them to be compiled and/or linked with different options. 106 | 107 | As a special case, there are pre-defined targets for different MirageOS 108 | runtimes (currently [mirage-xen] and [mirage-freestanding]). These are 109 | automatically tagged with the required compilation options. 110 | 111 | {b Note} If your paths already contain [+], OCamlbuild solver is likely to 112 | get confused. Assume that the meaning of [+] in paths has been hijacked by 113 | [ocb-stubblr]. The new semantics of [+] is accessible only through 114 | transcendental hermenautics. 115 | 116 | *) 117 | 118 | 119 | (** {1:interface Interface} *) 120 | 121 | open Ocamlbuild_plugin 122 | 123 | type ocb_hook = Ocamlbuild_plugin.hook -> unit 124 | 125 | type path = Pathname.t 126 | 127 | val init : ?incdirs:bool -> ?mllibs:path list -> ocb_hook 128 | (** [init ?incdirs ?paths] initializes the plugin. 129 | 130 | [incdirs] causes {{!include_include_dirs}[include_include_dirs]} to be 131 | called on initialisation. Defaults to [true]. 132 | 133 | [mllibs] are passed to {{!ocaml_libs}[ocaml_libs]}, to detect any 134 | [.mllib] files and enable their corresponding [use_] tags. 135 | Use [[]] to disable. Defaults to [["."]]. *) 136 | 137 | (** {2:utilities Utilities} *) 138 | 139 | val ocaml_libs : ?mllibs:path list -> ocb_hook 140 | (** [ocaml_libs ~mllibs] calls {!Ocamlbuild_plugin.ocaml_lib} on every [.mllib] 141 | found in [mllibs]. It's a shortcut to enable [use_] tag for every 142 | [.mllib] in the project. 143 | 144 | [mllibs] is a list of files or directories. Directories in the list are 145 | searched recursively. [mllibs] defaults to [["."]]. *) 146 | 147 | val include_include_dirs : ocb_hook 148 | (** [include_include_dirs] will add [-I dir] when linking OCaml programs and 149 | [cmxs] for every [dir] marked as [include]. *) 150 | 151 | val ccopt : ?tags:string list -> string -> ocb_hook 152 | (** [ccopt tags options] adds [-ccopt options] when compiling the C sources 153 | tagged with [~tags]. 154 | 155 | [tags] defaults to [[]]. *) 156 | 157 | val cclib : ?tags:string list -> string -> ocb_hook 158 | (** [cclib tags options] adds [-cclib options] when linking the C libraries 159 | tagged with [~tags]. 160 | 161 | [tags] defaults to [[]]. *) 162 | 163 | val ldopt : ?tags:string list -> string -> ocb_hook 164 | (** [ldopt tags options] adds [-ldopt options] when linking the C libraries 165 | tagged with [~tags]. 166 | 167 | [tags] defaults to [[]]. *) 168 | 169 | val after_rules : (unit -> unit) -> ocb_hook 170 | (** [after_rules f] is [function After_rules -> f () | _ -> ()]. *) 171 | 172 | val dispatchv : ocb_hook list -> unit 173 | (** [dispatchv hooks] is a shortcut for registering several 174 | {{!ocb_hook}ocb_hooks}. 175 | 176 | It is equivalent to [Ocamlbuild_plugin.dispatch hookf] where [hookf] is a 177 | function that applies each hook from [hooks] in order. *) 178 | 179 | val (&) : ocb_hook -> ocb_hook -> ocb_hook 180 | (** [h1 & h2] is a hook combining [h1] and [h2]. *) 181 | 182 | (** Query [pkg-config]. 183 | 184 | [pkg-config] is invoked with the environment extended with the equivalent of 185 | {[PKG_CONFIG_PATH=$(opam config var prefix)/{lib,share}/pkgconfig]}. This 186 | means that any [.pc] files in the current Opam switch take precedence over 187 | the system-wide ones. *) 188 | module Pkg_config : sig 189 | 190 | val run : flags:string list -> string -> [`Nonexistent | `Res of string] 191 | (** [run ~flags package] queries [pkg-config] about [package], using [flags]. 192 | 193 | [`Nonexistent] means that the package was not found. Otherwise, 194 | [`Res output] is whatever [pkg-config] prints on the standard output. 195 | 196 | {b Note} Currently, all errors in [pkg-config] invocation results in 197 | [`Nonexistent]. *) 198 | end 199 | 200 | (** {2 OS and machine detection} 201 | 202 | These utilities are included because it is sometimes necessary to change 203 | options for building C libraries depending on the host OS and architecture. *) 204 | 205 | type os = [ 206 | `Linux | `Hurd | `Darwin | `FreeBSD | `OpenBSD | `NetBSD 207 | | `DragonFly | `KFreeBSD | `Haiku | `HP_UX | `AIX | `Interix 208 | | `Minix | `QNX | `SunOS 209 | | `Cygwin of string | `Mingw of string | `Uwin of string | `UNKNOWN of string 210 | ] 211 | (** A selection of popular operating systems. *) 212 | 213 | type machine = [ `x86_64 | `x86 | `ARMv6 | `ARMv7 | `UNKNOWN of string ] 214 | (** A selection of machine architectures supported by OCaml. *) 215 | 216 | val os : unit -> os 217 | (** [os ()] is the normalized result of [uname -s]. *) 218 | 219 | val machine : unit -> machine 220 | (** [machine ()] is the normalized result of [uname -m]. *) 221 | 222 | (** {1:examples Examples} 223 | 224 | Assume a project laid out like the following: 225 | 226 | Project dir: 227 | {[./myocamlbuild.ml 228 | ./_tags 229 | ./src/foo.ml 230 | ./src/stubs.c 231 | ./src/extra/defs.h 232 | ./src/libstubs.clib 233 | ./src/foo.mllib 234 | ./exe/demo.ml]} 235 | 236 | The content of [src/foo.mllib]: 237 | {[Foo]} 238 | 239 | The content of [src/libstubs.clib]: 240 | {[stubs.o]} 241 | 242 | The content of [_tags]: 243 | {[: include]} 244 | 245 | {2 Basic integration} 246 | 247 | Initialize the plugin from [myocamlbuild.ml]: 248 | {[let () = Ocamlbuild_plugin.dispatch Ocb_stubblr.init]} 249 | 250 | The file [src/extra/defs.h] will be automatically used when compiling 251 | [src/stubs.c], if needed. 252 | 253 | Adding the tag 254 | {[: link_stubs(src/libstubs)]} 255 | will record the link flag [-lstubs] in [foo.cm{,x}a], causing executables 256 | that use them to link against [libstubs.a]/[dllstubs.so]. 257 | 258 | {2 [pkg-config]} 259 | 260 | Adding the tag 261 | {[: pkg-config(sdl2 relax)]} 262 | will cause [stubs.c] to be compiled with C flags from [sdl2.pc], and 263 | [foo.cm{,x}a] to record the link flags. 264 | 265 | If [SDL2] is not installed, [relax] will cause it to be ignored. 266 | 267 | {2 In-tree executables} 268 | 269 | To build [demo.native] and/or [demo.byte], [_tags] needs to contain 270 | 271 | {[: use_foo]} 272 | 273 | causing [demo.{native,byte}] to build against [foo.cm{,x}a] and inherit its 274 | link flags. The archive, in turn, contains flags for linking to the stub 275 | library ([link_stubs()]), and linking to [SDL2] ([pkg-config()]). 276 | 277 | {2 Multi-lib} 278 | 279 | Invoking [ocamlbuild X/fnord/src/libstubs+fnord.a] will build 280 | [libstubs+fnord.a]. 281 | 282 | If [_tags] contains 283 | {[: ccopt(-flub) 284 | : ccopt(-DA)]} 285 | 286 | then [libstubs+fnord.a] will {e not} be compiled with [-flub]. Instead, it 287 | will be compiled with the pre-processor symbol [A] defined. 288 | 289 | {2 Mirage} 290 | 291 | If using [Topkg], register the [.clib] file using 292 | {!Ocb_stubblr_topkg.mirage}. 293 | 294 | {[Pkg.describe ... @ fun c -> 295 | ... 296 | Ok [ Pkg.clib "path/to/libstubs.clib"; 297 | Ocb_stubblr_topkg.mirage "path/to/libstubs.clib"]]} 298 | 299 | Otherwise, arrange for building and installation of 300 | [X//path/to/libstubs+.a] for all MirageOS [TARGET]s. 301 | 302 | Use of these alternate archives is a matter of MirageOS. 303 | 304 | {2 Composition} 305 | 306 | {[let myhook = function 307 | | After_rules -> ... 308 | | ...]} 309 | 310 | {[let () = Ocb_stubblr.(dispatchv [init; myhook])]} 311 | 312 | {[let () = dispatch Ocb_stubblr.(init & myhook)]} 313 | *) 314 | --------------------------------------------------------------------------------