This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
|
|
@ -0,0 +1,351 @@
|
|||
module List = ListLabels
|
||||
module String = StringLabels
|
||||
|
||||
module Json = struct
|
||||
type t =
|
||||
[ `Int of int
|
||||
| `Float of float
|
||||
| `String of string
|
||||
| `List of t list
|
||||
| `Bool of bool
|
||||
| `Assoc of (string * t) list
|
||||
| `Null
|
||||
]
|
||||
end
|
||||
|
||||
module Timestamp : sig
|
||||
type t
|
||||
|
||||
val to_json : t -> Json.t
|
||||
val of_float_seconds : float -> t
|
||||
val to_float_seconds : t -> float
|
||||
end = struct
|
||||
type t = float
|
||||
|
||||
let of_float_seconds x = x
|
||||
let to_float_seconds x = x
|
||||
|
||||
let to_json f =
|
||||
let n = int_of_float @@ (f *. 1_000_000.) in
|
||||
`Int n
|
||||
;;
|
||||
end
|
||||
|
||||
module Id = struct
|
||||
type t =
|
||||
[ `Int of int
|
||||
| `String of string
|
||||
]
|
||||
|
||||
let create x = x
|
||||
|
||||
let to_string = function
|
||||
| `String s -> s
|
||||
| `Int i -> string_of_int i
|
||||
;;
|
||||
|
||||
let to_json (t : t) = (t :> Json.t)
|
||||
let field id = "id", to_json id
|
||||
end
|
||||
|
||||
module Stack_frame = struct
|
||||
module Raw = struct
|
||||
type t = string list
|
||||
|
||||
let create t = t
|
||||
let to_json t = `List (List.map t ~f:(fun s -> `String s))
|
||||
end
|
||||
|
||||
type t =
|
||||
{ parent : Id.t option
|
||||
; name : string
|
||||
; category : string
|
||||
}
|
||||
|
||||
let create ?parent ~name ~category () = { parent; name; category }
|
||||
|
||||
let to_json { parent; name; category } : Json.t =
|
||||
let json = [ "name", `String name; "category", `String category ] in
|
||||
let json =
|
||||
match parent with
|
||||
| None -> json
|
||||
| Some id -> ("parent", Id.to_json id) :: json
|
||||
in
|
||||
`Assoc json
|
||||
;;
|
||||
end
|
||||
|
||||
module Event = struct
|
||||
[@@@ocaml.warning "-37"]
|
||||
|
||||
module Timestamp = Timestamp
|
||||
|
||||
type common_fields =
|
||||
{ name : string
|
||||
; cat : string list
|
||||
; ts : Timestamp.t
|
||||
; tts : Timestamp.t option
|
||||
; pid : int
|
||||
; tid : int
|
||||
; cname : string option
|
||||
; stackframe : [ `Id of Id.t | `Raw of Stack_frame.Raw.t ] option
|
||||
}
|
||||
|
||||
let common_fields ?tts ?cname ?(cat = []) ?(pid = 0) ?(tid = 0) ?stackframe ~ts ~name ()
|
||||
=
|
||||
{ tts; cname; cat; ts; pid; tid; name; stackframe }
|
||||
;;
|
||||
|
||||
let set_ts t ts = { t with ts }
|
||||
let ts t = t.ts
|
||||
|
||||
type scope =
|
||||
| Global
|
||||
| Process
|
||||
| Thread
|
||||
|
||||
type async =
|
||||
| Start
|
||||
| Instant
|
||||
| End
|
||||
|
||||
type args = (string * Json.t) list
|
||||
|
||||
type object_kind =
|
||||
| New
|
||||
| Snapshot of
|
||||
{ cat : string list option
|
||||
; args : args
|
||||
}
|
||||
| Destroy
|
||||
|
||||
type metadata =
|
||||
| Process_name of
|
||||
{ pid : int
|
||||
; name : string
|
||||
}
|
||||
| Process_labels of
|
||||
{ pid : int
|
||||
; labels : string
|
||||
}
|
||||
| Thread_name of
|
||||
{ tid : int
|
||||
; pid : int
|
||||
; name : string
|
||||
}
|
||||
| Process_sort_index of
|
||||
{ pid : int
|
||||
; sort_index : int
|
||||
}
|
||||
| Thread_sort_index of
|
||||
{ pid : int
|
||||
; tid : int
|
||||
; sort_index : int
|
||||
}
|
||||
|
||||
(* TODO support flow, samples, references, memory dumps *)
|
||||
type t =
|
||||
| Counter of common_fields * args * Id.t option
|
||||
| Duration_start of common_fields * args * Id.t option
|
||||
| Duration_end of
|
||||
{ pid : int
|
||||
; tid : int
|
||||
; ts : float
|
||||
; args : args option
|
||||
}
|
||||
| Complete of
|
||||
{ common : common_fields
|
||||
; args : args option
|
||||
; dur : Timestamp.t
|
||||
; tdur : Timestamp.t option
|
||||
}
|
||||
| Instant of common_fields * scope option * args option
|
||||
| Async of
|
||||
{ common : common_fields
|
||||
; async : async
|
||||
; scope : string option
|
||||
; id : Id.t
|
||||
; args : args option
|
||||
}
|
||||
| Object of
|
||||
{ common : common_fields
|
||||
; object_kind : object_kind
|
||||
; id : Id.t
|
||||
; scope : string option
|
||||
}
|
||||
| Metadata of metadata
|
||||
|
||||
let phase s = "ph", `String s
|
||||
|
||||
let add_field_opt to_field field fields =
|
||||
match field with
|
||||
| None -> fields
|
||||
| Some f -> to_field f :: fields
|
||||
;;
|
||||
|
||||
let json_fields_of_common_fields { name; cat; ts; tts; pid; tid; cname; stackframe } =
|
||||
let fields =
|
||||
[ "name", `String name
|
||||
; "cat", `String (String.concat ~sep:"," cat)
|
||||
; "ts", Timestamp.to_json ts
|
||||
; "pid", `Int pid
|
||||
; "tid", `Int tid
|
||||
]
|
||||
in
|
||||
let fields = add_field_opt (fun cname -> "cname", `String cname) cname fields in
|
||||
let fields = add_field_opt (fun tts -> "tts", Timestamp.to_json tts) tts fields in
|
||||
add_field_opt
|
||||
(fun stackframe ->
|
||||
match stackframe with
|
||||
| `Id id -> "sf", Id.to_json id
|
||||
| `Raw r -> "stack", Stack_frame.Raw.to_json r)
|
||||
stackframe
|
||||
fields
|
||||
;;
|
||||
|
||||
let json_of_scope = function
|
||||
| Global -> `String "g"
|
||||
| Process -> `String "p"
|
||||
| Thread -> `String "t"
|
||||
;;
|
||||
|
||||
let args_field fields = "args", `Assoc fields
|
||||
|
||||
let json_fields_of_metadata m =
|
||||
let fields =
|
||||
let common pid name = [ "name", `String name; "pid", `Int pid ] in
|
||||
match m with
|
||||
| Process_name { pid; name } ->
|
||||
args_field [ "name", `String name ] :: common pid "thread_name"
|
||||
| Process_labels { pid; labels } ->
|
||||
args_field [ "labels", `String labels ] :: common pid "process_labels"
|
||||
| Thread_name { tid; pid; name } ->
|
||||
("tid", `Int tid)
|
||||
:: args_field [ "name", `String name ]
|
||||
:: common pid "process_name"
|
||||
| Process_sort_index { pid; sort_index } ->
|
||||
args_field [ "sort_index", `Int sort_index ] :: common pid "process_sort_index"
|
||||
| Thread_sort_index { pid; sort_index; tid } ->
|
||||
("tid", `Int tid)
|
||||
:: args_field [ "sort_index", `Int sort_index ]
|
||||
:: common pid "thread_sort_index"
|
||||
in
|
||||
phase "M" :: fields
|
||||
;;
|
||||
|
||||
let to_json_fields : t -> (string * Json.t) list = function
|
||||
| Counter (common, args, id) ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = phase "C" :: args_field args :: fields in
|
||||
add_field_opt Id.field id fields
|
||||
| Duration_start (common, args, id) ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = phase "B" :: args_field args :: fields in
|
||||
add_field_opt Id.field id fields
|
||||
| Duration_end { pid; tid; ts; args } ->
|
||||
let fields = [ "tid", `Int tid; "pid", `Int pid; "ts", `Float ts; phase "E" ] in
|
||||
add_field_opt args_field args fields
|
||||
| Complete { common; dur; args; tdur } ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = phase "X" :: ("dur", Timestamp.to_json dur) :: fields in
|
||||
let fields =
|
||||
add_field_opt (fun tdur -> "tdur", Timestamp.to_json tdur) tdur fields
|
||||
in
|
||||
add_field_opt args_field args fields
|
||||
| Instant (common, scope, args) ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = phase "i" :: fields in
|
||||
let fields = add_field_opt (fun s -> "s", json_of_scope s) scope fields in
|
||||
add_field_opt args_field args fields
|
||||
| Async { common; async; scope; id; args } ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = Id.field id :: fields in
|
||||
let fields =
|
||||
let ph =
|
||||
let s =
|
||||
match async with
|
||||
| Start -> "b"
|
||||
| Instant -> "n"
|
||||
| End -> "e"
|
||||
in
|
||||
phase s
|
||||
in
|
||||
ph :: fields
|
||||
in
|
||||
let fields = add_field_opt (fun s -> "scope", `String s) scope fields in
|
||||
add_field_opt args_field args fields
|
||||
| Object { common; object_kind; id; scope } ->
|
||||
let fields = json_fields_of_common_fields common in
|
||||
let fields = Id.field id :: fields in
|
||||
let fields =
|
||||
let ph, args =
|
||||
match object_kind with
|
||||
| New -> "N", None
|
||||
| Destroy -> "D", None
|
||||
| Snapshot { cat; args } ->
|
||||
let snapshot =
|
||||
add_field_opt
|
||||
(fun cat -> "cat", `String (String.concat ~sep:"," cat))
|
||||
cat
|
||||
args
|
||||
in
|
||||
"O", Some [ "snapshot", `Assoc snapshot ]
|
||||
in
|
||||
let fields = phase ph :: fields in
|
||||
add_field_opt args_field args fields
|
||||
in
|
||||
add_field_opt (fun s -> "scope", `String s) scope fields
|
||||
| Metadata m -> json_fields_of_metadata m
|
||||
;;
|
||||
|
||||
let to_json t = `Assoc (to_json_fields t)
|
||||
let counter ?id common args = Counter (common, args, id)
|
||||
let complete ?tdur ?args ~dur common = Complete { common; tdur; dur; args }
|
||||
let async ?scope ?args id async common = Async { common; args; scope; id; async }
|
||||
let instant ?args ?scope common = Instant (common, scope, args)
|
||||
end
|
||||
|
||||
module Output_object = struct
|
||||
type t =
|
||||
{ displayTimeUnit : [ `Ms | `Ns ] option
|
||||
; traceEvents : Event.t list
|
||||
; stackFrames : (Id.t * Stack_frame.t) list option
|
||||
; extra_fields : (string * Json.t) list option
|
||||
}
|
||||
|
||||
let to_json { displayTimeUnit; traceEvents; extra_fields; stackFrames } =
|
||||
let json = [ "traceEvents", `List (List.map traceEvents ~f:Event.to_json) ] in
|
||||
let json =
|
||||
match displayTimeUnit with
|
||||
| None -> json
|
||||
| Some u ->
|
||||
( "displayTimeUnit"
|
||||
, `String
|
||||
(match u with
|
||||
| `Ms -> "ms"
|
||||
| `Ns -> "ns") )
|
||||
:: json
|
||||
in
|
||||
let json : (string * Json.t) list =
|
||||
match stackFrames with
|
||||
| None -> json
|
||||
| Some frames ->
|
||||
let frames =
|
||||
List.map frames ~f:(fun (id, frame) ->
|
||||
let id = Id.to_string id in
|
||||
id, Stack_frame.to_json frame)
|
||||
in
|
||||
("stackFrames", `Assoc frames) :: json
|
||||
in
|
||||
let json =
|
||||
match extra_fields with
|
||||
| None -> json
|
||||
| Some extra_fields -> json @ extra_fields
|
||||
in
|
||||
`Assoc json
|
||||
;;
|
||||
|
||||
let create ?displayTimeUnit ?extra_fields ?stackFrames ~traceEvents () =
|
||||
{ displayTimeUnit; extra_fields; traceEvents; stackFrames }
|
||||
;;
|
||||
end
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
[@@@alert unstable "The API of this library is not stable and may change without notice."]
|
||||
[@@@alert "-unstable"]
|
||||
|
||||
(** Output trace data to a file in Chrome's trace_event format. This format is
|
||||
compatible with chrome trace viewer [chrome://tracing].
|
||||
|
||||
Trace viewer is a part of the catapult project
|
||||
(https://github.com/catapult-project/catapult/blob/master/tracing/README.md).
|
||||
|
||||
The trace format is documented at:
|
||||
https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview *)
|
||||
|
||||
module Json : sig
|
||||
(** Simplifies JSON type *)
|
||||
type t =
|
||||
[ `Int of int
|
||||
| `Float of float
|
||||
| `String of string
|
||||
| `List of t list
|
||||
| `Bool of bool
|
||||
| `Assoc of (string * t) list
|
||||
| `Null
|
||||
]
|
||||
end
|
||||
|
||||
module Id : sig
|
||||
type t
|
||||
|
||||
val create : [ `String of string | `Int of int ] -> t
|
||||
end
|
||||
|
||||
module Stack_frame : sig
|
||||
module Raw : sig
|
||||
type t
|
||||
|
||||
val create : string list -> t
|
||||
end
|
||||
|
||||
type t
|
||||
|
||||
val create : ?parent:Id.t -> name:string -> category:string -> unit -> t
|
||||
end
|
||||
|
||||
module Event : sig
|
||||
type t
|
||||
|
||||
module Timestamp : sig
|
||||
type t
|
||||
|
||||
val of_float_seconds : float -> t
|
||||
val to_float_seconds : t -> float
|
||||
end
|
||||
|
||||
type common_fields
|
||||
|
||||
val common_fields
|
||||
: ?tts:Timestamp.t
|
||||
-> ?cname:string
|
||||
-> ?cat:string list
|
||||
-> ?pid:int
|
||||
-> ?tid:int
|
||||
-> ?stackframe:[ `Id of Id.t | `Raw of Stack_frame.Raw.t ]
|
||||
-> ts:Timestamp.t
|
||||
-> name:string
|
||||
-> unit
|
||||
-> common_fields
|
||||
|
||||
val ts : common_fields -> Timestamp.t
|
||||
val set_ts : common_fields -> Timestamp.t -> common_fields
|
||||
|
||||
type args = (string * Json.t) list
|
||||
|
||||
(** Create a counter event *)
|
||||
val counter : ?id:Id.t -> common_fields -> args -> t
|
||||
|
||||
type async =
|
||||
| Start
|
||||
| Instant
|
||||
| End
|
||||
|
||||
val async : ?scope:string -> ?args:args -> Id.t -> async -> common_fields -> t
|
||||
val complete : ?tdur:Timestamp.t -> ?args:args -> dur:Timestamp.t -> common_fields -> t
|
||||
val to_json : t -> Json.t
|
||||
|
||||
(** The scope of an instant event. The scopes below come from the standard
|
||||
reference for this format *)
|
||||
type scope =
|
||||
| Global
|
||||
| Process
|
||||
| Thread
|
||||
|
||||
(** Create an instant event. *)
|
||||
val instant : ?args:args -> ?scope:scope -> common_fields -> t
|
||||
end
|
||||
|
||||
module Output_object : sig
|
||||
(** The object format provided in whole *)
|
||||
|
||||
type t
|
||||
|
||||
val create
|
||||
: ?displayTimeUnit:[ `Ms | `Ns ]
|
||||
-> ?extra_fields:(string * Json.t) list
|
||||
-> ?stackFrames:(Id.t * Stack_frame.t) list
|
||||
-> traceEvents:Event.t list
|
||||
-> unit
|
||||
-> t
|
||||
|
||||
val to_json : t -> Json.t
|
||||
end
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
(library
|
||||
(name chrome_trace)
|
||||
(public_name chrome-trace)
|
||||
(synopsis "Emit catapult trace files, compatible with chrome://tracing"))
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
open Stdune
|
||||
open Dune_tests_common
|
||||
|
||||
let () = init ()
|
||||
let buf = Buffer.create 0
|
||||
|
||||
let c =
|
||||
let write s = Buffer.add_string buf s in
|
||||
let close () = () in
|
||||
let flush () = () in
|
||||
Dune_stats.create (Custom { write; close; flush }) ~extended_build_job_info:false
|
||||
;;
|
||||
|
||||
let () =
|
||||
let module Event = Chrome_trace.Event in
|
||||
let module Id = Chrome_trace.Id in
|
||||
let module Timestamp = Event.Timestamp in
|
||||
let events =
|
||||
[ Event.complete
|
||||
~dur:(Timestamp.of_float_seconds 1.)
|
||||
~args:[ "foo", `String "bar" ]
|
||||
(Event.common_fields ~ts:(Timestamp.of_float_seconds 0.5) ~name:"foo" ())
|
||||
; Event.counter
|
||||
(Event.common_fields ~ts:(Timestamp.of_float_seconds 0.5) ~name:"cnt" ())
|
||||
[ "bar", `Int 250 ]
|
||||
; Event.async
|
||||
(Id.create (`String "foo"))
|
||||
Event.Start
|
||||
(Event.common_fields ~ts:(Timestamp.of_float_seconds 0.5) ~name:"async" ())
|
||||
~args:[ "foo", `Int 100 ]
|
||||
]
|
||||
in
|
||||
List.iter events ~f:(Dune_stats.emit c);
|
||||
Dune_stats.close c
|
||||
;;
|
||||
|
||||
let buffer_lines () = String.split_lines (Buffer.contents buf)
|
||||
|
||||
let%expect_test _ =
|
||||
Format.printf
|
||||
"%a@."
|
||||
Pp.to_fmt
|
||||
(Pp.vbox (Pp.concat_map (buffer_lines ()) ~sep:Pp.cut ~f:Pp.verbatim));
|
||||
[%expect
|
||||
{|
|
||||
[{"args":{"foo":"bar"},"ph":"X","dur":1000000,"name":"foo","cat":"","ts":500000,"pid":0,"tid":0}
|
||||
,{"ph":"C","args":{"bar":250},"name":"cnt","cat":"","ts":500000,"pid":0,"tid":0}
|
||||
,{"args":{"foo":100},"ph":"b","id":"foo","name":"async","cat":"","ts":500000,"pid":0,"tid":0}
|
||||
]
|
||||
|}]
|
||||
;;
|
||||
16
unikernel/duniverse/dune_/otherlibs/chrome-trace/test/dune
Normal file
16
unikernel/duniverse/dune_/otherlibs/chrome-trace/test/dune
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
(library
|
||||
(name chrome_trace_tests)
|
||||
(inline_tests)
|
||||
(libraries
|
||||
dune_tests_common
|
||||
stdune
|
||||
dune_stats
|
||||
chrome_trace
|
||||
;; This is because of the (implicit_transitive_deps false)
|
||||
;; in dune-project
|
||||
ppx_expect.config
|
||||
ppx_expect.config_types
|
||||
base
|
||||
ppx_inline_test.config)
|
||||
(preprocess
|
||||
(pps ppx_expect)))
|
||||
3
unikernel/duniverse/dune_/otherlibs/configurator/dune
Normal file
3
unikernel/duniverse/dune_/otherlibs/configurator/dune
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(env
|
||||
(_
|
||||
(flags :standard \ -alert -unstable)))
|
||||
|
|
@ -0,0 +1 @@
|
|||
module V1 = V1
|
||||
17
unikernel/duniverse/dune_/otherlibs/configurator/src/dune
Normal file
17
unikernel/duniverse/dune_/otherlibs/configurator/src/dune
Normal 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))
|
||||
|
|
@ -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:" ")
|
||||
;;
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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-" }
|
||||
{}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
(executable
|
||||
(name mk))
|
||||
|
||||
(rule
|
||||
(with-stdout-to
|
||||
flags.sexp
|
||||
(run ./mk.exe -ocamlv %{ocaml_version})))
|
||||
|
|
@ -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")
|
||||
;;
|
||||
347
unikernel/duniverse/dune_/otherlibs/configurator/src/import.ml
Normal file
347
unikernel/duniverse/dune_/otherlibs/configurator/src/import.ml
Normal 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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
812
unikernel/duniverse/dune_/otherlibs/configurator/src/v1.ml
Normal file
812
unikernel/duniverse/dune_/otherlibs/configurator/src/v1.ml
Normal 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)
|
||||
;;
|
||||
185
unikernel/duniverse/dune_/otherlibs/configurator/src/v1.mli
Normal file
185
unikernel/duniverse/dune_/otherlibs/configurator/src/v1.mli
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name discover)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 2.8)
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
let () =
|
||||
let module C = Configurator.V1 in
|
||||
C.main ~name:"foo" (fun _c ->
|
||||
C.Flags.write_lines "foo" ["asdf"])
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
(executable
|
||||
(name discover)
|
||||
(modules discover)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 1.1)
|
||||
|
|
@ -0,0 +1 @@
|
|||
$ dune exec ./discover.exe
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name run)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name run)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 1.0)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name run)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name configure)
|
||||
(libraries dune.configurator))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(rule
|
||||
(targets c_flags.sexp)
|
||||
(action (run configure/configure.exe -target %{targets})))
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 2.0)
|
||||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
)
|
||||
|
|
@ -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)))
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 3.8)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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)))
|
||||
|
|
@ -0,0 +1 @@
|
|||
(lang dune 1.7)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
(test
|
||||
(name test_configurator)
|
||||
(package dune-configurator)
|
||||
(libraries configurator))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module Configurator = Configurator.V1
|
||||
|
||||
let () = Configurator.main ~name:"test_configurator" (fun _ -> ())
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let action =
|
||||
let open O in
|
||||
let path_to_dependency = read_file ~path:(Path.of_string "foo_or_bar") in
|
||||
path_to_dependency
|
||||
|> stage ~f:(fun path_to_dependency ->
|
||||
let+ data = read_file ~path:(Path.of_string path_to_dependency) in
|
||||
print_endline data)
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
(executables
|
||||
(names simple_concatenation choosing_dependency)
|
||||
(libraries dune_action_plugin))
|
||||
|
||||
(alias
|
||||
(name examples)
|
||||
(deps simple_concatenation.exe))
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let action =
|
||||
let source1 = read_file ~path:(Path.of_string "source1")
|
||||
and source2 = read_file ~path:(Path.of_string "source2") in
|
||||
both source1 source2
|
||||
|> stage ~f:(fun (source1, source2) ->
|
||||
let data = source1 ^ source2 in
|
||||
write_file ~path:(Path.of_string "target") ~data)
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
(library
|
||||
(name dune_action_plugin)
|
||||
(public_name dune-action-plugin)
|
||||
(libraries stdune csexp dune-glob unix dune-rpc.private)
|
||||
(synopsis
|
||||
"[Internal] Monadic interface for defining scripts with dynamic or complex sets of dependencies."))
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
open Import
|
||||
|
||||
module V1 = struct
|
||||
module Path = Path
|
||||
module Glob = Dune_glob.V1
|
||||
open Protocol
|
||||
|
||||
module Execution_error = struct
|
||||
exception E of string
|
||||
|
||||
let raise string = raise (E string)
|
||||
|
||||
let raise_on_fs_error = function
|
||||
| Error message -> raise message
|
||||
| Ok result -> result
|
||||
;;
|
||||
end
|
||||
|
||||
module Fs : sig
|
||||
val read_directory : string -> (string list, string) result
|
||||
val read_file : string -> (string, string) result
|
||||
val write_file : string -> string -> (unit, string) result
|
||||
end = struct
|
||||
let catch_system_exceptions f ~name =
|
||||
try Ok (f ()) with
|
||||
| Unix.Unix_error (error, syscall, arg) ->
|
||||
let error = Unix_error.Detailed.create error ~syscall ~arg in
|
||||
Error (name ^ ": " ^ Unix_error.Detailed.to_string_hum error)
|
||||
| Sys_error error -> Error (name ^ ": " ^ error)
|
||||
;;
|
||||
|
||||
let read_directory =
|
||||
let rec loop dh acc =
|
||||
match Unix.readdir dh with
|
||||
| "." | ".." -> loop dh acc
|
||||
| s -> loop dh (s :: acc)
|
||||
| exception End_of_file -> acc
|
||||
in
|
||||
fun path ->
|
||||
catch_system_exceptions ~name:"read_directory" (fun () ->
|
||||
let dh = Unix.opendir path in
|
||||
Exn.protect
|
||||
~f:(fun () -> loop dh [] |> List.sort ~compare:String.compare)
|
||||
~finally:(fun () -> Unix.closedir dh))
|
||||
;;
|
||||
|
||||
let read_file path =
|
||||
catch_system_exceptions ~name:"read_file" (fun () -> Io.String_path.read_file path)
|
||||
;;
|
||||
|
||||
let write_file path data =
|
||||
catch_system_exceptions ~name:"write_file" (fun () ->
|
||||
Io.String_path.write_file path data)
|
||||
;;
|
||||
end
|
||||
|
||||
module Stage = struct
|
||||
type 'a t =
|
||||
{ action : unit -> 'a
|
||||
; dependencies : Dependency.Set.t
|
||||
; targets : String.Set.t
|
||||
}
|
||||
|
||||
let map (t : 'a t) ~f = { t with action = (fun () -> f (t.action ())) }
|
||||
|
||||
let both (t1 : 'a t) (t2 : 'b t) =
|
||||
{ action = (fun () -> t1.action (), t2.action ())
|
||||
; dependencies = Dependency.Set.union t1.dependencies t2.dependencies
|
||||
; targets = String.Set.union t1.targets t2.targets
|
||||
}
|
||||
;;
|
||||
end
|
||||
|
||||
(* Construction inspired by free monad. *)
|
||||
type 'a t =
|
||||
| Pure of 'a
|
||||
| Stage of 'a t Stage.t
|
||||
|
||||
let lift_stage stage = Stage (Stage.map stage ~f:(fun a -> Pure a))
|
||||
|
||||
let rec map (t : 'a t) ~f =
|
||||
match t with
|
||||
| Pure a -> Pure (f a)
|
||||
| Stage at -> Stage (Stage.map ~f:(map ~f) at)
|
||||
;;
|
||||
|
||||
let rec stage (t : 'a t) ~f =
|
||||
match t with
|
||||
| Pure a -> f a
|
||||
| Stage at -> Stage (Stage.map ~f:(stage ~f) at)
|
||||
;;
|
||||
|
||||
let return a = Pure a
|
||||
|
||||
let rec both (t1 : 'a t) (t2 : 'b t) =
|
||||
match t1, t2 with
|
||||
| Pure a1, _ -> map ~f:(fun a2 -> a1, a2) t2
|
||||
| _, Pure a2 -> map ~f:(fun a1 -> a1, a2) t1
|
||||
| Stage at1, Stage at2 ->
|
||||
Stage (Stage.both at1 at2 |> Stage.map ~f:(fun (am1, am2) -> both am1 am2))
|
||||
;;
|
||||
|
||||
let read_file ~path =
|
||||
let path = Path.to_string path in
|
||||
let action () = Fs.read_file path |> Execution_error.raise_on_fs_error in
|
||||
lift_stage
|
||||
{ action
|
||||
; dependencies = Dependency.Set.singleton (File path)
|
||||
; targets = String.Set.empty
|
||||
}
|
||||
;;
|
||||
|
||||
let write_file ~path ~data =
|
||||
let path = Path.to_string path in
|
||||
let action () = Fs.write_file path data |> Execution_error.raise_on_fs_error in
|
||||
lift_stage
|
||||
{ action; dependencies = Dependency.Set.empty; targets = String.Set.singleton path }
|
||||
;;
|
||||
|
||||
(* TODO jstaron: If program tries to read empty directory, dune does not copy
|
||||
it to `_build` so we get a "No such file or directory" error. *)
|
||||
let read_directory_with_glob ~path ~glob =
|
||||
let path = Path.to_string path in
|
||||
let action () =
|
||||
Fs.read_directory path
|
||||
|> Execution_error.raise_on_fs_error
|
||||
|> List.filter ~f:(Glob.test glob)
|
||||
in
|
||||
lift_stage
|
||||
{ action
|
||||
; dependencies =
|
||||
Dependency.Set.singleton (Glob { path; glob = Glob.to_string glob })
|
||||
; targets = String.Set.empty
|
||||
}
|
||||
;;
|
||||
|
||||
let rec run_by_dune t context =
|
||||
match t with
|
||||
| Pure () -> Context.respond context Done
|
||||
| Stage at ->
|
||||
let allowed_targets = Context.targets context in
|
||||
let disallowed_targets = String.Set.diff at.targets allowed_targets in
|
||||
(match String.Set.to_list disallowed_targets with
|
||||
| [] -> ()
|
||||
| [ t ] ->
|
||||
Execution_error.raise
|
||||
(Printf.sprintf
|
||||
"%s is written despite not being declared as a target in dune file. To \
|
||||
fix, add it to target list in dune file."
|
||||
t)
|
||||
| ts ->
|
||||
Execution_error.raise
|
||||
(Printf.sprintf
|
||||
"Following files were written despite not being declared as targets in \
|
||||
dune file:\n\
|
||||
%sTo fix, add them to target list in dune file."
|
||||
(ts |> String.concat ~sep:"\n")));
|
||||
let prepared_dependencies = Context.prepared_dependencies context in
|
||||
let required_dependencies =
|
||||
Dependency.Set.diff at.dependencies prepared_dependencies
|
||||
in
|
||||
if Dependency.Set.is_empty required_dependencies
|
||||
then run_by_dune (at.action ()) context
|
||||
else Context.respond context (Need_more_deps required_dependencies)
|
||||
;;
|
||||
|
||||
(* If executable is not run by dune, assume that all dependencies are already
|
||||
prepared and no target checking is done. *)
|
||||
let rec run_outside_of_dune t =
|
||||
match t with
|
||||
| Pure () -> ()
|
||||
| Stage at -> run_outside_of_dune (at.action ())
|
||||
;;
|
||||
|
||||
let do_run t =
|
||||
match Protocol.Context.create () with
|
||||
| Run_outside_of_dune -> run_outside_of_dune t
|
||||
| Error message ->
|
||||
Execution_error.raise
|
||||
(Printf.sprintf
|
||||
"Error during communication with dune. %s Did you use different dune version \
|
||||
to compile the executable?"
|
||||
message)
|
||||
| Ok context -> run_by_dune t context
|
||||
;;
|
||||
|
||||
let run t =
|
||||
try
|
||||
do_run t;
|
||||
exit 0
|
||||
with
|
||||
| Execution_error.E message ->
|
||||
prerr_endline message;
|
||||
exit 1
|
||||
;;
|
||||
|
||||
module O = struct
|
||||
let ( let+ ) at f = map at ~f
|
||||
let ( and+ ) = both
|
||||
end
|
||||
|
||||
module Private = struct
|
||||
module Protocol = Protocol
|
||||
|
||||
let do_run = do_run
|
||||
|
||||
module Execution_error = Execution_error
|
||||
end
|
||||
end
|
||||
|
||||
module Private = V1.Private
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
[@@@alert unstable "The API of this library is not stable and may change without notice."]
|
||||
[@@@alert "-unstable"]
|
||||
|
||||
module V1 : sig
|
||||
(** Applicative and monadic interface for declaring dependencies.
|
||||
|
||||
This module is intended to be used as an interface for declaring
|
||||
dependencies of a computation. Dependencies can be declared dynamically -
|
||||
the list of dependencies can depend on previous dependencies.
|
||||
|
||||
Note: Monadic "bind" is provided, but it can be very costly. It's called
|
||||
[stage] to discourage people from overusing it. When dune decides that the
|
||||
action needs to be re-run, it runs (nontrivial) stages one by one, and
|
||||
starts a process from scratch for every stage. So a linear chain of binds
|
||||
leads to a linear number of program re-runs, and therefore overall
|
||||
quadratic time complexity. This also means that using non-deterministic
|
||||
mutable state can lead to surprising results. (note that with the current
|
||||
implementation, nontrivial stages are those that have some dependencies,
|
||||
so a stage that merely writes out some targets is "free") *)
|
||||
|
||||
module Path = Path
|
||||
|
||||
type 'a t
|
||||
|
||||
(** {1:monadic_interface Applicative/monadic interface} *)
|
||||
|
||||
(** [return a] creates a pure computation resulting in [a]. *)
|
||||
val return : 'a -> 'a t
|
||||
|
||||
(** If [at] is a computation resulting in [a] then [map at ~f] is a
|
||||
computation resulting in [f a]. *)
|
||||
val map : 'a t -> f:('a -> 'b) -> 'b t
|
||||
|
||||
(** If [at] is a computation resulting in [a] and [bt] is computation
|
||||
resulting in [b] then [both at bt] is a computation resulting in [(a, b)]. *)
|
||||
val both : 'a t -> 'b t -> ('a * 'b) t
|
||||
|
||||
(** If [at] is a computation resulting in value of type ['a] and [f] is a
|
||||
function taking value of type ['a] and returning a computation [bt] then
|
||||
[stage a ~f] is a computation that is equivalent to staging computation
|
||||
[bt] after computation [at].
|
||||
|
||||
Note: This is a monadic "bind" function. This function is costly so
|
||||
different name was chosen to discourage excessive use. *)
|
||||
val stage : 'a t -> f:('a -> 'b t) -> 'b t
|
||||
|
||||
(** {1 Syntax sugar for applicative subset} *)
|
||||
|
||||
(** Syntax sugar for applicative subset of the interface. Syntax sugar for
|
||||
[stage] is not provided to prevent accidental use.*)
|
||||
module O : sig
|
||||
(** {[
|
||||
let+ a = g in
|
||||
h
|
||||
]}
|
||||
|
||||
is equivalent to:
|
||||
|
||||
{[
|
||||
map g ~f:(fun a -> h)
|
||||
]} *)
|
||||
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
|
||||
|
||||
(** {[
|
||||
let+ a1 = g1
|
||||
and+ a2 = g2 in
|
||||
h
|
||||
]}
|
||||
|
||||
is equivalent to:
|
||||
|
||||
{[
|
||||
both g1 g2 |> map ~f:(fun (a1, a2) -> h)
|
||||
]} *)
|
||||
val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t
|
||||
end
|
||||
|
||||
(** {1:interaction Declaring dependencies and interacting with filesystem} *)
|
||||
|
||||
(** [read_file ~path:file] returns a computation depending on a [file] to be
|
||||
run and resulting in a file content. *)
|
||||
val read_file : path:Path.t -> string t
|
||||
|
||||
(** [write_file ~path:file ~data] returns a computation that writes [data] to
|
||||
a [file].
|
||||
|
||||
Note: [file] must be declared as a target in dune build file. *)
|
||||
val write_file : path:Path.t -> data:string -> unit t
|
||||
|
||||
(** [read_directory_with_glob ~path:directory ~glob] returns a computation
|
||||
depending on a listing of a [directory] (including source and target
|
||||
files) filtered by glob and resulting in that listing.
|
||||
|
||||
It's better to specify as narrow filtering by [glob] as possible (as
|
||||
opposed to filtering afterwards) because this makes dune aware of the
|
||||
filtering, so dune won't re-run the action when the directory changes in
|
||||
an unimportant way.
|
||||
|
||||
BUG: [read_directory_with_glob] doesn't work correctly for empty
|
||||
directories.
|
||||
|
||||
BUG: the returned listing includes directories even though that dependency
|
||||
is not tracked. *)
|
||||
val read_directory_with_glob : path:Path.t -> glob:Dune_glob.V1.t -> string list t
|
||||
|
||||
(** {1:running Running the computation} *)
|
||||
|
||||
(** Runs the computation. This function never returns. *)
|
||||
val run : unit t -> 'a
|
||||
end
|
||||
|
||||
(* [Private] module should only be used by dune itself. Its stability will not
|
||||
be maintained by the future library releases. *)
|
||||
module Private : sig
|
||||
module Protocol = Protocol
|
||||
|
||||
val do_run : unit V1.t -> unit
|
||||
|
||||
module Execution_error : sig
|
||||
exception E of string
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
include struct
|
||||
open Stdune
|
||||
module Unix_error = Unix_error
|
||||
module List = List
|
||||
module Set = Set
|
||||
module Exn = Exn
|
||||
module String = String
|
||||
module Io = Io
|
||||
module Sexp = Sexp
|
||||
module Option = Option
|
||||
module Comparable = Comparable
|
||||
module Result = Result
|
||||
module Map = Map
|
||||
end
|
||||
|
||||
module Conv = Dune_rpc_private.Conv
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
type t = string
|
||||
|
||||
let concat = Filename.concat
|
||||
let to_string t = t
|
||||
|
||||
let of_string path =
|
||||
match Filename.is_relative path with
|
||||
| false ->
|
||||
invalid_arg
|
||||
(Printf.sprintf
|
||||
"Path \"%s\" is absolute. All paths used with dune-action-plugin must be \
|
||||
relative."
|
||||
path)
|
||||
| true -> path
|
||||
;;
|
||||
|
||||
module O = struct
|
||||
let ( ^/ ) = concat
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
(** Representation of paths for "dune_action_plugin" library. *)
|
||||
|
||||
(** We shouldn't use absolute paths when communicating with Dune, so this module
|
||||
allows user to represent only relative paths. *)
|
||||
|
||||
type t
|
||||
|
||||
module O : sig
|
||||
(** Concatenate two paths. *)
|
||||
val ( ^/ ) : t -> t -> t
|
||||
end
|
||||
|
||||
(** Concatenate two paths. *)
|
||||
val concat : t -> t -> t
|
||||
|
||||
val to_string : t -> string
|
||||
|
||||
(** Convert path to string. Throws an Invalid_argument exception if passed
|
||||
string is not a relative path. *)
|
||||
val of_string : string -> t
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
open Import
|
||||
|
||||
let run_by_dune_env_variable = "DUNE_DYNAMIC_RUN_CLIENT"
|
||||
|
||||
module Error = Sexpable_intf.Error
|
||||
|
||||
module Dependency = struct
|
||||
module T = struct
|
||||
type t =
|
||||
| File of string
|
||||
| Directory of string
|
||||
| Glob of
|
||||
{ path : string
|
||||
; glob : string
|
||||
}
|
||||
|
||||
let conv =
|
||||
let open Conv in
|
||||
let file = constr "File" string (fun s -> File s) in
|
||||
let directory = constr "Directory" string (fun s -> Directory s) in
|
||||
let glob_cstr =
|
||||
constr "Glob" (pair string string) (fun (path, glob) -> Glob { path; glob })
|
||||
in
|
||||
sum
|
||||
[ econstr file; econstr directory; econstr glob_cstr ]
|
||||
(function
|
||||
| File s -> case s file
|
||||
| Directory s -> case s directory
|
||||
| Glob { path; glob } -> case (path, glob) glob_cstr)
|
||||
;;
|
||||
|
||||
let compare x y =
|
||||
match x, y with
|
||||
| File x, File y -> String.compare x y
|
||||
| File _, _ -> Lt
|
||||
| _, File _ -> Gt
|
||||
| Directory x, Directory y -> String.compare x y
|
||||
| Directory _, _ -> Lt
|
||||
| _, Directory _ -> Gt
|
||||
| Glob { path; glob }, Glob t ->
|
||||
let open Ordering.O in
|
||||
let= () = String.compare path t.path in
|
||||
String.compare glob t.glob
|
||||
;;
|
||||
|
||||
let to_dyn = Dyn.opaque
|
||||
end
|
||||
|
||||
include T
|
||||
module O = Comparable.Make (T)
|
||||
module Map = O.Map
|
||||
|
||||
module Set = struct
|
||||
include O.Set
|
||||
|
||||
let conv : t Conv.value = Conv.iso (Conv.list conv) of_list to_list
|
||||
end
|
||||
end
|
||||
|
||||
module Greeting = struct
|
||||
module T = struct
|
||||
type t =
|
||||
{ run_arguments_fn : string
|
||||
; response_fn : string
|
||||
}
|
||||
|
||||
let conv =
|
||||
let open Conv in
|
||||
let to_ (run_arguments_fn, response_fn) = { run_arguments_fn; response_fn } in
|
||||
let from { run_arguments_fn; response_fn } = run_arguments_fn, response_fn in
|
||||
iso (pair string string) to_ from
|
||||
;;
|
||||
|
||||
let version = 0
|
||||
end
|
||||
|
||||
include T
|
||||
include Sexpable_intf.Make (T)
|
||||
end
|
||||
|
||||
module Run_arguments = struct
|
||||
module T = struct
|
||||
type t =
|
||||
{ prepared_dependencies : Dependency.Set.t
|
||||
; targets : String.Set.t
|
||||
}
|
||||
|
||||
let conv =
|
||||
let from { prepared_dependencies; targets } = prepared_dependencies, targets in
|
||||
let to_ (prepared_dependencies, targets) = { prepared_dependencies; targets } in
|
||||
let string_set =
|
||||
Conv.iso Conv.(list string) String.Set.of_list String.Set.to_list
|
||||
in
|
||||
let conv = Conv.pair Dependency.Set.conv string_set in
|
||||
Conv.iso conv to_ from
|
||||
;;
|
||||
|
||||
let version = 0
|
||||
end
|
||||
|
||||
include T
|
||||
include Sexpable_intf.Make (T)
|
||||
end
|
||||
|
||||
module Response = struct
|
||||
module T = struct
|
||||
type t =
|
||||
| Done
|
||||
| Need_more_deps of Dependency.Set.t
|
||||
|
||||
let conv =
|
||||
let open Conv in
|
||||
let done_ = constr "Done" unit (fun () -> Done) in
|
||||
let need_more_deps =
|
||||
constr "Need_more_deps" Dependency.Set.conv (fun deps -> Need_more_deps deps)
|
||||
in
|
||||
sum
|
||||
[ econstr done_; econstr need_more_deps ]
|
||||
(function
|
||||
| Done -> case () done_
|
||||
| Need_more_deps deps -> case deps need_more_deps)
|
||||
;;
|
||||
|
||||
let version = 0
|
||||
end
|
||||
|
||||
include T
|
||||
include Sexpable_intf.Make (T)
|
||||
end
|
||||
|
||||
module Context = struct
|
||||
type t =
|
||||
{ response_fn : string
|
||||
; prepared_dependencies : Dependency.Set.t
|
||||
; targets : String.Set.t
|
||||
}
|
||||
|
||||
type create_result =
|
||||
| Ok of t
|
||||
| Run_outside_of_dune
|
||||
| Error of string
|
||||
|
||||
let cannot_parse_error = Error "Can not parse dune message."
|
||||
|
||||
let version_mismatch_error =
|
||||
Error
|
||||
"Dune version is incompatible with dune-action-plugin library version that was \
|
||||
used to build this executable."
|
||||
;;
|
||||
|
||||
let cannot_read_file = Error "Cannot read file containing dune message."
|
||||
let file_not_found_error = Error "Cannot find file containing dune message."
|
||||
|
||||
let create () =
|
||||
match Sys.getenv_opt run_by_dune_env_variable with
|
||||
| None -> Run_outside_of_dune
|
||||
| Some value ->
|
||||
(match Csexp.parse_string value with
|
||||
| Error _ -> cannot_parse_error
|
||||
| Ok sexp ->
|
||||
(match Greeting.of_sexp sexp with
|
||||
| Error (Version_mismatch _) -> version_mismatch_error
|
||||
| Error Parse_error -> cannot_parse_error
|
||||
| Ok greeting ->
|
||||
(match
|
||||
( Result.try_with (fun () ->
|
||||
Io.String_path.read_file greeting.run_arguments_fn)
|
||||
, Sys.file_exists greeting.response_fn )
|
||||
with
|
||||
| _, false -> file_not_found_error
|
||||
| Error _, _ -> cannot_read_file
|
||||
| Ok data, true ->
|
||||
(match Csexp.parse_string data with
|
||||
| Error _ -> cannot_parse_error
|
||||
| Ok sexp ->
|
||||
(match Run_arguments.of_sexp sexp with
|
||||
| Error (Version_mismatch _) -> version_mismatch_error
|
||||
| Error Parse_error -> cannot_parse_error
|
||||
| Ok { prepared_dependencies; targets } ->
|
||||
Ok
|
||||
{ response_fn = greeting.response_fn
|
||||
; prepared_dependencies
|
||||
; targets
|
||||
})))))
|
||||
;;
|
||||
|
||||
let prepared_dependencies (t : t) = t.prepared_dependencies
|
||||
let targets (t : t) = t.targets
|
||||
|
||||
let respond (t : t) response =
|
||||
let data = Response.to_sexp response |> Csexp.to_string in
|
||||
Io.String_path.write_file t.response_fn data
|
||||
;;
|
||||
end
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
open Import
|
||||
open Sexpable_intf
|
||||
module Error : module type of Error
|
||||
|
||||
module Dependency : sig
|
||||
type t =
|
||||
| File of string
|
||||
| Directory of string
|
||||
| Glob of
|
||||
{ path : string
|
||||
; glob : string
|
||||
}
|
||||
|
||||
module Map : Map.S with type key = t
|
||||
|
||||
module Set : sig
|
||||
include Set.S with type elt = t and type 'a map = 'a Map.t
|
||||
end
|
||||
end
|
||||
|
||||
module Greeting : sig
|
||||
type t =
|
||||
{ run_arguments_fn : string
|
||||
; response_fn : string
|
||||
}
|
||||
|
||||
include Sexpable with type t := t
|
||||
end
|
||||
|
||||
module Run_arguments : sig
|
||||
type t =
|
||||
{ prepared_dependencies : Dependency.Set.t
|
||||
; targets : String.Set.t
|
||||
}
|
||||
|
||||
include Sexpable with type t := t
|
||||
end
|
||||
|
||||
module Response : sig
|
||||
type t =
|
||||
| Done
|
||||
| Need_more_deps of Dependency.Set.t
|
||||
|
||||
include Sexpable with type t := t
|
||||
end
|
||||
|
||||
(** Dune sets this environment variable to pass [Greeting.t] to client. *)
|
||||
val run_by_dune_env_variable : string
|
||||
|
||||
module Context : sig
|
||||
type t
|
||||
|
||||
type create_result =
|
||||
| Ok of t
|
||||
| Run_outside_of_dune
|
||||
| Error of string
|
||||
|
||||
val create : unit -> create_result
|
||||
val prepared_dependencies : t -> Dependency.Set.t
|
||||
val targets : t -> String.Set.t
|
||||
val respond : t -> Response.t -> unit
|
||||
end
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
open Import
|
||||
|
||||
module Error = struct
|
||||
type t =
|
||||
| Version_mismatch of int
|
||||
| Parse_error
|
||||
end
|
||||
|
||||
module type Sexpable = sig
|
||||
type t
|
||||
|
||||
val to_sexp : t -> Sexp.t
|
||||
val of_sexp : Sexp.t -> (t, Error.t) result
|
||||
end
|
||||
|
||||
module type S = sig
|
||||
type t
|
||||
|
||||
val conv : t Conv.value
|
||||
val version : int
|
||||
end
|
||||
|
||||
module Make (Type : S) = struct
|
||||
let conv =
|
||||
let open Conv in
|
||||
pair int Type.conv
|
||||
;;
|
||||
|
||||
let of_sexp sexp : (_, Error.t) result =
|
||||
match Conv.of_sexp Conv.(pair int sexp) ~version:(0, 0) sexp with
|
||||
| Error _ -> Error Parse_error
|
||||
| Ok (version, sexp) ->
|
||||
(match Int.equal version Type.version with
|
||||
| false -> Error (Version_mismatch version)
|
||||
| true ->
|
||||
(match Conv.of_sexp Type.conv ~version:(0, 0) sexp with
|
||||
| Error _ -> Error Parse_error
|
||||
| Ok v -> Ok v))
|
||||
;;
|
||||
|
||||
let to_sexp t = Conv.to_sexp conv (Type.version, t)
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune-action-plugin dune-glob))
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
open Dune_action_plugin.V1
|
||||
module Glob = Dune_glob.V1
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
let+ listing =
|
||||
read_directory_with_glob ~glob:Glob.universal ~path:(Path.of_string "some_dir")
|
||||
in
|
||||
String.concat "; " listing |> Printf.printf "Directory listing: [%s]"
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (data_only_dirs some_dir)
|
||||
> \
|
||||
> (rule
|
||||
> (alias runtest)
|
||||
> (action (dynamic-run ./foo.exe)))
|
||||
> EOF
|
||||
|
||||
$ mkdir some_dir
|
||||
$ touch some_dir/some_file1
|
||||
$ touch some_dir/some_file2
|
||||
|
||||
$ cp ./bin/foo.exe ./
|
||||
|
||||
$ dune runtest
|
||||
Directory listing: [some_file1; some_file2]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune-action-plugin))
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
let+ data = read_file ~path:(Path.of_string "some_file") in
|
||||
print_endline data
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
This test checks that 'dynamic-run' will not be reexecuted if
|
||||
dependencies do not change (have the same digest) even if
|
||||
they were forced to rebuild.
|
||||
|
||||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (target some_file)
|
||||
> (deps (universe))
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building some_file!\n")
|
||||
> (with-stdout-to %{target} (echo "Hello from some_file!")))))
|
||||
> \
|
||||
> (rule
|
||||
> (alias runtest)
|
||||
> (action (dynamic-run ./foo.exe)))
|
||||
> EOF
|
||||
|
||||
$ cp ./bin/foo.exe ./
|
||||
|
||||
$ dune runtest
|
||||
Building some_file!
|
||||
Hello from some_file!
|
||||
|
||||
$ dune runtest
|
||||
Building some_file!
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune_action_plugin dune_glob))
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
open Dune_action_plugin.V1
|
||||
module Glob = Dune_glob.V1
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
let glob = Glob.of_string "some_file*" in
|
||||
let+ listing = read_directory_with_glob ~path:(Path.of_string "some_dir") ~glob in
|
||||
String.concat "\n" listing |> print_endline
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (alias runtest)
|
||||
> (action (dynamic-run ./foo.exe)))
|
||||
> EOF
|
||||
|
||||
$ mkdir some_dir
|
||||
|
||||
$ cat > some_dir/dune << EOF
|
||||
> (rule
|
||||
> (target some_file)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building some_file!\n")
|
||||
> (with-stdout-to %{target} (echo "")))))
|
||||
> \
|
||||
> (rule
|
||||
> (target another_file)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "SHOULD NOT BE PRINTED!")
|
||||
> (with-stdout-to %{target} (echo "")))))
|
||||
> \
|
||||
> (rule
|
||||
> (target some_file_but_different)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building some_file_but_different!\n")
|
||||
> (with-stdout-to %{target} (echo "")))))
|
||||
> EOF
|
||||
|
||||
$ cp ./bin/foo.exe ./
|
||||
|
||||
$ dune runtest
|
||||
Building some_file!
|
||||
Building some_file_but_different!
|
||||
some_file
|
||||
some_file_but_different
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune-action-plugin dune-glob))
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
open Dune_action_plugin.V1
|
||||
module Glob = Dune_glob.V1
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
let+ _ = read_directory_with_glob ~glob:Glob.universal ~path:(Path.of_string ".")
|
||||
and+ _ = write_file ~path:(Path.of_string "some_file") ~data:"Hello from some_file!" in
|
||||
()
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (target some_file)
|
||||
> (action
|
||||
> (dynamic-run ./foo.exe)))
|
||||
> EOF
|
||||
|
||||
$ cp ./bin/foo.exe ./
|
||||
|
||||
$ dune build some_file 2>&1 | awk '/Internal error/,/unable to serialize/'
|
||||
Internal error, please report upstream including the contents of _build/log.
|
||||
Description:
|
||||
("unable to serialize exception",
|
||||
|
||||
^ This is not great. There is no actual dependency cycle, dune is just
|
||||
interpreting glob dependency too coarsely (it builds all files instead
|
||||
of just bringing the directory listing up to date).
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo1 foo2)
|
||||
(libraries dune-action-plugin))
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let path = Path.of_string "some_file1"
|
||||
let action = read_file ~path |> stage ~f:(fun data -> write_file ~path ~data)
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let path = Path.of_string "some_file2"
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
write_file ~path ~data:"Hello from some_file2!"
|
||||
|> stage ~f:(fun () ->
|
||||
let+ data = read_file ~path in
|
||||
print_endline data)
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (target some_file1)
|
||||
> (action
|
||||
> (dynamic-run ./foo1.exe)))
|
||||
> \
|
||||
> (rule
|
||||
> (target some_file2)
|
||||
> (action
|
||||
> (dynamic-run ./foo2.exe)))
|
||||
> EOF
|
||||
|
||||
$ cp ./bin/foo1.exe ./
|
||||
$ cp ./bin/foo2.exe ./
|
||||
|
||||
$ dune build some_file1
|
||||
Error: Dependency cycle between:
|
||||
_build/default/some_file1
|
||||
[1]
|
||||
|
||||
$ dune build some_file2
|
||||
Error: Dependency cycle between:
|
||||
_build/default/some_file2
|
||||
[1]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let action =
|
||||
let open Dune_action_plugin.V1.O in
|
||||
let switch = read_file ~path:(Path.of_string "foo_or_bar") in
|
||||
stage switch ~f:(fun file ->
|
||||
let+ data = read_file ~path:(Path.of_string file) in
|
||||
print_endline data)
|
||||
;;
|
||||
|
||||
let () = run action
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names client)
|
||||
(libraries dune-action-plugin))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
This test checks that in case the dependency of multi staged computation changes,
|
||||
only the dependencies up to this stage are rebuilt.
|
||||
|
||||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (deps bar_source)
|
||||
> (target bar)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building bar!\n")
|
||||
> (copy %{deps} %{target}))))
|
||||
> \
|
||||
> (rule
|
||||
> (deps foo_source)
|
||||
> (target foo)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building foo!\n")
|
||||
> (copy %{deps} %{target}))))
|
||||
> \
|
||||
> (rule
|
||||
> (deps foo_or_bar_source)
|
||||
> (target foo_or_bar)
|
||||
> (action
|
||||
> (progn
|
||||
> (echo "Building foo_or_bar!\n")
|
||||
> (copy %{deps} %{target}))))
|
||||
> \
|
||||
> (rule
|
||||
> (alias runtest)
|
||||
> (action (dynamic-run ./client.exe)))
|
||||
> EOF
|
||||
|
||||
$ cp ./bin/client.exe ./
|
||||
|
||||
$ printf "foo" > foo_or_bar_source
|
||||
$ printf "Hello from foo!" > foo_source
|
||||
$ printf "SHOULD NOT BE PRINTED!" > bar_source
|
||||
|
||||
$ dune runtest
|
||||
Building foo_or_bar!
|
||||
Building foo!
|
||||
Hello from foo!
|
||||
|
||||
$ printf "bar" > foo_or_bar_source
|
||||
$ printf "SHOULD NOT BE PRINTED!" > foo_source
|
||||
$ printf "Hello from bar!" > bar_source
|
||||
|
||||
$ dune runtest
|
||||
Building foo_or_bar!
|
||||
Building bar!
|
||||
Hello from bar!
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
(cram
|
||||
(applies_to :whole_subtree)
|
||||
(alias run_dynamic)
|
||||
(deps
|
||||
(package dune)))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune-action-plugin))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let () = run (return ())
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
Check that multiple 'dynamic-run' commands within single action are
|
||||
detected and error is printed even if the rule is not executed.
|
||||
|
||||
$ cat > dune-project << EOF
|
||||
> (lang dune 2.0)
|
||||
> (using action-plugin 0.1)
|
||||
> EOF
|
||||
|
||||
$ cat > dune << EOF
|
||||
> (rule
|
||||
> (target some_file)
|
||||
> (action
|
||||
> (progn
|
||||
> (dynamic-run ./foo.exe some_arg)
|
||||
> (dynamic-run ./foo.exe another_arg))))
|
||||
> \
|
||||
> (alias
|
||||
> (name runtest)
|
||||
> (action
|
||||
> (echo "SHOULD NOT BE PRINTED")))
|
||||
> EOF
|
||||
|
||||
$ cp ../bin/foo.exe ./
|
||||
|
||||
$ dune runtest
|
||||
File "dune", lines 4-6, characters 2-84:
|
||||
4 | (progn
|
||||
5 | (dynamic-run ./foo.exe some_arg)
|
||||
6 | (dynamic-run ./foo.exe another_arg))))
|
||||
Error: Multiple 'dynamic-run' commands within single action are not
|
||||
supported.
|
||||
[1]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(executables
|
||||
(names foo)
|
||||
(libraries dune_action_plugin))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
open Dune_action_plugin.V1
|
||||
|
||||
let () = run (return (print_endline "Hello from foo!"))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(cram
|
||||
(deps
|
||||
(glob_files bin/*.exe)))
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue