This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,3 @@
(env
(_
(flags :standard \ -alert -unstable)))

View file

@ -0,0 +1 @@
module V1 = V1

View file

@ -0,0 +1,17 @@
(ocamllex extract_obj)
(library
(name configurator)
(public_name dune-configurator)
(private_modules import dune_lang ocaml_config)
(libraries unix csexp)
(flags
(:standard
-safe-string
(:include flags/flags.sexp)))
(special_builtin_support
(configurator
(api_version 1))))
(documentation
(package dune-configurator))

View file

@ -0,0 +1,87 @@
open Import
module Escape : sig
val quoted : string -> string
end = struct
let quote_length s =
let n = ref 0 in
let len = String.length s in
for i = 0 to len - 1 do
n
:= !n
+
match String.unsafe_get s i with
| '\"' | '\\' | '\n' | '\t' | '\r' | '\b' -> 2
| '%' -> if i + 1 < len && s.[i + 1] = '{' then 2 else 1
| ' ' .. '~' -> 1
| _ -> 4
done;
!n
;;
let escape_to s ~dst:s' ~ofs =
let n = ref ofs in
let len = String.length s in
for i = 0 to len - 1 do
(match String.unsafe_get s i with
| ('\"' | '\\') as c ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n c
| '\n' ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n 'n'
| '\t' ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n 't'
| '\r' ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n 'r'
| '\b' ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n 'b'
| '%' when i + 1 < len && s.[i + 1] = '{' ->
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n '%'
| ' ' .. '~' as c -> Bytes.unsafe_set s' !n c
| c ->
let a = Char.code c in
Bytes.unsafe_set s' !n '\\';
incr n;
Bytes.unsafe_set s' !n (Char.unsafe_chr (48 + (a / 100)));
incr n;
Bytes.unsafe_set s' !n (Char.unsafe_chr (48 + (a / 10 mod 10)));
incr n;
Bytes.unsafe_set s' !n (Char.unsafe_chr (48 + (a mod 10))));
incr n
done
;;
(* Surround [s] with quotes, escaping it if necessary. *)
let quoted s =
let len = String.length s in
let n = quote_length s in
let s' = Bytes.create (n + 2) in
Bytes.unsafe_set s' 0 '"';
if len = 0 || n > len
then escape_to s ~dst:s' ~ofs:1
else Bytes.blit_string ~src:s ~src_pos:0 ~dst:s' ~dst_pos:1 ~len;
Bytes.unsafe_set s' (n + 1) '"';
Bytes.unsafe_to_string s'
;;
end
type t =
| Quoted_string of string
| List of t list
let rec to_string t =
match t with
| Quoted_string s -> Escape.quoted s
| List l -> Printf.sprintf "(%s)" (List.map l ~f:to_string |> String.concat ~sep:" ")
;;

View file

@ -0,0 +1,6 @@
(** Subset of dune_lang to print flag lists *)
type t =
| Quoted_string of string
| List of t list
val to_string : t -> string

View file

@ -0,0 +1,3 @@
(** Read and extract the strings between a pair of BEGIN-\d+- and -END
delimiters. This is used to extract the compile time values from .obj files *)
val extract : (int * string) list -> Lexing.lexbuf -> (int * string) list

View file

@ -0,0 +1,11 @@
{}
rule extract acc = parse
| "BEGIN-" (['0' - '9']+ as i) "-"
{ read acc (int_of_string i) (Buffer.create 8) lexbuf }
| _ { extract acc lexbuf }
| eof { List.rev acc }
and read acc i b = parse
| "-END" { extract ((i, Buffer.contents b) :: acc) lexbuf }
| _ as c { Buffer.add_char b c; read acc i b lexbuf }
| eof { failwith "Unterminated BEGIN-" }
{}

View file

@ -0,0 +1,7 @@
(executable
(name mk))
(rule
(with-stdout-to
flags.sexp
(run ./mk.exe -ocamlv %{ocaml_version})))

View file

@ -0,0 +1,18 @@
open Printf
let parse_version s = Scanf.sscanf s "%d.%d.%d" (fun a b c -> a, b, c)
let () =
let usage = sprintf "%s -ocamlv version" (Filename.basename Sys.executable_name) in
let ocaml_version = ref "" in
let anon _ = raise (Arg.Bad "anonymous arguments aren't accepted") in
Arg.parse
[ "-ocamlv", Arg.String (fun s -> ocaml_version := s), "Version of ocaml being used" ]
anon
usage;
if !ocaml_version = ""
then raise (Arg.Bad "Provide version with -ocamlv")
else (
let x, y, _ = parse_version !ocaml_version in
if x >= 4 && y > 2 then printf "()\n" else printf "(-w -50)\n")
;;

View file

@ -0,0 +1,347 @@
let sprintf = Printf.sprintf
let eprintf = Printf.eprintf
let ( ^/ ) = Filename.concat
exception Fatal_error of string
let die fmt = Printf.ksprintf (fun s -> raise (Fatal_error s)) fmt
let warn fmt = Printf.ksprintf (fun msg -> prerr_endline ("Warning: " ^ msg)) fmt
module Result = struct
type ('a, 'b) t = ('a, 'b) result =
| Ok of 'a
| Error of 'b
let to_option = function
| Ok x -> Some x
| Error _ -> None
;;
end
module Exn = struct
external reraise : exn -> _ = "%reraise"
let protectx x ~f ~finally =
match f x with
| y ->
finally x;
y
| exception e ->
finally x;
raise e
;;
let protect ~f ~finally = protectx () ~f ~finally
include struct
[@@@ocaml.warning "-32"]
let raise_with_backtrace exn _bt = reraise exn
end
include Printexc
end
module Option = struct
let map t ~f =
match t with
| None -> None
| Some x -> Some (f x)
;;
let some_if cond x = if cond then Some x else None
let some x = Some x
let iter t ~f =
match t with
| None -> ()
| Some x -> f x
;;
module O = struct
let ( >>= ) x f =
match x with
| None -> None
| Some x -> f x
;;
let ( >>| ) x f = map x ~f
end
end
module List = struct
include ListLabels
let rec find_map l ~f =
match l with
| [] -> None
| x :: l ->
(match f x with
| None -> find_map l ~f
| Some _ as res -> res)
;;
end
module Array = ArrayLabels
module Bool = struct
let of_string s =
match bool_of_string s with
| s -> Some s
| exception Invalid_argument _ -> None
;;
end
module Map (S : Map.OrderedType) = struct
module M = MoreLabels.Map.Make (S)
include M
let update (type a) (t : a t) (key : M.key) ~(f : a option -> a option) : a t =
let v =
match find key t with
| exception Not_found -> None
| v -> Some v
in
match f v, v with
| None, None -> t
| None, Some _ -> remove key t
| Some data, _ -> add ~key ~data t
;;
let find m k =
match find k m with
| exception Not_found -> None
| s -> Some s
;;
let set t k v = add ~key:k ~data:v t
let of_list =
let rec loop acc = function
| [] -> Result.Ok acc
| (k, v) :: l ->
(match find acc k with
| None -> loop (set acc k v) l
| Some v_old -> Error (k, v_old, v))
in
fun l -> loop empty l
;;
let of_list_exn l =
match of_list l with
| Ok s -> s
| Error (_, _, _) -> failwith "Map.of_list_exn: duplicate key"
;;
end
module Int = struct
let of_string s =
match int_of_string s with
| s -> Some s
| exception Failure _ -> None
;;
module Map = struct
include Map (struct
type t = int
let compare = compare
end)
end
end
module Bytes = struct
include struct
[@@@ocaml.warning "-32"]
let blit_string ~(src : string) ~src_pos ~(dst : Bytes.t) ~dst_pos ~len =
for i = 0 to len - 1 do
Bytes.set dst (i + dst_pos) src.[i + src_pos]
done
;;
end
include BytesLabels
end
module String = struct
include StringLabels
module Map = Map (String)
let take s i = sub s ~pos:0 ~len:(min i (String.length s))
let drop s n =
let len = length s in
sub s ~pos:(min n len) ~len:(max (len - n) 0)
;;
let index s i =
match String.index s i with
| exception Not_found -> None
| s -> Some s
;;
let split_lines s =
let rec loop ~last_is_cr ~acc i j =
if j = length s
then (
let acc =
if j = i || (j = i + 1 && last_is_cr)
then acc
else sub s ~pos:i ~len:(j - i) :: acc
in
List.rev acc)
else (
match s.[j] with
| '\r' -> loop ~last_is_cr:true ~acc i (j + 1)
| '\n' ->
let line =
let len = if last_is_cr then j - i - 1 else j - i in
sub s ~pos:i ~len
in
loop ~acc:(line :: acc) (j + 1) (j + 1) ~last_is_cr:false
| _ -> loop ~acc i (j + 1) ~last_is_cr:false)
in
loop ~acc:[] 0 0 ~last_is_cr:false
;;
let exists =
let rec loop s i len f =
if i = len then false else f (unsafe_get s i) || loop s (i + 1) len f
in
fun s ~f -> loop s 0 (length s) f
;;
let is_empty = function
| "" -> true
| _ -> false
;;
let extract_words s ~is_word_char =
let rec skip_blanks i =
if i = length s
then []
else if is_word_char s.[i]
then parse_word i (i + 1)
else skip_blanks (i + 1)
and parse_word i j =
if j = length s
then [ sub s ~pos:i ~len:(j - i) ]
else if is_word_char s.[j]
then parse_word i (j + 1)
else sub s ~pos:i ~len:(j - i) :: skip_blanks (j + 1)
in
skip_blanks 0
;;
let extract_comma_space_separated_words s =
extract_words s ~is_word_char:(function
| ',' | ' ' | '\t' | '\n' -> false
| _ -> true)
;;
let extract_blank_separated_words s =
extract_words s ~is_word_char:(function
| ' ' | '\t' -> false
| _ -> true)
;;
let split s ~on =
let rec loop i j =
if j = length s
then [ sub s ~pos:i ~len:(j - i) ]
else if s.[j] = on
then sub s ~pos:i ~len:(j - i) :: loop (j + 1) (j + 1)
else loop i (j + 1)
in
loop 0 0
;;
end
module Io = struct
let open_in ?(binary = true) fn = if binary then open_in_bin fn else open_in fn
let open_out ?(binary = true) fn = if binary then open_out_bin fn else open_out fn
let input_lines =
let rec loop ic acc =
match input_line ic with
| exception End_of_file -> List.rev acc
| line -> loop ic (line :: acc)
in
fun ic -> loop ic []
;;
let with_file_in ?binary fn ~f = Exn.protectx (open_in ?binary fn) ~finally:close_in ~f
let with_file_out ?binary p ~f = Exn.protectx (open_out ?binary p) ~finally:close_out ~f
let write_file ?binary fn data =
with_file_out ?binary fn ~f:(fun oc -> output_string oc data)
;;
let write_lines ?binary fn lines =
with_file_out ?binary fn ~f:(fun oc ->
List.iter
~f:(fun line ->
output_string oc line;
output_string oc "\n")
lines)
;;
let read_all =
(* We use 65536 because that is the size of OCaml's IO buffers. *)
let chunk_size = 65536 in
(* Generic function for channels such that seeking is unsupported or
broken *)
let read_all_generic t buffer =
let rec loop () =
Buffer.add_channel buffer t chunk_size;
loop ()
in
try loop () with
| End_of_file -> Buffer.contents buffer
in
fun t ->
(* Optimisation for regular files: if the channel supports seeking, we
compute the length of the file so that we read exactly what we need and
avoid an extra memory copy. We expect that most files Dune reads are
regular files so this optimizations seems worth it. *)
match in_channel_length t with
| exception Sys_error _ -> read_all_generic t (Buffer.create chunk_size)
| n ->
let s = really_input_string t n in
(* For some files [in_channel_length] returns an invalid value. For
instance for files in /proc it returns [0]. So we try to read one
more character to make sure we did indeed reach the end of the
file *)
(match input_char t with
| exception End_of_file -> s
| c ->
(* The [+ chunk_size] is to make sure there is at least [chunk_size]
free space so that the first [Buffer.add_channel buffer t
chunk_size] in [read_all_generic] does not grow the buffer. *)
let buffer = Buffer.create (String.length s + 1 + chunk_size) in
Buffer.add_string buffer s;
Buffer.add_char buffer c;
read_all_generic t buffer)
;;
let read_file ?binary fn = with_file_in fn ~f:read_all ?binary
let with_lexbuf_from_file fn ~f =
with_file_in fn ~f:(fun ic ->
let lb = Lexing.from_channel ic in
lb.lex_curr_p <- { pos_fname = fn; pos_lnum = 1; pos_bol = 0; pos_cnum = 0 };
f lb)
;;
end
module Sexp = struct
module T = struct
type t =
| Atom of string
| List of t list
end
include T
include Csexp.Make (T)
end

View file

@ -0,0 +1,62 @@
{1 [dune-configurator] - Helper library for gathering system configuration }
[dune-configurator] is a small library that helps writing OCaml scripts that
test features available on the system, in order to generate [config.h]
files for instance.
Among other things, dune-configurator allows one to:
- test if a C program compiles
- query [pkg-config]
- import [#define] from OCaml header files
- generate a [config.h] file
{2 API Documentation }
The entry point for this library is {!Configurator.V1}.
{2 Example }
The following happens in a [dune] project that contains some C code that needs
to link against [libpng].
The following program ([discover/discover.ml]) uses [dune-configurator] to
query [pkg-config] and create [cflags.sexp] and [libs.sexp]:
{[
let () =
Configurator.V1.main ~name:"libpng"
(fun c ->
let pkg_config =
match Configurator.V1.Pkg_config.get c with
| Some p -> p
| None -> failwith "Cannot find pkg-config"
in
let conf = Configurator.V1.Pkg_config.query ~package:"libpng" in
Configurator.V1.Flags.write_sexp "cflags.sexp" conf.cflags;
Configurator.V1.Flags.write_sexp "libs.sexp" conf.libs)
]}
It can be built using the following [discover/dune] file:
{v
(executable
(name discover)
(libraries dune-configurator))
(rule
(targets cflags.sexp libs.sexp)
(action
(run ./discover.exe)))
v}
And used when building the C code in the following [dune] file:
{v
(library
(name png)
(foreign_stubs
(language c)
(names bindings)
(flags :standard (:include discover/cflags.sexp)))
(c_library_flags :standard (:include discover/libs.sexp)))
v}

View file

@ -0,0 +1,29 @@
open Import
module Vars = struct
type t = string String.Map.t
let of_lines lines =
let rec loop acc = function
| [] -> Ok acc
| line :: lines ->
(match String.index line ':' with
| Some i ->
let x =
(* skipping 2 chars because we also need to skip the space *)
String.take line i, String.drop line (i + 2)
in
loop (x :: acc) lines
| None -> Error (Printf.sprintf "Unrecognized line: %S" line))
in
match loop [] lines with
| Error _ as e -> e
| Ok s ->
(match String.Map.of_list s with
| Ok _ as s -> s
| Error (var, _, _) -> Error (sprintf "Variable %S present twice." var))
;;
let of_list_exn = String.Map.of_list_exn
let find t x = String.Map.find t x
end

View file

@ -0,0 +1,11 @@
(** Represent the parsed but uninterpreted output of [ocamlc -config] or
contents of [Makefile.config]. *)
module Vars : sig
type t
val find : t -> string -> string option
val of_list_exn : (string * string) list -> t
(** Parse the output of [ocamlc -config] given as a list of lines. *)
val of_lines : string list -> (t, string) result
end

View file

@ -0,0 +1,812 @@
open Import
let die = die
type t =
{ name : string
; dest_dir : string
; log : string -> unit
; mutable counter : int
; ext_obj : string
; c_compiler : string
; stdlib_dir : string
; ccomp_type : string
; c_libraries : string list
; ocamlc_config : Ocaml_config.Vars.t
; ocamlc_config_cmd : string
}
let rec rm_rf dir =
Array.iter (Sys.readdir dir) ~f:(fun fn ->
let fn = dir ^/ fn in
if Sys.is_directory fn then rm_rf fn else Unix.unlink fn);
Unix.rmdir dir
;;
module Temp = struct
(* Copied from filename.ml and adapted for directories *)
let prng = lazy (Random.State.make_self_init ())
let gen_name ~temp_dir ~prefix ~suffix =
let rnd = Random.State.bits (Lazy.force prng) land 0xFFFFFF in
temp_dir ^/ Printf.sprintf "%s%06x%s" prefix rnd suffix
;;
let create ~prefix ~suffix ~mk =
let temp_dir = Filename.get_temp_dir_name () in
let rec try_name counter =
let name = gen_name ~temp_dir ~prefix ~suffix in
match mk name with
| () -> name
| exception Unix.Unix_error _ when counter < 1000 -> try_name (counter + 1)
in
try_name 0
;;
let create_temp_dir ~prefix ~suffix =
let dir = create ~prefix ~suffix ~mk:(fun name -> Unix.mkdir name 0o700) in
at_exit (fun () -> rm_rf dir);
dir
;;
end
module Flags = struct
let extract_words = String.extract_words
let extract_comma_space_separated_words = String.extract_comma_space_separated_words
let extract_blank_separated_words = String.extract_blank_separated_words
let write_lines path s = Io.write_lines path s
let write_sexp path s =
let sexp = Dune_lang.List (List.map s ~f:(fun s -> Dune_lang.Quoted_string s)) in
Io.write_file path (Dune_lang.to_string sexp)
;;
end
module Find_in_path = struct
let path_sep = if Sys.win32 then ';' else ':'
let get_path () =
match Sys.getenv "PATH" with
| exception Not_found -> []
| s -> String.split s ~on:path_sep
;;
let exe = if Sys.win32 then ".exe" else ""
let prog_not_found prog = die "Program %s not found in PATH" prog
let best_prog dir prog =
let fn = dir ^/ prog ^ ".opt" ^ exe in
if Sys.file_exists fn
then Some fn
else (
let fn = dir ^/ prog ^ exe in
if Sys.file_exists fn then Some fn else None)
;;
let find_ocaml_prog prog =
match List.find_map (get_path ()) ~f:(fun dir -> best_prog dir prog) with
| None -> prog_not_found prog
| Some fn -> fn
;;
let which prog =
if Filename.is_implicit prog
then
List.find_map (get_path ()) ~f:(fun dir ->
let fn = dir ^/ prog ^ exe in
Option.some_if (Sys.file_exists fn) fn)
else (
let fn = if Filename.check_suffix prog exe then prog else prog ^ exe in
Option.some_if (Sys.file_exists fn) fn)
;;
end
let logf t fmt = Printf.ksprintf t.log fmt
let gen_id t =
let n = t.counter in
t.counter <- n + 1;
n
;;
let quote_if_needed =
let need_quote = function
| ' ' | '\"' -> true
| _ -> false
in
fun s ->
if String.is_empty s || String.exists ~f:need_quote s then Filename.quote s else s
;;
module Process = struct
type result =
{ exit_code : int
; stdout : string
; stderr : string
}
let command_line prog args =
String.concat ~sep:" " (List.map (prog :: args) ~f:quote_if_needed)
;;
let run_process t ?dir ?env prog args =
let prog_command_line = command_line prog args in
logf t "run: %s" prog_command_line;
let n = gen_id t in
let create_process =
let args = Array.of_list (prog :: args) in
match env with
| None -> Unix.create_process prog args
| Some env ->
let env = Array.of_list env in
Unix.create_process_env prog args env
in
let stdout_fn = t.dest_dir ^/ sprintf "stdout-%d" n in
let stderr_fn = t.dest_dir ^/ sprintf "stderr-%d" n in
let status =
let run () =
let openfile f =
Unix.openfile f [ O_WRONLY; O_CREAT; O_TRUNC; O_SHARE_DELETE ] 0o666
in
let stdout = openfile stdout_fn in
let stderr = openfile stderr_fn in
let stdin, stdin_w = Unix.pipe () in
Unix.close stdin_w;
let p = create_process stdin stdout stderr in
Unix.close stdin;
Unix.close stdout;
Unix.close stderr;
let _pid, status = Unix.waitpid [] p in
status
in
match dir with
| None -> run ()
| Some d ->
let old_dir = Sys.getcwd () in
Exn.protect
~f:(fun () ->
Sys.chdir d;
run ())
~finally:(fun () -> Sys.chdir old_dir)
in
match status with
| Unix.WSIGNALED signal -> die "signal %d killed process: %s" signal prog_command_line
| WSTOPPED signal -> die "signal %d stopped process: %s" signal prog_command_line
| WEXITED exit_code ->
logf t "-> process exited with code %d" exit_code;
let stdout = Io.read_file stdout_fn in
let stderr = Io.read_file stderr_fn in
logf t "-> stdout:";
List.iter (String.split_lines stdout) ~f:(logf t " | %s");
logf t "-> stderr:";
List.iter (String.split_lines stderr) ~f:(logf t " | %s");
{ exit_code; stdout; stderr }
;;
(* [cmd] which cannot be quoted (such as [t.c_compiler] which contains some
flags) followed by additional arguments. *)
let command_args cmd args =
String.concat ~sep:" " (cmd :: List.map args ~f:quote_if_needed)
;;
let run_command t ?dir ?(env = []) cmd =
logf t "run: %s" cmd;
let n = gen_id t in
let stdout_fn = t.dest_dir ^/ sprintf "stdout-%d" n in
let stderr_fn = t.dest_dir ^/ sprintf "stderr-%d" n in
let in_dir =
match dir with
| None -> ""
| Some dir -> sprintf "cd %s && " (Filename.quote dir)
in
let with_env =
match env with
| [] -> ""
| _ -> "env " ^ String.concat ~sep:" " env
in
let exit_code =
Printf.ksprintf
Sys.command
"%s%s %s > %s 2> %s"
in_dir
with_env
cmd
(Filename.quote stdout_fn)
(Filename.quote stderr_fn)
in
let stdout = Io.read_file stdout_fn in
let stderr = Io.read_file stderr_fn in
logf t "-> process exited with code %d" exit_code;
logf t "-> stdout:";
List.iter (String.split_lines stdout) ~f:(logf t " | %s");
logf t "-> stderr:";
List.iter (String.split_lines stderr) ~f:(logf t " | %s");
{ exit_code; stdout; stderr }
;;
let run_command_capture_exn t ?dir ?env cmd =
let { exit_code; stdout; stderr } = run_command t ?dir ?env cmd in
if exit_code <> 0
then die "command exited with code %d: %s" exit_code cmd
else if not (String.is_empty stderr)
then die "command has non-empty stderr: %s" cmd
else stdout
;;
let run_command_ok t ?dir ?env cmd = (run_command t ?dir ?env cmd).exit_code = 0
let run t ?dir ?env prog args = run_command t ?dir ?env (command_line prog args)
let run_capture_exn t ?dir ?env prog args =
run_command_capture_exn t ?dir ?env (command_line prog args)
;;
let run_ok t ?dir ?env prog args = run_command_ok t ?dir ?env (command_line prog args)
end
let ocaml_config_var t var = Ocaml_config.Vars.find t.ocamlc_config var
let ocaml_config_var_exn t var =
match Ocaml_config.Vars.find t.ocamlc_config var with
| None -> die "variable %S not found in the output of `%s`" var t.ocamlc_config_cmd
| Some s -> s
;;
type config =
{ ocamlc : string
; vars : Ocaml_config.Vars.t
}
let dune_is_too_old ~min:v =
die
"You seem to be running dune < %s. This version of dune-configurator requires at \
least dune %s."
v
v
;;
let read_dot_dune_configurator_file ~build_dir =
let file = Filename.concat build_dir ".dune/configurator.v2" in
if not (Sys.file_exists file) then dune_is_too_old ~min:"2.6";
let open Sexp in
let unable_to_parse err = die "Unable to parse %S.@.%s@." file err in
let sexp =
match Io.with_file_in file ~f:Sexp.input with
| Ok s -> s
| Error e -> unable_to_parse e
in
match sexp with
| Atom _ -> unable_to_parse "unexpected atom"
| List xs ->
let field name =
match
List.find_map xs ~f:(function
| List [ Atom name'; f ] when name = name' -> Some f
| _ -> None)
with
| None -> die "unable to find field %S" name
| Some f -> f
in
let ocamlc =
match field "ocamlc" with
| Atom o -> o
| _ -> die "invalid ocamlc field"
in
let vars =
let bindings =
match field "ocaml_config_vars" with
| List bindings ->
List.map bindings ~f:(function
| List [ Atom k; Atom v ] -> k, v
| _ -> die "invalid output")
| _ -> die "invalid output"
in
Ocaml_config.Vars.of_list_exn bindings
in
{ ocamlc; vars }
;;
let fill_in_fields_that_depends_on_ocamlc_config t =
let get = ocaml_config_var_exn t in
let get_flags var = get var |> String.trim |> Flags.extract_blank_separated_words in
let c_compiler, c_libraries =
match Ocaml_config.Vars.find t.ocamlc_config "c_compiler" with
| Some c_comp -> c_comp ^ " " ^ get "ocamlc_cflags", get_flags "native_c_libraries"
| None -> get "bytecomp_c_compiler", get_flags "bytecomp_c_libraries"
in
{ t with
ext_obj = get "ext_obj"
; c_compiler
; stdlib_dir = get "standard_library"
; ccomp_type = get "ccomp_type"
; c_libraries
}
;;
let create_from_inside_dune ~dest_dir ~log ~build_dir ~name =
let dest_dir =
match dest_dir with
| Some dir -> dir
| None -> Temp.create_temp_dir ~prefix:"ocaml-configurator" ~suffix:""
in
let { ocamlc; vars = ocamlc_config } = read_dot_dune_configurator_file ~build_dir in
let ocamlc_config_cmd = Process.command_line ocamlc [ "-config" ] in
fill_in_fields_that_depends_on_ocamlc_config
{ name
; log
; dest_dir
; counter = 0
; ocamlc_config
; ocamlc_config_cmd
; ext_obj = ""
; c_compiler = ""
; stdlib_dir = ""
; ccomp_type = ""
; c_libraries = []
}
;;
let create ?dest_dir ?ocamlc ?(log = ignore) name =
let inside_dune =
match Sys.getenv "INSIDE_DUNE" with
| exception Not_found -> None
| n -> Some n
in
match ocamlc, inside_dune with
| None, Some build_dir when build_dir <> "1" ->
create_from_inside_dune ~dest_dir ~log ~build_dir ~name
| _ ->
let dest_dir =
match dest_dir with
| Some dir -> dir
| None -> Temp.create_temp_dir ~prefix:"ocaml-configurator" ~suffix:""
in
let ocamlc =
match ocamlc with
| Some fn -> fn
| None -> Find_in_path.find_ocaml_prog "ocamlc"
in
let ocamlc_config_cmd = Process.command_line ocamlc [ "-config" ] in
let t =
{ name
; log
; dest_dir
; counter = 0
; ext_obj = ""
; c_compiler = ""
; stdlib_dir = ""
; ccomp_type = ""
; c_libraries = []
; ocamlc_config = Ocaml_config.Vars.of_list_exn []
; ocamlc_config_cmd
}
in
let ocamlc_config =
let ocamlc_config_output =
Process.run_command_capture_exn t ~dir:dest_dir ocamlc_config_cmd
|> String.split_lines
in
match Ocaml_config.Vars.of_lines ocamlc_config_output with
| Ok x -> x
| Error msg -> die "Failed to parse the output of '%s':@\n%s" ocamlc_config_cmd msg
in
fill_in_fields_that_depends_on_ocamlc_config { t with ocamlc_config }
;;
let is_msvc t =
match t.ccomp_type with
| "msvc" -> true
| _ -> false
;;
let compile_and_link_c_prog t ?(c_flags = []) ?(link_flags = []) code =
let dir = t.dest_dir ^/ sprintf "c-test-%d" (gen_id t) in
Unix.mkdir dir 0o777;
let base = dir ^/ "test" in
let c_fname = base ^ ".c" in
let exe_fname = base ^ ".exe" in
Io.write_file c_fname code;
logf t "compiling c program:";
List.iter (String.split_lines code) ~f:(logf t " | %s");
let run_ok args =
Process.run_command_ok t ~dir (Process.command_args t.c_compiler args)
in
let output_flag = if is_msvc t then [ "-Fe" ^ exe_fname ] else [ "-o"; exe_fname ] in
let ok =
run_ok
(List.concat
[ c_flags
; [ "-I"; t.stdlib_dir ]
; output_flag
; [ c_fname ]
; t.c_libraries
; link_flags
])
in
if ok then Ok () else Error ()
;;
let compile_c_prog t ?(c_flags = []) code =
let dir = t.dest_dir ^/ sprintf "c-test-%d" (gen_id t) in
Unix.mkdir dir 0o777;
let base = dir ^/ "test" in
let c_fname = base ^ ".c" in
let obj_fname = base ^ t.ext_obj in
Io.write_file c_fname code;
logf t "compiling c program:";
List.iter (String.split_lines code) ~f:(logf t " | %s");
let ok =
let output_flag = if is_msvc t then [ "-Fo" ^ obj_fname ] else [ "-o"; obj_fname ] in
Process.run_command_ok
t
~dir
(Process.command_args
t.c_compiler
(List.concat
[ c_flags
; [ "-I"; t.stdlib_dir ]
; output_flag
; [ "-c"; c_fname ]
; t.c_libraries
]))
in
if ok then Ok obj_fname else Error ()
;;
let c_test t ?c_flags ?link_flags code =
match compile_and_link_c_prog t ?c_flags ?link_flags code with
| Ok _ -> true
| Error _ -> false
;;
module C_define = struct
module Type = struct
type t =
| Switch
| Int
| String
let name = function
| Switch -> "bool"
| Int -> "int"
| String -> "string"
;;
end
module Value = struct
type t =
| Switch of bool
| Int of int
| String of string
end
let extract_program ?prelude includes vars =
let has_type t = List.exists vars ~f:(fun (_, t') -> t = t') in
let buf = Buffer.create 1024 in
let pr fmt = Printf.bprintf buf (fmt ^^ "\n") in
List.iter includes ~f:(pr "#include <%s>");
pr "";
Option.iter prelude ~f:(pr "%s");
if has_type Type.Int
then
pr
{|
#define DUNE_ABS(x) ((x >= 0)? x: -(x))
#define DUNE_D0(x) ('0'+(DUNE_ABS(x)/1 )%%10)
#define DUNE_D1(x) ('0'+(DUNE_ABS(x)/10 )%%10), DUNE_D0(x)
#define DUNE_D2(x) ('0'+(DUNE_ABS(x)/100 )%%10), DUNE_D1(x)
#define DUNE_D3(x) ('0'+(DUNE_ABS(x)/1000 )%%10), DUNE_D2(x)
#define DUNE_D4(x) ('0'+(DUNE_ABS(x)/10000 )%%10), DUNE_D3(x)
#define DUNE_D5(x) ('0'+(DUNE_ABS(x)/100000 )%%10), DUNE_D4(x)
#define DUNE_D6(x) ('0'+(DUNE_ABS(x)/1000000 )%%10), DUNE_D5(x)
#define DUNE_D7(x) ('0'+(DUNE_ABS(x)/10000000 )%%10), DUNE_D6(x)
#define DUNE_D8(x) ('0'+(DUNE_ABS(x)/100000000 )%%10), DUNE_D7(x)
#define DUNE_D9(x) ('0'+(DUNE_ABS(x)/1000000000)%%10), DUNE_D8(x)
#define DUNE_SIGN(x) ((x >= 0)? '0': '-')
|};
List.iteri vars ~f:(fun i (name, t) ->
match t with
| Type.Int ->
let c_arr_i =
let b = Buffer.create 8 in
let is = string_of_int i in
for i = 0 to String.length is - 1 do
Printf.bprintf b "'%c', " is.[i]
done;
Buffer.contents b
in
pr
{|
const char s%i[] = {
'B', 'E', 'G', 'I', 'N', '-', %s'-',
DUNE_SIGN((%s)),
DUNE_D9((%s)),
'-', 'E', 'N', 'D'
};
|}
i
c_arr_i
name
name
| String -> pr {|const char *s%i = "BEGIN-%i-" %s "-END";|} i i name
| Switch ->
pr
{|
#ifdef %s
const char *s%i = "BEGIN-%i-true-END";
#else
const char *s%i = "BEGIN-%i-false-END";
#endif
|}
name
i
i
i
i);
Buffer.contents buf
;;
let extract_values obj_file vars =
let values =
Io.with_lexbuf_from_file obj_file ~f:(Extract_obj.extract [])
|> List.fold_left ~init:Int.Map.empty ~f:(fun acc (key, v) ->
Int.Map.update acc key ~f:(function
| None -> Some [ v ]
| Some vs -> Some (v :: vs)))
in
List.mapi vars ~f:(fun i (name, t) ->
let raw_vals =
match Int.Map.find values i with
| Some v -> v
| None -> die "Unable to get value for %s" name
in
let parse_val_or_exn f =
let f x =
match f x with
| Some s -> s
| None ->
die
"Unable to read variable %S of type %s. Invalid value %S in %s found"
name
(Type.name t)
x
obj_file
in
let vs =
List.map ~f:(fun x -> x, f x) raw_vals
|> List.sort_uniq ~cmp:(fun (_, x) (_, y) -> compare x y)
in
match vs with
| [] -> assert false
| [ (_, v) ] -> v
| vs ->
let vs = List.map ~f:fst vs in
die
"Duplicate values for %s:\n%s"
name
(vs |> List.map ~f:(sprintf "- %s") |> String.concat ~sep:"\n")
in
let value =
match t with
| Type.Switch -> Value.Switch (parse_val_or_exn Bool.of_string)
| Int -> Value.Int (parse_val_or_exn Int.of_string)
| String -> String (parse_val_or_exn Option.some)
in
name, value)
;;
let import t ?prelude ?c_flags ~includes vars =
let program = extract_program ?prelude ("stdio.h" :: includes) vars in
match compile_c_prog t ?c_flags program with
| Error _ -> die "failed to compile program"
| Ok obj -> extract_values obj vars
;;
let gen_header_file t ~fname ?protection_var vars =
let protection_var =
match protection_var with
| Some v -> v
| None ->
String.map
(t.name ^ "_" ^ Filename.basename fname)
~f:(function
| 'a' .. 'z' as c -> Char.uppercase_ascii c
| ('A' .. 'Z' | '0' .. '9') as c -> c
| _ -> '_')
in
let vars = List.sort vars ~cmp:(fun (a, _) (b, _) -> compare a b) in
let lines =
List.map vars ~f:(fun (name, value) ->
match (value : Value.t) with
| Switch false -> sprintf "#undef %s" name
| Switch true -> sprintf "#define %s" name
| Int n -> sprintf "#define %s (%d)" name n
| String s -> sprintf "#define %s %S" name s)
in
let lines =
List.concat
[ [ sprintf "#ifndef %s" protection_var; sprintf "#define %s" protection_var ]
; lines
; [ "#endif" ]
]
in
logf t "writing header file %s" fname;
List.iter lines ~f:(logf t " | %s");
let tmp_fname = fname ^ ".tmp" in
Io.write_lines tmp_fname lines;
Sys.rename tmp_fname fname
;;
end
let which t prog =
logf t "which: %s" prog;
let x = Find_in_path.which prog in
logf
t
"-> %s"
(match x with
| None -> "not found"
| Some fn -> "found: " ^ quote_if_needed fn);
x
;;
module Pkg_config = struct
type nonrec t =
{ pkg_config : string
; pkg_config_args : string list
; configurator : t
}
let get c =
let get_pkg_config_args default =
match Sys.getenv "PKG_CONFIG_ARGN" with
| s -> String.split ~on:' ' s
| exception Not_found -> default
in
match Sys.getenv "PKG_CONFIG" with
| s ->
Option.map (which c s) ~f:(fun pkg_config ->
let pkg_config_args = get_pkg_config_args [] in
{ pkg_config; pkg_config_args; configurator = c })
| exception Not_found ->
(match which c "pkgconf" with
| None ->
Option.map (which c "pkg-config") ~f:(fun pkg_config ->
let pkg_config_args = get_pkg_config_args [] in
{ pkg_config; pkg_config_args; configurator = c })
| Some pkg_config ->
let pkg_config_args =
get_pkg_config_args
(match ocaml_config_var c "target" with
| None -> []
| Some target -> [ "--personality"; target ])
in
Some { pkg_config; pkg_config_args; configurator = c })
;;
type package_conf =
{ libs : string list
; cflags : string list
}
let gen_query t ~package ~expr =
let c = t.configurator in
let dir = c.dest_dir in
let expr =
match expr with
| Some e -> e
| None ->
if
String.exists package ~f:(function
| '=' | '>' | '<' -> true
| _ -> false)
then
warn
"Package name %S contains invalid characters. Use Pkg_config.query_expr to \
construct proper queries"
package;
package
in
let env =
match ocaml_config_var c "system" with
| Some "macosx" ->
let open Option.O in
which c "brew"
>>= fun brew ->
let new_pkg_config_path =
let prefix = String.trim (Process.run_capture_exn c ~dir brew [ "--prefix" ]) in
let p = sprintf "%s/opt/%s/lib/pkgconfig" (quote_if_needed prefix) package in
Option.some_if
(match Sys.is_directory p with
| s -> s
| exception Sys_error _ -> false)
p
in
new_pkg_config_path
>>| fun new_pkg_config_path ->
let _PKG_CONFIG_PATH = "PKG_CONFIG_PATH" in
let pkg_config_path =
match Sys.getenv _PKG_CONFIG_PATH with
| s -> s ^ ":"
| exception Not_found -> ""
in
[ sprintf "%s=%s%s" _PKG_CONFIG_PATH pkg_config_path new_pkg_config_path ]
| _ -> None
in
let pc_flags = "--print-errors" in
let { Process.exit_code; stderr; _ } =
Process.run_process c ~dir ?env t.pkg_config (t.pkg_config_args @ [ pc_flags; expr ])
in
if exit_code = 0
then (
let run what =
match
String.trim
(Process.run_capture_exn
c
~dir
?env
t.pkg_config
(t.pkg_config_args @ [ what; package ]))
with
| "" -> []
| s -> String.extract_blank_separated_words s
in
Ok { libs = run "--libs"; cflags = run "--cflags" })
else Error stderr
;;
let query t ~package = Result.to_option @@ gen_query t ~package ~expr:None
let query_expr t ~package ~expr =
Result.to_option @@ gen_query t ~package ~expr:(Some expr)
;;
let query_expr_err t ~package ~expr = gen_query t ~package ~expr:(Some expr)
end
let main ?(args = []) ~name f =
let build_dir =
match Sys.getenv "INSIDE_DUNE" with
| exception Not_found ->
die
"Configurator scripts must be run with Dune. To manually run a script, use $ \
dune exec."
| "1" -> dune_is_too_old ~min:"2.3"
| s -> s
in
let verbose = ref false in
let dest_dir = ref None in
let args =
Arg.align
([ "-verbose", Arg.Set verbose, " be verbose"
; ( "-dest-dir"
, Arg.String (fun s -> dest_dir := Some s)
, "DIR save temporary files to this directory" )
]
@ args)
in
let anon s = raise (Arg.Bad (sprintf "don't know what to do with %s" s)) in
let usage = sprintf "%s [OPTIONS]" (Filename.basename Sys.executable_name) in
Arg.parse args anon usage;
let log_db = ref [] in
let log s = log_db := s :: !log_db in
try
let t =
create_from_inside_dune
~dest_dir:!dest_dir
~log:(if !verbose then prerr_endline else log)
~build_dir
~name
in
f t
with
| exn ->
let bt = Printexc.get_raw_backtrace () in
List.iter (List.rev !log_db) ~f:(eprintf "%s\n");
(match exn with
| Fatal_error msg ->
eprintf "Error: %s\n%!" msg;
exit 1
| _ -> Exn.raise_with_backtrace exn bt)
;;

View file

@ -0,0 +1,185 @@
type t
val create
: ?dest_dir:string
-> ?ocamlc:string
-> ?log:(string -> unit)
-> string (** name, such as library name *)
-> t
(** Return the value associated to a variable in the output of [ocamlc -config] *)
val ocaml_config_var : t -> string -> string option
val ocaml_config_var_exn : t -> string -> string
(** [c_test t ?c_flags ?link_flags c_code] try to compile and link the C code
given in [c_code]. Return whether compilation was successful. *)
val c_test
: t
-> ?c_flags:string list (** default: [] *)
-> ?link_flags:string list (** default: [] *)
-> string
-> bool
module C_define : sig
module Type : sig
type t =
| Switch (** defined/undefined *)
| Int
| String
end
module Value : sig
type t =
| Switch of bool
| Int of int
| String of string
end
(** Import some #define from the given header files. For instance:
{v
# C.C_define.import c ~includes:"caml/config.h" ["ARCH_SIXTYFOUR", Switch];;
- (string * Configurator.C_define.Value.t) list = ["ARCH_SIXTYFOUR", Switch true]
v} *)
val import
: t
-> ?prelude:string
(** Define extra code be used with extracting values below. Note that
the compiled code is never executed. *)
-> ?c_flags:string list
-> includes:string list
-> (string * Type.t) list
-> (string * Value.t) list
(** Generate a C header file containing the following #define.
[protection_var] is used to enclose the file with:
{[
#ifndef BLAH #define BLAH ... #endif
]}
If not specified, it is inferred from the name given to [create] and the
filename. *)
val gen_header_file
: t
-> fname:string
-> ?protection_var:string
-> (string * Value.t) list
-> unit
end
module Pkg_config : sig
type configurator = t
type t
(** Search a pkg-config implementation in PATH. Use the one
defined in [PKG_CONFIG] environment variable if set else try
[pkgconf] then [pkg-config]. Append the [PKG_CONFIG_PATH]
environment variable to the searched pathes. Returns [None] if
nothing is not found. *)
val get : configurator -> t option
type package_conf =
{ libs : string list
; cflags : string list
}
(** [query t ~package] query pkg-config for the [package]. The package must
not contain a version constraint. Multiple, unversioned packages are
separated with spaces, for example "gtk+-3.0 gtksourceview-3.0". By
default, the OCaml compiler [target] is passed to pkgconf as
[--personality] argument. An alternative list of arguments can be
specified by setting the [PKG_CONFIG_ARGN] environment variable.
Returns [None] if [package] is not available *)
val query : t -> package:string -> package_conf option
val query_expr : t -> package:string -> expr:string -> package_conf option
[@@ocaml.deprecated "please use [query_expr_err]"]
(** [query_expr_err t ~package ~expr] query pkg-config for the [package].
[expr] may contain a version constraint, for example "gtk+-3.0 >= 3.18".
[package] must be just the name of the package. If [expr] is specified,
[package] must be specified as well. By default, the OCaml compiler
"target" is passed to pkgconf as [--personality] argument. An
alternative list of arguments can be specified by setting the
[PKG_CONFIG_ARGN] environment variable.
Returns [Error error_msg] if [package] is not available *)
val query_expr_err
: t
-> package:string
-> expr:string
-> (package_conf, string) result
end
with type configurator := t
module Flags : sig
(** [write_sexp fname s] writes the list of strings [s] to the file [fname] in
an appropriate format so that it can used in [dune] files with
[(:include [fname])]. *)
val write_sexp : string -> string list -> unit
(** [write_lines fname s] writes the list of string [s] to the file [fname]
with one line per string so that it can be used in Dune action rules with
[%{read-lines:<path>}]. *)
val write_lines : string -> string list -> unit
(** [extract_comma_space_separated_words s] returns a list of words in [s]
that are separated by a newline, tab, space or comma character. *)
val extract_comma_space_separated_words : string -> string list
(** [extract_blank_separated_words s] returns a list of words in [s] that are
separated by a tab or space character. *)
val extract_blank_separated_words : string -> string list
(** [extract_words s ~is_word_char] will split the string [s] into a list of
words. A valid word character is defined by the [is_word_char] predicate
returning true and anything else is considered a separator. Any blank
words are filtered out of the results. *)
val extract_words : string -> is_word_char:(char -> bool) -> string list
end
(** [which t prog] seek [prog] in the PATH and return the name of the program
prefixed with the first path where it is found. Return [None] the the
program is not found. *)
val which : t -> string -> string option
(** Execute external programs. *)
module Process : sig
type result =
{ exit_code : int
; stdout : string
; stderr : string
}
(** [run t prog args] runs [prog] with arguments [args] and returns its exit
status together with the content of stdout and stderr. The action is
logged.
@param dir change to [dir] before running the command.
@param env specify additional environment variables as a list of the form
NAME=VALUE. *)
val run : t -> ?dir:string -> ?env:string list -> string -> string list -> result
(** [run_capture_exn t prog args] same as [run t prog args] but returns
[stdout] and {!die} if the error code is nonzero or there is some output
on [stderr]. *)
val run_capture_exn
: t
-> ?dir:string
-> ?env:string list
-> string
-> string list
-> string
(** [run_ok t prog args] same as [run t prog args] but only cares whether the
execution terminated successfully (i.e., returned an error code of [0]). *)
val run_ok : t -> ?dir:string -> ?env:string list -> string -> string list -> bool
end
(** Typical entry point for configurator programs *)
val main : ?args:(Arg.key * Arg.spec * Arg.doc) list -> name:string -> (t -> unit) -> unit
(** Abort execution. If raised from within [main], the argument of [die] is
printed as [Error: <message>]. *)
val die : ('a, unit, string, 'b) format4 -> 'a

View file

@ -0,0 +1,26 @@
module C = Configurator.V1
let unix = {|
#include <math.h>
void *addr = &sin;
int main(void) {
return 0;
}
|}
let windows = {|
#include <winsock2.h>
void *addr = &gethostname;
int main(void) {
return 0;
}
|}
let main c =
let code = if Sys.os_type = "Win32" then windows else unix in
let b = C.c_test c code in
let f = open_out_bin "out" in
output_char f (if b then '1' else '0');
close_out f
let () = C.main ~name:"configurator-c-libraries" main

View file

@ -0,0 +1,3 @@
(executable
(name discover)
(libraries dune.configurator))

View file

@ -0,0 +1,13 @@
Test that configurator always picks the value of the `c_libraries`
flag from `ocamlc -config`. If not, there's a failure to link a
configuration test program that uses functions from these libraries.
For that, we need functions outside of libc. On Unix, that would be
`sin(3)` that requires the `-lm` flag for the math library, and on
Windows `gethostname` that requires WinSock2 (`ws2_32.dll`).
link successfully
==================================
$ dune exec -- ./discover.exe
$ cat out
1

View file

@ -0,0 +1,4 @@
let () =
let module C = Configurator.V1 in
C.main ~name:"foo" (fun _c ->
C.Flags.write_lines "foo" ["asdf"])

View file

@ -0,0 +1,4 @@
(executable
(name discover)
(modules discover)
(libraries dune.configurator))

View file

@ -0,0 +1 @@
$ dune exec ./discover.exe

View file

@ -0,0 +1,3 @@
(executable
(name run)
(libraries dune.configurator))

View file

@ -0,0 +1,16 @@
module Configurator = Configurator.V1
let () =
Configurator.main ~name:"c_test" (fun t ->
let c_result =
Configurator.c_test t {c|
#include <stdio.h>
int main(void)
{
printf("Hello, World!");
return 0;
}
|c} in
assert c_result;
print_endline "Successfully compiled c program"
)

View file

@ -0,0 +1,3 @@
(executable
(name run)
(libraries dune.configurator))

View file

@ -0,0 +1,19 @@
module Configurator = Configurator.V1
let () =
begin match Sys.getenv "INSIDE_DUNE" with
| exception Not_found -> failwith "INSIDE_DUNE is not passed"
| "1" -> print_endline "INSIDE_DUNE is from an old dune"
| dir -> print_endline "INSIDE_DUNE is present";
let config_path = ".dune/configurator.v2" in
Printf.printf "%s file is %s\n" config_path
(if Sys.file_exists (Filename.concat dir config_path) then
"present"
else
"not present")
end;
Configurator.main ~name:"config" (fun t ->
match Configurator.ocaml_config_var t "version" with
| None -> failwith "version is absent"
| Some _ -> print_endline "version is present"
)

View file

@ -0,0 +1,3 @@
(executable
(name run)
(libraries dune.configurator))

View file

@ -0,0 +1,24 @@
module Configurator = Configurator.V1
let () =
let module C_define = Configurator.C_define in
Configurator.main ~name:"c_test" (fun t ->
C_define.import t
~prelude:"#define CONFIGURATOR_TESTING \"foobar\"\n\
#define CONFIGURATOR_NEG_INT -127\n"
~includes:["caml/config.h"]
[ "CAML_CONFIG_H", C_define.Type.Switch
; "Page_log", C_define.Type.Int
; "CONFIGURATOR_TESTING", C_define.Type.String
; "CONFIGURATOR_NEG_INT", C_define.Type.Int
; "sizeof(char)", C_define.Type.Int
]
|> List.iter (fun (n, v) ->
Printf.printf "%s=%s\n"
n (match v with
| C_define.Value.String s -> s
| Int i -> string_of_int i
| Switch b -> string_of_bool b
)
)
)

View file

@ -0,0 +1,17 @@
Show that config values are present
$ dune exec config/run.exe
INSIDE_DUNE is present
.dune/configurator.v2 file is present
version is present
We're able to compile C program successfully
$ dune exec c_test/run.exe
Successfully compiled c program
Importing #define's from code is successful
$ dune exec import-define/run.exe
CAML_CONFIG_H=true
Page_log=12
CONFIGURATOR_TESTING=foobar
CONFIGURATOR_NEG_INT=-127
sizeof(char)=1

View file

@ -0,0 +1,13 @@
(cram
(deps
(package dune)
(package dune-configurator)))
(cram
(applies_to pkg-config-quoting)
(deps %{bin:pkg-config}))
(cram
(enabled_if
(<> %{ocaml-config:system} win))
(applies_to configurator.t))

View file

@ -0,0 +1,39 @@
(*
* OWL - OCaml Scientific and Engineering Computing
* Copyright (c) 2016-2022 Liang Wang <liang@ocaml.xyz>
*)
module Configurator = Configurator.V1
let header = {|
#define TEST "test"
|}
let default_cflags c =
let test =
let headerfile =
let file, fd =
Filename.open_temp_file ~mode:[ Open_wronly ] "discover" "test.h"
in
output_string fd header;
close_out fd;
file
in
let platform =
assert (Sys.file_exists headerfile);
Configurator.C_define.import c ~includes:[ headerfile ] [ ("TEST", String) ]
in
match List.map snd platform with
| [ String "test" ] -> `test
| _ -> `unknown
in
match test with `test -> [] | _ -> assert false
let () =
let flags_file = ref "" in
let args = ["-target", Arg.Set_string flags_file , "flags file"] in
Configurator.main ~args ~name:"test" (fun c ->
let libs = [] in
let cflags = default_cflags c in
let conf : Configurator.Pkg_config.package_conf = { cflags; libs } in
Configurator.Flags.write_sexp !flags_file conf.cflags)

View file

@ -0,0 +1,3 @@
(executable
(name configure)
(libraries dune.configurator))

View file

@ -0,0 +1,3 @@
(rule
(targets c_flags.sexp)
(action (run configure/configure.exe -target %{targets})))

View file

@ -0,0 +1,6 @@
Test that dune-configurator's `C_define.import` is able to include
custom header files correctly.
C_define.import functions properly
====================================================================
$ dune build ./c_flags.sexp

View file

@ -0,0 +1,13 @@
module C = Configurator.V1
let () =
C.main ~name:"config_test" (fun t ->
let pkg_config =
match C.Pkg_config.get t with
| None -> assert false
| Some p -> p
in
let query package = ignore (C.Pkg_config.query pkg_config ~package) in
query "dummy-pkg";
)

View file

@ -0,0 +1,19 @@
(executable
(name pkgconf)
(modules pkgconf))
(env
(_
(binaries
(./pkgconf.exe as pkgconf))))
(executable
(name config_test)
(libraries dune.configurator)
(modules config_test))
(rule
(alias default)
(deps %{bin:pkgconf})
(action
(run ./config_test.exe -verbose)))

View file

@ -0,0 +1,8 @@
(* We'd like to use String.equal but that's OCaml >= 4.03 *)
let not_flag x = not ("--print-errors" = x)
let () =
let args = List.tl (Array.to_list Sys.argv) in
let args = List.filter not_flag args in
Format.printf "@[<v>%a@]@."
(Format.pp_print_list Format.pp_print_string) args

View file

@ -0,0 +1,46 @@
$ unset PKG_CONFIG_ARGN
$ unset PKG_CONFIG
These tests show that setting `PKG_CONFIG_ARGN` passes extra args to `pkg-config`
$ dune build 2>&1 | awk '/run:.*bin\/pkgconf/{a=1}/stderr/{a=0}a' | sed s/$(ocamlc -config | sed -n "/^target:/ {s/target: //; p; }")/\$TARGET/g
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --personality $TARGET --print-errors dummy-pkg
-> process exited with code 0
-> stdout:
| --personality
| $TARGET
| dummy-pkg
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --personality $TARGET --cflags dummy-pkg
-> process exited with code 0
-> stdout:
| --personality
| $TARGET
| --cflags
| dummy-pkg
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --personality $TARGET --libs dummy-pkg
-> process exited with code 0
-> stdout:
| --personality
| $TARGET
| --libs
| dummy-pkg
$ dune clean
$ PKG_CONFIG_ARGN="--static" dune build 2>&1 | awk '/run:.*bin\/pkgconf/{a=1}/stderr/{a=0}a'
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --static --print-errors dummy-pkg
-> process exited with code 0
-> stdout:
| --static
| dummy-pkg
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --static --cflags dummy-pkg
-> process exited with code 0
-> stdout:
| --static
| --cflags
| dummy-pkg
run: $TESTCASE_ROOT/_build/default/.bin/pkgconf --static --libs dummy-pkg
-> process exited with code 0
-> stdout:
| --static
| --libs
| dummy-pkg

View file

@ -0,0 +1,15 @@
module C = Configurator.V1
let () =
C.main ~name:"config_test" (fun t ->
let pkg_config =
match C.Pkg_config.get t with
| None -> assert false
| Some p -> p
in
let query package = ignore (C.Pkg_config.query pkg_config ~package) in
query "gtk+-quartz-3.0";
query "gtk+-quartz-3.0 >= 3.18";
query "gtksourceview-3.0 >= 3.18"
)

View file

@ -0,0 +1,14 @@
(executable
(name pkg_config)
(public_name pkg-config)
(modules pkg_config))
(executable
(name config_test)
(libraries dune.configurator)
(modules config_test))
(alias
(name default)
(deps (package pkg-config))
(action (run ./config_test.exe -verbose)))

View file

@ -0,0 +1,8 @@
(* We'd like to use String.equal but that's OCaml >= 4.03 *)
let not_flag x = not ("--print-errors" = x)
let () =
let args = List.tl (Array.to_list Sys.argv) in
let args = List.filter not_flag args in
Format.printf "@[<v>%a@]@."
(Format.pp_print_list Format.pp_print_string) args

View file

@ -0,0 +1,44 @@
These tests show how various pkg-config invocations get quotes (and test specifying a custom PKG_CONFIG):
$ PKG_CONFIG=$PWD/_build/install/default/bin/pkg-config dune build 2>&1 | awk '/run:.*bin\/pkg-config/{a=1}/stderr/{a=0}a'
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --print-errors gtk+-quartz-3.0
-> process exited with code 0
-> stdout:
| gtk+-quartz-3.0
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --cflags gtk+-quartz-3.0
-> process exited with code 0
-> stdout:
| --cflags
| gtk+-quartz-3.0
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --libs gtk+-quartz-3.0
-> process exited with code 0
-> stdout:
| --libs
| gtk+-quartz-3.0
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --print-errors 'gtk+-quartz-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| gtk+-quartz-3.0 >= 3.18
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --cflags 'gtk+-quartz-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| --cflags
| gtk+-quartz-3.0 >= 3.18
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --libs 'gtk+-quartz-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| --libs
| gtk+-quartz-3.0 >= 3.18
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --print-errors 'gtksourceview-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| gtksourceview-3.0 >= 3.18
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --cflags 'gtksourceview-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| --cflags
| gtksourceview-3.0 >= 3.18
run: $TESTCASE_ROOT/_build/install/default/bin/pkg-config --libs 'gtksourceview-3.0 >= 3.18'
-> process exited with code 0
-> stdout:
| --libs
| gtksourceview-3.0 >= 3.18

View file

@ -0,0 +1,4 @@
(test
(name test_configurator)
(package dune-configurator)
(libraries configurator))

View file

@ -0,0 +1,3 @@
module Configurator = Configurator.V1
let () = Configurator.main ~name:"test_configurator" (fun _ -> ())