Source file unic_cli.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
let src = Logs.Src.create "uniq.cli"
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
module Log = (val Logs.src_log src : Logs.LOG)
open Cmdliner
let existing_filepath =
let parser str =
match Fpath.of_string str with
| Ok v when Sys.file_exists str && Sys.is_regular_file str -> Ok v
| Ok v -> error_msgf "%a does not exist" Fpath.pp v
| Error _ as err -> err
in
Arg.conv (parser, Fpath.pp)
let existing_dirpath =
let parser str =
match Fpath.of_string str with
| Ok v when Sys.file_exists str && Sys.is_directory str ->
Ok (Fpath.to_dir_path v)
| Ok v -> error_msgf "%a does not exist" Fpath.pp v
| Error _ as err -> err
in
Arg.conv (parser, Fpath.pp)
let s_output = "OUTPUT OPTIONS"
let s_logs = "LOGS OPTIONS"
let verbosity = Logs_cli.level ~docs:s_logs ()
let renderer = Fmt_cli.style_renderer ~docs:s_output ()
let utf_8 =
let doc = "Allow binaries to emit UTF-8 characters." in
let open Arg in
value & opt bool true & info [ "with-utf-8" ] ~doc ~docs:s_output
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
let neg fn = fun x -> not (fn x)
let reporter sources ppf =
let re = Stdlib.Option.map Re.compile sources in
let print src =
let some re = (neg List.is_empty) (Re.matches re (Logs.Src.name src)) in
Stdlib.Option.fold ~none:true ~some re
in
let report src level ~over k msgf =
let k _ = over (); k () in
let pp _tags k ppf fmt =
Fmt.kpf k ppf
("[%a]%a[%a]: " ^^ fmt ^^ "\n%!")
Fmt.(styled `Cyan int)
(Stdlib.Domain.self () :> int)
Logs_fmt.pp_header (level, header)
Fmt.(styled `Magenta string)
(Logs.Src.name src)
in
match (level, print src) with
| Logs.Debug, false -> k ()
| _, true | _ -> msgf @@ fun ? ?tags fmt -> pp header tags k ppf fmt
in
{ Logs.report }
let regexp : (string * [ `None | `Re of Re.t ]) Arg.conv =
let parser str =
match Re.Pcre.re str with
| re -> Ok (str, `Re re)
| exception _ -> error_msgf "Invalid PCRegexp: %S" str
in
let pp ppf (str, _) = Fmt.string ppf str in
Arg.conv (parser, pp)
let sources =
let doc = "A regexp (PCRE syntax) to identify which log we print." in
let open Arg in
value
& opt_all regexp [ ("", `None) ]
& info [ "l" ] ~doc ~docs:s_logs ~docv:"REGEXP"
let setup_sources = function
| [ (_, `None) ] -> None
| res ->
let res = List.map snd res in
let res =
List.fold_left
(fun acc -> function `Re re -> re :: acc | _ -> acc)
[] res
in
Some (Re.alt res)
let setup_sources = Term.(const setup_sources $ sources)
let setup_logs utf_8 style_renderer sources level =
Stdlib.Option.iter (Fmt.set_style_renderer Fmt.stdout) style_renderer;
Stdlib.Option.iter (Fmt.set_style_renderer Fmt.stderr) style_renderer;
Fmt.set_utf_8 Fmt.stdout utf_8;
Fmt.set_utf_8 Fmt.stderr utf_8;
Logs.set_level level;
Logs.set_reporter (reporter sources Fmt.stderr);
Stdlib.Option.is_none level
let setup_logs =
Term.(const setup_logs $ utf_8 $ renderer $ setup_sources $ verbosity)
let s_ocamlfind = "OCAMLFIND OPTIONS"
let ocamlfind's_directories =
let doc = "The source directory containing the META files." in
let open Arg in
value
& opt_all existing_dirpath []
& info [ "I" ] ~doc ~docs:s_ocamlfind ~docv:"DIRECTORY"
let setup_ocamlfind toolchain user's_directories =
let cmd =
match toolchain with
| None -> Bos.Cmd.(v "ocamlfind" % "printconf" % "path")
| Some t ->
Bos.Cmd.(v "ocamlfind" % "-toolchain" % t % "printconf" % "path")
in
let ( let* ) = Result.bind in
let directories =
let* exists = Bos.OS.Cmd.exists cmd in
if exists then
let r = Bos.OS.Cmd.run_out cmd in
let* directories, _ = Bos.OS.Cmd.out_lines ~trim:true r in
let directories =
List.fold_left
(fun acc path ->
match Fpath.of_string path with
| Ok fpath when Sys.file_exists path && Sys.is_directory path ->
fpath :: acc
| Ok _ -> acc
| Error (`Msg _) ->
Log.warn (fun m ->
m "ocamlfind returned an invalid path: %S" path);
acc)
[] directories
in
Ok directories
else Ok []
in
let directories = Result.value ~default:[] directories in
Log.debug (fun m ->
m "ocamlfind directories: @[<hov>%a@]"
Fmt.(list ~sep:(any ",@ ") Fpath.pp)
directories);
List.rev_append directories user's_directories
let toolchain =
let doc =
"Use the $(b,ocamlfind) toolchain $(i,NAME) (e.g. $(b,solo5)) instead of \
the host one: both the OCaml configuration and the package roots are \
taken from that cross toolchain."
in
let open Arg in
value
& opt (some string) None
& info [ "toolchain" ] ~doc ~docs:s_ocamlfind ~docv:"NAME"
let setup_ocamlfind =
Term.(const setup_ocamlfind $ toolchain $ ocamlfind's_directories)
let s_ocaml = "OCAML OPTIONS"
let compiler =
let doc = "The compiler chosen (bytecode or native)." in
let parser str =
match String.lowercase_ascii str with
| "bytecode" -> Ok `Bytecode
| "native" -> Ok `Native
| _ -> error_msgf "Invalid compiler %S (must be bytecode or native)" str
in
let pp ppf = function
| `Bytecode -> Fmt.string ppf "bytecode"
| `Native -> Fmt.string ppf "native"
in
let compiler = Arg.conv (parser, pp) in
let open Arg in
value
& opt compiler `Native
& info [ "compiler" ] ~doc ~docs:s_ocaml ~docv:"COMPILER"
let setup_ocaml toolchain compiler =
let compiler =
match compiler with `Native -> "ocamlopt" | `Bytecode -> "ocamlc"
in
match Uniq_cfg.from ?toolchain compiler () with
| Ok (where, cfg) -> Some (where, cfg)
| Error (`Msg msg) ->
Log.warn (fun m ->
m "Impossible to get the configuration of OCaml: %s" msg);
None
let setup_ocaml = Term.(const setup_ocaml $ toolchain $ compiler)
let modname =
let parser str =
match Modname.of_string str with
| Ok _ as v -> v
| Error (`Msg msg) -> Error (`Msg msg)
in
Arg.conv ~docv:"MODULE" (parser, Modname.pp)
let path =
let parser str =
match Fpath.of_string str with
| Ok v ->
let v =
if Sys.file_exists str && Sys.is_directory str then
Fpath.to_dir_path v
else v
in
Ok v
| Error _ as err -> err
in
Arg.conv (parser, Fpath.pp)
type preference =
| Use of Modname.t * Uniq_meta.Path.t
| Prefer of Uniq_meta.Path.t
let preference =
let parser str =
let ( let* ) = Result.bind in
match String.index_opt str ':' with
| Some i ->
let m = String.sub str 0 i in
let p = String.sub str (succ i) (String.length str - succ i) in
let* m = Modname.of_string m in
let* p = Uniq_meta.Path.of_string p in
Ok (Use (m, p))
| None ->
let* p = Uniq_meta.Path.of_string str in
Ok (Prefer p)
in
let pp ppf = function
| Use (m, p) -> Fmt.pf ppf "%a:%a" Modname.pp m Uniq_meta.Path.pp p
| Prefer p -> Uniq_meta.Path.pp ppf p
in
Arg.conv (parser, pp)
let apply t = function
| Use (m, p) -> Uniq_policy.use t m p
| Prefer p -> Uniq_policy.prefer t p
let setup_policy filepath preferences =
let policy =
match filepath with
| None -> Uniq_policy.empty
| Some filepath ->
let policy = Uniq_policy.load filepath in
let fn _ =
Log.warn (fun m ->
m "Invalid configuration file (you can validate it with %a): %a"
Fmt.(styled `Bold string)
"bcfg validate" Fpath.pp filepath)
in
Result.iter_error fn policy;
Result.value ~default:Uniq_policy.empty policy
in
List.fold_left apply policy (List.concat preferences)
let configuration =
let doc = "A $(b,bcfg) policy file to disambiguate package choices." in
let open Arg in
value
& opt (some existing_filepath) None
& info [ "c"; "config" ] ~doc ~docv:"FILE"
let preferences =
let doc =
"Prefer an ocamlfind package to provide a module when several do: \
$(b,MODULE:PACKAGE) for a given module (e.g. $(b,Term:cmdliner)), or just \
$(b,PACKAGE) as a global preference. Repeatable or comma-separated, and \
applied on top of the policy file ($(b,--config))."
in
let open Arg in
value
& opt_all (list preference) []
& info [ "prefer" ] ~doc ~docv:"[MODULE:]PACKAGE"
let setup_policy = Term.(const setup_policy $ configuration $ preferences)