Source file opamRepositoryState.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
open OpamTypes
open OpamStd.Op
open OpamStateTypes
let log fmt = OpamConsole.log "RSTATE" fmt
let slog = OpamConsole.slog
module Cache = struct
type t = {
cached_repofiles: (repository_name * OpamFile.Repo.t) list;
cached_opams: (repository_name * OpamFile.OPAM.t OpamPackage.Map.t) list;
}
module C = OpamCached.Make (struct
type nonrec t = t
let name = "repository"
end)
let remove () =
let root = OpamStateConfig.(!r.root_dir) in
let cache_dir = OpamPath.state_cache_dir root in
let remove_cache_file file =
if OpamFilename.check_suffix file ".cache" then
OpamFilename.remove file
in
List.iter remove_cache_file (OpamFilename.files cache_dir)
let save rt =
let file = OpamPath.state_cache rt.repos_global.root in
let filter_out_nourl repos_map =
OpamRepositoryName.Map.filter
(fun name _ ->
try
(OpamRepositoryName.Map.find name rt.repositories).repo_url <>
OpamUrl.empty
with Not_found -> false)
repos_map
in
let t =
{ cached_repofiles =
OpamRepositoryName.Map.bindings
(filter_out_nourl rt.repos_definitions);
cached_opams =
OpamRepositoryName.Map.bindings
(filter_out_nourl rt.repo_opams);
}
in
remove ();
C.save file t
let load root =
let file = OpamPath.state_cache root in
match C.load file with
| Some cache ->
Some
(OpamRepositoryName.Map.of_list cache.cached_repofiles,
OpamRepositoryName.Map.of_list cache.cached_opams)
| None -> None
end
let load_opams_from_dir repo_name repo_root =
let rec aux r dir =
if OpamFilename.exists_dir dir then
let fnames = Sys.readdir (OpamFilename.Dir.to_string dir) in
if Array.fold_left (fun a f -> a || f = "opam") false fnames then
match OpamFileTools.read_repo_opam ~repo_name ~repo_root dir with
| Some opam ->
(try
let nv =
OpamPackage.of_string
OpamFilename.(Base.to_string (basename_dir dir))
in
OpamPackage.Map.add nv opam r
with Failure _ ->
log "ERR: directory name not a valid package: ignored %s"
OpamFilename.(to_string Op.(dir // "opam"));
r)
| None ->
log "ERR: Could not load %s, ignored"
OpamFilename.(to_string Op.(dir // "opam"));
r
else
Array.fold_left (fun r name -> aux r OpamFilename.Op.(dir / name))
r fnames
else r
in
aux OpamPackage.Map.empty (OpamRepositoryPath.packages_dir repo_root)
let load_repo repo repo_root =
let t = OpamConsole.timer () in
let repo_def =
OpamFile.Repo.safe_read (OpamRepositoryPath.repo repo_root)
|> OpamFile.Repo.with_root_url repo.repo_url
in
let opams = load_opams_from_dir repo.repo_name repo_root in
log "loaded opam files from repo %s in %.3fs"
(OpamRepositoryName.to_string repo.repo_name)
(t ());
repo_def, opams
let clean_repo_tmp tmp_dir =
if Lazy.is_val tmp_dir then
(let dir = Lazy.force tmp_dir in
OpamFilename.rmdir dir;
let parent = OpamFilename.dirname_dir dir in
if OpamFilename.dir_is_empty parent then
OpamFilename.rmdir parent)
let remove_from_repos_tmp rt name =
try
clean_repo_tmp (Hashtbl.find rt.repos_tmp name);
Hashtbl.remove rt.repos_tmp name
with Not_found -> ()
let cleanup rt =
Hashtbl.iter (fun _ tmp_dir -> clean_repo_tmp tmp_dir) rt.repos_tmp;
Hashtbl.clear rt.repos_tmp
let get_root_raw root repos_tmp name =
match Hashtbl.find repos_tmp name with
| lazy repo_root -> repo_root
| exception Not_found -> OpamRepositoryPath.root root name
let get_root rt name =
get_root_raw rt.repos_global.root rt.repos_tmp name
let get_repo_root rt repo =
get_root_raw rt.repos_global.root rt.repos_tmp repo.repo_name
let load lock_kind gt =
log "LOAD-REPOSITORY-STATE %@ %a" (slog OpamFilename.Dir.to_string) gt.root;
let lock = OpamFilename.flock lock_kind (OpamPath.repos_lock gt.root) in
let repos_map = OpamStateConfig.Repos.safe_read ~lock_kind gt in
if OpamStateConfig.is_newer_than_self gt then
log "root version (%s) is greater than running binary's (%s); \
load with best-effort (read-only)"
(OpamVersion.to_string (OpamFile.Config.opam_root_version gt.config))
(OpamVersion.to_string (OpamFile.Config.root_version));
let mk_repo name url_opt = {
repo_name = name;
repo_url = OpamStd.Option.Op.((url_opt >>| fst) +! OpamUrl.empty);
repo_trust = OpamStd.Option.Op.(url_opt >>= snd);
} in
let uncached =
OpamRepositoryName.Map.filter (fun _ url -> url = None) repos_map
in
let repositories = OpamRepositoryName.Map.mapi mk_repo repos_map in
let repos_tmp_root = lazy (OpamFilename.mk_tmp_dir ()) in
let repos_tmp = Hashtbl.create 23 in
OpamRepositoryName.Map.iter (fun name repo ->
let uncompressed_root = OpamRepositoryPath.root gt.root repo.repo_name in
let tar = OpamRepositoryPath.tar gt.root repo.repo_name in
if not (OpamFilename.exists_dir uncompressed_root) &&
OpamFilename.exists tar
then
let tmp = lazy (
let tmp_root = Lazy.force repos_tmp_root in
try
OpamFilename.extract_in tar tmp_root;
OpamFilename.Op.(tmp_root / OpamRepositoryName.to_string name)
with Failure s ->
OpamFilename.remove tar;
OpamConsole.error_and_exit `Aborted
"%s.\nRun `opam update --repositories %s` to fix the issue"
s (OpamRepositoryName.to_string name);
) in
Hashtbl.add repos_tmp name tmp
) repositories;
let make_rt repos_definitions opams =
let rt = {
repos_global = (gt :> unlocked global_state);
repos_lock = lock;
repos_tmp;
repositories;
repos_definitions;
repo_opams = opams;
} in
OpamStd.Sys.at_exit (fun () -> cleanup rt);
rt
in
match Cache.load gt.root with
| Some (repofiles, opams) when OpamRepositoryName.Map.is_empty uncached ->
log "Cache found";
make_rt repofiles opams
| Some (repofiles, opams) ->
log "Cache found, loading repositories without remote only";
OpamFilename.with_flock_upgrade `Lock_read lock @@ fun _ ->
let repofiles, opams =
OpamRepositoryName.Map.fold (fun name url (defs, opams) ->
let repo = mk_repo name url in
let repo_def, repo_opams =
load_repo repo (get_root_raw gt.root repos_tmp name)
in
OpamRepositoryName.Map.add name repo_def defs,
OpamRepositoryName.Map.add name repo_opams opams)
uncached (repofiles, opams)
in
make_rt repofiles opams
| None ->
log "No cache found";
OpamFilename.with_flock_upgrade `Lock_read lock @@ fun _ ->
let repofiles, opams =
OpamRepositoryName.Map.fold (fun name url (defs, opams) ->
let repo = mk_repo name url in
let repo_def, repo_opams =
load_repo repo (get_root_raw gt.root repos_tmp name)
in
OpamRepositoryName.Map.add name repo_def defs,
OpamRepositoryName.Map.add name repo_opams opams)
repos_map (OpamRepositoryName.Map.empty, OpamRepositoryName.Map.empty)
in
let rt = make_rt repofiles opams in
Cache.save rt;
rt
let find_package_opt rt repo_list nv =
List.fold_left (function
| None ->
fun repo_name ->
OpamStd.Option.Op.(
OpamRepositoryName.Map.find_opt repo_name rt.repo_opams >>=
OpamPackage.Map.find_opt nv >>| fun opam ->
repo_name, opam
)
| some -> fun _ -> some)
None repo_list
let build_index rt repo_list =
List.fold_left (fun acc repo_name ->
try
let repo_opams = OpamRepositoryName.Map.find repo_name rt.repo_opams in
OpamPackage.Map.union (fun a _ -> a) acc repo_opams
with Not_found ->
acc)
OpamPackage.Map.empty
repo_list
let get_repo rt name = OpamRepositoryName.Map.find name rt.repositories
let unlock ?cleanup:(cln=true) rt =
if cln then cleanup rt;
OpamSystem.funlock rt.repos_lock;
(rt :> unlocked repos_state)
let drop ?cleanup rt =
let _ = unlock ?cleanup rt in ()
let with_write_lock ?dontblock rt f =
if OpamStateConfig.is_newer_than_self rt.repos_global then
OpamConsole.error_and_exit `Locked
"The opam root has been upgraded by a newer version of opam-state \
and cannot be written to";
let ret, rt =
OpamFilename.with_flock_upgrade `Lock_write ?dontblock rt.repos_lock
@@ fun _ -> f ({ rt with repos_lock = rt.repos_lock } : rw repos_state)
in
ret, { rt with repos_lock = rt.repos_lock }
let with_ lock gt f =
let rt = load lock gt in
OpamStd.Exn.finally (fun () -> drop rt) (fun () -> f rt)
let write_config rt =
OpamFile.Repos_config.write (OpamPath.repos_config rt.repos_global.root)
(OpamRepositoryName.Map.map (fun r ->
if r.repo_url = OpamUrl.empty then None
else Some (r.repo_url, r.repo_trust))
rt.repositories)
let check_last_update () =
if OpamCoreConfig.(!r.debug_level) < 0 then () else
let last_update =
OpamFilename.written_since
(OpamPath.state_cache (OpamStateConfig.(!r.root_dir)))
in
if last_update > float_of_int (3600*24*21) then
OpamConsole.note "It seems you have not updated your repositories \
for a while. Consider updating them with:\n%s\n"
(OpamConsole.colorise `bold "opam update");