Source file uniq_meta.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
let src = Logs.Src.create "uniq.meta"
module Log = (val Logs.src_log src : Logs.LOG)
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
let ( let* ) = Result.bind
let absolute =
let cwd = Fpath.v (Sys.getcwd ()) in
fun path ->
let path = if Fpath.is_rel path then Fpath.(cwd // path) else path in
Fpath.normalize path
type t =
| Node of { name: string; value: string; contents: t list }
(** [name] "[value]" ( [contents] ), like [package "lib" ( ... )] *)
| Set of { name: string; predicates: predicate list; value: string }
(** [name] [(...) as predicates] = [value], like
[archive(native) = "lib.cmxa"]*)
| Add of { name: string; predicates: predicate list; value: string }
(** [name] [(...) as predicates] = [value], like
[archive(native) += "lib.cmxa"]*)
and predicate = Include of string | Exclude of string
let pp_predicate ppf = function
| Include p -> Fmt.string ppf p
| Exclude p -> Fmt.pf ppf "-%s" p
let rec pp ppf = function
| Node { name; value; contents } ->
Fmt.pf ppf "%s %S (@\n@[<2>%a@]@\n)" name value
Fmt.(list ~sep:(any "@\n") pp)
contents
| Set { name; predicates= []; value } -> Fmt.pf ppf "%s = %S" name value
| Set { name; predicates; value } ->
Fmt.pf ppf "%s(%a) = %S" name
Fmt.(list ~sep:(any ",") pp_predicate)
predicates value
| Add { name; predicates= []; value } -> Fmt.pf ppf "%s += %S" name value
| Add { name; predicates; value } ->
Fmt.pf ppf "%s(%a) += %S" name
Fmt.(list ~sep:(any ",") pp_predicate)
predicates value
module Assoc = struct
type t = (string * string list) list
let add k v t =
match List.assoc_opt k t with
| Some vs ->
let vs = List.sort_uniq String.compare (v :: vs) in
(k, vs) :: List.remove_assoc k t
| None -> (k, [ v ]) :: t
let set k v t =
match List.assoc_opt k t with
| Some _ -> (k, [ v ]) :: List.remove_assoc k t
| None -> (k, [ v ]) :: t
end
module Path = struct
type t = string list
let of_string str =
let pkg = String.split_on_char '.' str in
let rec go = function
| [] -> Ok pkg
| "" :: _ -> error_msgf "Invalid package name: %S" str
| _ :: rest -> go rest
in
go pkg
let of_string_exn str =
match of_string str with
| Ok pkg -> pkg
| Error (`Msg msg) -> invalid_arg msg
let pp ppf pkg = Fmt.string ppf (String.concat "." pkg)
let equal a b = try List.for_all2 String.equal a b with _ -> false
let compare = List.compare String.compare
let parent = function
| [] -> None
| segs -> Some (List.rev (List.tl (List.rev segs)))
module Set = Set.Make (struct
type nonrec t = t
let compare = compare
end)
module Map = Map.Make (struct
type nonrec t = t
let compare = compare
end)
end
let incl ~predicates ps =
let one = function
| Include p -> List.exists (String.equal p) predicates
| Exclude p -> not (List.exists (String.equal p) predicates)
in
List.exists one ps
let find_directory ~predicates contents =
let rec go result = function
| [] -> result
| Add { name= "directory"; predicates= []; value } :: rest ->
if Stdlib.Option.is_none result then go (Some value) rest
else go result rest
| Add { name= "directory"; predicates= ps; value } :: rest ->
if incl ~predicates ps && Stdlib.Option.is_none result then
go (Some value) rest
else go result rest
| Set { name= "directory"; predicates= []; value } :: rest ->
go (Some value) rest
| Set { name= "directory"; predicates= ps; value } :: rest ->
if incl ~predicates ps then go (Some value) rest else go result rest
| _ :: rest -> go result rest
in
go None contents
let compile ~predicates t ks =
let rec go ~directory acc t = function
| [] ->
let rec go acc = function
| [] ->
let acc = List.remove_assoc "directory" acc in
("directory", [ directory ]) :: acc
| Node _ :: rest -> go acc rest
| Add { name; predicates= []; value } :: rest ->
go (Assoc.add name value acc) rest
| Set { name; predicates= []; value } :: rest ->
go (Assoc.set name value acc) rest
| Add { name; predicates= ps; value } :: rest ->
if incl ~predicates ps then go (Assoc.add name value acc) rest
else go acc rest
| Set { name; predicates= ps; value } :: rest ->
if incl ~predicates ps then go (Assoc.set name value acc) rest
else go acc rest
in
go acc t
| k :: ks -> (
match t with
| [] -> acc
| Node { name= "package"; value; contents } :: rest ->
let directory' =
match find_directory ~predicates contents with
| Some v -> Filename.concat directory v
| None -> directory
in
if k = value then go ~directory:directory' acc contents ks
else go ~directory acc rest (k :: ks)
| _ :: rest -> go ~directory acc rest (k :: ks))
in
go ~directory:"" [] t ks
exception Parser_error of string
let raise_parser_error lexbuf fmt =
let p = Lexing.lexeme_start_p lexbuf in
let c = p.Lexing.pos_cnum - p.Lexing.pos_bol + 1 in
Fmt.kstr
(fun msg -> raise (Parser_error msg))
("%s (l.%d c.%d): " ^^ fmt)
p.Lexing.pos_fname p.Lexing.pos_lnum c
let pp_token ppf = function
| Uniq_meta_lexer.Name name -> Fmt.string ppf name
| String str -> Fmt.pf ppf "%S" str
| Minus -> Fmt.string ppf "-"
| Lparen -> Fmt.string ppf "("
| Rparen -> Fmt.string ppf ")"
| Comma -> Fmt.string ppf ","
| Equal -> Fmt.string ppf "="
| Plus_equal -> Fmt.string ppf "+="
| Eof -> Fmt.string ppf "#eof"
let invalid_token lexbuf token =
raise_parser_error lexbuf "Invalid token %a" pp_token token
let lparen lexbuf =
match Uniq_meta_lexer.token lexbuf with
| Lparen -> ()
| token -> invalid_token lexbuf token
let name lexbuf =
match Uniq_meta_lexer.token lexbuf with
| Name name -> name
| token -> invalid_token lexbuf token
let string lexbuf =
match Uniq_meta_lexer.token lexbuf with
| String str -> str
| token -> invalid_token lexbuf token
let rec predicates lexbuf acc =
match Uniq_meta_lexer.token lexbuf with
| Rparen -> List.rev acc
| Name predicate ->
begin match Uniq_meta_lexer.token lexbuf with
| Comma -> predicates lexbuf (Include predicate :: acc)
| Rparen -> List.rev (Include predicate :: acc)
| token -> invalid_token lexbuf token
end
| Minus ->
let predicate = name lexbuf in
begin match Uniq_meta_lexer.token lexbuf with
| Comma -> predicates lexbuf (Exclude predicate :: acc)
| Rparen -> List.rev (Exclude predicate :: acc)
| token -> invalid_token lexbuf token
end
| token -> invalid_token lexbuf token
let rec parser lexbuf depth acc =
match Uniq_meta_lexer.token lexbuf with
| Rparen when depth > 0 -> List.rev acc
| Rparen ->
raise_parser_error lexbuf
"Closing parenthesis without matching opening one"
| Eof when depth = 0 -> List.rev acc
| Eof -> raise_parser_error lexbuf "%d closing parenthesis missing" depth
| Name name ->
begin match Uniq_meta_lexer.token lexbuf with
| String value ->
lparen lexbuf;
let contents = parser lexbuf (succ depth) [] in
parser lexbuf depth (Node { name; value; contents } :: acc)
| Equal ->
let value = string lexbuf in
parser lexbuf depth (Set { name; predicates= []; value } :: acc)
| Plus_equal ->
let value = string lexbuf in
parser lexbuf depth (Add { name; predicates= []; value } :: acc)
| Lparen ->
let predicates = predicates lexbuf [] in
begin match Uniq_meta_lexer.token lexbuf with
| Equal ->
let value = string lexbuf in
parser lexbuf depth (Set { name; predicates; value } :: acc)
| Plus_equal ->
let value = string lexbuf in
parser lexbuf depth (Add { name; predicates; value } :: acc)
| token -> invalid_token lexbuf token
end
| token -> invalid_token lexbuf token
end
| token -> invalid_token lexbuf token
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
let parser lexbuf =
try Ok (parser lexbuf 0 []) with
| Parser_error err -> Error (`Msg err)
| Uniq_meta_lexer.Lexical_error (msg, f, l, c) ->
error_msgf "%s at l.%d, c.%d: %s" f l c msg
let parser path =
Log.debug (fun m -> m "parse %a" Fpath.pp path);
let ( let@ ) finally fn = Fun.protect ~finally fn in
let ic = open_in (Fpath.to_string path) in
let@ _ = fun () -> close_in ic in
let lexbuf = Lexing.from_channel ic in
Lexing.set_filename lexbuf (Fpath.to_string path);
parser lexbuf
let rec incl us vs =
match (us, vs) with
| u :: us, v :: vs -> if u = v then incl us vs else false
| [], _ | _, [] -> true
let rec diff us vs =
match (us, vs) with
| u :: us, v :: vs ->
if u = v then diff us vs else error_msgf "Different paths (%S <> %S)" u v
| [], x | x, [] -> Ok x
let relativize ~roots path =
let rec go = function
| [] -> assert false
| root :: roots ->
if Fpath.is_prefix root path then
match Fpath.relativize ~root path with
| Some rel -> (root, rel)
| None -> go roots
else go roots
in
go roots
let search ~roots ?(predicates = [ "native"; "byte" ]) meta_path =
let ( >>= ) = Result.bind in
let ( >>| ) x fn = Result.map fn x in
let elements path =
if Sys.is_directory (Fpath.to_string path) then Ok false
else if Fpath.basename path = "META" then Ok true
else Ok false
in
let traverse path =
if List.exists (Fpath.equal path) roots then Ok true
else begin
let _, rel = relativize ~roots path in
let meta_path' = List.filter (fun s -> s <> "") (Fpath.segs rel) in
Ok (incl meta_path meta_path')
end
in
let fold path acc =
let root, rel = relativize ~roots path in
let package = Fpath.(rem_empty_seg (parent rel)) in
let meta_path' = Fpath.(segs package) in
match
diff meta_path meta_path' >>= fun ks ->
parser path >>| fun meta -> compile ~predicates meta ks
with
| Ok descr -> Fpath.Map.add Fpath.(root // parent rel) descr acc
| Error (`Msg msg) ->
Log.warn (fun m ->
m "impossible to extract the META file of %a: %s" Fpath.pp path msg);
acc
in
let err _path _ = Ok () in
Bos.OS.Path.fold ~err ~dotfiles:false ~elements:(`Sat elements)
~traverse:(`Sat traverse) fold Fpath.Map.empty roots
>>| Fpath.Map.bindings
let requires descr =
Stdlib.Option.value ~default:[] (List.assoc_opt "requires" descr)
|> List.concat_map
(Astring.String.fields ~empty:false ~is_sep:Astring.Char.Ascii.is_white)
|> List.map Path.of_string_exn
let dependencies_of (_path, descr) = requires descr
exception Cycle
let get_dependencies (_, path, descr) graph =
let deps = dependencies_of (path, descr) in
let fn name =
match List.find_opt (fun (name', _, _) -> Path.equal name name') graph with
| Some node -> [ node ]
| None -> []
in
List.concat_map fn deps
type graph = (Path.t * Fpath.t * Assoc.t) list
let dfs (graph : graph) visited start =
let rec explore path visited node =
if List.mem node path then raise Cycle
else if List.mem node visited then visited
else
let new_path = node :: path in
let edges = get_dependencies node graph in
let visited = List.fold_left (explore new_path) visited edges in
node :: visited
in
explore [] visited start
let sort graph =
let fn visited node = dfs graph visited node in
List.fold_left fn [] graph
let ancestors ~roots ?(predicates = [ "native"; "byte" ]) mpath =
let rec go acc visited = function
| [] -> Ok acc
| mpath :: todo when List.mem mpath visited -> go acc visited todo
| mpath :: todo ->
begin match search ~roots ~predicates mpath with
| Ok pkgs ->
let requires = List.concat (List.map dependencies_of pkgs) in
let fn (path, descr) = (mpath, path, descr) in
let pkgs = List.map fn pkgs in
go (List.rev_append pkgs acc) (mpath :: visited)
(List.rev_append requires todo)
| Error _ as err -> err
end
in
let* lst = go [] [] [ mpath ] in
Ok (sort lst |> List.rev)
let to_artifacts pkgs =
let ( let* ) = Result.bind in
let fn acc (path, pkg) =
match acc with
| Error _ as err -> err
| Ok acc ->
let directory = List.assoc_opt "directory" pkg in
let* directory =
match directory with
| Some [ dir ] -> (
match Fpath.of_string dir with
| Ok rel -> Ok Fpath.(path // rel)
| Error _ -> Ok Fpath.(path / dir))
| Some _ ->
error_msgf "Multiple directories referenced by %a" Fpath.pp
Fpath.(path / "META")
| None -> Ok path
in
let directory = Fpath.to_dir_path directory in
let archive = List.assoc_opt "archive" pkg in
let archive = Stdlib.Option.value ~default:[] archive in
let keep a =
match Filename.extension a with
| ".cma" | ".cmxa" | ".cmo" | ".cmx" -> true
| _ -> false
in
let archive = List.filter keep archive in
let archive = List.map (Fpath.add_seg directory) archive in
let archive =
List.filter (fun p -> Sys.file_exists (Fpath.to_string p)) archive
in
Ok (List.rev_append archive acc)
in
let* paths = List.fold_left fn (Ok []) pkgs in
Uniq_info.vs paths
let subpaths (meta : t list) : string list list =
let rec go prefix acc = function
| [] -> acc
| Node { name= "package"; value; contents; _ } :: rest ->
let path = prefix @ [ value ] in
let sub = go path [] contents in
go prefix ((path :: sub) @ acc) rest
| _ :: rest -> go prefix acc rest
in
[] :: go [] [] meta
module MSet = Set.Make (Modname)
let submodules path =
let cmi = Cmi_format.read_cmi path in
let fn = function
| Types.Sig_module (name, _, _, _, _) -> Some (Modname.v (Ident.name name))
| _ -> None
in
match List.filter_map fn cmi.cmi_sign with
| value -> value
| exception _ -> []
let package_directory dname descr =
match List.assoc_opt "directory" descr with
| Some [ d ] when d <> "" ->
begin match Fpath.of_string d with
| Ok rel -> Fpath.(dname // rel |> to_dir_path)
| Error _ -> Fpath.to_dir_path dname
end
| _ -> Fpath.to_dir_path dname
let has_archive descr =
match List.assoc_opt "archive" descr with
| Some archives -> List.exists (fun a -> a <> "") archives
| None -> false
let register_if_target targets full modname acc =
if MSet.mem modname targets then
let fn = function
| None -> Some [ full ]
| Some pkgs -> Some (full :: pkgs)
in
Modname.Map.update modname fn acc
else acc
let scan_cmis ~and_submodules ~targets ~full ~dname acc =
if Sys.file_exists dname && Sys.is_directory dname then
let files = Sys.readdir dname in
let fn acc fname =
if Filename.check_suffix fname ".cmi" then
let base = Filename.chop_suffix fname ".cmi" in
let modname = Modname.v (String.capitalize_ascii base) in
let acc = register_if_target targets full modname acc in
if and_submodules then
let subs = submodules (Filename.concat dname fname) in
let fn acc modname = register_if_target targets full modname acc in
List.fold_left fn acc subs
else acc
else acc
in
Array.fold_left fn acc files
else acc
let with_a_cmi filepath =
let filepath = Fpath.set_ext "cmi" filepath in
let filepath = Fpath.to_string filepath in
Sys.file_exists filepath && Sys.is_regular_file filepath
let scan_mlis ~targets ~full ~dname acc =
if Sys.file_exists dname && Sys.is_directory dname then
let files = Sys.readdir dname in
let fn acc fname =
let filepath = Fpath.(v dname / fname) in
if Filename.check_suffix fname ".mli" && with_a_cmi filepath then
try
let filepath = Fpath.(v dname / fname) in
let kind = { Read.format= Read.Src; kind= M2l.Signature } in
let namespace = Namespaced.make (Filename.chop_suffix fname ".mli") in
let v =
Comp_unit.read_file Uniq_ml.Param.fault_handler kind
(Fpath.to_string filepath) namespace
in
let modname = Namespaced.module_name namespace in
let modules =
Uniq_info.collect_modules_on_mli ~modname v.Comp_unit.code
in
let fn path acc =
match Uniq_info.Path.to_list path with
| _ :: _ :: rem ->
let fn acc m = register_if_target targets full m acc in
List.fold_left fn acc rem
| _ -> acc
in
Uniq_info.Path.Set.fold fn modules acc
with _exn -> acc
else acc
in
Array.fold_left fn acc files
else acc
let walk_meta_files ~roots ~predicates ~and_submodules ?(intf = `Cmi) ~targets
acc =
let elements path =
let str = Fpath.to_string path in
if not (Sys.file_exists str) then Ok false
else if Sys.is_directory str then Ok false
else Ok (Fpath.basename path = "META")
in
let fn meta acc =
let _, rel = relativize ~roots meta in
let segs = Fpath.(segs (rem_empty_seg (parent rel))) in
let base = List.filter (fun s -> s <> "") segs in
match parser meta with
| Error _ -> acc
| Ok m ->
let metad =
let open Fpath in
parent meta |> rem_empty_seg |> to_dir_path |> to_string
in
let fn acc local =
let full = base @ local in
let descr = compile ~predicates m local in
let dname =
Fpath.to_string
(package_directory Fpath.(parent meta |> rem_empty_seg) descr)
in
let owns_dir = local = [] || dname <> metad in
if has_archive descr && owns_dir then
match intf with
| `Cmi -> scan_cmis ~and_submodules ~targets ~full ~dname acc
| `Mli -> scan_mlis ~targets ~full ~dname acc
else acc
in
List.fold_left fn acc (subpaths m)
in
let err _path _ = Ok () in
Bos.OS.Path.fold ~err ~dotfiles:false ~elements:(`Sat elements) ~traverse:`Any
fn acc roots
|> Result.value ~default:acc
let dedup result =
let seen = Hashtbl.create 16 in
let fn modname pkgs acc =
let fn pkg =
let key = String.concat "." pkg in
match Hashtbl.find seen key with
| _ -> false
| exception Not_found -> Hashtbl.add seen key (); true
in
let uniques = List.filter fn (List.rev pkgs) in
Hashtbl.reset seen; (modname, uniques) :: acc
in
Modname.Map.fold fn result [] |> List.rev
let find_providers ~roots ?(predicates = [ "native"; "byte" ]) modules =
let targets = List.fold_left (fun s m -> MSet.add m s) MSet.empty modules in
let result =
walk_meta_files ~roots ~predicates ~and_submodules:false ~targets
Modname.Map.empty
in
let resolved =
Modname.Map.fold (fun m _ s -> MSet.add m s) result MSet.empty
in
let remaining = MSet.diff targets resolved in
let result =
if MSet.is_empty remaining then result
else
walk_meta_files ~roots ~predicates ~and_submodules:true ~targets:remaining
result
in
let resolved =
Modname.Map.fold (fun m _ s -> MSet.add m s) result MSet.empty
in
let remaining = MSet.diff remaining resolved in
let result =
if MSet.is_empty remaining then result
else
walk_meta_files ~roots ~predicates ~and_submodules:true ~intf:`Mli
~targets:remaining result
in
dedup result
type archive = Stdlib of Fpath.t | Library of Path.t * Fpath.t * Assoc.t
type package = {
pkg: Path.t
; meta_dirpath: Fpath.t
; dirpath: Fpath.t
; descr: Assoc.t
}
let packages_with_archive ?(predicates = [ "native"; "byte" ]) roots =
let elements path =
let str = Fpath.to_string path in
if not (Sys.file_exists str) then Ok false
else if Sys.is_directory str then Ok false
else Ok (Fpath.basename path = "META")
in
let fn meta acc =
let _, rel = relativize ~roots meta in
let segs = Fpath.(segs (rem_empty_seg (parent rel))) in
let base = List.filter (fun s -> s <> "") segs in
match parser meta with
| Error _ -> acc
| Ok m ->
let metad = Fpath.(parent meta |> rem_empty_seg) in
let fn acc local =
let full = base @ local in
let descr = compile ~predicates m local in
if has_archive descr then
let dname = package_directory metad descr in
{ pkg= full; meta_dirpath= metad; dirpath= dname; descr } :: acc
else acc
in
List.fold_left fn acc (subpaths m)
in
let err _path _ = Ok () in
Bos.OS.Path.fold ~err ~dotfiles:false ~elements:(`Sat elements) ~traverse:`Any
fn [] roots
|> Result.value ~default:[]
let dir_owns_cmi ~dname ~modname ~crc =
match crc with
| None -> false
| Some crc ->
let dir = Fpath.to_string dname in
if Sys.file_exists dir && Sys.is_directory dir then
let same_crc filepath =
match Uniq_info.v filepath with
| Ok info ->
let crc' = Uniq_info.crc_of info modname in
Stdlib.Option.map (Uniq_digest.equal crc) crc'
|> Stdlib.Option.value ~default:false
| Error _ -> false
in
let check fname =
try
let base = Filename.chop_suffix fname ".cmi" in
Modname.compare Modname.(v (normalize base)) modname = 0
&& same_crc Fpath.(dname / fname)
with _exn -> false
in
Array.exists check (Sys.readdir dir)
else false
let stdlib_package dir =
let dirpath = absolute (Fpath.to_dir_path dir) in
Stdlib dirpath
let from_cmi_to_impl ~roots ~packages:candidates ?stdlib
?(disambiguate = fun _ paths -> List.hd paths) filepath =
if Fpath.mem_ext [ ".cmi" ] filepath = false then
invalid_arg "You must give a *.cmi file";
if List.exists (fun root -> Fpath.is_rooted ~root filepath) roots = false then
Fmt.invalid_arg "The given *.cmi (%a) is not a part of your roots" Fpath.pp
filepath;
let* info = Uniq_info.v filepath in
if Uniq_info.is_a_cmi info = false then
Fmt.invalid_arg "The given *.cmi (%a) is not a valid CMI file" Fpath.pp
filepath;
let modname = Uniq_info.modname info in
let crc = Uniq_info.crc_of info modname in
let cmi_dirpath = absolute Fpath.(parent filepath |> to_dir_path) in
let is_stdlib =
match stdlib with
| Some dirpath ->
Fpath.equal (absolute (Fpath.to_dir_path dirpath)) cmi_dirpath
| None -> false
in
if is_stdlib then Ok (Some (stdlib_package (Stdlib.Option.get stdlib)))
else
let owns { dirpath; _ } = dir_owns_cmi ~dname:dirpath ~modname ~crc in
let pick { pkg; meta_dirpath; descr; _ } =
Library (pkg, meta_dirpath, descr)
in
let same_dir { dirpath; _ } = Fpath.equal (absolute dirpath) cmi_dirpath in
let packs_module { meta_dirpath; descr; _ } =
match to_artifacts [ (meta_dirpath, descr) ] with
| Error _ -> false
| Ok archives ->
let provides info =
let fn (path, _) =
match List.rev (Uniq_info.Path.to_list path) with
| leaf :: _ -> Modname.compare leaf modname = 0
| [] -> false
in
List.exists fn (Uniq_info.exports info)
in
List.exists provides archives
in
let choose = function
| [] -> None
| [ pkg ] -> Some pkg
| pkgs -> (
match List.filter packs_module pkgs with
| [] -> Some (List.hd pkgs)
| [ pkg ] -> Some pkg
| _ :: _ as several -> (
let chosen =
disambiguate modname (List.map (fun p -> p.pkg) several)
in
match
List.find_opt (fun p -> Path.equal p.pkg chosen) several
with
| Some pkg -> Some pkg
| None -> Some (List.hd several)))
in
let siblings path =
match Path.parent path with
| Some (_ :: _ as parent) ->
let fn c =
(not (Path.equal c.pkg path))
&&
match Path.parent c.pkg with
| Some p -> Path.equal p parent
| None -> false
in
List.filter fn candidates
| _ -> []
in
let result =
match List.filter same_dir candidates with
| [ pkg ] -> (
match siblings pkg.pkg with
| [] -> Some pkg
| sibs -> choose (pkg :: sibs))
| _ :: _ :: _ as several -> choose several
| [] -> choose (List.filter owns candidates)
in
Ok (Stdlib.Option.map pick result)
let archives_of ~roots ?(predicates = [ "native"; "byte" ]) = function
| Stdlib dirpath ->
let dirpath = absolute (Fpath.to_dir_path dirpath) in
let name =
if List.mem "native" predicates then "stdlib.cmxa" else "stdlib.cma"
in
let path = Fpath.(dirpath / name) in
if Sys.file_exists (Fpath.to_string path) then Uniq_info.vs [ path ]
else Ok []
| Library (pkg, _, _) ->
let* descrs = search ~roots ~predicates pkg in
to_artifacts descrs