Source file opamAdminCommand.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
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
open OpamTypes
open OpamProcess.Job.Op
open OpamStateTypes
open Cmdliner
type command = unit Cmdliner.Term.t * Cmdliner.Cmd.info
let checked_repo_root () =
let repo_root = OpamFilename.cwd () in
if not (OpamFilename.exists_dir (OpamRepositoryPath.packages_dir repo_root))
then
OpamConsole.error_and_exit `Bad_arguments
"No repository found in current directory.\n\
Please make sure there is a \"packages%s\" directory" OpamArg.dir_sep;
repo_root
let global_options cli =
let apply_cli options = { options with OpamArg.cli = options.OpamArg.cli} in
Term.(const apply_cli $ OpamArg.global_options cli)
let admin_command_doc =
"Tools for repository administrators"
let admin_command_man = [
`S Manpage.s_description;
`P (Printf.sprintf
"This command can perform various actions on repositories in the opam \
format. It is expected to be run from the root of a repository, i.e. a \
directory containing a 'repo' file and a subdirectory 'packages%s' \
holding package definition within subdirectories. A 'compilers%s' \
subdirectory (opam repository format version < 2) will also be used by \
the $(b,upgrade-format) subcommand."
OpamArg.dir_sep OpamArg.dir_sep)
]
let index_command_doc =
"Generate an inclusive index file for serving over HTTP."
let index_command cli =
let command = "index" in
let doc = index_command_doc in
let man = [
`S Manpage.s_description;
`P "An opam repository can be served over HTTP or HTTPS using any web \
server. To that purpose, an inclusive index needs to be generated \
first: this command generates the files the opam client will expect \
when fetching from an HTTP remote, and should be run after any changes \
are done to the contents of the repository."
]
in
let urls_txt_arg cli =
OpamArg.mk_vflag ~cli `minimal_urls_txt [
OpamArg.cli_original, `no_urls_txt, ["no-urls-txt"],
"Don't generate a 'urls.txt' file. That index file is no longer \
needed from opam 2.0 on, but is still used by older versions.";
OpamArg.cli_original, `full_urls_txt, ["full-urls-txt"],
"Generate an inclusive 'urls.txt', for a repository that will be \
used by opam versions earlier than 2.0.";
OpamArg.cli_original, `minimal_urls_txt, ["minimal-urls-txt"],
"Generate a minimal 'urls.txt' file, that only includes the 'repo' \
file. This allows opam versions earlier than 2.0 to read that file, \
and be properly redirected to a repository dedicated to their \
version, assuming a suitable 'redirect:' field is defined, instead \
of failing. This is the default.";
]
in
let cmd global_options urls_txt () =
OpamArg.apply_global_options cli global_options;
let repo_root = checked_repo_root () in
let repo_file = OpamRepositoryPath.repo repo_root in
let repo_def =
match OpamFile.Repo.read_opt repo_file with
| None ->
OpamConsole.warning "No \"repo\" file found. Creating a minimal one.";
OpamFile.Repo.create ()
| Some r -> r
in
let repo_stamp =
let date () =
let t = Unix.gmtime (Unix.time ()) in
Printf.sprintf "%04d-%02d-%02d %02d:%02d"
(t.Unix.tm_year + 1900) (t.Unix.tm_mon +1) t.Unix.tm_mday
t.Unix.tm_hour t.Unix.tm_min
in
match OpamUrl.guess_version_control (OpamFilename.Dir.to_string repo_root)
with
| None -> date ()
| Some vcs ->
let module VCS = (val OpamRepository.find_backend_by_kind vcs) in
match OpamProcess.Job.run (VCS.revision repo_root) with
| None -> date ()
| Some hash -> hash
in
let repo_def = OpamFile.Repo.with_stamp repo_stamp repo_def in
OpamFile.Repo.write repo_file repo_def;
if urls_txt <> `no_urls_txt then
(OpamConsole.msg "Generating urls.txt...\n";
OpamFilename.of_string "repo" ::
(if urls_txt = `full_urls_txt then
OpamFilename.rec_files OpamFilename.Op.(repo_root / "compilers") @
OpamFilename.rec_files (OpamRepositoryPath.packages_dir repo_root)
else []) |>
List.fold_left (fun set f ->
if not (OpamFilename.exists f) then set else
let attr = OpamFilename.to_attribute repo_root f in
OpamFilename.Attribute.Set.add attr set
) OpamFilename.Attribute.Set.empty |>
OpamFile.File_attributes.write
(OpamFile.make (OpamFilename.of_string "urls.txt")));
OpamConsole.msg "Generating index.tar.gz...\n";
OpamHTTP.make_index_tar_gz repo_root;
OpamConsole.msg "Done.\n";
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $ urls_txt_arg cli)
let cache_urls repo_root repo_def =
let global_dl_cache =
OpamStd.Option.Op.(OpamStateConfig.(load ~lock_kind:`Lock_read !r.root_dir) +!
OpamFile.Config.empty)
|> OpamFile.Config.dl_cache
in
let repo_dl_cache =
OpamStd.List.filter_map (fun rel ->
if OpamStd.String.contains ~sub:"://" rel
then OpamUrl.parse_opt ~handle_suffix:false rel
else Some OpamUrl.Op.(OpamUrl.of_string
(OpamFilename.Dir.to_string repo_root) / rel))
(OpamFile.Repo.dl_cache repo_def)
in
repo_dl_cache @ global_dl_cache
let package_files_to_cache repo_root cache_dir cache_urls
~recheck ?link (nv, prefix) =
match
OpamFileTools.read_opam
(OpamRepositoryPath.packages repo_root prefix nv)
with
| None -> Done (OpamPackage.Map.empty)
| Some opam ->
let add_to_cache ?name urlf errors =
let label =
OpamPackage.to_string nv ^
OpamStd.Option.to_string ((^) "/") name
in
let checksums =
OpamHash.sort (OpamFile.URL.checksum urlf)
in
match checksums with
| [] ->
OpamConsole.warning "[%s] no checksum, not caching"
(OpamConsole.colorise `green label);
Done errors
| _::_ ->
let cache_files =
List.map (OpamRepository.cache_file cache_dir) checksums
in
let error_opt =
if not recheck && List.for_all OpamFilename.exists cache_files then
Done None
else
OpamRepository.pull_file_to_cache label
~cache_urls ~cache_dir
checksums
(OpamFile.URL.url urlf :: OpamFile.URL.mirrors urlf)
@@| fun r -> match OpamRepository.report_fetch_result nv r with
| Not_available (_,m) -> Some m
| Up_to_date () | Result () -> None
in
error_opt @@| function
| Some m ->
OpamPackage.Map.update nv (fun l -> m::l) [] errors
| None ->
OpamStd.Option.iter (fun link_dir ->
let name =
OpamStd.Option.default
(OpamUrl.basename (OpamFile.URL.url urlf))
name
in
let link =
OpamFilename.Op.(link_dir / OpamPackage.to_string nv // name)
in
OpamFilename.link ~relative:true ~target:(List.hd cache_files) ~link)
link;
errors
in
let urls =
(match OpamFile.OPAM.url opam with
| None -> []
| Some urlf -> [add_to_cache urlf]) @
(List.map (fun (name,urlf) ->
add_to_cache ~name:(OpamFilename.Base.to_string name) urlf)
(OpamFile.OPAM.extra_sources opam))
in
OpamProcess.Job.seq urls OpamPackage.Map.empty
let cache_command_doc = "Fills a local cache of package archives"
let cache_command cli =
let command = "cache" in
let doc = cache_command_doc in
let man = [
`S Manpage.s_description;
`P "Downloads the archives for all packages to fill a local cache, that \
can be used when serving the repository."
]
in
let cache_dir_arg =
Arg.(value & pos 0 OpamArg.dirname (OpamFilename.Dir.of_string "./cache") &
info [] ~docv:"DIR" ~doc:
"Name of the cache directory to use.")
in
let no_repo_update_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["no-repo-update";"n"]
"Don't check, create or update the 'repo' file to point to the \
generated cache ('archive-mirrors:' field)."
in
let link_arg =
OpamArg.mk_opt ~cli OpamArg.cli_original ["link"] "DIR"
(Printf.sprintf
"Create reverse symbolic links to the archives within $(i,DIR), in \
the form $(b,DIR%sPKG.VERSION%sFILENAME)."
OpamArg.dir_sep OpamArg.dir_sep)
Arg.(some OpamArg.dirname) None
in
let jobs_arg =
OpamArg.mk_opt ~cli OpamArg.cli_original ["jobs"; "j"]
"JOBS" "Number of parallel downloads"
OpamArg.positive_integer 8
in
let recheck_arg =
OpamArg.mk_flag ~cli OpamArg.(cli_from cli2_2) ["check-all"; "c"]
"Run a full integrity check on the existing cache. If this is not set, \
only missing cache files are handled."
in
let cmd global_options cache_dir no_repo_update link jobs recheck () =
OpamArg.apply_global_options cli global_options;
let recheck = recheck || OpamCLIVersion.Op.(cli @< OpamArg.cli2_2) in
let repo_root = checked_repo_root () in
let repo_file = OpamRepositoryPath.repo repo_root in
let repo_def = OpamFile.Repo.safe_read repo_file in
let pkg_prefixes = OpamRepository.packages_with_prefixes repo_root in
let cache_urls = cache_urls repo_root repo_def in
let errors =
OpamParallel.reduce ~jobs
~nil:OpamPackage.Map.empty
~merge:(OpamPackage.Map.union (fun a _ -> a))
~command:(package_files_to_cache repo_root cache_dir cache_urls
~recheck ?link)
(List.sort (fun (nv1,_) (nv2,_) ->
match compare (Hashtbl.hash nv1) (Hashtbl.hash nv2) with
| 0 -> compare nv1 nv2
| n -> n)
(OpamPackage.Map.bindings pkg_prefixes))
in
let cache_dir_url = OpamFilename.remove_prefix_dir repo_root cache_dir in
if not no_repo_update then
if not (List.mem cache_dir_url (OpamFile.Repo.dl_cache repo_def)) then
(OpamConsole.msg "Adding %s to %s...\n"
cache_dir_url (OpamFile.to_string repo_file);
OpamFile.Repo.write repo_file
(OpamFile.Repo.with_dl_cache
(cache_dir_url :: OpamFile.Repo.dl_cache repo_def)
repo_def));
if not (OpamPackage.Map.is_empty errors) then (
OpamConsole.error "Got some errors while processing: %s"
(OpamStd.List.concat_map ", " OpamPackage.to_string
(OpamPackage.Map.keys errors));
OpamConsole.errmsg "%s"
(OpamStd.Format.itemize (fun (nv,el) ->
Printf.sprintf "[%s] %s" (OpamPackage.to_string nv)
(String.concat "\n" el))
(OpamPackage.Map.bindings errors))
);
OpamConsole.msg "Done.\n";
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $
cache_dir_arg $ no_repo_update_arg $ link_arg $ jobs_arg $
recheck_arg)
let packages_with_prefixes repo_root packages =
let pkgs_map = OpamRepository.packages_with_prefixes repo_root in
if packages = [] then pkgs_map else
let pkgs_map, missing_pkgs =
List.fold_left (fun (map, missing) (n,vo)->
match vo with
| Some v ->
let nv = OpamPackage.create n v in
(match OpamPackage.Map.find_opt nv pkgs_map with
| Some pre ->( OpamPackage.Map.add nv pre map), missing
| None -> map, (n,vo)::missing)
| None ->
let n_map = OpamPackage.packages_of_name_map pkgs_map n in
if OpamPackage.Map.is_empty n_map then
map, (n,vo)::missing
else
(OpamPackage.Map.union (fun _nv _nv' ->
OpamStd.Sys.exit_because `Internal_error)
n_map map),
missing
) (OpamPackage.Map.empty, []) packages
in
if missing_pkgs <> [] then
OpamConsole.warning "Not found package%s %s. Ignoring them."
(match missing_pkgs with | [_] -> "" | _ -> "s")
(OpamStd.Format.pretty_list
(List.map
(fun (n,vo) ->
OpamConsole.colorise `underline
(match vo with
| Some v -> OpamPackage.to_string (OpamPackage.create n v)
| None -> OpamPackage.Name.to_string n))
(List.rev missing_pkgs)));
pkgs_map
let update_extrafiles_command_doc =
"Add extra-files to an opam repository."
let update_extrafiles_command cli =
let command = "update-extrafiles" in
let doc = update_extrafiles_command_doc in
let man = [
`S Manpage.s_description;
`P "This command scans through package definitions, and add extrafiles \
field as requested."
]
in
let hash_type_arg =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_2) ["hash"]
"HASH_ALGO" "The hash, or hashes to be added"
(Arg.some OpamArg.hash_kinds) None
in
let packages =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_2) ["p";"packages"]
"PACKAGES" "Only add extra files for the given packages"
Arg.(list OpamArg.package) []
in
let cmd global_options hash_type packages () =
OpamArg.apply_global_options cli global_options;
let repo_root = checked_repo_root () in
let pkg_prefixes = packages_with_prefixes repo_root packages in
let compute ?kind file =
OpamHash.compute ?kind (OpamFilename.to_string file)
in
let has_error =
OpamPackage.Map.fold (fun nv prefix has_error ->
let opam_file = OpamRepositoryPath.opam repo_root prefix nv in
let opam = OpamFile.OPAM.read opam_file in
let has_error =
if OpamFile.exists (OpamRepositoryPath.url repo_root prefix nv) then
(OpamConsole.warning "Not updating external URL file at %s"
(OpamFile.to_string
(OpamRepositoryPath.url repo_root prefix nv));
true)
else has_error
in
let files_dir = OpamRepositoryPath.files repo_root prefix nv in
if OpamFilename.exists_dir files_dir then
(let files =
OpamFilename.rec_files files_dir
|> List.map (fun file ->
file,
OpamFilename.Base.of_string
(OpamFilename.remove_prefix files_dir file))
in
let =
OpamStd.Option.default [] (OpamFile.OPAM.extra_files opam)
in
let = OpamFilename.Base.Map.of_list extra_files in
let xfiles =
List.fold_left (fun (file, base) ->
match OpamFilename.Base.Map.find_opt base extra_files with
| Some hash ->
let kind = OpamHash.kind hash in
let xhash = compute ~kind file in
if OpamHash.compare hash xhash <> 0 then
OpamFilename.Base.Map.add base
(OpamStd.Option.map_default (fun kind ->
compute ~kind file) xhash hash_type)
extra
else
(match hash_type with
| Some kind ->
OpamFilename.Base.Map.add base (compute ~kind file)
extra
| None -> extra)
| None ->
OpamFilename.Base.Map.add base
(compute ?kind:hash_type file) extra)
extra_files files
in
if not (OpamFilename.Base.Map.equal
OpamHash.equal extra_files xfiles) then
let xfiles = OpamFilename.Base.Map.bindings xfiles in
let opam1 = OpamFile.OPAM.with_extra_files xfiles opam in
OpamFile.OPAM.write_with_preserved_format opam_file opam1);
has_error)
pkg_prefixes false
in
if has_error then OpamStd.Sys.exit_because `Sync_error
else OpamStd.Sys.exit_because `Success
in
OpamArg.mk_command ~cli OpamArg.(cli_from cli2_2) command ~doc ~man
Term.(const cmd $ global_options cli
$ hash_type_arg $ packages)
let migrate_extrafiles_command_doc =
"Move extra-files to extra-source."
let migrate_extrafiles_command cli =
let command = "migrate-extrafiles" in
let doc = migrate_extrafiles_command_doc in
let man = [
`S Manpage.s_description;
`P "This command scans through package definitions, and migrates all \
$(i,extra-files) fields to $(i,extra-source) as requested."
] in
let hash_type_arg =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_4) ["hash"]
"HASH_ALGO" "The hash, or hashes to be added"
OpamArg.hash_kinds `SHA256
in
let packages =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_4) ["p";"packages"]
"PACKAGES" "Only add extra files for the given packages"
Arg.(list OpamArg.package) []
in
let local_dir_arg =
let doc =
Arg.info ~docv:"DIR"
~doc:"Name of the local directory where to put the extra-files. \
They will be put into \
DIR/patches/<pkgname>/<pkgname.version>/filenameYY" []
in
Arg.(required & pos 0 (some OpamArg.dirname) None & doc)
and url_prefix_arg =
let doc =
Arg.info ~docv:"URL"
~doc:"Prefix of the URL to emit into extra-sources." []
in
Arg.(required & pos 1 (some OpamArg.url) None & doc)
in
let cmd global_options kind packages local_dir url_prefix () =
OpamArg.apply_global_options cli global_options;
let repo_root = checked_repo_root () in
let pkg_prefixes = packages_with_prefixes repo_root packages in
let compute ?kind file =
OpamHash.compute ?kind (OpamFilename.to_string file)
in
let has_error =
OpamPackage.Map.fold (fun nv prefix has_error ->
let opam_file = OpamRepositoryPath.opam repo_root prefix nv in
let opam = OpamFile.OPAM.read opam_file in
let has_error =
let urlfile = OpamRepositoryPath.url repo_root prefix nv in
if OpamFile.exists urlfile then
(OpamConsole.warning
"Not updating external URL file at %s, \
consider upgrading your repository with 'opam admin upgrade'"
(OpamFile.to_string urlfile);
true)
else has_error
in
let files_dir = OpamRepositoryPath.files repo_root prefix nv in
if OpamFilename.exists_dir files_dir then
(let files =
OpamFilename.rec_files files_dir
|> List.map (fun file ->
file,
OpamFilename.Base.of_string
(OpamFilename.remove_prefix files_dir file))
in
match files with
| [] -> has_error
| _ ->
let hashes =
List.map (fun (file, base) ->
let xhash = compute ~kind file in
file, base, xhash)
files
in
let patch_prefix = "patches" in
let =
List.map (fun (src, base, hash) ->
let name = OpamPackage.name_to_string nv in
let name_version = OpamPackage.to_string nv in
let dst_dir = OpamFilename.Op.(
local_dir / patch_prefix / name / name_version
) in
let dst = OpamFilename.create dst_dir base in
OpamFilename.mkdir dst_dir;
OpamFilename.copy ~src ~dst;
let url =
let file = OpamFilename.Base.to_string base in
OpamFile.URL.create ~checksum:[hash]
OpamUrl.Op.(url_prefix / patch_prefix
/ name / name_version/ file)
in
base, url)
hashes
in
OpamFilename.cleandir files_dir;
OpamFilename.rmdir_cleanup files_dir;
let = OpamFile.OPAM.extra_sources opam in
let opam1 =
OpamFile.OPAM.with_extra_sources
(my_extra_sources @ extra_sources) opam
in
let opam1 = OpamFile.OPAM.with_extra_files_opt None opam1 in
OpamFile.OPAM.write_with_preserved_format opam_file opam1;
has_error)
else
has_error)
pkg_prefixes false
in
if has_error then OpamStd.Sys.exit_because `Sync_error
else OpamStd.Sys.exit_because `Success
in
OpamArg.mk_command ~cli OpamArg.(cli_from cli2_4) command ~doc ~man
Term.(const cmd $ global_options cli $ hash_type_arg $ packages
$ local_dir_arg $ url_prefix_arg)
let add_hashes_command_doc =
"Add archive hashes to an opam repository."
let add_hashes_command cli =
let command = "add-hashes" in
let doc = add_hashes_command_doc in
let man = [
`S Manpage.s_description;
`P "This command scans through package definitions, and add hashes as \
requested (fetching the archives if required). A cache is generated \
in <opamroot>/download-cache/hash-cache for subsequent runs.";
]
in
let hash_types_arg =
OpamArg.nonempty_arg_list "HASH_ALGO" "The hash, or hashes to be added"
OpamArg.hash_kinds
in
let packages =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_1) ["p";"packages"]
"PACKAGES" "Only add hashes for the given packages"
Arg.(list OpamArg.package) []
in
let replace_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["replace"]
"Replace the existing hashes rather than adding to them"
in
let create_hash_tables hash_cache_dir =
let t = Hashtbl.create (List.length OpamHash.all_kinds) in
List.iter (fun k1 ->
List.iter (fun k2 ->
if k1 <> k2 then (
let cache_file : string list list OpamFile.t =
OpamFile.make @@ OpamFilename.Op.(
hash_cache_dir //
(OpamHash.string_of_kind k1 ^ "_to_" ^
OpamHash.string_of_kind k2))
in
let t_mapping = Hashtbl.create 187 in
(OpamStd.Option.default [] (OpamFile.Lines.read_opt cache_file)
|> List.iter @@ function
| [src; dst] ->
Hashtbl.add t_mapping
(OpamHash.of_string src) (OpamHash.of_string dst)
| _ -> failwith ("Bad cache at "^OpamFile.to_string cache_file));
Hashtbl.add t (k1,k2) (cache_file, t_mapping);
))
OpamHash.all_kinds
)
OpamHash.all_kinds;
t
in
let save_hashes hash_tables =
Hashtbl.iter (fun _ (file, tbl) ->
Hashtbl.fold
(fun src dst l -> [OpamHash.to_string src; OpamHash.to_string dst]::l)
tbl [] |> fun lines ->
try OpamFile.Lines.write file lines with e ->
OpamStd.Exn.fatal e;
OpamConsole.log "ADMIN"
"Could not write hash cache to %s, skipping (%s)"
(OpamFile.to_string file)
(Printexc.to_string e))
hash_tables
in
let additions_count = ref 0 in
let get_hash hash_tables ~cache_urls ~cache_dir kind known_hashes url =
let found =
List.fold_left (fun result hash ->
match result with
| None ->
let known_kind = OpamHash.kind hash in
let _, tbl = Hashtbl.find hash_tables (known_kind, kind) in
(try Some (Hashtbl.find tbl hash) with Not_found -> None)
| some -> some)
None known_hashes
in
match found with
| Some h -> Some h
| None ->
let h =
OpamProcess.Job.run @@
OpamFilename.with_tmp_dir_job @@ fun dir ->
let f = OpamFilename.Op.(dir // OpamUrl.basename url) in
OpamProcess.Job.ignore_errors ~default:None
(fun () ->
OpamRepository.pull_file (OpamUrl.to_string url)
~cache_dir ~cache_urls
f known_hashes [url]
@@| function
| Result () | Up_to_date () ->
OpamHash.compute ~kind (OpamFilename.to_string f)
|> OpamStd.Option.some
| Not_available _ -> None)
in
(match h with
| Some h ->
List.iter (fun h0 ->
Hashtbl.replace
(snd (Hashtbl.find hash_tables (OpamHash.kind h0, kind)))
h0 h
) known_hashes;
incr additions_count;
if !additions_count mod 20 = 0 then save_hashes hash_tables
| None -> ());
h
in
let cmd global_options hash_types replace packages () =
OpamArg.apply_global_options cli global_options;
let cache_dir =
OpamRepositoryPath.download_cache OpamStateConfig.(!r.root_dir)
in
let hash_cache_dir = OpamFilename.Op.(cache_dir / "hash-cache") in
let hash_tables = create_hash_tables hash_cache_dir in
let repo_root = checked_repo_root () in
let cache_urls =
cache_urls repo_root
(OpamFile.Repo.safe_read (OpamRepositoryPath.repo repo_root))
in
let pkg_prefixes = packages_with_prefixes repo_root packages in
let has_error =
OpamPackage.Map.fold (fun nv prefix has_error ->
let opam_file = OpamRepositoryPath.opam repo_root prefix nv in
let opam = OpamFile.OPAM.read opam_file in
let has_error =
if OpamFile.exists (OpamRepositoryPath.url repo_root prefix nv) then
(OpamConsole.warning "Not updating external URL file at %s"
(OpamFile.to_string (OpamRepositoryPath.url repo_root prefix nv));
true)
else has_error
in
let process_url has_error urlf =
let hashes = OpamFile.URL.checksum urlf in
let hashes =
if replace then
List.filter (fun h -> List.mem (OpamHash.kind h) hash_types)
hashes
else hashes
in
let has_error, hashes =
List.fold_left (fun (has_error, hashes) kind ->
if List.exists (fun h -> OpamHash.kind h = kind) hashes
then has_error, hashes else
match get_hash hash_tables ~cache_urls ~cache_dir kind
hashes (OpamFile.URL.url urlf) with
| Some h -> has_error, hashes @ [h]
| None ->
OpamConsole.error "Could not get hash for %s: %s"
(OpamPackage.to_string nv)
(OpamUrl.to_string (OpamFile.URL.url urlf));
true, hashes)
(has_error, hashes)
hash_types
in
has_error, OpamFile.URL.with_checksum hashes urlf
in
let has_error, url_opt =
match OpamFile.OPAM.url opam with
| None -> has_error, None
| Some urlf ->
let has_error, urlf = process_url has_error urlf in
has_error, Some urlf
in
let has_error, =
List.fold_right (fun (basename, urlf) (has_error, acc) ->
let has_error, urlf = process_url has_error urlf in
has_error, (basename, urlf) :: acc)
(OpamFile.OPAM.extra_sources opam)
(has_error, [])
in
let opam1 = OpamFile.OPAM.with_url_opt url_opt opam in
let opam1 = OpamFile.OPAM.with_extra_sources extra_sources opam1 in
if opam1 <> opam then
OpamFile.OPAM.write_with_preserved_format opam_file opam1;
has_error
)
pkg_prefixes false
in
save_hashes hash_tables;
if has_error then OpamStd.Sys.exit_because `Sync_error
else OpamStd.Sys.exit_because `Success
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $
hash_types_arg $ replace_arg $ packages)
let upgrade_command_doc =
"Upgrades repository from earlier opam versions."
let upgrade_command cli =
let command = "upgrade" in
let doc = upgrade_command_doc in
let man = [
`S Manpage.s_description;
`P (Printf.sprintf
"This command reads repositories from earlier opam versions, and \
converts them to repositories suitable for the current opam version. \
Packages might be created or renamed, and any compilers defined in the \
old format ('compilers%s' directory) will be turned into packages, \
using a pre-defined hierarchy that assumes OCaml compilers."
OpamArg.dir_sep)
]
in
let clear_cache_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["clear-cache"]
(Printf.sprintf
"Instead of running the upgrade, clear the cache of archive hashes (held \
in ~%s.cache), that is used to avoid re-downloading files to obtain \
their hashes at every run." OpamArg.dir_sep)
in
let create_mirror_arg =
OpamArg.mk_opt ~cli OpamArg.cli_original ["m"; "mirror"] "URL"
"Don't overwrite the current repository, but put an upgraded mirror in \
place in a subdirectory, with proper redirections. Needs the URL the \
repository will be served from to put in the redirects (older versions \
of opam don't understand relative redirects)."
Arg.(some OpamArg.url) None
in
let cmd global_options clear_cache create_mirror () =
OpamArg.apply_global_options cli global_options;
if clear_cache then OpamAdminRepoUpgrade.clear_cache ()
else match create_mirror with
| None ->
OpamAdminRepoUpgrade.do_upgrade (OpamFilename.cwd ());
if OpamFilename.exists (OpamFilename.of_string "index.tar.gz") ||
OpamFilename.exists (OpamFilename.of_string "urls.txt")
then
OpamConsole.note
"Indexes need updating: you should now run:\n\
\n\
\ opam admin index"
| Some m -> OpamAdminRepoUpgrade.do_upgrade_mirror (OpamFilename.cwd ()) m
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $
clear_cache_arg $ create_mirror_arg)
let lint_command_doc =
"Runs 'opam lint' and reports on a whole repository"
let lint_command cli =
let command = "lint" in
let doc = lint_command_doc in
let man = [
`S Manpage.s_description;
`P "This command gathers linting results on all files in a repository. The \
warnings and errors to show or hide can be selected"
]
in
let short_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["s";"short"]
"Print only packages and warning/error numbers, without explanations"
in
let list_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["list";"l"]
"Only list package names, without warning details"
in
let include_arg =
OpamArg.arg_list "INT" "Show only these warnings"
OpamArg.positive_integer
in
let exclude_arg =
OpamArg.mk_opt_all ~cli OpamArg.cli_original ["exclude";"x"] "INT"
"Exclude the given warnings or errors"
OpamArg.positive_integer
in
let ignore_arg =
OpamArg.mk_opt_all ~cli OpamArg.cli_original ["ignore-packages";"i"] "INT"
"Ignore any packages having one of these warnings or errors"
OpamArg.positive_integer
in
let warn_error_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["warn-error";"W"]
"Return failure on any warnings, not only on errors"
in
let cmd global_options short list incl excl ign warn_error () =
OpamArg.apply_global_options cli global_options;
let repo_root = OpamFilename.cwd () in
if not (OpamFilename.exists_dir OpamFilename.Op.(repo_root / "packages"))
then
OpamConsole.error_and_exit `Bad_arguments
"No repository found in current directory.\n\
Please make sure there is a \"packages\" directory";
let pkg_prefixes = OpamRepository.packages_with_prefixes repo_root in
let ret =
OpamPackage.Map.fold (fun nv prefix ret ->
let opam_file = OpamRepositoryPath.opam repo_root prefix nv in
let w, _ = OpamFileTools.lint_file ~handle_dirname:true opam_file in
if List.exists (fun (n,_,_) -> List.mem n ign) w then ret else
let w =
List.filter (fun (n,_,_) ->
(incl = [] || List.mem n incl) && not (List.mem n excl))
w
in
if w <> [] then
if list then
OpamConsole.msg "%s\n" (OpamPackage.to_string nv)
else if short then
OpamConsole.msg "%s %s\n" (OpamPackage.to_string nv)
(OpamStd.List.concat_map " " (fun (n,k,_) ->
OpamConsole.colorise
(match k with `Warning -> `yellow | `Error -> `red)
(string_of_int n))
w)
else begin
OpamConsole.carriage_delete ();
OpamConsole.msg "In %s:\n%s\n"
(OpamPackage.to_string nv)
(OpamFileTools.warns_to_string w)
end;
ret && not (warn_error && w <> [] ||
List.exists (fun (_,k,_) -> k = `Error) w))
pkg_prefixes
true
in
OpamStd.Sys.exit_because (if ret then `Success else `False)
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $
short_arg $ list_arg $ include_arg $ exclude_arg $ ignore_arg $
warn_error_arg)
let check_command_doc =
"Runs some consistency checks on a repository"
let check_command cli =
let command = "check" in
let doc = check_command_doc in
let man = [
`S Manpage.s_description;
`P "This command runs consistency checks on a repository, and prints a \
report to stdout. Checks include packages that are not installable \
(due e.g. to a missing dependency) and dependency cycles. The \
'available' field is ignored for these checks, that is, all packages \
are supposed to be available. By default, all checks are run."
]
in
let print_short_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["s";"short"]
"Only output a list of uninstallable packages"
in
let installability_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["installability"]
"Do the installability check (and disable the others by default)"
in
let cycles_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["cycles"]
"Do the cycles check (and disable the others by default)"
in
let obsolete_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["obsolete"]
"Analyse for obsolete packages"
in
let cmd global_options print_short
installability cycles obsolete () =
OpamArg.apply_global_options cli global_options;
let repo_root = checked_repo_root () in
let installability, cycles, obsolete =
if installability || cycles || obsolete
then installability, cycles, obsolete
else true, true, false
in
let pkgs, unav_roots, uninstallable, cycle_packages, obsolete =
OpamAdminCheck.check
~quiet:print_short ~installability ~cycles ~obsolete
repo_root
in
let all_ok =
OpamPackage.Set.is_empty uninstallable &&
OpamPackage.Set.is_empty cycle_packages &&
OpamPackage.Set.is_empty obsolete
in
let open OpamPackage.Set.Op in
(if print_short then
OpamConsole.msg "%s\n"
(OpamStd.List.concat_map "\n" OpamPackage.to_string
(OpamPackage.Set.elements
(uninstallable ++ cycle_packages ++ obsolete)))
else if all_ok then
OpamConsole.msg "No issues detected on this repository's %d packages\n"
(OpamPackage.Set.cardinal pkgs)
else
let pr set msg =
if OpamPackage.Set.is_empty set then ""
else Printf.sprintf "- %d %s\n" (OpamPackage.Set.cardinal set) msg
in
OpamConsole.msg "Summary: out of %d packages (%d distinct names)\n\
%s%s%s%s\n"
(OpamPackage.Set.cardinal pkgs)
(OpamPackage.Name.Set.cardinal (OpamPackage.names_of_packages pkgs))
(pr unav_roots "uninstallable roots")
(pr (uninstallable -- unav_roots) "uninstallable dependent packages")
(pr (cycle_packages -- uninstallable)
"packages part of dependency cycles")
(pr obsolete "obsolete packages"));
OpamStd.Sys.exit_because (if all_ok then `Success else `False)
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $ print_short_arg
$ installability_arg $ cycles_arg $ obsolete_arg)
let compare_versions_command_doc = "Compare two package versions"
let compare_versions_command cli =
let operator : OpamFormula.relop option Term.t =
let make_flag (relop : OpamFormula.relop) =
let flag_name =
match relop with
| `Eq -> "eq"
| `Neq -> "neq"
| `Geq -> "geq"
| `Gt -> "gt"
| `Leq -> "leq"
| `Lt -> "lt"
in
let doc = Printf.sprintf "assert V0 %s V1" (OpamFormula.string_of_relop relop) in
OpamArg.(cli_from cli2_4), Some relop, [ flag_name ], doc
in
OpamArg.mk_vflag ~cli None (List.map make_flag OpamFormula.all_relop)
in
let version_arg n =
let doc =
Arg.info
~docv:(Printf.sprintf "V%d" n)
~doc:"Package version to compare" []
in
Arg.(required & pos n (some OpamArg.package_version) None & doc)
in
let command = "compare-versions" in
let doc = compare_versions_command_doc in
let man = [
`S Manpage.s_description;
`P "This command compares 2 package versions. By default it outputs 'V0 OP \
V1' to the console with OP in {<,>,=} such that the equation holds. \
When an operator is supplied, the output is suppressed and the result \
of the comparison is checked against the provided operator: the command \
exits 0 if the comparison holds, and 1 otherwise. For example:";
`Pre "\n\
\\$ opam admin compare-versions 0.0.9 0.0.10\n\
0.0.9 < 0.0.10\n\
\n\
\\$ opam admin compare-versions 0.0.9 --lt 0.0.10\n\
[0]\n\
\n\
\\$ opam admin compare-versions 0.0.9 --eq 0.0.10\n\
[1]";
`S Manpage.s_arguments;
`S Manpage.s_options;
]
in
let cmd v0 v1 operator () =
match operator with
| None ->
let result = OpamPackage.Version.compare v0 v1 in
OpamConsole.formatted_msg "%s %s %s\n"
(OpamPackage.Version.to_string v0)
(if result < 0 then "<" else if result = 0 then "=" else ">")
(OpamPackage.Version.to_string v1)
| Some operator ->
OpamStd.Sys.exit_because
(if OpamFormula.eval_relop operator v0 v1
then `Success
else `False)
in
OpamArg.mk_command ~cli OpamArg.(cli_from cli2_4) command ~doc ~man
Term.(const cmd $ version_arg 0 $ version_arg 1 $ operator)
let pattern_list_arg =
OpamArg.arg_list "PATTERNS"
"Package patterns with globs. matching against $(b,NAME) or \
$(b,NAME.VERSION)"
Arg.string
let env_arg cli =
OpamArg.mk_opt ~cli OpamArg.cli_original ["environment"]
"VAR=VALUE[,VAR=VALUE]"
(Printf.sprintf
"Use the given opam environment, in the form of a list of \
comma-separated 'var=value' bindings, when resolving variables. This \
is used e.g. when computing available packages: if undefined, \
availability of packages will be assumed as soon as it can not be \
resolved purely from globally defined variables. Note that, unless \
overridden, variables like 'root' or 'opam-version' may be taken \
from the current opam installation. What is defined in \
$(i,~%s.opam%sconfig) is always ignored."
OpamArg.dir_sep OpamArg.dir_sep)
Arg.(list string) []
let state_selection_arg cli =
OpamArg.mk_vflag ~cli ~section:OpamArg.order_sensible_selector_section
OpamListCommand.Available [
OpamArg.cli_original, OpamListCommand.Any, ["A";"all"],
"Include all, even uninstalled or unavailable packages";
OpamArg.cli_original, OpamListCommand.Available, ["a";"available"],
"List only packages that are available according to the defined \
$(b,environment). Without $(b,--environment), this will include \
any packages for which availability is not resolvable at this \
point.";
OpamArg.cli_original, OpamListCommand.Installable, ["installable"],
"List only packages that are installable according to the defined \
$(b,environment) (this calls the solver and may be more costly; \
a package depending on an unavailable one may be available, but \
is never installable)";
OpamArg.cli_from OpamArg.cli2_3, OpamListCommand.Latests_only,
["latests-only"], "List only the latest version of each package.";
]
let get_virtual_switch_state repo_root env =
let env =
List.map (fun s ->
match OpamStd.String.cut_at s '=' with
| Some (var,value) -> OpamVariable.of_string var, S value
| None -> OpamVariable.of_string s, B true)
env
in
let repo = {
repo_name = OpamRepositoryName.of_string "local";
repo_url = OpamUrl.empty;
repo_trust = None;
} in
let repo_file = OpamRepositoryPath.repo repo_root in
let repo_def = OpamFile.Repo.safe_read repo_file in
let opams =
OpamRepositoryState.load_opams_from_dir repo.repo_name repo_root
in
let gt = {
global_lock = OpamSystem.lock_none;
root = OpamStateConfig.(!r.root_dir);
config = OpamStd.Option.Op.(OpamStateConfig.(
load ~lock_kind:`Lock_read !r.root_dir) +!
OpamFile.Config.empty);
global_variables = OpamVariable.Map.empty;
global_state_to_upgrade = { gtc_repo = false; gtc_switch = false; };
} in
let singl x = OpamRepositoryName.Map.singleton repo.repo_name x in
let repos_tmp =
let t = Hashtbl.create 1 in
Hashtbl.add t repo.repo_name (lazy repo_root); t
in
let rt = {
repos_global = gt;
repos_lock = OpamSystem.lock_none;
repositories = singl repo;
repos_definitions = singl repo_def;
repo_opams = singl opams;
repos_tmp;
} in
let gt =
{gt with global_variables =
OpamVariable.Map.of_list @@
List.map (fun (var, value) ->
var, (lazy (Some value), "Manually defined"))
env }
in
OpamSwitchState.load_virtual
~repos_list:[repo.repo_name]
~avail_default:(env = [])
gt rt
let or_arg cli =
OpamArg.mk_flag ~cli OpamArg.cli_original ~section:OpamArg.package_selection_section ["or"]
"Instead of selecting packages that match $(i,all) the \
criteria, select packages that match $(i,any) of them"
let list_command_doc = "Lists packages from a repository"
let list_command cli =
let command = "list" in
let doc = list_command_doc in
let man = [
`S Manpage.s_description;
`P "This command is similar to 'opam list', but allows listing packages \
directly from a repository instead of what is available in a given \
opam installation.";
`S Manpage.s_arguments;
`S Manpage.s_options;
`S OpamArg.order_sensible_selector_section;
`P "These options are sensible to the order they are given to the command. \
For example: $(b,--available --latests-only) will first list all the \
available packages, then choose only the latest packages in that set; \
while $(b,--latests-only --available) will first lists all the latest \
packages, then only show the ones that are available in that set.";
`S OpamArg.package_selection_section;
`P "These options will always be given to the command in the same order, \
regardless of the order they have been given to the command.";
`S OpamArg.package_listing_section;
]
in
let cmd
global_options package_selection disjunction state_selection
package_listing env packages () =
OpamArg.apply_global_options cli global_options;
let format =
let force_all_versions =
match packages with
| [single] ->
let nameglob =
match OpamStd.String.cut_at single '.' with
| None -> single
| Some (n, _v) -> n
in
(try ignore (OpamPackage.Name.of_string nameglob); true
with Failure _ -> false)
| _ -> false
in
package_listing ~force_all_versions
in
let pattern_selector = OpamListCommand.pattern_selector packages in
let join =
if disjunction then OpamFormula.ors else OpamFormula.ands
in
let filter =
OpamFormula.ands [
Atom state_selection;
join (pattern_selector ::
List.map (fun x -> Atom x) package_selection);
]
in
let st = get_virtual_switch_state (OpamFilename.cwd ()) env in
if not format.OpamListCommand.short && filter <> OpamFormula.Empty then
OpamConsole.msg "# Packages matching: %s\n"
(OpamListCommand.string_of_formula filter);
let results =
OpamListCommand.filter ~base:st.packages st filter
in
OpamListCommand.display st format results
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $ OpamArg.package_selection cli $
or_arg cli $ state_selection_arg cli $ OpamArg.package_listing cli $
env_arg cli $ pattern_list_arg)
let filter_command_doc = "Filters a repository to only keep selected packages"
let filter_command cli =
let command = "filter" in
let doc = filter_command_doc in
let man = [
`S Manpage.s_description;
`P "This command removes all package definitions that don't match the \
search criteria (specified similarly to 'opam admin list') from a \
repository.";
`S Manpage.s_arguments;
`S Manpage.s_options;
`S OpamArg.package_selection_section;
]
in
let remove_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["remove"]
"Invert the behaviour and remove the matching packages, keeping the ones \
that don't match."
in
let dryrun_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["dry-run"]
"List the removal commands, without actually performing them"
in
let cmd
global_options package_selection disjunction state_selection env
remove dryrun packages () =
OpamArg.apply_global_options cli global_options;
let repo_root = OpamFilename.cwd () in
let pattern_selector = OpamListCommand.pattern_selector packages in
let join =
if disjunction then OpamFormula.ors else OpamFormula.ands
in
let filter =
OpamFormula.ands [
Atom state_selection;
join
(pattern_selector ::
List.map (fun x -> Atom x) package_selection)
]
in
let st = get_virtual_switch_state repo_root env in
let packages = OpamListCommand.filter ~base:st.packages st filter in
if OpamPackage.Set.is_empty packages then
if remove then
(OpamConsole.warning "No packages match the selection criteria";
OpamStd.Sys.exit_because `Success)
else
OpamConsole.error_and_exit `Not_found
"No packages match the selection criteria";
let num_total = OpamPackage.Set.cardinal st.packages in
let num_selected = OpamPackage.Set.cardinal packages in
if remove then
OpamConsole.formatted_msg
"The following %d packages will be REMOVED from the repository (%d \
packages will be kept):\n%s\n"
num_selected (num_total - num_selected)
(OpamStd.List.concat_map " " OpamPackage.to_string
(OpamPackage.Set.elements packages))
else
OpamConsole.formatted_msg
"The following %d packages will be kept in the repository (%d packages \
will be REMOVED):\n%s\n"
num_selected (num_total - num_selected)
(OpamStd.List.concat_map " " OpamPackage.to_string
(OpamPackage.Set.elements packages));
let packages =
if remove then packages else OpamPackage.Set.Op.(st.packages -- packages)
in
if not (dryrun || OpamConsole.confirm "Confirm?") then
OpamStd.Sys.exit_because `Aborted
else
let pkg_prefixes = OpamRepository.packages_with_prefixes repo_root in
OpamPackage.Map.iter (fun nv prefix ->
if OpamPackage.Set.mem nv packages then
let d = OpamRepositoryPath.packages repo_root prefix nv in
if dryrun then
OpamConsole.msg "rm -rf %s\n" (OpamFilename.Dir.to_string d)
else
(OpamFilename.cleandir d;
OpamFilename.rmdir_cleanup d))
pkg_prefixes
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $ OpamArg.package_selection cli $
or_arg cli $ state_selection_arg cli $ env_arg cli $ remove_arg $
dryrun_arg $
pattern_list_arg)
let add_constraint_command_doc =
"Adds version constraints on all dependencies towards a given package"
let add_constraint_command cli =
let command = "add-constraint" in
let doc = add_constraint_command_doc in
let man = [
`S Manpage.s_description;
`P "This command searches to all dependencies towards a given package, and \
adds a version constraint to them. It is particularly useful to add \
upper bounds to existing dependencies when a new, incompatible major \
version of a library is added to a repository. The new version \
constraint is merged with the existing one, and simplified if \
possible (e.g. $(b,>=3 & >5) becomes $(b,>5)).";
`S Manpage.s_arguments;
`S Manpage.s_options;
]
in
let atom_arg =
Arg.(required & pos 0 (some OpamArg.atom) None
& info [] ~docv:"PACKAGE" ~doc:
"A package name with a version constraint, e.g. $(b,name>=version). \
If no version constraint is specified, the command will just \
simplify existing version constraints on dependencies to the named \
package.")
in
let force_arg =
OpamArg.mk_flag ~cli OpamArg.cli_original ["force"]
"Force updating of constraints even if the resulting constraint is \
unsatisfiable (e.g. when adding $(b,>3) to the constraint \
$(b,<2)). The default in this case is to print a warning and keep \
the existing constraint unchanged."
in
let packages =
OpamArg.mk_opt ~cli OpamArg.(cli_from cli2_2) ["p";"packages"]
"PACKAGES" "Only add constraints for the given packages"
Arg.(list OpamArg.package) []
in
let cmd global_options force atom packages () =
OpamArg.apply_global_options cli global_options;
let repo_root = checked_repo_root () in
let pkg_prefixes = packages_with_prefixes repo_root packages in
let name, cstr_opt = atom in
let cstr = match cstr_opt with
| Some (relop, v) ->
OpamFormula.Atom
(Constraint (relop, FString (OpamPackage.Version.to_string v)))
| None ->
OpamFormula.Empty
in
let add_cstr op cstr nv n c =
let f = op [ cstr; c] in
match OpamFilter.simplify_extended_version_formula f with
| Some f -> f
| None ->
if force then f
else
(OpamConsole.warning
"In package %s, updated constraint %s cannot be satisfied, not \
updating (use `--force' to update anyway)"
(OpamPackage.to_string nv)
(OpamConsole.colorise `bold
(OpamFilter.string_of_filtered_formula
(Atom (n, f))));
c)
in
OpamPackage.Map.iter (fun nv prefix ->
let opam_file = OpamRepositoryPath.opam repo_root prefix nv in
let opam = OpamFile.OPAM.read opam_file in
let deps0 = OpamFile.OPAM.depends opam in
let deps =
OpamFormula.map (function
| (n,c as atom) ->
if n = name then Atom (n, (add_cstr OpamFormula.ands cstr nv n c))
else Atom atom)
deps0
in
let depopts0 = OpamFile.OPAM.depopts opam in
let conflicts0 = OpamFile.OPAM.conflicts opam in
let contains name =
OpamFormula.fold_left (fun contains (n,_) ->
contains || n = name) false
in
let conflicts =
if contains name depopts0 then
match cstr_opt with
| Some (relop, v) ->
let icstr =
OpamFormula.Atom
(Constraint (OpamFormula.neg_relop relop,
FString (OpamPackage.Version.to_string v)))
in
if contains name conflicts0 then
OpamFormula.map (function
| (n,c as atom) ->
if n = name then Atom (n, (add_cstr OpamFormula.ors icstr nv n c))
else Atom atom)
conflicts0
else
OpamFormula.ors [ conflicts0; Atom (name, icstr) ]
| None -> conflicts0
else conflicts0
in
if deps <> deps0 || conflicts <> conflicts0 then
OpamFile.OPAM.write_with_preserved_format opam_file
(OpamFile.OPAM.with_depends deps opam
|> OpamFile.OPAM.with_conflicts conflicts))
pkg_prefixes
in
OpamArg.mk_command ~cli OpamArg.cli_original command ~doc ~man
Term.(const cmd $ global_options cli $ force_arg $ atom_arg $ packages)
let help =
let doc = "Display help about opam admin and opam admin subcommands." in
let man = [
`S Manpage.s_description;
`P "Prints help about opam admin commands.";
`P "Use `$(mname) help topics' to get the full list of help topics.";
] in
let topic =
let doc = Arg.info [] ~docv:"TOPIC" ~doc:"The topic to get help on." in
Arg.(value & pos 0 (some string) None & doc )
in
let help man_format cmds topic = match topic with
| None -> `Help (`Pager, None)
| Some topic ->
let topics = "topics" :: cmds in
let conv, _ = Cmdliner.Arg.enum (List.rev_map (fun s -> (s, s)) topics) in
match conv topic with
| `Error e -> `Error (false, e)
| `Ok t when t = "topics" ->
List.iter (OpamConsole.msg "%s\n") cmds; `Ok ()
| `Ok t -> `Help (man_format, Some t) in
Term.(ret (const help $Arg.man_format $Term.choice_names $topic)),
Cmd.info "help" ~doc ~man
let admin_subcommands cli =
let index_command = index_command cli in
[
index_command; OpamArg.make_command_alias ~cli index_command "make";
cache_command cli;
upgrade_command cli;
lint_command cli;
check_command cli;
compare_versions_command cli;
list_command cli;
filter_command cli;
add_constraint_command cli;
add_hashes_command cli;
update_extrafiles_command cli;
migrate_extrafiles_command cli;
help;
]
let default_subcommand cli =
let man =
admin_command_man @ [
`S Manpage.s_commands;
`S "COMMAND ALIASES";
] @ OpamArg.help_sections cli
in
let usage global_options =
OpamArg.apply_global_options cli global_options;
OpamConsole.formatted_msg
"usage: opam admin [--version]\n\
\ [--help]\n\
\ <command> [<args>]\n\
\n\
The most commonly used opam admin commands are:\n\
\ index %s\n\
\ cache %s\n\
\ upgrade-format %s\n\
\n\
See 'opam admin <command> --help' for more information on a specific \
command.\n"
index_command_doc
cache_command_doc
upgrade_command_doc
in
Term.(const usage $ global_options cli),
Cmd.info "opam admin"
~version:(OpamVersion.to_string OpamVersion.current)
~sdocs:OpamArg.global_option_section
~doc:admin_command_doc
~man
let get_cmdliner_parser cli =
default_subcommand cli, admin_subcommands cli