package opam-client

  1. Overview
  2. Docs

Source file opamConfigCommand.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
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
(**************************************************************************)
(*                                                                        *)
(*    Copyright 2012-2020 OCamlPro                                        *)
(*    Copyright 2012 INRIA                                                *)
(*                                                                        *)
(*  All rights reserved. This file is distributed under the terms of the  *)
(*  GNU Lesser General Public License version 2.1, with the special       *)
(*  exception on linking described in the file LICENSE.                   *)
(*                                                                        *)
(**************************************************************************)

let log fmt = OpamConsole.log "CONFIG" fmt
let slog = OpamConsole.slog

open OpamParserTypes.FullPos
open OpamTypes
open OpamTypesBase
open OpamStateTypes

(* List all the available variables *)
let list t ns =
  log "config-list";
  if ns = [] then () else
  let list_vars name =
    if OpamPackage.Name.to_string name = "-" then
      let conf = t.switch_config in
      List.map (fun (v,c) ->
          OpamVariable.Full.global v,
          OpamVariable.string_of_variable_contents c,
          "")
        (conf.OpamFile.Switch_config.variables)
    else
    let nv = OpamSwitchState.get_package t name in
    let pkg_vars =
      try
        let opam = OpamSwitchState.opam t nv in
        let env = OpamPackageVar.resolve ~opam t in
        OpamStd.List.filter_map (fun (vname, desc) ->
            let v = OpamVariable.(Full.create name (of_string vname)) in
            try
              let c = OpamFilter.ident_string env (OpamFilter.ident_of_var v) in
              Some (v, c, desc)
            with Failure _ -> None)
          OpamPackageVar.package_variable_names
      with Not_found -> []
    in
    let conf_vars =
      try
        let conf = OpamSwitchState.package_config t name in
        List.map (fun (v,c) ->
            OpamVariable.Full.create name v,
            OpamVariable.string_of_variable_contents c,
            "")
          (OpamFile.Dot_config.bindings conf)
      with Not_found -> []
    in
    pkg_vars @ conf_vars
  in
  let vars = List.flatten (List.map list_vars ns) in
  let (%) s col = OpamConsole.colorise col s in
  List.map (fun (variable, value, descr) -> [
        OpamVariable.Full.to_string variable % `bold;
        value % `blue;
        if descr = "" then "" else "# "^descr;
      ]) vars |>
  OpamStd.Format.align_table |>
  OpamConsole.print_table stdout ~sep:" "

let rec print_env = function
  | [] -> ()
  | (k, v, comment) :: r ->
    if OpamConsole.verbose () then
      OpamStd.Option.iter (OpamConsole.msg ": %s;\n") comment;
    if not (List.exists (fun (k1, _, _) -> k = k1) r) || OpamConsole.verbose ()
    then
      OpamConsole.msg "%s='%s'; export %s;\n"
        k (OpamStd.Env.escape_single_quotes v) k;
    print_env r

let rec print_csh_env = function
  | [] -> ()
  | (k, v, comment) :: r ->
    if OpamConsole.verbose () then
      OpamStd.Option.iter (OpamConsole.msg ": %s;\n") comment;
    if not (List.exists (fun (k1, _, _) -> k = k1) r) || OpamConsole.verbose ()
    then
      OpamConsole.msg "setenv %s '%s';\n"
        k (OpamStd.Env.escape_single_quotes v);
    print_csh_env r

let print_sexp_env env =
  let rec aux = function
    | [] -> ()
    | (k, v, _) :: r ->
      if not (List.exists (fun (k1, _, _) -> k = k1) r) then
        OpamConsole.msg "  (%S %S)\n" k v;
      aux r
  in
  OpamConsole.msg "(\n";
  aux env;
  OpamConsole.msg ")\n"

