Source file commandPostInstall.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
open EzCompat
open Ezcmd.TYPES
open EzFile.OP
open EzConfig.OP
open OpamParserTypes
let cmd_name = "post-install"
let parse_opam_file file_name =
if Sys.file_exists file_name then begin
let opam = OpamParser.file file_name in
Misc.global_log "%s read" opam.file_name;
opam.file_contents
end else begin
Misc.global_log "%s does not exist" file_name;
[]
end
let add_conflict depends conflicts_ref name _option =
if EzCompat.StringSet.mem name depends then
()
else
conflicts_ref := name :: !conflicts_ref
let rec is_post_option = function
| Ident (_, "post" ) -> true
| Logop (_, `And, v1, v2 ) -> is_post_option v1 || is_post_option v2
| _ -> false
let rec is_build_option = function
| Ident (_, "build" ) -> true
| Logop (_, `And, v1, v2 ) -> is_build_option v1 || is_build_option v2
| _ -> false
let add_post_depend ~dependset ~buildset post_depends name option =
if not ( EzCompat.StringSet.mem name dependset ) &&
List.exists is_post_option option then
post_depends := name :: !post_depends
else
if List.exists is_build_option option then
buildset := StringSet.add name !buildset
let iter_value_list v f =
let iter_value v =
match v with
| String (_, name) -> f name [ String ( ("",0,0), "") ]
| Option (_, String (_, name), option) -> f name option
| _
->
Misc.global_log "warning: unexpected depend value %s"
( OpamPrinter.value v)
in
match v with
| List (_, values) ->
List.iter iter_value values
| v -> iter_value v
let digest s = Digest.to_hex ( Digest.string s)
let short s = String.sub s 0 8
let digest_sources () =
let b = Buffer.create 10_000 in
let rec iter dir =
let files = try Sys.readdir dir with exn ->
Printf.kprintf failwith "digest_sources: exception %s with Sys.readdir(%S)"
( Printexc.to_string exn ) dir
in
Array.sort compare files ;
Array.iter (fun file ->
let file = dir // file in
Buffer.add_string b file; Buffer.add_char b '\n';
match Unix.lstat file with
| exception exn ->
Printf.kprintf
failwith "digest_sources: exception %s with Unix.lstat(%S)"
( Printexc.to_string exn ) dir
| st ->
let s =
match st.Unix.st_kind with
| S_REG -> "S_REG" ^ Digest.to_hex ( Digest.file file )
| S_LNK -> "S_LNK" ^ Unix.readlink file
| S_DIR -> iter file ; "S_DIR"
| S_CHR -> "S_CHR"
| S_BLK -> "S_BLK"
| S_FIFO -> "S_FIFO"
| S_SOCK -> "S_SOCK"
in
Buffer.add_string b s; Buffer.add_char b '\n';
) files
in
iter ".";
let s = Buffer.contents b in
EzFile.write_file
( Printf.sprintf "/tmp/buffer.%d" (Unix.getpid ()) ) s;
digest s
let compute_hash ?source_md5 ~name ~version ~depends () =
let missing_versions = ref [] in
let packages_dir =
Globals.opambin_switch_packages_dir () in
Misc.global_log "depends: %S" depends;
let depends = EzString.split depends ' ' in
let dependset = ref EzCompat.StringSet.empty in
let depends = List.map (fun nv ->
let name, _ = EzString.cut_at nv '.' in
let file_name = packages_dir // name in
let version = match
open_in file_name
with
| exception _ ->
missing_versions := file_name :: !missing_versions;
"UNKNOWN"
| ic ->
let version = input_line ic in
close_in ic;
version
in
dependset := EzCompat.StringSet.add name !dependset;
( name, version )
) depends in
let depends_nv = List.map (fun ( name, version ) ->
Printf.sprintf "%s.%s" name version
) depends in
let switch = Misc.current_switch () in
let temp_dir = Globals.opambin_switch_temp_dir () in
EzFile.make_dir ~p:true temp_dir ;
let ( source_md5, opam_file ) = match source_md5 with
| Some source_md5 ->
source_md5, Globals.backup_opam ~name
| None ->
let digest_sources = digest_sources () in
let opam_file = Globals.marker_opam in
let oc = Unix.openfile opam_file
[ Unix.O_CREAT; Unix.O_WRONLY ; Unix.O_TRUNC ] 0o644 in
let nv = Printf.sprintf "%s.%s" name version in
Misc.call ~stdout:oc
[| "opam" ; "show" ; nv ; "--raw" ; "--safe" ; "--switch" ; switch |];
Unix.close oc;
let opam_content = EzFile.read_file opam_file in
Misc.global_log "File (%s):\n%s" name opam_content;
let digest_opam = digest opam_content in
Misc.global_log "Opam (%s): %s" name digest_opam;
let package_uid = digest ( digest_opam ^ digest_sources ) in
Misc.global_log "package_uid(%s): %s" name package_uid;
let s = Printf.sprintf "%s.%s|%s|%s"
name version package_uid (String.concat "," depends_nv) in
Misc.global_log "source(%s) : %s" name s ;
short ( digest s ), opam_file
in
Misc.global_log "source_md5 (%s): %s" name source_md5;
( source_md5, depends, !dependset, !missing_versions, opam_file )
let error_on_missing =
match Sys.getenv "OPAM_BIN_FORCE" with
| exception _ -> false
| _ -> true
let write_bin_stub ~name ~version ~new_version ~repo_dir =
let bin_name = name ^ "+bin" in
let nv = Printf.sprintf "%s.%s" bin_name version in
let package_dir = repo_dir // "packages" // bin_name // nv in
EzFile.make_dir ~p:true package_dir;
let s = Printf.sprintf {|
opam-version: "2.0"
name: %S
maintainer: "%s"
description: "This package is an alias for %s binary package"
depends: [
%S {= %S }
]
%s
|}
bin_name
Globals.command
name
name new_version
(if bin_name = "ocaml+bin" then
"flags: compiler"
else
"")
in
EzFile.write_file ( package_dir // "opam" ) s;
let package_files_dir = package_dir // "files" in
EzFile.make_dir ~p:true package_files_dir ;
EzFile.write_file ( package_files_dir //
Globals.package_info )
( Printf.sprintf "depend:%s:%s\n" name new_version )
let commit ~name ~version ~depends files =
if not !!Config.create_enabled then
Misc.info ~name ~version "create-package disabled"
else
let backup_skip = Globals.backup_skip ~name in
if Sys.file_exists backup_skip then
Misc.info ~name ~version "%s" (EzFile.read_file backup_skip)
else
if Sys.file_exists Globals.marker_cached then
Misc.info ~name ~version "binary package installed from cache"
else
let opam_switch_prefix = Globals.opam_switch_prefix () in
let packages_dir =
Globals.opambin_switch_packages_dir () in
if Sys.file_exists ( packages_dir // name ) then
Misc.info ~name ~version "already a binary package"
else
let temp_dir = Globals.opambin_switch_temp_dir () in
Misc.global_log "package %s is not a binary archive" name ;
Misc.global_log "creating binary archive...";
EzFile.make_dir ~p:true temp_dir ;
let source_md5 =
EzFile.read_file ( Globals.backup_source ~name ) in
let ( source_md5, depends, dependset, missing_versions, opam_file ) =
compute_hash ~source_md5 ~name ~version ~depends () in
if missing_versions <> [] then begin
Misc.global_log
"Error in %s: cannot load binary versions from %s\n%!"
Globals.command
(String.concat " " missing_versions);
Misc.global_log
" => binary archive disabled for %s.%s" name version;
Misc.info ~name ~version "missing binary deps";
if error_on_missing then begin
Printf.eprintf "Error: OPAM_BIN_FORCE set, but missing binary dep.\n%!";
exit 2
end
end
else
let binary_archive = temp_dir // name ^ "-bin.tar.gz" in
Unix.chdir opam_switch_prefix;
Misc.tar_zcf ~prefix:"prefix" binary_archive files;
Unix.chdir Globals.curdir;
Misc.global_log "create binary archive DONE";
let bin_md5 =
digest ( EzFile.read_file binary_archive )
in
Misc.global_log "bin md5 = %s" bin_md5;
let final_md5 = Printf.sprintf "%s+%s"
source_md5 ( short bin_md5 ) in
let new_version = Printf.sprintf "%s+bin+%s" version final_md5 in
EzFile.make_dir ~p:true packages_dir ;
let oc = open_out ( packages_dir // name ) in
output_string oc new_version ;
close_out oc;
let file_contents = parse_opam_file opam_file in
EzFile.make_dir ~p:true Globals.opambin_store_archives_dir;
let final_binary_archive_basename =
Printf.sprintf "%s.%s-bin.tar.gz" name new_version
in
let final_binary_archive =
Globals.opambin_store_archives_dir // final_binary_archive_basename
in
Sys.rename binary_archive final_binary_archive;
let cache_dir =
Globals.opambin_cache_dir //
"md5" // String.sub bin_md5 0 2 in
let cached_archive = cache_dir // bin_md5 in
if not ( Sys.file_exists cached_archive ) then begin
EzFile.make_dir ~p:true cache_dir;
Misc.call [| "cp"; final_binary_archive ; cached_archive |];
end;
let nv = Printf.sprintf "%s.%s" name new_version in
let package_dir =
Globals.opambin_store_repo_packages_dir // name // nv in
EzFile.make_dir ~p:true package_dir;
let package_files_dir = package_dir // "files" in
EzFile.make_dir ~p:true package_files_dir;
let oc = open_out ( package_files_dir //
Globals.package_version ) in
output_string oc new_version;
close_out oc;
let config_file =
Globals.opam_switch_internal_config_dir ()
// ( name ^ ".config" )
in
let has_config_file =
if Sys.file_exists config_file then begin
let s = EzFile.read_file config_file in
EzFile.write_file ( package_files_dir //
Globals.package_config ) s;
true
end
else
false
in
let opam =
let post_depends = ref [] in
let conflicts = ref [] in
let opam_depends = ref None in
let opam_depopts = ref None in
let file_contents =
List.fold_left (fun acc v ->
match v with
| Variable (_, name, value) -> begin
match name with
| "name"
| "maintainer"
| "authors"
| "opam-version"
| "synopsis"
| "description"
| "homepage"
| "bug-reports"
| "license"
| "tags"
| "dev-repo"
| "post-messages"
| "doc"
| "setenv"
| "conflict-class"
| "flags"
| "depexts"
-> v :: acc
| "version"
| "build"
| "install"
| "remove"
| "extra-files"
->
acc
| "depends" ->
opam_depends := Some value ;
acc
| "depopts" ->
opam_depopts := Some value ;
acc
| _ ->
Misc.global_log
"discarding unknown field %S" name;
acc
end
| _ -> acc
) [] file_contents in
let build_depends =
match !opam_depends with
| None -> StringSet.empty
| Some value ->
let buildset = ref StringSet.empty in
iter_value_list value
( add_post_depend ~dependset ~buildset post_depends);
!buildset
in
let actual_depends =
match !opam_depopts with
| None ->
let actual_depends = ref [] in
List.iter (fun (name, version) ->
if not ( StringSet.mem name build_depends ) then
actual_depends := ( name, version ) :: !actual_depends
) depends;
!actual_depends
| Some value ->
iter_value_list value
( add_conflict dependset conflicts);
depends
in
let file_contents =
Misc.opam_variable "install"
{|
[
[ "mkdir" "-p" "%%{prefix}%%/etc/%s/packages" ]
[ "rm" "-f" "%s" ]
[ "cp" "-aT" "." "%%{prefix}%%" ]%s
[ "mv" "%%{prefix}%%/%s" "%%{prefix}%%/etc/%s/packages/%s" ]
]
|}
Globals.command
Globals.package_info
(if has_config_file then
Printf.sprintf {|
[ "mv" "%%{prefix}%%/%s" "%s.config" ]
|}
Globals.package_config name
else "")
Globals.package_version Globals.command name
:: file_contents
in
let file_contents =
Misc.opam_variable "depends"
"[ %s %s ]"
(String.concat " "
(List.map (fun (name, version) ->
Printf.sprintf "%S {= %S }" name version
) actual_depends))
(String.concat " "
(List.map (fun name ->
Printf.sprintf "%S { post }" name
) !post_depends))
:: file_contents
in
let file_contents =
if files = [] then
file_contents
else
Misc.opam_section "url" [
Misc.opam_variable
"src"
"%S"
(!!Config.base_url
// "archives" // final_binary_archive_basename);
Misc.opam_variable
"checksum"
{| [ "md5=%s" ] |} bin_md5
] :: file_contents
in
let file_contents =
if !conflicts = [] then
file_contents
else
Misc.opam_variable "conflicts"
"[ %s ]"
( String.concat " "
( List.map (fun s ->
Printf.sprintf "%S" s) !conflicts ))
:: file_contents
in
let file_contents = List.rev file_contents in
{ file_contents ; file_name = "" }
in
let s = OpamPrinter.opamfile opam in
EzFile.write_file ( package_dir // "opam" ) s;
write_bin_stub ~name ~version ~new_version
~repo_dir:Globals.opambin_store_repo_dir;
let oc = open_out ( package_files_dir //
Globals.package_info ) in
List.iter (fun (name, version) ->
Printf.fprintf oc "depend:%s:%s\n" name version
) depends ;
Unix.chdir opam_switch_prefix;
let total_nbytes = ref 0 in
List.iter (fun file ->
match Unix.lstat file with
| exception _ -> ()
| st ->
Printf.fprintf oc "file:%09d:%s:%s\n"
(let size = st.Unix.st_size in
total_nbytes := !total_nbytes + size ;
size
)
(match st.Unix.st_kind with
| S_REG -> "reg"
| S_DIR -> "dir"
| S_LNK -> "lnk"
| _ -> "oth"
)
file
) files ;
Unix.chdir Globals.curdir;
Printf.fprintf oc "total:%05d:nfiles\n" (List.length files) ;
Printf.fprintf oc "total:%09d:nbytes\n" !total_nbytes ;
close_out oc;
Misc.info ~name ~version:new_version "binary package created";
Misc.global_log "Binary package for %s.%s created successfully"
name version
let action args =
Misc.log_cmd cmd_name args;
Misc.make_cache_dir ();
match args with
| name :: version :: depends :: files ->
if !!Config.enabled then begin
if !!Config.share_enabled then begin
let dir = Globals.opam_switch_prefix () in
let files = EzList.tail_map (fun file -> dir // file ) files in
Share.files files ;
end;
commit ~name ~version ~depends files
end
| _ ->
Printf.eprintf
"Unexpected args: usage is '%s %s name version depends files...'\n%!" Globals.command cmd_name;
exit 2
let cmd =
let args = ref [] in
Arg.{
cmd_name ;
cmd_action = (fun () -> action !args) ;
cmd_args = [
[], Anons (fun list -> args := list),
Ezcmd.info "args"
];
cmd_man = [];
cmd_doc = "(opam hook) Create binary archive after install";
}