Source file opam_repomin.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
module Env = struct
type t = string -> OpamVariable.variable_contents option
let extend_env ~os base_env var =
match var with
| "sys-ocaml-libc" ->
if os = "linux" then Some (OpamVariable.S "glibc") else None
| _ -> base_env var
let create ~arch ~os ~os_distribution ~os_family ~os_version () =
let base_env =
Opam_0install.Dir_context.std_env ~arch ~os ~os_distribution ~os_family
~os_version ()
in
extend_env ~os base_env
let default () =
create ~arch:"x86_64" ~os:"linux" ~os_distribution:"debian"
~os_family:"debian" ~os_version:"" ()
end
module Repo = struct
type t = { packages_dir : Fpath.t }
let load path =
let packages_dir = Fpath.(path / "packages") in
match Bos.OS.Dir.exists packages_dir with
| Ok true -> Ok { packages_dir }
| Ok false ->
Error (`Msg (Fmt.str "Not a valid opam repository: %a" Fpath.pp path))
| Error e -> Error e
let packages_dir t = t.packages_dir
let list_dirs path ~f =
Bos.OS.Dir.contents path |> Result.value ~default:[] |> List.filter_map f
let list_packages t =
list_dirs t.packages_dir ~f:(fun d ->
if Fpath.is_dir_path d || Sys.is_directory (Fpath.to_string d) then
Some (OpamPackage.Name.of_string (Fpath.basename d))
else None)
let list_versions t name =
let pkg_dir = Fpath.(t.packages_dir / OpamPackage.Name.to_string name) in
list_dirs pkg_dir ~f:(fun d ->
let base = Fpath.basename d in
match OpamPackage.of_string_opt base with
| Some pkg when OpamPackage.name pkg = name ->
Some (OpamPackage.version pkg)
| _ -> None)
let has_package t pkg =
let pkg_dir =
Fpath.(
t.packages_dir
/ OpamPackage.name_to_string pkg
/ OpamPackage.to_string pkg)
in
Bos.OS.Dir.exists pkg_dir |> Result.value ~default:false
let opam_file t pkg =
let opam_path =
Fpath.(
t.packages_dir
/ OpamPackage.name_to_string pkg
/ OpamPackage.to_string pkg / "opam")
in
if Bos.OS.File.exists opam_path |> Result.value ~default:false then
let opam_file = OpamFilename.of_string (Fpath.to_string opam_path) in
Some (OpamFile.OPAM.read (OpamFile.make opam_file))
else None
end
module Solver = struct
type solution = { packages : OpamPackage.t list }
module Dir_context = Opam_0install.Dir_context
module Solver_impl = Opam_0install.Solver.Make (Dir_context)
let solve ~env ~overlay ~full ~compiler =
let overlay_packages = Repo.list_packages overlay in
Logs.info (fun m ->
m "Found %d packages in overlay" (List.length overlay_packages));
if overlay_packages = [] then
Error (`Msg "No packages found in overlay repository")
else
let compiler_name = OpamPackage.name compiler in
let compiler_version = OpamPackage.version compiler in
let constraints =
OpamPackage.Name.Map.singleton compiler_name (`Eq, compiler_version)
in
let pins =
overlay_packages
|> List.filter_map (fun name ->
match Repo.list_versions overlay name with
| [] -> None
| versions -> (
let sorted =
List.sort
(fun a b -> OpamPackage.Version.compare b a)
versions
in
let version = List.hd sorted in
let pkg = OpamPackage.create name version in
match Repo.opam_file overlay pkg with
| Some opam -> Some (name, (version, opam))
| None -> None))
|> OpamPackage.Name.Map.of_list
in
let full_packages_dir = Fpath.to_string (Repo.packages_dir full) in
let context =
Dir_context.create ~constraints ~env ~pins full_packages_dir
in
let to_solve = compiler_name :: overlay_packages in
Logs.info (fun m -> m "Solving for %d packages" (List.length to_solve));
match Solver_impl.solve context to_solve with
| Ok selections ->
let packages = Solver_impl.packages_of_result selections in
Logs.info (fun m ->
m "Solution found with %d packages" (List.length packages));
Ok { packages }
| Error problem ->
let msg = Solver_impl.diagnostics problem in
Error (`Msg (Fmt.str "Solver failed: %s" msg))
let packages t = t.packages
let packages_from_full t ~overlay =
List.filter (fun pkg -> not (Repo.has_package overlay pkg)) t.packages
let packages_from_overlay t ~overlay =
List.filter (fun pkg -> Repo.has_package overlay pkg) t.packages
end
module Output = struct
type copy_result = {
copied : OpamPackage.t list;
skipped : OpamPackage.t list;
}
let dry_run ~src ~packages =
List.filter (fun pkg -> Repo.has_package src pkg) packages
let ( let* ) = Result.bind
let copy ~src ~dst ~packages =
let dst_packages = Fpath.(dst / "packages") in
let* _ = Bos.OS.Dir.create ~path:true dst_packages in
let copied = ref [] in
let skipped = ref [] in
let copy_one pkg =
let name = OpamPackage.name_to_string pkg in
let full_name = OpamPackage.to_string pkg in
let src_dir = Fpath.(Repo.packages_dir src / name / full_name) in
let dst_name_dir = Fpath.(dst_packages / name) in
let dst_dir = Fpath.(dst_name_dir / full_name) in
if Bos.OS.Dir.exists dst_dir |> Result.value ~default:false then (
skipped := pkg :: !skipped;
Ok ())
else
let* _ = Bos.OS.Dir.create ~path:true dst_name_dir in
let* () =
Bos.(
OS.Cmd.run
Cmd.(
v "cp" % "-r" % Fpath.to_string src_dir
% Fpath.to_string dst_dir))
in
copied := pkg :: !copied;
Ok ()
in
let rec copy_all = function
| [] -> Ok { copied = List.rev !copied; skipped = List.rev !skipped }
| pkg :: rest ->
let* () = copy_one pkg in
copy_all rest
in
let to_copy = List.filter (fun pkg -> Repo.has_package src pkg) packages in
copy_all to_copy
end
let ( let* ) = Result.bind
let run ~env ~overlay ~full ~compiler ~output ~dry_run:is_dry_run =
Logs.info (fun m -> m "Loading overlay repository: %a" Fpath.pp overlay);
let* overlay_repo = Repo.load overlay in
Logs.info (fun m -> m "Loading full repository: %a" Fpath.pp full);
let* full_repo = Repo.load full in
Logs.info (fun m ->
m "Solving with compiler: %a"
Fmt.(using OpamPackage.to_string string)
compiler);
let* solution =
Solver.solve ~env ~overlay:overlay_repo ~full:full_repo ~compiler
in
let from_full = Solver.packages_from_full solution ~overlay:overlay_repo in
let from_overlay =
Solver.packages_from_overlay solution ~overlay:overlay_repo
in
Logs.info (fun m ->
m "Need to copy %d packages from full repository, %d from overlay"
(List.length from_full) (List.length from_overlay));
if is_dry_run then (
Fmt.pr "@[<v>Packages to copy from full repository:@,";
List.iter (fun pkg -> Fmt.pr " %s@," (OpamPackage.to_string pkg)) from_full;
Fmt.pr "@,Packages to copy from overlay:@,";
List.iter
(fun pkg -> Fmt.pr " %s@," (OpamPackage.to_string pkg))
from_overlay;
Fmt.pr "@]@.";
Ok ())
else
let* result1 = Output.copy ~src:full_repo ~dst:output ~packages:from_full in
let* result2 =
Output.copy ~src:overlay_repo ~dst:output ~packages:from_overlay
in
Logs.info (fun m ->
m "Copied %d packages from full, %d from overlay, skipped %d"
(List.length result1.copied)
(List.length result2.copied)
(List.length result1.skipped + List.length result2.skipped));
Ok ()