let rec print_fish_env env =
  let set_arr_cmd ?(modf=fun x -> x) k v =
    let v = modf @@ OpamStd.String.split v ':' in
    OpamConsole.msg "set -gx %s %s;\n" k
      (OpamStd.List.concat_map " "
         (fun v ->
            Printf.sprintf "'%s'"
              (OpamStd.Env.escape_single_quotes ~using_backslashes:true v))
         v)
  in
  (* set manpath if and only if fish version >= 2.7 *)
  let manpath_cmd v =
    OpamConsole.msg "%s" (
      (* test for existence of `argparse` builtin, introduced in fish 2.7 .
       * use `grep' instead of `builtin string match' so that old fish versions do not
       *     produce unwanted error messages on stderr.
       * use `grep' inside a `/bin/sh' fragment so that nothing is written to stdout or
       *     stderr if `grep' does not exist. *)
      "builtin -n | /bin/sh -c 'grep -q \\'^argparse$\\'' 1>/dev/null 2>/dev/null; and "
    ) ;
    let modf = function | x::v' -> (":"^x)::v' | v -> v in
    set_arr_cmd ~modf "MANPATH" v in
  match env with
  | [] -> ()
  | (k, v, _) :: r ->
    if not (List.exists (fun (k1, _, _) -> k = k1) r) then
      (match k with
       | "PATH" | "CDPATH" ->
         (* This function assumes that `v` does not include any variable
          * expansions and that the directory names are written in full. See the
          * opamState.ml for details *)
         set_arr_cmd k v
       | "MANPATH" ->
         manpath_cmd v
       | _ ->
         OpamConsole.msg "set -gx %s '%s';\n"
           k (OpamStd.Env.escape_single_quotes ~using_backslashes:true v));
    print_fish_env r

let print_eval_env ~csh ~sexp ~fish env =
  if sexp then
    print_sexp_env env
  else if csh then
    print_csh_env env
  else if fish then
    print_fish_env env
  else
    print_env env

let ensure_env_aux ?(set_opamroot=false) ?(set_opamswitch=false) ?(force_path=true) gt switch =
  let env_file = OpamPath.Switch.environment gt.root switch in
  if not (OpamFile.exists env_file) then
    Some (OpamSwitchState.with_ `Lock_none gt @@ fun st ->
          let upd =
            OpamEnv.updates ~set_opamroot ~set_opamswitch ~force_path st
          in
          log "Missing environment file, regenerate it";
          if not (OpamCoreConfig.(!r.safe_mode)) then
            (let _, st =
               OpamSwitchState.with_write_lock st @@ fun st ->
               (OpamFile.Environment.write env_file upd), st
             in OpamSwitchState.drop st);
          OpamEnv.add [] upd)
  else
    None

let ensure_env gt switch = ignore (ensure_env_aux gt switch)

let env gt switch ?(set_opamroot=false) ?(set_opamswitch=false)
    ~csh ~sexp ~fish ~inplace_path =
  log "config-env";
  let opamroot_not_current =
    let current = gt.root in
    let default = OpamStateConfig.(default.root_dir) in
    match OpamStateConfig.E.root () with
    | None -> current <> default
    | Some r -> OpamFilename.Dir.of_string r <> current
  in
  let opamswitch_not_current =
    let default =
      OpamStd.Option.Op.(++)
        (OpamStateConfig.get_current_switch_from_cwd gt.root)
        (OpamFile.Config.switch gt.config)
    in
    match OpamStateConfig.E.switch () with
    | None | Some "" ->
      Some (OpamStateConfig.resolve_local_switch gt.root switch) <> default
    | Some s ->
      OpamStateConfig.resolve_local_switch gt.root (OpamSwitch.of_string s) <>
      OpamStateConfig.resolve_local_switch gt.root switch
  in
  if opamroot_not_current && not set_opamroot then
    OpamConsole.note
      "To make opam select %s as its root in the current shell, add %s or set \
       %s"
      (OpamFilename.Dir.to_string gt.root)
      (OpamConsole.colorise `bold "--set-root")
      (OpamConsole.colorise `bold "OPAMROOT");
  if opamswitch_not_current && not set_opamswitch then
    OpamConsole.note
      "To make opam select the switch %s in the current shell, add %s or set \
       %s"
      (OpamSwitch.to_string switch)
      (OpamConsole.colorise `bold "--set-switch")
      (OpamConsole.colorise `bold "OPAMSWITCH");
  let force_path = not inplace_path in
  let env =
    match ensure_env_aux ~set_opamroot ~set_opamswitch ~force_path gt switch with
    | Some env -> env
    | None ->
      OpamEnv.get_opam_raw
        ~set_opamroot ~set_opamswitch ~force_path
        gt.root switch
  in
  print_eval_env ~csh ~sexp ~fish env
[@@ocaml.warning "-16"]

let subst gt fs =
  log "config-substitute";
  OpamSwitchState.with_ `Lock_none gt @@ fun st ->
  List.iter
    (OpamFilter.expand_interpolations_in_file (OpamPackageVar.resolve st))
    fs

let expand gt str =
  log "config-expand";
  OpamSwitchState.with_ `Lock_none gt @@ fun st ->
  OpamConsole.msg "%s\n"
    (OpamFilter.expand_string ~default:(fun _ -> "")
       (OpamPackageVar.resolve st) str)

let exec gt ~set_opamroot ~set_opamswitch ~inplace_path command =
  log "config-exec command=%a" (slog (String.concat " ")) command;
  let switch = OpamStateConfig.get_switch () in
  let st_lazy = lazy (
    let rt = OpamRepositoryState.load `Lock_none gt in
    OpamSwitchState.load `Lock_none gt rt switch
  ) in
  let env_file = OpamPath.Switch.environment gt.root switch in
  let env =
    if OpamFile.exists env_file then
      let base = List.map (fun (v,va) -> v,va,None) (OpamStd.Env.list ()) in
      OpamEnv.get_opam_raw ~base
        ~set_opamroot ~set_opamswitch ~force_path:(not inplace_path)
        gt.root switch
    else
      OpamEnv.get_full ~set_opamroot ~set_opamswitch
        ~force_path:(not inplace_path)
        (Lazy.force st_lazy)
  in
  let env = OpamTypesBase.env_array env in
  let resolve var =
    OpamPackageVar.resolve (Lazy.force st_lazy) var
  in
  let cmd, args =
    match
      List.map (OpamFilter.expand_string ~default:(fun _ -> "") resolve) command
    with
    | []        -> OpamSystem.internal_error "Empty command"
    | h::_ as l -> h, Array.of_list l
  in
  (* it's OK not to release [st_lazy] since we are certain everything will be
     cleaned up anyway *)
  match OpamSystem.resolve_command ~env cmd with
  | Some cmd -> raise (OpamStd.Sys.Exec (cmd, args, env))
  | None ->
    OpamConsole.error "Command not found '%s'" cmd;
    raise (OpamStd.Sys.Exit 127)


(** Options and Variables settings *)

(** Option settings *)

(* For function that takes two config and update (add or remove) elements in a
   field. Used for appending or deleting element in config file fields *)
type 'config fld_updater =  ('config -> 'config -> 'config)

