├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.org ├── configurator.opam └── src ├── configurator.ml ├── configurator.mli └── jbuild /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.install 3 | *.merlin 4 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This repository contains open source software that is developed and 2 | maintained by [Jane Street][js]. 3 | 4 | Contributions to this project are welcome and should be submitted via 5 | GitHub pull requests. 6 | 7 | Signing contributions 8 | --------------------- 9 | 10 | We require that you sign your contributions. Your signature certifies 11 | that you wrote the patch or otherwise have the right to pass it on as 12 | an open-source patch. The rules are pretty simple: if you can certify 13 | the below (from [developercertificate.org][dco]): 14 | 15 | ``` 16 | Developer Certificate of Origin 17 | Version 1.1 18 | 19 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 20 | 1 Letterman Drive 21 | Suite D4700 22 | San Francisco, CA, 94129 23 | 24 | Everyone is permitted to copy and distribute verbatim copies of this 25 | license document, but changing it is not allowed. 26 | 27 | 28 | Developer's Certificate of Origin 1.1 29 | 30 | By making a contribution to this project, I certify that: 31 | 32 | (a) The contribution was created in whole or in part by me and I 33 | have the right to submit it under the open source license 34 | indicated in the file; or 35 | 36 | (b) The contribution is based upon previous work that, to the best 37 | of my knowledge, is covered under an appropriate open source 38 | license and I have the right under that license to submit that 39 | work with modifications, whether created in whole or in part 40 | by me, under the same open source license (unless I am 41 | permitted to submit under a different license), as indicated 42 | in the file; or 43 | 44 | (c) The contribution was provided directly to me by some other 45 | person who certified (a), (b) or (c) and I have not modified 46 | it. 47 | 48 | (d) I understand and agree that this project and the contribution 49 | are public and that a record of the contribution (including all 50 | personal information I submit with it, including my sign-off) is 51 | maintained indefinitely and may be redistributed consistent with 52 | this project or the open source license(s) involved. 53 | ``` 54 | 55 | Then you just add a line to every git commit message: 56 | 57 | ``` 58 | Signed-off-by: Joe Smith 59 | ``` 60 | 61 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 62 | 63 | If you set your `user.name` and `user.email` git configs, you can sign 64 | your commit automatically with git commit -s. 65 | 66 | [dco]: http://developercertificate.org/ 67 | [js]: https://opensource.janestreet.com/ 68 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016--2018 Jane Street Group, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),) 2 | 3 | # Default rule 4 | default: 5 | jbuilder build @install 6 | 7 | install: 8 | jbuilder install $(INSTALL_ARGS) 9 | 10 | uninstall: 11 | jbuilder uninstall $(INSTALL_ARGS) 12 | 13 | reinstall: uninstall install 14 | 15 | clean: 16 | rm -rf _build 17 | 18 | .PHONY: default install uninstall reinstall clean 19 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Configurator 2 | 3 | Configurator is a small library that helps writing OCaml scripts that 4 | test features available on the system, in order to generate config.h 5 | files for instance. 6 | 7 | Configurator allows one to: 8 | - test if a C program compiles 9 | - query pkg-config 10 | - import #define from OCaml header files 11 | - generate config.h file 12 | 13 | For instance: 14 | 15 | #+begin_src ocaml 16 | open Base 17 | module C = Configurator 18 | 19 | let clock_gettime_code = {| 20 | #include 21 | 22 | int main() 23 | { 24 | struct timespec ts; 25 | clock_gettime(CLOCK_REALTIME, &ts); 26 | return 0; 27 | } 28 | |} 29 | 30 | let () = 31 | C.main ~name:"foo" (fun c -> 32 | let has_clock_gettime = C.c_test c clock_gettime_code ~link_flags:["-lrt"] in 33 | 34 | C.C_define.gen_header_file c ~fname:"config.h" 35 | [ "HAS_CLOCK_GETTIME", Switch has_clock_gettime ]); 36 | #+end_src 37 | -------------------------------------------------------------------------------- /configurator.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | maintainer: "opensource@janestreet.com" 3 | authors: ["Jane Street Group, LLC "] 4 | homepage: "https://github.com/janestreet/configurator" 5 | bug-reports: "https://github.com/janestreet/configurator/issues" 6 | dev-repo: "git+https://github.com/janestreet/configurator.git" 7 | license: "MIT" 8 | build: [ 9 | ["jbuilder" "build" "-p" name "-j" jobs] 10 | ] 11 | depends: [ 12 | "base" 13 | "stdio" 14 | "jbuilder" {build & >= "1.0+beta18.1"} 15 | ] 16 | available: [ ocaml-version >= "4.04.2" ] 17 | descr: " 18 | Helper library for gathering system configuration 19 | 20 | Configurator is a small library that helps writing OCaml scripts that 21 | test features available on the system, in order to generate config.h 22 | files for instance. 23 | 24 | Configurator allows one to: 25 | - test if a C program compiles 26 | - query pkg-config 27 | - import #define from OCaml header files 28 | - generate config.h file 29 | " 30 | -------------------------------------------------------------------------------- /src/configurator.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | open Stdio 3 | 4 | module Sys = Caml.Sys 5 | module Fn = Caml.Filename 6 | module Arg = Caml.Arg 7 | module Buffer = Caml.Buffer 8 | module Pervasives = Caml.Pervasives 9 | 10 | let ( ^^ ) = Caml.( ^^ ) 11 | let ( ^/ ) = Fn.concat 12 | let sprintf = Printf.sprintf 13 | 14 | exception Fatal_error of string 15 | 16 | let die fmt = 17 | Printf.ksprintf (fun s -> 18 | raise (Fatal_error s); 19 | ) fmt 20 | 21 | type t = 22 | { name : string 23 | ; dest_dir : string 24 | ; ocamlc : string 25 | ; log : string -> unit 26 | ; mutable counter : int 27 | ; ext_obj : string 28 | ; c_compiler : string 29 | ; stdlib_dir : string 30 | ; ccomp_type : string 31 | ; ocamlc_config : string Map.M(String).t 32 | ; ocamlc_config_cmd : string 33 | } 34 | 35 | let rec rm_rf dir = 36 | Array.iter (Sys.readdir dir) ~f:(fun fn -> 37 | let fn = dir ^/ fn in 38 | if Sys.is_directory fn then 39 | rm_rf fn 40 | else 41 | Unix.unlink fn); 42 | Unix.rmdir dir 43 | 44 | module Temp = struct 45 | (* Copied from filename.ml and adapted for directories *) 46 | 47 | let prng = lazy(Random.State.make_self_init ()) 48 | 49 | let gen_name ~temp_dir ~prefix ~suffix = 50 | let rnd = Int.bit_and (Random.State.bits (Lazy.force prng)) 0xFFFFFF in 51 | temp_dir ^/ (Printf.sprintf "%s%06x%s" prefix rnd suffix) 52 | 53 | let create ~prefix ~suffix ~mk = 54 | let temp_dir = Fn.get_temp_dir_name () in 55 | let rec try_name counter = 56 | let name = gen_name ~temp_dir ~prefix ~suffix in 57 | match mk name with 58 | | () -> name 59 | | exception (Unix.Unix_error _) when counter < 1000 -> 60 | try_name (counter + 1) 61 | in 62 | try_name 0 63 | 64 | let create_temp_dir ~prefix ~suffix = 65 | let dir = create ~prefix ~suffix ~mk:(fun name -> Unix.mkdir name 0o700) in 66 | Caml.at_exit (fun () -> rm_rf dir); 67 | dir 68 | end 69 | 70 | module Find_in_path = struct 71 | let path_sep = 72 | if Sys.win32 then 73 | ';' 74 | else 75 | ':' 76 | 77 | let get_path () = 78 | match Sys.getenv "PATH" with 79 | | exception (Not_found_s _ | Caml.Not_found) -> [] 80 | | s -> String.split s ~on:path_sep 81 | 82 | let exe = if Sys.win32 then ".exe" else "" 83 | 84 | let prog_not_found prog = 85 | die "Program %s not found in PATH" prog 86 | 87 | let best_prog dir prog = 88 | let fn = dir ^/ prog ^ ".opt" ^ exe in 89 | if Sys.file_exists fn then 90 | Some fn 91 | else 92 | let fn = dir ^/ prog ^ exe in 93 | if Sys.file_exists fn then 94 | Some fn 95 | else 96 | None 97 | 98 | let find_ocaml_prog prog = 99 | match 100 | List.find_map (get_path ()) ~f:(fun dir -> 101 | best_prog dir prog) 102 | with 103 | | None -> prog_not_found prog 104 | | Some fn -> fn 105 | 106 | let find prog = 107 | List.find_map (get_path ()) ~f:(fun dir -> 108 | let fn = dir ^/ prog ^ exe in 109 | Option.some_if (Sys.file_exists fn) fn) 110 | end 111 | 112 | let logf t fmt = Printf.ksprintf t.log fmt 113 | 114 | let gen_id t = 115 | let n = t.counter in 116 | t.counter <- n + 1; 117 | n 118 | 119 | type run_result = 120 | { exit_code : int 121 | ; stdout : string 122 | ; stderr : string 123 | } 124 | 125 | let quote = 126 | let need_quote = function 127 | | ' ' | '\"' -> true 128 | | _ -> false 129 | in 130 | fun s -> 131 | if String.is_empty s || String.exists ~f:need_quote s 132 | then Fn.quote s 133 | else s 134 | 135 | let command_line prog args = 136 | String.concat ~sep:" " (List.map (prog :: args) ~f:quote) 137 | 138 | let run t ~dir cmd = 139 | logf t "run: %s" cmd; 140 | let n = gen_id t in 141 | let stdout_fn = t.dest_dir ^/ sprintf "stdout-%d" n in 142 | let stderr_fn = t.dest_dir ^/ sprintf "stderr-%d" n in 143 | let exit_code = 144 | Printf.ksprintf 145 | Sys.command "cd %s && %s > %s 2> %s" 146 | (Fn.quote dir) 147 | cmd 148 | (Fn.quote stdout_fn) 149 | (Fn.quote stderr_fn) 150 | in 151 | let stdout = In_channel.read_all stdout_fn in 152 | let stderr = In_channel.read_all stderr_fn in 153 | logf t "-> process exited with code %d" exit_code; 154 | logf t "-> stdout:"; 155 | List.iter (String.split_lines stdout) ~f:(logf t " | %s"); 156 | logf t "-> stderr:"; 157 | List.iter (String.split_lines stderr) ~f:(logf t " | %s"); 158 | { exit_code; stdout; stderr } 159 | 160 | let run_capture_exn t ~dir cmd = 161 | let { exit_code; stdout; stderr } = run t ~dir cmd in 162 | if exit_code <> 0 then 163 | die "command exited with code %d: %s" exit_code cmd 164 | else if not (String.is_empty stderr) then 165 | die "command has non-empty stderr: %s" cmd 166 | else 167 | stdout 168 | 169 | let run_ok t ~dir cmd = (run t ~dir cmd).exit_code = 0 170 | 171 | let get_ocaml_config_var_exn ~ocamlc_config_cmd map var = 172 | match Map.find map var with 173 | | None -> die "variable %S not found in the output of `%s`" var ocamlc_config_cmd 174 | | Some s -> s 175 | 176 | let ocaml_config_var t var = Map.find t.ocamlc_config var 177 | let ocaml_config_var_exn t var = 178 | get_ocaml_config_var_exn t.ocamlc_config var 179 | ~ocamlc_config_cmd:t.ocamlc_config_cmd 180 | 181 | let create ?dest_dir ?ocamlc ?(log=ignore) name = 182 | let dest_dir = 183 | match dest_dir with 184 | | Some dir -> dir 185 | | None -> Temp.create_temp_dir ~prefix:"ocaml-configurator" ~suffix:"" 186 | in 187 | let ocamlc = 188 | match ocamlc with 189 | | Some fn -> fn 190 | | None -> Find_in_path.find_ocaml_prog "ocamlc" 191 | in 192 | let ocamlc_config_cmd = command_line ocamlc ["-config"] in 193 | let t = 194 | { name 195 | ; ocamlc 196 | ; log 197 | ; dest_dir 198 | ; counter = 0 199 | ; ext_obj = "" 200 | ; c_compiler = "" 201 | ; stdlib_dir = "" 202 | ; ccomp_type = "" 203 | ; ocamlc_config = Map.empty (module String) 204 | ; ocamlc_config_cmd 205 | } 206 | in 207 | let ocamlc_config = 208 | let colon_space = String.Search_pattern.create ": " in 209 | run_capture_exn t ~dir:dest_dir ocamlc_config_cmd 210 | |> String.split_lines 211 | |> List.map ~f:(fun line -> 212 | match String.Search_pattern.index colon_space ~in_:line with 213 | | Some i -> 214 | (String.sub line ~pos:0 ~len:i, 215 | String.sub line ~pos:(i + 2) ~len:(String.length line - i - 2)) 216 | | None -> 217 | die "unrecognized line in the output of `%s`: %s" ocamlc_config_cmd 218 | line) 219 | |> Map.of_alist (module String) 220 | |> function 221 | | `Ok x -> x 222 | | `Duplicate_key key -> 223 | die "variable %S present twice in the output of `%s`" key ocamlc_config_cmd 224 | in 225 | let get = get_ocaml_config_var_exn ocamlc_config ~ocamlc_config_cmd in 226 | let c_compiler = 227 | match Map.find ocamlc_config "c_compiler" with 228 | | Some c_comp -> c_comp ^ " " ^ get "ocamlc_cflags" 229 | | None -> get "bytecomp_c_compiler" 230 | in 231 | { t with 232 | ocamlc_config 233 | ; ext_obj = get "ext_obj" 234 | ; c_compiler 235 | ; stdlib_dir = get "standard_library" 236 | ; ccomp_type = get "ccomp_type" 237 | } 238 | 239 | let need_to_compile_and_link_separately t = 240 | (* Vague memory from writing the discover.ml script for Lwt... *) 241 | match t.ccomp_type with 242 | | "msvc" -> true 243 | | _ -> false 244 | 245 | let compile_c_prog t ?(c_flags=[]) ?(link_flags=[]) code = 246 | let dir = t.dest_dir ^/ sprintf "c-test-%d" (gen_id t) in 247 | Unix.mkdir dir 0o777; 248 | let base = dir ^/ "test" in 249 | let c_fname = base ^ ".c" in 250 | let obj_fname = base ^ t.ext_obj in 251 | let exe_fname = base ^ ".exe" in 252 | Out_channel.write_all c_fname ~data:code; 253 | logf t "compiling c program:"; 254 | List.iter (String.split_lines code) ~f:(logf t " | %s"); 255 | let run_ok args = 256 | run_ok t ~dir 257 | (String.concat ~sep:" " 258 | (t.c_compiler :: List.map args ~f:Fn.quote)) 259 | in 260 | let ok = 261 | if need_to_compile_and_link_separately t then 262 | run_ok (c_flags @ ["-I"; t.stdlib_dir; "-c"; c_fname]) && 263 | run_ok ("-o" :: exe_fname :: obj_fname :: link_flags) 264 | else 265 | run_ok 266 | (List.concat 267 | [ c_flags 268 | ; [ "-I"; t.stdlib_dir 269 | ; "-o"; exe_fname 270 | ; c_fname 271 | ] 272 | ; link_flags 273 | ]) 274 | in 275 | if ok then Ok exe_fname else Error () 276 | 277 | let c_test t ?c_flags ?link_flags code = 278 | match compile_c_prog t ?c_flags ?link_flags code with 279 | | Ok _ -> true 280 | | Error _ -> false 281 | 282 | module C_define = struct 283 | module Type = struct 284 | type t = 285 | | Switch 286 | | Int 287 | | String 288 | 289 | let compare x y = 290 | match x, y with 291 | | Switch, Switch -> 0 292 | | Int, Int -> 0 293 | | String, String -> 0 294 | | Switch, (Int | String) -> 1 295 | | (Int | String), Switch -> -1 296 | | Int, String -> 1 297 | | String, Int -> -1 298 | 299 | let sexp_of_t = function 300 | | Switch -> Sexp.Atom "switch" 301 | | Int -> Sexp.Atom "int" 302 | | String -> Sexp.Atom "string" 303 | 304 | let t_of_sexp = function 305 | | Sexp.Atom "switch" -> Switch 306 | | Sexp.Atom "int" -> Int 307 | | Sexp.Atom "string" -> String 308 | | s -> raise (Sexp.Of_sexp_error (Failure "C_define.Type.t_of_sexp", s)) 309 | end 310 | 311 | module Value = struct 312 | type t = 313 | | Switch of bool 314 | | Int of int 315 | | String of string 316 | 317 | let compare x y = 318 | match x, y with 319 | | Switch x, Switch y -> Bool.compare x y 320 | | Int x, Int y -> Int.compare x y 321 | | String x, String y -> String.compare x y 322 | | Switch _, (Int _ | String _) -> 1 323 | | (Int _ | String _), Switch _ -> -1 324 | | Int _, String _ -> 1 325 | | String _, Int _ -> -1 326 | 327 | let sexp_of_t = 328 | let open Sexp in 329 | function 330 | | Switch b -> List [Atom "switch"; Bool.sexp_of_t b] 331 | | Int i -> List [Atom "int"; Int.sexp_of_t i] 332 | | String s -> List [Atom "string"; String.sexp_of_t s] 333 | 334 | let t_of_sexp = 335 | let open Sexp in 336 | function 337 | | List [Atom "switch"; x] -> Switch (Bool.t_of_sexp x) 338 | | List [Atom "int"; x] -> Int (Int.t_of_sexp x) 339 | | List [Atom "string"; x] -> String (String.t_of_sexp x) 340 | | s -> raise (Sexp.Of_sexp_error (Failure "C_define.Value.t_of_sexp", s)) 341 | end 342 | 343 | let import t ?c_flags ?link_flags ~includes vars = 344 | let buf = Buffer.create 1024 in 345 | let pr fmt = Printf.bprintf buf (fmt ^^ "\n") in 346 | let includes = "stdio.h" :: includes in 347 | List.iter includes ~f:(pr "#include <%s>"); 348 | pr ""; 349 | pr "int main()"; 350 | pr "{"; 351 | List.iter vars ~f:(fun (name, (kind : Type.t)) -> 352 | match kind with 353 | | Switch -> 354 | pr {|#if defined(%s)|} name; 355 | pr {| printf("%s=b:true\n");|} name; 356 | pr {|#else|}; 357 | pr {| printf("%s=b:false\n");|} name; 358 | pr {|#endif|} 359 | | Int -> 360 | pr {| printf("%s=i:%%d\n", %s);|} name name 361 | | String -> 362 | pr {| printf("%s=s:%%s\n", %s);|} name name); 363 | pr " return 0;"; 364 | pr "}"; 365 | let code = Buffer.contents buf in 366 | match compile_c_prog t ?c_flags ?link_flags code with 367 | | Error () -> die "failed to compile program" 368 | | Ok exe -> 369 | run_capture_exn t ~dir:(Fn.dirname exe) (command_line exe []) 370 | |> String.split_lines 371 | |> List.map ~f:(fun s : (string * Value.t) -> 372 | let var, data = String.lsplit2_exn s ~on:'=' in 373 | (var, 374 | match String.lsplit2_exn data ~on:':' with 375 | | "b", s -> Switch (Bool.of_string s) 376 | | "i", s -> Int (Int. of_string s) 377 | | "s", s -> String s 378 | | _ -> assert false)) 379 | 380 | let gen_header_file t ~fname ?protection_var vars = 381 | let protection_var = 382 | match protection_var with 383 | | Some v -> v 384 | | None -> 385 | String.map (t.name ^ "_" ^ Fn.basename fname) ~f:(function 386 | | 'a'..'z' as c -> Char.uppercase c 387 | | 'A'..'Z' | '0'..'9' as c -> c 388 | | _ -> '_') 389 | in 390 | let vars = List.sort vars ~compare:(fun (a, _) (b, _) -> String.compare a b) in 391 | let lines = 392 | List.map vars ~f:(fun (name, value) -> 393 | match (value : Value.t) with 394 | | Switch false -> sprintf "#undef %s" name 395 | | Switch true -> sprintf "#define %s" name 396 | | Int n -> sprintf "#define %s (%d)" name n 397 | | String s -> sprintf "#define %s %S" name s) 398 | in 399 | let lines = 400 | List.concat 401 | [ [ sprintf "#ifndef %s" protection_var 402 | ; sprintf "#define %s" protection_var 403 | ] 404 | ; lines 405 | ; [ "#endif" ] 406 | ] 407 | in 408 | logf t "writing header file %s" fname; 409 | List.iter lines ~f:(logf t " | %s"); 410 | let tmp_fname = fname ^ ".tmp" in 411 | Out_channel.write_lines tmp_fname lines; 412 | Sys.rename tmp_fname fname 413 | end 414 | 415 | 416 | let find_in_path t prog = 417 | logf t "find_in_path: %s" prog; 418 | let x = Find_in_path.find prog in 419 | logf t "-> %s" 420 | (match x with 421 | | None -> "not found" 422 | | Some fn -> "found: " ^ quote fn); 423 | x 424 | 425 | module Pkg_config = struct 426 | type nonrec t = 427 | { pkg_config : string 428 | ; configurator : t 429 | } 430 | 431 | let get c = 432 | Option.map (find_in_path c "pkg-config") ~f:(fun pkg_config -> 433 | { pkg_config; configurator = c }) 434 | 435 | type package_conf = 436 | { libs : string list 437 | ; cflags : string list 438 | } 439 | 440 | let query t ~package = 441 | let package = quote package in 442 | let pkg_config = quote t.pkg_config in 443 | let c = t.configurator in 444 | let dir = c.dest_dir in 445 | let env = 446 | match ocaml_config_var c "system" with 447 | | Some "macosx" -> begin 448 | match find_in_path c "brew" with 449 | | Some brew -> 450 | let prefix = 451 | String.strip (run_capture_exn c ~dir (command_line brew ["--prefix"])) 452 | in 453 | sprintf "env PKG_CONFIG_PATH=%s/opt/%s/lib/pkgconfig:$PKG_CONFIG_PATH " 454 | (quote prefix) package 455 | | None -> 456 | "" 457 | end 458 | | _ -> "" 459 | in 460 | if run_ok c ~dir (sprintf "%s%s %s" env pkg_config package) then 461 | let run what = 462 | match 463 | String.strip 464 | (run_capture_exn c ~dir (sprintf "%s%s %s %s" env pkg_config what package)) 465 | with 466 | | "" -> [] 467 | | s -> String.split s ~on:' ' 468 | in 469 | Some 470 | { libs = run "--libs" 471 | ; cflags = run "--cflags" 472 | } 473 | else 474 | None 475 | end 476 | 477 | let main ?(args=[]) ~name f = 478 | let ocamlc = ref None in 479 | let verbose = ref false in 480 | let dest_dir = ref None in 481 | let args = 482 | Arg.align 483 | ([ "-ocamlc", Arg.String (fun s -> ocamlc := Some s), 484 | "PATH ocamlc command to use" 485 | ; "-verbose", Arg.Set verbose, 486 | " be verbose" 487 | ; "-dest-dir", Arg.String (fun s -> dest_dir := Some s), 488 | "DIR save temporary files to this directory" 489 | ] @ args) 490 | in 491 | let anon s = raise (Arg.Bad (sprintf "don't know what to do with %s" s)) in 492 | let usage = sprintf "%s [OPTIONS]" (Fn.basename Sys.executable_name) in 493 | Arg.parse args anon usage; 494 | let log_db = ref [] in 495 | let log s = log_db := s :: !log_db in 496 | let t = 497 | create 498 | ?dest_dir:!dest_dir 499 | ?ocamlc:!ocamlc 500 | ~log:(if !verbose then prerr_endline else log) 501 | name 502 | in 503 | try 504 | f t 505 | with exn -> 506 | List.iter (List.rev !log_db) ~f:(eprintf "%s\n"); 507 | match exn with 508 | | Fatal_error msg -> 509 | eprintf "Error: %s\n%!" msg; 510 | Caml.exit 1 511 | | exn -> raise exn 512 | -------------------------------------------------------------------------------- /src/configurator.mli: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | type t 4 | 5 | val create 6 | : ?dest_dir:string 7 | -> ?ocamlc:string 8 | -> ?log:(string -> unit) 9 | -> string (** name, such as library name *) 10 | -> t 11 | 12 | (** Return the value associated to a variable in the output of [ocamlc -config] *) 13 | val ocaml_config_var : t -> string -> string option 14 | val ocaml_config_var_exn : t -> string -> string 15 | 16 | (** [c_test t ?c_flags ?link_flags c_code] try to compile and link the C code given in 17 | [c_code]. Return whether compilation was successful. *) 18 | val c_test 19 | : t 20 | -> ?c_flags: string list (** default: [] *) 21 | -> ?link_flags:string list (** default: [] *) 22 | -> string 23 | -> bool 24 | 25 | module C_define : sig 26 | module Type : sig 27 | type t = 28 | | Switch (** defined/undefined *) 29 | | Int 30 | | String 31 | 32 | val sexp_of_t : t -> Sexp.t 33 | val t_of_sexp : Sexp.t -> t 34 | 35 | val compare : t -> t -> int 36 | end 37 | 38 | module Value : sig 39 | type t = 40 | | Switch of bool 41 | | Int of int 42 | | String of string 43 | 44 | val sexp_of_t : t -> Sexp.t 45 | val t_of_sexp : Sexp.t -> t 46 | 47 | val compare : t -> t -> int 48 | end 49 | 50 | (** Import some #define from the given header files. For instance: 51 | 52 | {[ 53 | # C.C_define.import c ~includes:"caml/config.h" ["ARCH_SIXTYFOUR", Switch];; 54 | - (string * Configurator.C_define.Value.t) list = ["ARCH_SIXTYFOUR", Switch true] 55 | ]} 56 | *) 57 | val import 58 | : t 59 | -> ?c_flags: string list 60 | -> ?link_flags:string list 61 | -> includes: string list 62 | -> (string * Type.t ) list 63 | -> (string * Value.t) list 64 | 65 | (** Generate a C header file containing the following #define. [protection_var] is used 66 | to enclose the file with: 67 | 68 | {[ 69 | #ifndef BLAH 70 | #define BLAH 71 | ... 72 | #endif 73 | ]} 74 | 75 | If not specified, it is inferred from the name given to [create] and the 76 | filename. *) 77 | val gen_header_file 78 | : t 79 | -> fname:string 80 | -> ?protection_var:string 81 | -> (string * Value.t) list -> unit 82 | end 83 | 84 | module Pkg_config : sig 85 | type configurator = t 86 | type t 87 | 88 | (** Returns [None] if pkg-config is not installed *) 89 | val get : configurator -> t option 90 | 91 | type package_conf = 92 | { libs : string list 93 | ; cflags : string list 94 | } 95 | 96 | (** Returns [None] if [package] is not available *) 97 | val query : t -> package:string -> package_conf option 98 | end with type configurator := t 99 | 100 | (** Typical entry point for configurator programs *) 101 | val main 102 | : ?args:(Caml.Arg.key * Caml.Arg.spec * Caml.Arg.doc) list 103 | -> name:string 104 | -> (t -> unit) 105 | -> unit 106 | 107 | (** Abort execution. If raised from within [main], the argument of [die] is printed as 108 | [Error: ]. *) 109 | val die : ('a, unit, string, 'b) format4 -> 'a 110 | -------------------------------------------------------------------------------- /src/jbuild: -------------------------------------------------------------------------------- 1 | (library 2 | ((name configurator) 3 | (public_name configurator) 4 | (libraries (base stdio unix)) 5 | (preprocess no_preprocessing))) 6 | 7 | 8 | (jbuild_version 1) 9 | --------------------------------------------------------------------------------