Source file opamCliMain.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
open OpamCmdliner
open OpamStateTypes
open OpamTypesBase
exception InvalidCLI of OpamCLIVersion.Sourced.t
exception InvalidFlagContent of string * (string * string) option
exception InvalidNewFlag of OpamCLIVersion.Sourced.t * string * OpamCLIVersion.t
let raise_invalid_cli :
(OpamCLIVersion.Sourced.t, string option) result -> 'a
= function
| Ok ocli -> raise (InvalidCLI ocli)
| Error None -> raise (InvalidFlagContent ("cli", None))
| Error (Some invalid) ->
raise (InvalidFlagContent ("cli", Some (invalid, "major.minor")))
let raise_invalid_confirm_level invalid =
let invalid =
Stdlib.Option.map (fun i ->
i, "one of " ^
(OpamArg.confirm_enum
|> List.map (fun (_,s,_) -> Printf.sprintf "`%s'" s)
|> OpamStd.Format.pretty_list ~last:"or"))
invalid
in
raise (InvalidFlagContent ("confirm-level", invalid))
let rec filter_cli_arg cli acc args =
match args with
| []
| "--" :: _ -> (cli, List.rev_append acc args)
| "--cl" :: args -> filter_cli_arg cli acc ("--cli"::args)
| ["--cli"] | "--cli" :: "--" :: _ -> raise_invalid_cli (Error None)
| "--cli" :: arg :: args ->
let version =
match OpamCLIVersion.of_string_opt arg with
| Some cli ->
let ocli = cli, `Command_line in
if OpamCLIVersion.is_supported cli then ocli else
raise_invalid_cli (Ok ocli)
| None -> raise_invalid_cli (Error (Some arg))
in
filter_cli_arg (Some version) acc args
| arg :: args ->
match OpamStd.String.cut_at arg '=' with
| Some ("--cl", value)
| Some ("--cli", value) ->
filter_cli_arg cli acc ("--cli"::value::args)
| _ ->
filter_cli_arg cli (arg::acc) args
let is_confirm_level =
OpamStd.String.is_prefix_of ~from:4 ~full:"--confirm-level"
let rec preprocess_argv cli yes_args confirm args =
let yes = yes_args <> [] in
match args with
| [] ->
(cli, yes, confirm, yes_args)
| "--" :: _ ->
(cli, yes, confirm, yes_args @ args)
| ("-y" | "--y" | "--ye" | "--yes") as yes_opt :: args ->
preprocess_argv cli [yes_opt] confirm args
| ([c] | c :: "--" :: _) when is_confirm_level c ->
raise_invalid_confirm_level None
| confirm_level :: cl_arg :: args when is_confirm_level confirm_level ->
let answer =
match List.find_opt (fun (_,n,_) -> n = cl_arg)
OpamArg.confirm_enum with
| Some (_, _, a) -> a
| None -> raise_invalid_confirm_level (Some cl_arg)
in
preprocess_argv cli yes_args (Some answer) args
| "--cl" :: args -> preprocess_argv cli yes_args confirm ("--cli"::args)
| ["--cli"] | "--cli" :: "--" :: _ -> raise_invalid_cli (Error None)
| "--cli" :: arg :: args ->
let version =
match OpamCLIVersion.of_string_opt arg with
| Some cli ->
let ocli = cli, `Command_line in
if OpamCLIVersion.is_supported cli then ocli else
raise_invalid_cli (Ok ocli)
| _ -> raise_invalid_cli (Error (Some arg))
in
preprocess_argv (Some version) yes_args confirm args
| arg :: rest ->
match OpamStd.String.cut_at arg '=' with
| Some ("--cl", value)
| Some ("--cli", value) ->
preprocess_argv cli yes_args confirm ("--cli"::value::rest)
| Some (pre, value) when is_confirm_level pre ->
preprocess_argv cli yes_args confirm ("--confirm-level"::value::rest)
| _ ->
if OpamCommands.is_builtin_command arg then
let (cli, rest) = filter_cli_arg cli [] rest in
(cli, yes, confirm, arg :: (yes_args @ rest))
else
(cli, yes, confirm, args)
let check_and_run_external_commands () =
let (cli, yes, confirm_level, argv) =
match Array.to_list Sys.argv with
| prog::args ->
let (ocli, yes, confirm, args) = preprocess_argv None [] None args in
let ocli =
match ocli with
| Some ((cli, _) as ocli) ->
if OpamCLIVersion.(cli < (2, 1)) then begin
let cli = OpamCLIVersion.to_string cli in
OpamConsole.warning
"%s cannot be understood by opam %s; set %s to %s instead."
(OpamConsole.colorise `bold ("--cli=" ^ cli)) cli
(OpamConsole.colorise `bold "OPAMCLI") (OpamConsole.colorise `bold cli)
end;
ocli
| None ->
match OpamCLIVersion.Sourced.env (OpamClientConfig.E.cli ()) with
| Some ((cli, _) as ocli) ->
if OpamCLIVersion.is_supported cli then
let () =
if OpamCLIVersion.(cli >= (2, 1)) then
let flag = "--cli=" ^ OpamCLIVersion.(to_string cli) in
OpamConsole.warning
"OPAMCLI should only ever be set to %s - use '%s' instead."
(OpamConsole.colorise `bold "2.0")
(OpamConsole.colorise `bold flag)
in
ocli
else
raise_invalid_cli (Ok ocli)
| None ->
OpamCLIVersion.Sourced.current
in
let confirm =
match confirm with
| Some _ when OpamCLIVersion.(fst ocli < (2,1)) ->
raise (InvalidNewFlag (ocli, "confirm-level",
OpamCLIVersion.of_string "2.1"))
| _ -> confirm
in
(ocli, yes, confirm, prog::args)
| args -> (OpamCLIVersion.Sourced.current, false, None, args)
in
match argv with
| [] | [_] -> (cli, argv)
| _ :: name :: args ->
if String.length name > 0 && name.[0] = '-'
|| OpamCommands.is_builtin_command name
then (cli, argv)
else
let command = OpamPath.plugin_prefix ^ name in
OpamArg.init_opam_env_variabes cli;
let yes = if yes then Some (Some true) else None in
OpamCoreConfig.init ?yes ?confirm_level ();
OpamFormatConfig.init ();
let root_from, root_dir = OpamStateConfig.opamroot_with_provenance () in
let has_init, root_upgraded =
match OpamStateConfig.load_defaults ~lock_kind:`Lock_read root_dir with
| None -> (false, false)
| Some config ->
let root_upgraded =
let cmp =
OpamVersion.compare OpamFile.Config.root_version
(OpamFile.Config.opam_root_version config)
in
if cmp < 0 then
OpamConsole.error_and_exit `Configuration_error
"%s reports a newer opam version, aborting."
(OpamFilename.Dir.to_string root_dir)
else
cmp = 0
in
(true, root_upgraded)
in
let plugins_bin = OpamPath.plugins_bin root_dir in
let plugin_symlink_present =
OpamFilename.is_symlink (OpamPath.plugin_bin root_dir (OpamPackage.Name.of_string name))
in
let env =
if has_init then
let updates = [
env_update_resolved "PATH" PlusEq
(OpamFilename.Dir.to_string plugins_bin)
] in
OpamStateConfig.init ~root_from ~root_dir ();
match OpamStateConfig.get_switch_opt () with
| None -> env_array (OpamEnv.get_pure ~updates ())
| Some sw ->
env_array
(OpamEnv.full_with_path ~force_path:false ~updates root_dir sw)
else
Unix.environment ()
in
match OpamSystem.resolve_command ~env command with
| Some command when plugin_symlink_present && root_upgraded ->
let argv = Array.of_list (command :: args) in
raise (OpamStd.Sys.Exec (command, argv, env))
| None when not has_init -> (cli, argv)
| cmd ->
match OpamStateConfig.get_switch_opt () with
| None -> (cli, argv)
| Some sw ->
OpamGlobalState.with_ `Lock_none @@ fun gt ->
OpamSwitchState.with_ `Lock_none gt ~switch:sw @@ fun st ->
let prefixed_name = OpamPath.plugin_prefix ^ name in
let candidates =
OpamPackage.packages_of_names
st.packages
(OpamPackage.Name.Set.of_list @@
(List.filter_map
(fun s ->
try Some (OpamPackage.Name.of_string s)
with Failure _ -> None)
[ prefixed_name; name ]))
in
let plugins =
OpamPackage.Set.filter (fun nv ->
OpamFile.OPAM.has_flag Pkgflag_Plugin (OpamSwitchState.opam st nv))
candidates
in
let plugins =
if OpamPackage.Set.is_empty plugins then
plugins
else
OpamPackage.Set.inter plugins (Lazy.force st.available_packages)
in
let installed = OpamPackage.Set.inter plugins st.installed in
if OpamPackage.Set.is_empty candidates then (cli, argv)
else if not OpamPackage.Set.(is_empty installed) && cmd = None then
(OpamConsole.error
"Plugin %s is already installed, but no %s command was found.\n\
Try upgrading, and report to the package maintainer if \
the problem persists."
(OpamPackage.to_string (OpamPackage.Set.choose installed))
command;
exit (OpamStd.Sys.get_exit_code `Package_operation_error))
else if OpamPackage.Set.is_empty plugins then
(OpamConsole.error
"%s is not a known command or plugin (package %s does \
not have the 'plugin' flag set)."
name
(OpamPackage.to_string (OpamPackage.Set.max_elt candidates));
exit (OpamStd.Sys.get_exit_code `Bad_arguments))
else if
(if cmd = None then
OpamConsole.confirm "Opam plugin \"%s\" is not installed. \
Install it on the current switch?"
else
OpamConsole.confirm "Opam plugin \"%s\" may require upgrading/reinstalling. \
Reinstall the plugin on the current switch?") name
then
let nv =
try
if cmd = None then
raise Not_found
else
OpamPackage.package_of_name installed (OpamPackage.Name.of_string prefixed_name)
with Not_found ->
try
OpamPackage.max_version plugins
(OpamPackage.Name.of_string prefixed_name)
with Not_found ->
OpamPackage.max_version plugins
(OpamPackage.Name.of_string name)
in
OpamRepositoryConfig.init ();
OpamSolverConfig.init ();
OpamClientConfig.init ();
OpamSwitchState.with_ `Lock_write gt (fun st ->
OpamSwitchState.drop @@ (
if cmd = None then
OpamClient.install st [OpamSolution.eq_atom_of_package nv]
else if root_upgraded then
OpamClient.reinstall st [OpamSolution.eq_atom_of_package nv]
else
OpamClient.upgrade st ~all:false [OpamSolution.eq_atom_of_package nv])
);
match OpamSystem.resolve_command ~env command with
| None ->
OpamConsole.error_and_exit `Package_operation_error
"Plugin %s was installed, but no %s command was found.\n\
This is probably an error in the plugin package."
(OpamPackage.to_string nv)
command
| Some command ->
OpamConsole.header_msg "Carrying on to \"%s\""
(String.concat " " (Array.to_list Sys.argv));
OpamConsole.msg "\n";
let argv = Array.of_list (command :: args) in
raise (OpamStd.Sys.Exec (command, argv, env))
else (cli, argv)
let display_cli_error msg =
Format.eprintf
"@[<v>opam: @[%a@]@,@[Usage: @[opam COMMAND ...@]@]@,\
Try `opam --help' for more information.@]@."
Format.pp_print_text msg
let display_cli_error fmt =
Format.ksprintf display_cli_error fmt
let flush_all_noerror () =
(try flush stderr with _ -> ());
(try flush stdout with _ -> ())
let rec main_catch_all f =
try f () with
| OpamStd.Sys.Exit 0 -> ()
| OpamStd.Sys.Exec (cmd,args,env) ->
OpamStd.Sys.exec_at_exit ();
if Sys.win32 then
OpamProcess.create_process_env cmd args env
Unix.stdin Unix.stdout Unix.stderr
|> Unix.waitpid []
|> function
| _, Unix.WEXITED n -> exit n
| _, (Unix.WSIGNALED n | Unix.WSTOPPED n) -> exit (128 - n)
else
Unix.execvpe cmd args env
| OpamFormatUpgrade.Upgrade_done (conf, reinit) ->
main_catch_all @@ fun () ->
OpamConsole.header_msg "Rerunning init and update";
(match reinit with
| Some reinit ->
reinit conf;
OpamConsole.msg "Update done.\n";
exit (OpamStd.Sys.get_exit_code `Success)
| None ->
OpamClient.reinit ~interactive:true ~update_config:false
~bypass_checks:true conf (OpamStd.Sys.guess_shell_compat ());
OpamConsole.msg
"Update done, please now retry your command.\n";
exit (OpamStd.Sys.get_exit_code `Aborted))
| e ->
OpamStd.Exn.register_backtrace e;
(try Sys.set_signal Sys.sigpipe Sys.Signal_default
with Invalid_argument _ -> ());
flush_all_noerror ();
if (OpamConsole.verbose ()) then
OpamConsole.errmsg "'%s' failed.\n"
(String.concat " " (Array.to_list Sys.argv));
let exit_code = match e with
| OpamStd.Sys.Exit i ->
if (OpamConsole.debug ()) && i <> 0 then
OpamConsole.errmsg "%s" (OpamStd.Exn.pretty_backtrace e);
i
| OpamSystem.Internal_error _ ->
OpamConsole.errmsg "%s\n" (Printexc.to_string e);
OpamStd.Sys.get_exit_code `Internal_error
| OpamSystem.Process_error result ->
OpamConsole.errmsg "%s Command %S failed:\n%s\n"
(OpamConsole.colorise `red "[ERROR]")
(try
OpamStd.List.assoc String.equal "command" result.OpamProcess.r_info
with Not_found -> "")
(Printexc.to_string e);
OpamConsole.errmsg "%s" (OpamStd.Exn.pretty_backtrace e);
OpamStd.Sys.get_exit_code `Internal_error
| Sys.Break
| OpamParallel.Errors (_, (_, Sys.Break)::_, _) ->
OpamStd.Sys.get_exit_code `User_interrupt
| Sys_error e when e = "Broken pipe" ->
141
| InvalidCLI (cli, source) ->
let suffix =
if source = `Env then
" Please fix the value of the OPAMCLI environment variable, \
or use the '--cli <major>.<minor>' flag"
else
""
in
OpamConsole.error "opam command-line version %s is not supported.%s"
(OpamCLIVersion.to_string cli) suffix;
OpamStd.Sys.get_exit_code `Bad_arguments
| InvalidFlagContent (flag, None) ->
display_cli_error "option `--%s' needs an argument" flag;
OpamStd.Sys.get_exit_code `Bad_arguments
| InvalidFlagContent (flag, Some (invalid, expected)) ->
display_cli_error
"option `--%s': invalid value `%s', expected %s"
flag invalid expected;
OpamStd.Sys.get_exit_code `Bad_arguments
| InvalidNewFlag ((req_cli, _), flag, flag_cli) ->
display_cli_error
"--%s was added in version %s of the opam CLI, \
but version %s has been requested, which is older."
flag (OpamCLIVersion.to_string flag_cli)
(OpamCLIVersion.to_string req_cli);
OpamStd.Sys.get_exit_code `Bad_arguments
| Failure msg ->
OpamConsole.errmsg "Fatal error: %s\n" msg;
OpamConsole.errmsg "%s" (OpamStd.Exn.pretty_backtrace e);
OpamStd.Sys.get_exit_code `Internal_error
| _ ->
OpamConsole.errmsg "Fatal error:\n%s\n" (Printexc.to_string e);
OpamConsole.errmsg "%s" (OpamStd.Exn.pretty_backtrace e);
OpamStd.Sys.get_exit_code `Internal_error
in
exit exit_code
let run () =
Stdlib.Option.iter OpamVersion.set_git OpamGitVersion.version;
OpamSystem.init ();
OpamArg.preinit_opam_env_variables ();
main_catch_all @@ fun () ->
let cli, argv = check_and_run_external_commands () in
let (default, commands), argv1 =
match argv with
| prog :: command :: argv when OpamCommands.is_admin_subcommand command ->
OpamAdminCommand.get_cmdliner_parser cli, prog::argv
| _ ->
OpamCommands.get_cmdliner_parser cli, argv
in
let argv = Array.of_list argv1 in
let to_new_cmdliner_api (term, info) = Cmd.v info term in
let default, default_info = default in
let commands = List.map to_new_cmdliner_api commands in
match Cmd.eval_value ~catch:false ~argv (Cmd.group ~default default_info commands) with
| Error _ -> exit (OpamStd.Sys.get_exit_code `Bad_arguments)
| Ok _ -> exit (OpamStd.Sys.get_exit_code `Success)
let json_out () =
match OpamClientConfig.(!r.json_out) with
| None -> ()
| Some s ->
let file_name () =
match OpamStd.String.cut_at s '%' with
| None -> OpamFilename.of_string s
| Some (pfx, sfx) ->
let rec getname i =
let f = OpamFilename.of_string (Printf.sprintf "%s%d%s" pfx i sfx) in
if OpamFilename.exists f then getname (i+1) else f
in
getname 1
in
try
let f = OpamFilename.open_out (file_name ()) in
OpamJson.flush f;
close_out f
with e ->
OpamConsole.warning "Couldn't write json log: %s"
(Printexc.to_string e)
let main () =
OpamCoreConfig.set_in_opam ();
if Sys.win32 then begin
ignore (OpamStubs.setErrorMode (1 lor OpamStubs.getErrorMode ()));
OpamStubs.setConsoleToUTF8 ();
end;
OpamStd.Sys.at_exit (fun () ->
flush_all_noerror ();
json_out ()
);
run ()