(* Only some field can be modifiied. [Modifiable] is for user modifiable
   field, [InModifiable] for fields that can only be modified from inner opam
   code (see [set_var_global]).
   First argument is the addition function, the second the remove one. *)
type 'config fld_policy =
  | Atomic
  | Modifiable of 'config fld_updater * 'config fld_updater
  | InModifiable of 'config fld_updater * 'config fld_updater

(* "Configuration" of the [set_opt] function. As modification can be on global
   or config switch, on normal fields and sections, adding, removing, or
   overwritng values, this record type permits to aggregate all needed inputs.
   See [set_opt_global] and [set_opt_switch]. *)
type 'config confset =
  {
    stg_fields: (string * ('config, value) OpamPp.field_parser) list;
    (* Config file fields: field name and parser *)
    stg_allwd_fields:
      (string * 'config fld_policy * ('config -> 'config)) list;
    (* Config file updatable fields: field name, update policy, and function to
       revert the given field in config file *)
    stg_sections:
      (string * ('config, (string option * opamfile_item list) list)
         OpamPp.field_parser) list;
    (* Same as [stg_field] but for sections *)
    stg_allwd_sections:
      ((string * 'config fld_policy * ('config -> 'config)) list);
    (* Same as [stg_allwd_fields] but for sections *)
    stg_config: 'config;
    (* The config *)
    stg_write_config: 'config -> unit;
    (* Function to write the config file *)
    stg_doc: string;
    (* Global or switch specification, used to print final user message *)
  }

type whole_op =
  [ `Overwrite of string
  | `Revert ]

type append_op =
  [ `Add of string
  | `Remove of string ]

type update_op =
  [ append_op  | whole_op ]

let parse_update fv =
  let reg =
    Re.(compile @@ seq [
        group @@ seq [
          wordc;
          opt @@ (seq [ rep @@ alt [ wordc ; char '-' ]; wordc ])
        ];
        (opt @@ seq [
            (group @@ (alt [
                 str "+=";
                 str "-=";
                 str "==";
                 char '=';
               ]));
            opt @@ (group @@ rep1 any)
          ]);
      ])
  in
  let grs = Re.exec reg fv in
  let var = Re.Group.get grs 1 in
  let value =
    try
      let value =
        OpamStd.Option.of_Not_found (fun () -> Re.Group.get grs 3) ()
      in
      match Re.Group.get grs 2, value with
      | "+=", Some value -> `Add value
      | "-=", Some value -> `Remove value
      | ("=" | "=="), Some value -> `Overwrite value
      | ("=" | "=="), None -> `Revert
      | ("+=" | "-="), None -> raise (Invalid_argument "parse_update: rhs needed")
      | _, _ -> raise (Invalid_argument "parse_update: illegal operator")
    with Not_found ->  raise (Invalid_argument "parse_update: operator needed")
  in
  var, value

let whole_of_update_op = function
  | #whole_op as w -> w
  | _ -> raise Not_found

let parse_whole fv =
  let v, upd = parse_update fv in
  try v, (whole_of_update_op upd)
  with Not_found -> raise (Invalid_argument "parse_whole: append operator")

let global_doc = "global configuration"
let switch_doc switch =
  Printf.sprintf "switch %s"
    (OpamConsole.colorise `bold (OpamSwitch.to_string switch))

module OpamParser = OpamParser.FullPos
module OpamPrinter = OpamPrinter.FullPos

(* General setting option function. Takes the [field] to update, the [value]
   operation, [conf] the configuration according the config file (['config
   confest]). If [inner] is set, it allows the modification of [InModifiable]
   fields *)
let set_opt ?(inner=false) field value conf =
  let wrap allowed all parse =
    List.map (fun (field, pp) ->
        match OpamStd.List.find_opt (fun (x,_,_) -> x = field) allowed with
        | None -> field, None
        | Some (_, modd, default) ->
          let parse elem config =
            OpamPp.parse ~pos:OpamTypesBase.pos_null pp
              (config, Some (parse elem))
          in
          field,
          Some (parse, modd, default)
      ) all
  in
  let fields =
    (wrap conf.stg_allwd_fields conf.stg_fields
       (fun str_value ->
          OpamParser.value_from_string str_value "<command-line>"))
    @ (wrap conf.stg_allwd_sections conf.stg_sections
         (fun str_value ->
            [None,
             (OpamParser.string str_value "<command-line>").file_contents]))
  in
  let new_config =
    match OpamStd.List.assoc_opt field fields, value with
    | None, _ ->
      OpamConsole.error
        "There is no option named '%s'. The allowed options are:"
        (OpamConsole.colorise `underline field);
      OpamConsole.print_table stderr ~sep:" "
        (OpamStd.Format.as_aligned_table
           (OpamStd.List.filter_map
              (function fl, Some _ -> Some fl | _ -> None)
              fields));
      OpamStd.Sys.exit_because `Bad_arguments
    | Some None, _ ->
      OpamConsole.error_and_exit `Bad_arguments
        "Field %s is not modifiable" (OpamConsole.colorise `underline field)
    | Some (Some (_, Atomic, _)), (#append_op as ar) ->
      OpamConsole.error_and_exit `Bad_arguments
        "Field %s can't be %s" (OpamConsole.colorise `underline field)
        (match ar with `Add _ -> "appended" | `Remove _ -> "substracted")
    | Some (Some (_, InModifiable (_,_), _)), (#append_op as ar) when not inner ->
      OpamConsole.error_and_exit `Bad_arguments
        "Field %s can't be directly %s, use `opam var` instead"
        (OpamConsole.colorise `underline field)
        (match ar with `Add _ -> "appended to"
                     | `Remove _ -> "substracted from")
    | Some (Some (_, _, set_default)), `Revert ->
      set_default conf.stg_config
    | Some (Some (parse, fix_app, _)),
      ((`Add v | `Remove v | `Overwrite v) as req_value) ->
      (try
         let updf v = parse v conf.stg_config in
         match req_value, fix_app with
         | `Add value, (Modifiable (add, _) | InModifiable (add, _))  ->
           add (updf value) conf.stg_config
         | `Remove value, (Modifiable (_, rem) | InModifiable (_, rem)) ->
           rem (updf value) conf.stg_config
         | `Overwrite value, _ -> (updf value)
         | _, Atomic -> assert false
       with
       | (OpamPp.Bad_format (_,_) | Parsing.Parse_error) as e ->
         OpamConsole.error_and_exit `Bad_arguments
           "Parse error on the value of %s '%s': %s"
           (OpamConsole.colorise `underline field) v
           (OpamPp.string_of_bad_format e))
  in
  if conf.stg_config = new_config then
    OpamConsole.msg "No modification in %s\n" conf.stg_doc
  else
    (conf.stg_write_config new_config;
     OpamConsole.msg "%s field %s in %s\n"
       (match value with
        | `Add value ->  Printf.sprintf "Added '%s' to" value
        | `Remove value ->  Printf.sprintf "Removed '%s' from" value
        | `Overwrite value -> Printf.sprintf "Set to '%s' the" value
        | `Revert -> "Reverted")
       (OpamConsole.colorise `underline field)
       conf.stg_doc);
  new_config

let allwd_wrappers wdef wrappers with_wrappers  =
  let open OpamFile in
  List.map (fun (n, set, get) ->
      n,
      Modifiable (
        (fun nc c ->
           let w = wrappers c in
           let nw = wrappers nc in
           with_wrappers (set (get nw @ get w) w) c),
        (fun nc c ->
           let w = wrappers c in
           let nw = wrappers nc in
           let n_cmd =
             List.filter (fun cmd ->
                 None = OpamStd.List.find_opt (fun cmd' -> cmd = cmd') (get nw))
               (get w)
           in
           with_wrappers (set n_cmd w) c)
      ),
      fun c -> with_wrappers (set (get wdef) (wrappers c)) c)
    [
      "pre-build-commands",
      Wrappers.with_pre_build, Wrappers.pre_build;
      "pre-install-commands",
      Wrappers.with_pre_install, Wrappers.pre_install;
      "pre-remove-commands",
      Wrappers.with_pre_remove, Wrappers.pre_remove;
      "pre-session-commands",
      Wrappers.with_pre_session, Wrappers.pre_session;
      "wrap-build-commands",
      Wrappers.with_wrap_build, Wrappers.wrap_build;
      "wrap-install-commands",
      Wrappers.with_wrap_install, Wrappers.wrap_install;
      "wrap-remove-commands",
      Wrappers.with_pre_remove, Wrappers.pre_remove;
      "post-build-commands",
      Wrappers.with_post_build, Wrappers.post_build;
      "post-install-commands",
      Wrappers.with_post_install, Wrappers.post_install;
      "post-remove-commands",
      Wrappers.with_post_remove, Wrappers.post_remove;
      "post-session-commands",
      Wrappers.with_post_session, Wrappers.post_session;
    ]

let switch_allowed_fields, switch_allowed_sections =
  let allowed_fields =
    lazy (
      OpamFile.Switch_config.(
        [
          ("synopsis", Atomic,
           fun t -> { t with synopsis = empty.synopsis });
          ("setenv", Modifiable (
              (fun nc c -> { c with env = nc.env @ c.env }),
              (fun nc c ->
                 let env =
                   List.filter (fun (vr,op,vl,_) ->
                       None = OpamStd.List.find_opt (fun (vr',op',vl',_) ->
                           vr = vr' && op = op' && vl = vl') nc.env) c.env
                 in
                 { c with env })),
           fun t -> { t with env = empty.env });
          "depext-bypass", OpamSysPkg.Set.Op.(Modifiable (
              (fun nc c ->
                 { c with depext_bypass = nc.depext_bypass ++ c.depext_bypass }),
              (fun nc c ->
                 { c with depext_bypass = c.depext_bypass -- nc.depext_bypass })
            )),
          (fun t -> { t with depext_bypass = empty.depext_bypass });
        ] @ allwd_wrappers empty.wrappers wrappers
          (fun wrappers t -> { t with wrappers })))
  in
  let allowed_sections =
    let rem_elem new_elems elems =
      List.filter (fun n -> not (List.mem n new_elems)) elems
    in
    lazy (
      OpamFile.Switch_config.([
          ("variables", InModifiable (
              (fun nc c -> { c with variables = nc.variables @ c.variables }),
              (fun nc c ->
                 { c with variables = rem_elem nc.variables c.variables })),
           (fun c -> { c with variables = empty.variables }));
        ]))
  in
  (fun () -> Lazy.force allowed_fields),
  fun () -> Lazy.force allowed_sections

let confset_switch gt switch switch_config =
  let config_f = OpamPath.Switch.switch_config gt.root switch in
  let write new_config = OpamFile.Switch_config.write config_f new_config in
  { stg_fields = OpamFile.Switch_config.fields;
    stg_allwd_fields = switch_allowed_fields ();
    stg_sections = OpamFile.Switch_config.sections;
    stg_allwd_sections = switch_allowed_sections ();
    stg_config = switch_config;
    stg_write_config = write;
    stg_doc = switch_doc switch
  }

let with_switch:
  'a global_state -> 'b lock -> 'b switch_state option
  -> (switch -> OpamFile.Switch_config.t -> 'c) -> 'c =
  fun gt lock_kind st_opt k ->
  match st_opt with
  | Some st -> k st.switch st.switch_config
  | None ->
    let switch = OpamStateConfig.get_switch () in
    let switch_config =
      if lock_kind = `Lock_write then
        match OpamStateConfig.Switch.read_opt ~lock_kind gt switch with
        | Some c -> c
        | exception (OpamPp.Bad_version _ as e) ->
          OpamFormatUpgrade.hard_upgrade_from_2_1_intermediates gt.root;
          raise e
        | None -> OpamFile.Switch_config.empty
      else
        OpamStateConfig.Switch.safe_load ~lock_kind gt switch
    in
    let lock_file = OpamPath.Switch.lock gt.root switch in
    if switch_config = OpamFile.Switch_config.empty then
      OpamConsole.error "switch %s not found, display default values"
        (OpamSwitch.to_string switch);
    OpamFilename.with_flock lock_kind lock_file @@ fun _ ->
    k switch switch_config

let set_opt_switch_t ?inner gt switch switch_config field value =
  set_opt ?inner field value (confset_switch gt switch switch_config)

let set_opt_switch gt ?st field value =
  with_switch gt `Lock_write st @@ fun sw swc ->
  let switch_config = set_opt_switch_t ~inner:false gt sw swc field value in
  OpamStd.Option.map (fun st -> { st with switch_config }) st

let global_allowed_fields, global_allowed_sections =
  let allowed_fields =
    let open OpamStd.Option.Op in
    let open OpamFile in
    let in_config = OpamInitDefaults.init_config () in
    let wrapper_init = InitConfig.wrappers in_config in
    let upd_vars get set =
      (fun nc c ->  set (get nc @ get c) c),
      (fun nc c ->
         let gv = get nc in
         set (List.filter (fun (k,v,_) ->
             None = OpamStd.List.find_opt (fun (k',v',_) -> k = k' && v = v') gv)
             (get c)) c)
    in
    lazy ([
        "download-command", Atomic,
        Config.with_dl_tool_opt
          (InitConfig.dl_tool in_config ++ Config.dl_tool Config.empty);
        "download-jobs", Atomic,
        Config.with_dl_jobs
          (InitConfig.dl_jobs in_config +! Config.dl_jobs Config.empty);
        "jobs", Atomic,
        Config.with_jobs_opt
          (InitConfig.jobs in_config ++ Config.jobs Config.empty);
        "best-effort-prefix-criteria", Atomic,
        Config.with_best_effort_prefix_opt
          (Config.best_effort_prefix Config.empty);
        "solver", Atomic,
        Config.with_solver_opt
          (InitConfig.solver in_config ++ Config.solver Config.empty);
        "global-variables",
        (let add, rem =
           upd_vars Config.global_variables Config.with_global_variables
         in
         InModifiable (add, rem)),
        Config.with_global_variables (InitConfig.global_variables in_config);
        "eval-variables",
        (let add, rem =
           upd_vars Config.eval_variables Config.with_eval_variables
         in
         InModifiable (add, rem)),
        Config.with_eval_variables (InitConfig.eval_variables in_config);
        "repository-validation-command", Atomic,
        Config.with_validation_hook_opt (Config.validation_hook Config.empty);
        "depext", Atomic,
        Config.with_depext (Config.depext Config.empty);
        "depext-run-installs", Atomic,
        Config.with_depext_run_installs
          (Config.depext_run_installs Config.empty);
        "depext-cannot-install", Atomic,
        Config.with_depext_cannot_install
          (Config.depext_cannot_install Config.empty);
        "depext-bypass", OpamSysPkg.Set.Op.(Modifiable (
            (fun nc c -> Config.with_depext_bypass
                (Config.depext_bypass nc ++ Config.depext_bypass c) c),
            (fun nc c -> Config.with_depext_bypass
                (Config.depext_bypass c -- Config.depext_bypass nc) c)
          )),
        Config.with_depext_bypass (Config.depext_bypass Config.empty);
      ] @ List.map (fun f ->
        f, Atomic, Config.with_criteria
          (Config.criteria Config.empty))
        [ "solver-criteria";
          "solver-upgrade-criteria";
          "solver-fixup-criteria" ]
        @ allwd_wrappers wrapper_init Config.wrappers Config.with_wrappers
      )
  in
  (fun () -> Lazy.force allowed_fields),
  fun () -> []

let confset_global gt =
  let write new_config = OpamGlobalState.write {gt with config = new_config} in
  { stg_fields = OpamFile.Config.fields;
    stg_allwd_fields = global_allowed_fields ();
    stg_sections = [];
    stg_allwd_sections = global_allowed_sections ();
    stg_config = gt.config;
    stg_write_config = write;
    stg_doc = global_doc;
  }

let set_opt_global_t ?inner gt field value =
  let config =
    set_opt ?inner field value (confset_global gt)
  in
  { gt with config }

let set_opt_global = set_opt_global_t ~inner:false

(** Variable settings *)

(* "Configuration" of the [set_var] function. As these modification can be on
   global and switch config, this record aggregates all needed inputs. *)
type ('var,'config) var_confset =
  {
    stv_vars: 'var list;
    (* Variables list *)
    stv_find: 'var -> bool;
    (* Find function embedding a wanted var *)
    stv_config: 'config;
    (* State to use *)
    stv_varstr: string -> string;
    (* [stv_vars value] returns the string of the variable with the new value.
       It is used to give the overall value to [set_opt] functions. *)
    stv_set_opt: 'config -> update_op -> 'config;
    (* The [set_opt] function call [stv_set_opt state var_value] *)
    stv_remove_elem: 'var list -> 'config -> 'config;
    (* As variable can't be duplicated, a function to remove it from the list *)
    stv_write: 'config -> unit;
    (* Write the config file *)
    stv_doc: string;
    (* Global or switch specification, used to print final user message *)
  }

let set_var svar value conf =
  let var = OpamVariable.Full.of_string svar in
  let conf = conf (OpamVariable.Full.variable var) in
  if not (OpamVariable.Full.is_global var) then
    OpamConsole.error_and_exit `Bad_arguments
      "Only global variables may be set using this command";
  let global_vars = conf.stv_vars in
  let rest = List.filter (fun v -> not (conf.stv_find v)) global_vars in
  let config = conf.stv_remove_elem rest conf.stv_config in
  match value with
  | `Overwrite value -> conf.stv_set_opt config (`Add (conf.stv_varstr value))
  | `Revert ->
    (* only write, as the var is already removed *)
    if config = conf.stv_config then
      OpamConsole.msg "No modification in %s\n" conf.stv_doc
    else
      (conf.stv_write config;
       OpamConsole.msg "Removed variable %s in %s\n"
         (OpamConsole.colorise `underline svar)
         conf.stv_doc);
    config

let set_var_global gt var value =
  let config =
    set_var var value @@
    fun var ->
    let global_vars = OpamFile.Config.global_variables gt.config in
    { stv_vars = global_vars;
      stv_find = (fun (k,_,_) -> k = var);
      stv_config = gt.config;
      stv_varstr = (fun v ->
          OpamPrinter.Normalise.value (nullify_pos @@ List (nullify_pos @@ [
              nullify_pos @@ Ident (OpamVariable.to_string var);
              nullify_pos @@ String v;
              nullify_pos @@ String "Set through 'opam var'"
            ])));
      stv_set_opt = (fun config value ->
          let gt =
            set_opt_global_t ~inner:true { gt with config }
              "global-variables" value
          in gt.config);
      stv_remove_elem = (fun rest config ->
          OpamFile.Config.with_global_variables rest config
          |> OpamFile.Config.with_eval_variables
            (List.filter (fun (k,_,_) -> k <> var)
               (OpamFile.Config.eval_variables config)));
      stv_write = (fun config -> OpamGlobalState.write { gt with config });
      stv_doc = global_doc;
    } in
  { gt with config }

let set_var_switch gt ?st var value =
  let var_confset switch switch_config var =
    let switch_vars = switch_config.OpamFile.Switch_config.variables in
    { stv_vars = switch_vars;
      stv_find = (fun (k,_) -> k = var);
      stv_config = switch_config;
      stv_varstr = (fun v ->
          OpamStd.String.remove_suffix ~suffix:"\n" @@
          OpamPrinter.Normalise.items
            [ nullify_pos @@ Variable
                (nullify_pos @@ OpamVariable.to_string var,
                 nullify_pos @@ String v)]);
      stv_set_opt = (fun swc value ->
          set_opt_switch_t ~inner:true gt switch swc "variables" value);
      stv_remove_elem = (fun rest switch_config ->
          { switch_config with variables = rest });
      stv_write = (fun swc ->
          OpamFile.Switch_config.write
            (OpamPath.Switch.switch_config gt.root switch) swc);
      stv_doc = switch_doc switch;
    } in
  let switch_config =
    with_switch gt `Lock_write st @@ fun sw swc ->
    set_var var value (var_confset sw swc)
  in
  OpamStd.Option.map (fun st -> { st with switch_config }) st

(** Option and var list display *)

let print_fields fields =
  let fields =
    List.sort (fun (x,_) (x',_) -> compare x x') fields
    |> List.map (fun (name, value) ->
        let value = match value with
          | None -> "{}"
          | Some value -> (OpamPrinter.Normalise.value value)
        in
        [
          OpamConsole.colorise `bold name ;
          OpamConsole.colorise `blue value
        ])
  in
  OpamConsole.print_table stdout ~sep:"  "
    (OpamStd.Format.align_table fields)

let find_field field name_value =
  match OpamStd.List.find_opt (fun (name, _) -> name = field) name_value with
  | None -> (field, None)
  | Some (name, value) -> (name, Some value)

let find_section section name_value =
  let sections =
    List.find_all (fun (name, _) ->
        match OpamStd.String.cut_at name '.' with
        | None -> false
        | Some (name,_) -> name = section)
      name_value
  in
  match sections with
  | [] -> [section, None]
  | section -> List.map (fun (n,v) -> n, Some v) section

let options_list_t to_list conf =
  let name_value = to_list conf.stg_config in
  let fields =
    OpamStd.List.filter_map (fun (field, policy, _) ->
        match policy with
        | InModifiable _ -> None
        | _ -> Some (find_field field name_value))
      conf.stg_allwd_fields
  in
  let sections =
    OpamStd.List.filter_map (fun (field, policy, _) ->
        match policy with
        | InModifiable _ -> None
        | _ -> Some (find_section field name_value))
      conf.stg_allwd_sections
    |> List.flatten
  in
  print_fields (fields @ sections)

let options_list_switch ?st gt =
  with_switch gt `Lock_none st @@ fun sw swc ->
  options_list_t OpamFile.Switch_config.to_list (confset_switch gt sw swc)

let options_list_global gt =
  options_list_t OpamFile.Config.to_list (confset_global gt)

let options_list ?st gt =
  OpamConsole.header_msg "Global configuration";
  options_list_global gt;
  OpamConsole.header_msg "Switch configuration (%s)"
    (OpamSwitch.to_string (OpamStateConfig.get_switch ()));
  options_list_switch ?st gt

let vars_list_global gt =
  let (%) s col = OpamConsole.colorise col s in
  let all_global_vars =
    List.fold_left (fun acc (v,doc) ->
        OpamVariable.Map.add (OpamVariable.of_string v) doc acc)
      OpamVariable.Map.empty
      OpamPackageVar.global_variable_names
  in
  let all_global_vars =
    OpamVariable.Map.union (fun _ x -> x)
      all_global_vars
      (OpamVariable.Map.map snd gt.global_variables)
  in
  let env = OpamPackageVar.resolve_global gt in
  List.map (fun (var, doc) ->
      let content =
        OpamFilter.ident_string env ~default:"" ([],var,None)
      in
      let doc =
        if doc = OpamGlobalState.inferred_from_system then
          match OpamStd.Option.Op.(
              OpamVariable.Map.find_opt var gt.global_variables
              >>| fst
              >>= Lazy.force) with
          | Some c
            when (OpamVariable.string_of_variable_contents c) <> content ->
            "Set through local opam config or env"
          | _ -> doc
        else doc
      in
      [
        OpamVariable.to_string var % `bold;
        content % `blue;
        "#"; doc
      ])
    (List.sort (fun (x,_) (x',_) -> compare x x')
       (OpamVariable.Map.bindings all_global_vars)) |>
  OpamStd.Format.align_table |>
  OpamConsole.print_table stdout ~sep:" "

let vars_list_switch ?st gt =
  let (%) s col = OpamConsole.colorise col s in
  let switch, config =
    match st with
    | Some st -> st.switch, st.switch_config
    | None ->
      let switch = OpamStateConfig.get_switch () in
      switch,
      OpamStateConfig.Switch.safe_load ~lock_kind:`Lock_read gt switch
  in
  List.map (fun stdpath -> [
        OpamTypesBase.string_of_std_path stdpath % `bold;
        OpamPath.Switch.get_stdpath gt.root switch config stdpath |>
        OpamFilename.Dir.to_string |>
        OpamConsole.colorise `blue
      ])
    OpamTypesBase.all_std_paths @
  List.map (fun (var,value) -> [
        OpamVariable.to_string var % `bold;
        OpamVariable.string_of_variable_contents value % `blue;
      ])
    (List.sort (fun (x,_) (x',_) -> compare x' x)
       config.OpamFile.Switch_config.variables) |>
  OpamStd.Format.align_table |>
  OpamConsole.print_table stdout ~sep:" "

let vars_list ?st gt =
  let (%) s col = OpamConsole.colorise col s in
  OpamConsole.header_msg "Global opam variables";
  vars_list_global gt;
  OpamConsole.header_msg "Configuration variables from the current switch";
  vars_list_switch ?st gt;
  OpamConsole.header_msg "Package variables ('opam var --package PKG' to show)";
  List.map (fun (var, doc) -> [
        ("PKG:"^var) % `bold;
        "";
        "#";doc
      ])
    OpamPackageVar.package_variable_names |>
  OpamStd.Format.align_table |>
  OpamConsole.print_table stdout ~sep:" "

(* Specified option/var display *)

let option_show to_list conf field =
  match OpamStd.List.assoc_opt field conf.stg_fields with
  | Some pp ->
    (match OpamPp.print pp conf.stg_config with
     | _, Some value ->
       OpamConsole.msg "%s\n" (OpamPrinter.Normalise.value value)
     | _, None -> ())
  | None ->
    if List.mem_assoc field conf.stg_sections then
      let name_value = to_list conf.stg_config in
      let sections =
        OpamStd.List.filter_map (fun (name, v) ->
            match OpamStd.String.cut_at name '.' with
            | Some (name,elem) when name = field ->
              Some [ elem; OpamPrinter.Normalise.value v ]
            | _ -> None
          ) name_value
      in
      OpamConsole.print_table stdout ~sep:"  "
        (OpamStd.Format.align_table sections)
    else
      OpamConsole.error_and_exit `Not_found
        "Field or section %s not found" field

let option_show_switch gt ?st field =
  with_switch gt `Lock_none st @@ fun sw swc ->
  option_show OpamFile.Switch_config.to_list (confset_switch gt sw swc) field

let option_show_global gt field =
  option_show OpamFile.Config.to_list (confset_global gt) field

let var_show_t resolve ?switch v =
  match resolve (OpamVariable.Full.of_string v) with
  | Some c ->
    OpamConsole.msg "%s\n" (OpamVariable.string_of_variable_contents c)
  | None ->
    OpamConsole.error_and_exit `Not_found "Variable %s not found in %s" v
      (match switch with
           | None -> "global config"
           | Some switch -> "switch " ^ (OpamSwitch.to_string switch))

let is_switch_defined_var switch_config v =
  OpamFile.Switch_config.variable switch_config
    (OpamVariable.of_string v) <> None
  || (try let _path = OpamTypesBase.std_path_of_string v in true
      with Failure _ -> false)
  || OpamStd.String.contains_char v ':'

let var_switch_raw gt v =
  match OpamStateConfig.get_switch_opt () with
  | Some switch ->
    let switch_config =
      OpamStateConfig.Switch.safe_load ~lock_kind:`Lock_read gt switch
    in
    let rsc =
      if is_switch_defined_var switch_config v then
        OpamPackageVar.resolve_switch_raw gt switch switch_config
          (OpamVariable.Full.of_string v)
      else None
    in
    (match rsc with
     | Some c ->
       OpamConsole.msg "%s\n" (OpamVariable.string_of_variable_contents c);
       rsc
     | None -> None)
  | None -> None

let var_show_switch gt ?st v =
  if var_switch_raw gt v = None then
    let resolve_switch st =
      if is_switch_defined_var st.switch_config v then
        var_show_t (OpamPackageVar.resolve st) ~switch:st.switch v
      else
        OpamConsole.error_and_exit `Not_found
          "Variable %s not found in switch %s"
          v (OpamSwitch.to_string st.switch)
    in
    match st with
    | Some st -> resolve_switch st
    | None -> OpamSwitchState.with_ `Lock_none gt resolve_switch

let var_show_global gt f = var_show_t (OpamPackageVar.resolve_global gt) f

let var_show gt v =
  if var_switch_raw gt v = None then
    OpamSwitchState.with_ `Lock_none gt @@ fun st ->
    let switch =
      if is_switch_defined_var st.switch_config v then Some st.switch else None
    in
    var_show_t (OpamPackageVar.resolve st) ?switch v

(* detect scope *)
let get_scope field =
  let field =
    try fst (parse_update field)
    with Invalid_argument _ -> field
  in
  let find l = OpamStd.List.find_opt (fun (f,_) -> f = field) l in
  if find OpamFile.Switch_config.fields <> None
  || find OpamFile.Switch_config.sections <> None then
    `Switch
  else if find OpamFile.Config.fields <> None then
    `Global
  else `None field
OCaml

Innovation. Community. Security.