forked from ocsigen/eliom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtopkg.ml
358 lines (313 loc) · 12.6 KB
/
topkg.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
(*---------------------------------------------------------------------------
Copyright (c) 2014 Daniel C. Bünzli. All rights reserved.
Distributed under the BSD3 license, see license at the end of the file.
%%NAME%% release %%VERSION%%
---------------------------------------------------------------------------*)
(* Public api *)
(** Build environment access *)
module type Env = sig
val bool : string -> bool
(** [bool key] declares [key] as being a boolean key in the environment.
Specifing key=(true|false) on the command line becomes mandatory. *)
val native : bool
(** [native] is [bool "native"]. *)
val native_dynlink : bool
(** [native_dylink] is [bool "native-dynlink"] *)
end
(** Exts defines sets of file extensions. *)
module type Exts = sig
val interface : string list
(** [interface] is [[".mli"; ".cmi"; ".cmti"]] *)
val interface_opt : string list
(** [interface_opt] is [".cmx" :: interface] *)
val library : string list
(** [library] is [[".cma"; ".cmxa"; ".cmxs"; ".a"]] *)
val module_library : string list
(** [module_library] is [(interface_opt @ library)]. *)
end
(** Package description. *)
module type Pkg = sig
type builder = [ `OCamlbuild | `Other of string * string ]
(** The type for build tools. Either [`OCamlbuild] or an
[`Other (tool, bdir)] tool [tool] that generates its build artefacts
in [bdir]. *)
type moves
(** The type for install moves. *)
type field = ?cond:bool -> ?exts:string list -> ?dst:string -> ?target:string -> string -> moves
(** The type for field install functions. A call
[field cond exts dst path] generates install moves as follows:
{ul
{- If [cond] is [false] (defaults to [true]), no move is generated.}
{- If [exts] is present, generates a move for each path in
the list [List.map (fun e -> path ^ e) exts].}
{- If [dst] is present this path is used as the move destination
(allows to install in subdirectories). If absent [dst] is
[Filename.basename path].} *)
val lib : field
val bin : ?auto:bool -> field
(** If [auto] is true (defaults to false) generates
[path ^ ".native"] if {!Env.native} is [true] and
[path ^ ".byte"] if {!Env.native} is [false]. *)
val sbin : ?auto:bool -> field (** See {!bin}. *)
val toplevel : field
val share : field
val share_root : field
val etc : field
val doc : field
val misc : field
val stublibs : field
val man : field
val describe : string -> builder:builder -> moves list -> unit
(** [describe name builder moves] describes a package named [name] with
builder [builder] and install moves [moves]. *)
end
(* Implementation *)
module Topkg : sig
val cmd : [`Build | `Explain | `Help ]
val env : (string * bool) list
val err_parse : string -> 'a
val err_mdef : string -> 'a
val err_miss : string -> 'a
val err_file : string -> string -> 'a
val warn_unused : string -> unit
end = struct
(* Parses the command line. The actual cmd execution occurs in the call
to Pkg.describe. *)
let err fmt =
let k _ = exit 1 in
Format.kfprintf k Format.err_formatter ("%s: " ^^ fmt ^^ "@.") Sys.argv.(0)
let err_parse a = err "argument `%s' is not of the form key=(true|false)" a
let err_mdef a = err "bool `%s' is defined more than once" a
let err_miss a = err "argument `%s=(true|false)' is missing" a
let err_file f e = err "%s: %s" f e
let warn_unused k =
Format.eprintf "%s: warning: environment key `%s` unused.@." Sys.argv.(0) k
let cmd, env =
let rec parse_env acc = function (* not t.r. *)
| arg :: args ->
begin try
(* String.cut ... *)
let len = String.length arg in
let eq = String.index arg '=' in
let bool = bool_of_string (String.sub arg (eq + 1) (len - eq - 1)) in
let key = String.sub arg 0 eq in
if key = "" then raise Exit else
try ignore (List.assoc key acc); err_mdef key with
| Not_found -> parse_env ((key, bool) :: acc) args
with
| Invalid_argument _ | Not_found | Exit -> err_parse arg
end
| [] -> acc
in
match List.tl (Array.to_list Sys.argv) with
| "explain" :: args -> `Explain, parse_env [] args
| ("help" | "-h" | "--help" | "-help") :: args -> `Help, parse_env [] args
| args -> `Build, parse_env [] args
end
module Env : sig
include Env
val get : unit -> (string * bool) list
end = struct
let env = ref []
let get () = !env
let add_bool key b = env := (key, b) :: !env
let bool key =
let b = try List.assoc key Topkg.env with
| Not_found -> if Topkg.cmd = `Build then Topkg.err_miss key else true
in
add_bool key b; b
let native = bool "native"
let native_dynlink = bool "native-dynlink"
end
module Exts : Exts = struct
let interface = [".mli"; ".cmi"; ".cmti"]
let interface_opt = ".cmx" :: interface
let library = [".cma"; ".cmxa"; ".cmxs"; ".a"]
let module_library = (interface_opt @ library)
end
module Pkg : Pkg = struct
type builder = [ `OCamlbuild | `Other of string * string ]
type move = {
field_name : string;
target : string option;
source : string;
dest : string }
type moves = move list
type field = ?cond:bool -> ?exts:string list -> ?dst:string -> ?target:string -> string -> moves
let str = Printf.sprintf
let warn_unused () =
let keys = List.map fst Topkg.env in
let keys_used = List.map fst (Env.get ()) in
let unused = List.find_all (fun k -> not (List.mem k keys_used)) keys in
List.iter Topkg.warn_unused unused
let has_suffix = Filename.check_suffix
let build_command ?(exec_sep = " ") btool mvs =
let no_build = [ ".cmti"; ".cmt"] in
let exec = Buffer.create 1871 in
let add_target = function {field_name=field; source=src; dest=dst;target} ->
let target = match target with
| None -> src
| Some s -> s in
if not (List.exists (has_suffix target) no_build) then
Buffer.add_string exec (str "%s%s" exec_sep target)
in
Buffer.add_string exec btool;
List.iter add_target mvs;
Buffer.contents exec
let split_char sep p =
let len = String.length p in
let rec split beg cur =
if cur >= len then
if cur - beg > 0
then [String.sub p beg (cur - beg)]
else []
else if p.[cur] = sep then
String.sub p beg (cur - beg) :: split (cur + 1) (cur + 1)
else
split beg (cur + 1) in
split 0 0
let holes src dst =
let aux x = match split_char '%' x with
| [x;y] -> Some (x,y)
| _ -> None in
match aux src, aux dst with
| Some (s_path,s_suffix),Some(d_path,d_suffix) -> Some (s_path,s_suffix,d_path,d_suffix)
| None,None -> None
| Some (s_path,s_suffix),None -> Some (s_path,s_suffix,dst,s_suffix)
| _ -> assert false
let list_files bdir src dst =
match holes src dst with
| None -> [str "%s/%s" bdir src, dst]
| Some (s_path,s_suffix,d_path,d_suffix) ->
let p = bdir ^ "/" ^ s_path in
let l = Array.to_list (Sys.readdir p) in
let l = List.filter (fun f -> has_suffix f s_suffix) l in
List.map (fun s -> str "%s/%s%s" bdir s_path s,
str "%s%s%s" d_path (Filename.chop_suffix s s_suffix) d_suffix) l
let build_install bdir mvs =
let no_build = [ ".cmti"; ".cmt"] in
let install = Buffer.create 1871 in
let rec add_mvs current = function
| {field_name=field; source=src; dest=dst;target} :: mvs when field = current ->
let target = match target with
| None -> src
| Some s -> s in
let option = if List.exists (has_suffix target) no_build then "?" else "" in
List.iter (fun (src,dst) ->
Buffer.add_string install (str "\n \"%s%s\" {\"%s\"}" option src dst)
) (list_files bdir src dst);
add_mvs current mvs
| {field_name=field}::_ as mvs ->
if current <> "" (* first *) then Buffer.add_string install " ]\n";
Buffer.add_string install (str "%s: [" field);
add_mvs field mvs
| [] -> ()
in
add_mvs "" mvs;
Buffer.add_string install " ]\n";
Buffer.contents install
let pr = Format.printf
let pr_explanation btool bdir pkg mvs =
let env = Env.get () in
let exec = build_command ~exec_sep:" \\\n " btool mvs in
let install = build_install bdir mvs in
pr "@[<v>";
pr "Package name: %s@," pkg;
pr "Build tool: %s@," btool;
pr "Build directory: %s@," bdir;
pr "Environment:@, ";
List.iter (fun (k,v) -> pr "%s=%b@, " k v) (List.sort compare env);
pr "@,Build invocation:@,";
pr " %s@,@," exec;
pr "Install file:@,";
pr "%s@," install;
pr "@]";
()
let pr_help () =
pr "Usage example:@\n %s" Sys.argv.(0);
List.iter (fun (k,v) -> pr " %s=%b" k v) (List.sort compare (Env.get ()));
pr "@."
let build btool bdir pkg mvs =
let exec = build_command btool mvs in
let e = Sys.command exec in
if e <> 0 then exit e else
let install = build_install bdir mvs in
let install_file = pkg ^ ".install" in
try
let oc = open_out install_file in
output_string oc install; flush oc; close_out oc
with Sys_error e -> Topkg.err_file install_file e
let mvs ?(drop_exts = []) field ?(cond = true) ?(exts = []) ?dst ?target src =
if not cond then [] else
let mv src dst = {field_name=field; source=src; dest=dst; target} in
let expand exts s d = List.map (fun e -> mv (s ^ e) (d ^ e)) exts in
let dst = match dst with None -> Filename.basename src | Some dst -> dst in
let files = if exts = [] then [mv src dst] else expand exts src dst in
let keep {source=src} = not (List.exists (has_suffix src) drop_exts) in
List.find_all keep files
let lib =
let drop_exts =
if Env.native && not Env.native_dynlink then [ ".cmxs" ] else
if not Env.native then [ ".a"; ".cmx"; ".cmxa"; ".cmxs" ] else []
in
mvs ~drop_exts "lib"
let share = mvs "share"
let share_root = mvs "share_root"
let etc = mvs "etc"
let toplevel = mvs "toplevel"
let doc = mvs "doc"
let misc = mvs "misc"
let stublibs = mvs "stublibs"
let man = mvs "man"
let bin_drops = if not Env.native then [ ".native" ] else []
let bin_mvs field ?(auto = false) ?cond ?exts ?dst ?target src =
let src, dst =
if not auto then src, dst else
let dst = match dst with
| None -> Some (Filename.basename src)
| Some _ as dst -> dst
in
let src = if Env.native then src ^ ".native" else src ^ ".byte" in
src, dst
in
mvs ~drop_exts:bin_drops field ?cond ?dst ?exts ?target src
let bin = bin_mvs "bin"
let sbin = bin_mvs "sbin"
let describe pkg ~builder mvs =
let mvs = List.sort compare (List.flatten mvs) in
let btool, bdir = match builder with
| `OCamlbuild -> "ocamlbuild -use-ocamlfind -classic-display", "_build"
| `Other (btool, bdir) -> btool, bdir
in
match Topkg.cmd with
| `Explain -> pr_explanation btool bdir pkg mvs
| `Help -> pr_help ()
| `Build -> warn_unused (); build btool bdir pkg mvs
end
(*---------------------------------------------------------------------------
Copyright (c) 2014 Daniel C. Bünzli.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. Neither the name of Daniel C. Bünzli nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------*)