Source file uniq_solver.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
let src = Logs.Src.create "uniq.solver"
module Log = (val Logs.src_log src : Logs.LOG)
module MSet = Set.Make (Modname)
module Info = Uniq_info
module Digest = Uniq_digest
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
let somef fmt = Fmt.kstr Stdlib.Option.some fmt
type cfg = {
stdlib: bool
; recurse: bool
; exclude: Fpath.t list
; ignore: MSet.t
; forbid: MSet.t
}
type private_module = Modname.t * Uniq_digest.t option
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
let config ?(stdlib = true) ?(recurse = true) ?(exclude = []) ?(ignore = [])
?(forbid = []) () =
let ignore = MSet.of_list ignore in
let forbid = MSet.of_list forbid in
let exclude = List.map absolute exclude in
{ stdlib; recurse; exclude; ignore; forbid }
let to_ignore ~cfg modname = MSet.mem modname cfg.ignore
type providers = ?crc:Digest.t -> Modname.t -> Info.t option
type disambiguate = Modname.t -> Info.t list -> Info.t
exception Multiple_solutions of Modname.t * Digest.t option * Info.t list
let () =
let dummy = String.make (Digest.length * 2) '-' in
Printexc.register_printer @@ function
| Multiple_solutions (modname, crc, infos) ->
somef "Multiple solutions for %a (%a): @[<hov>%a@]" Modname.pp modname
Fmt.(option ~none:(const string dummy) Digest.pp)
crc
Fmt.(list ~sep:(any ";@ ") Info.pp)
infos
| _ -> None
let prune ?disambiguate infos modules =
let fn0 (m, crc) (p, crc') =
match (crc, crc') with
| Some crc, Some crc' when Digest.equal crc crc' ->
let part = Info.Path.singleton m in
Info.Path.is_a_part ~part p
| Some _, Some _ -> false
| Some _, None -> false
| None, Some _ | None, None ->
let part = Info.Path.singleton m in
Info.Path.is_a_part ~part p
in
let fn1 (m, crc) info =
let exports = Info.exports info in
let found = List.exists (fn0 (m, crc)) exports in
if found then Some info else None
in
let take (infos, _, rem) crc m solution =
Log.debug (fun mf -> mf "take %a for %a" Info.pp solution Modname.pp m);
let location = Info.location solution in
let fn info = Info.qualify info ~location ?crc `Intf m in
(List.map fn infos, true, rem)
in
let fn0 (infos, progress, rem) (m, crc) =
match List.filter_map (fn1 (m, crc)) infos with
| [ solution ] -> take (infos, progress, rem) crc m solution
| _ :: _ as solutions -> (
match disambiguate with
| Some choose -> take (infos, progress, rem) crc m (choose m solutions)
| None -> raise (Multiple_solutions (m, crc, solutions)))
| [] -> (infos, progress, (m, crc) :: rem)
in
let rec go infos modules =
let infos, progress, modules =
List.fold_left fn0 (infos, false, []) modules
in
if progress then go infos modules else (infos, modules)
in
go infos modules
let missing_intfs infos =
List.concat_map (fun info -> fst (Info.missing info)) infos
let not_in_forbidden_modules cfg modules =
match List.filter (fun (m, _) -> MSet.mem m cfg.forbid) modules with
| [] -> Ok ()
| forbidden ->
error_msgf "@[<hov>the project requires forbidden module(s): %a@]"
Fmt.(list ~sep:(any ",@ ") Modname.pp)
(List.map fst forbidden)
let sort = List.sort_uniq (fun (a, _) (b, _) -> Modname.compare a b)
let solve_intfs ?disambiguate ~cfg:({ recurse; exclude; stdlib; _ } as cfg)
~providers dirs =
let ( let* ) = Result.bind in
let fn = Uniq_resolve.Src.sources ~recurse ~exclude in
let dirs = List.map absolute dirs in
let dirs = List.map Fpath.to_dir_path dirs in
let srcs = List.map fn dirs in
let fn (infos, progress, rem) (m, crc) =
begin match providers ?crc m with
| None -> (infos, progress, (m, crc) :: rem)
| Some solution when List.exists (Info.equal solution) infos = false ->
let location = Info.location solution in
let fn info = Info.qualify info ~location ?crc `Intf m in
let infos = List.map fn infos in
Log.debug (fun m -> m "add %a" Info.pp solution);
(solution :: infos, true, rem)
| Some _solution ->
(infos, progress, (m, crc) :: rem)
end
in
let rec go infos =
match missing_intfs infos |> sort with
| [] -> Ok (infos, [])
| modules ->
let* () = not_in_forbidden_modules cfg modules in
let infos, modules = prune ?disambiguate infos modules in
let modules = sort modules in
let infos, progress, modules =
match modules with
| [] -> (infos, true, [])
| modules -> List.fold_left fn (infos, false, []) modules
in
if progress then go infos else Ok (infos, modules)
in
let* infos = Uniq_resolve.qualify ~stdlib srcs in
let* infos, modules =
try go infos
with Multiple_solutions (m, _crc, solutions) ->
error_msgf "@[<v>%a is provided by several incompatible interfaces:@,%a@]"
Modname.pp m
Fmt.(list ~sep:cut (any " " ++ Info.pp))
solutions
in
let fn (m, _) = not (MSet.mem m cfg.ignore) in
let modules = List.filter fn modules in
Ok (infos, modules)