Source file makeself_backend.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
open Stdlib
let (/) = Filename.concat
let (!$) v = "$" ^ v
let install_script_name = "install.sh"
let uninstall_script_name = "uninstall.sh"
let install_path_nv = "INSTALL_PATH"
let install_path_v = !$ install_path_nv
let is_user_install_nv = "IS_USER_INSTALL"
let is_user_install_v = !$ is_user_install_nv
let prefix_nv = "PREFIX"
let prefix_v = !$ prefix_nv
let mandir_nv = "MANDIR"
let mandir_v = !$ mandir_nv
let bindir_nv = "BINDIR"
let bindir_v = !$ bindir_nv
let opt = "/opt"
module Global = struct
let pre = "/usr/local"
let bin = pre / "bin"
let shareman = pre / "share/man"
let man = pre / "man"
end
module User = struct
let pre = "$HOME/.local"
let bin = pre / "bin"
let man = pre / "man"
end
let install_conf = "install.conf"
let load_conf = "load_conf"
let conf_version = "version"
let conf_plugins = "plugins"
let conf_lib = "lib"
let check_available = "check_available"
let check_lib = "check_lib"
let vars : Installer_config.vars = { install_path = install_path_v }
let def_load_conf =
let open Sh_script in
def_fun load_conf
[ assign ~var:"var_prefix" ~value:"$2"
; assign ~var:"conf" ~value:"$1"
; read_file ~line_var:"line" "$conf"
[ case "line"
[{pattern = {|""|\#*|}; commands = [continue]}]
; case "line"
[ {pattern = "*=*"; commands = []}
; { pattern = "*";
commands =
[ print_errf "Invalid line in $conf: $line"
; return 1
]
}
]
; assign ~var:"key" ~value:"${line%%=*}"
; assign ~var:"val" ~value:"${line#*=}"
; case "key"
[ { pattern = Printf.sprintf "*[!a-zA-Z0-9_]*"
; commands =
[ print_errf "Invalid configuration key in $conf: $key"
; return 1
]
}
; { pattern = "*"
; commands = [eval "$var_prefix$key=\\$val"] }
]
]
; return 0
]
let app_var_prefix = Plugin_utils.app_var_prefix
let call_load_conf ?var_prefix file =
let var_prefix_arg = Option.to_list var_prefix in
Sh_script.call_fun load_conf (file::var_prefix_arg)
let app_install_path ~app_name = prefix_v / app_name
let app_var ~var_prefix var = var_prefix ^ var
let plugins_var ~var_prefix = app_var ~var_prefix conf_plugins
let lib_var ~var_prefix = app_var ~var_prefix conf_lib
let find_and_load_conf app_name =
let open Sh_script in
let app_dir = app_install_path ~app_name in
let var_prefix = app_var_prefix app_name in
let conf = app_dir / install_conf in
if_ ((Dir_exists app_dir) && (File_exists conf))
[call_load_conf ~var_prefix conf]
~else_:
[ print_errf "Could not locate %s install path" app_name
; exit 1
]
()
let list_all_files ~install_dir ~bindir ~mandir
(ic : Installer_config.internal) =
install_dir ::
List.map
(fun (x : Installer_config.exec_file) ->
bindir / (Filename.basename x.path))
ic.exec_files
@ List.concat_map
(fun (section, files) ->
let dir = mandir / section in
List.map (fun x -> dir / (Filename.basename x)) files)
(Option.value ic.manpages ~default:[])
let check_makeself_installed () =
match Sys.command "command -v makeself >/dev/null 2>&1" with
| 0 -> ()
| _ ->
failwith
"Could not find makeself, \
Please install makeself and run this command again."
let set_user_prefixes =
let open Sh_script in
[ assign ~var:bindir_nv ~value:User.bin
; assign ~var:mandir_nv ~value:User.man
]
let setup_install_kind ~installer_name ~prefix =
let open Sh_script in
let dirname_nv = "dir_name" in
let dirname_v = !$ dirname_nv in
let abort path =
[
echof "Not running as root. Aborting.";
echof "Need root permission for %s" path;
echof "Please run again as root or use the install script --prefix \
option to set a custom install path";
echof "You can pass options to the install script by running \
./%s -- <install-script-options>" installer_name;
echof "example: ./%s -- --prefix /tmp" installer_name;
exit 1;
]
in
[
if_ (Dir_exists prefix)
[ assign ~var:dirname_nv ~value:prefix ]
~else_:[assign_eval dirname_nv (dirname prefix)]
();
if_ (Dir_exists dirname_v) [
if_ Is_not_root
[ if_ (Writable_as_user dirname_v)
(assign ~var:is_user_install_nv ~value:"true"::set_user_prefixes)
~else_:(abort dirname_v)
()
]
()
]
~else_:[
echof "Parent directory not found: %s" dirname_v;
echof "Aborting.";
exit 1;
] ();
]
let set_default_mandir =
let open Sh_script in
[ if_ (Dir_exists Global.shareman)
[assign ~var:mandir_nv ~value:Global.shareman]
~else_:[assign ~var:mandir_nv ~value:Global.man]
()
]
let set_root_prefixes =
let open Sh_script in
assign ~var:bindir_nv ~value:Global.bin :: set_default_mandir
let add_symlink ~install_dir ~in_ bundle_path =
let open Sh_script in
let base = Filename.basename bundle_path in
symlink ~target:(install_dir / bundle_path) ~link:(in_ / base)
let remove_symlink ?(name="symlink") ~in_ bundle_path =
let open Sh_script in
let link = in_ / (Filename.basename bundle_path) in
if_ (Link_exists link)
[ echof "Removing %s %s..." name link
; rm [link]
]
()
let set_install_vars ~install_dir =
let open Sh_script in
[ assign ~var:install_path_nv ~value:install_dir ]
let create_if_not_found dir =
let open Sh_script in
if_ (Not (Dir_exists dir)) [mkdir ~permissions:755 [dir]] ()
let true_install_binary ~install_dir ~env ~in_
(binary : Installer_config.exec_file) =
let open Sh_script in
let bundle_path = binary.path in
let base = Filename.basename bundle_path in
let true_binary = install_dir / bundle_path in
let installed_binary = in_ / base in
let install_cmds =
match env with
| [] -> [symlink ~target:true_binary ~link:installed_binary]
| _ ->
let set_vars =
List.map
(fun (var, value) ->
Printf.sprintf "%s=\\\"%s\\\" \\" var value)
env
in
let wrapper_script_lines =
"#!/usr/bin/env sh" ::
set_vars
@ [ Printf.sprintf "exec %s \\\"\\$@\\\"" true_binary ]
in
[ write_file installed_binary wrapper_script_lines
; chmod 755 [installed_binary]
]
in
echof "Adding %s to %s" base in_ :: install_cmds
let install_binary ~install_dir ~env ~in_ (binary : Installer_config.exec_file) =
if binary.symlink then
true_install_binary ~install_dir ~env ~in_ binary
else
[]
let install_manpages ~install_dir ~in_ manpages =
let open Sh_script in
let install_page ~section page = add_symlink ~install_dir ~in_:section page in
match manpages with
| [] -> []
| _ ->
let install_manpages =
List.concat_map
(fun (section, pages) ->
let section = in_ / section in
mkdir ~permissions:755 [section]
:: (List.map (install_page ~section) pages))
manpages
in
echof "Installing manpages to %s..." in_
:: install_manpages
let install_plugin ~install_dir (plugin : Installer_config.plugin) =
let open Sh_script in
let var_prefix = app_var_prefix plugin.app_name in
let lib_dir = !$ (lib_var ~var_prefix) in
let plugins_dir = !$ (plugins_var ~var_prefix) in
let add_symlink_if_missing ~install_dir ~in_ path =
let dst = in_ / (Filename.basename path) in
if_ ((Not (Link_exists dst)) && (Not (Dir_exists dst)))
[ add_symlink ~install_dir ~in_ path ]
()
in
[ echof "Installing plugin %s to %s..." plugin.name plugin.app_name
; add_symlink ~install_dir plugin.plugin_dir ~in_:plugins_dir
; add_symlink_if_missing ~install_dir plugin.lib_dir ~in_:lib_dir
]
@ (List.map
(fun dyn_dep -> add_symlink_if_missing ~install_dir dyn_dep ~in_:lib_dir)
plugin.dyn_deps)
let def_check_available prefix =
let open Sh_script in
def_fun check_available
[ if_ (Exists "$1")
[
print_errf "$1 already exists on the system! Aborting";
print_errf "Use %s/%s to uninstall it"
prefix uninstall_script_name;
exit 1
]
()
]
let def_check_lib =
let open Sh_script in
def_fun check_lib
[ if_ ((Exists "$1") && (Not (Dir_exists "$1")) && (Not (Link_exists "$1")))
[ print_errf
"$1 already exists and does not appear to be a library! Aborting"
; exit 1
]
()
]
let call_check_available path =
Sh_script.call_fun check_available [Printf.sprintf "%S" path]
let call_check_lib path =
Sh_script.call_fun check_lib [Printf.sprintf "%S" path]
let check_plugin_available (plugin : Installer_config.plugin) =
let var_prefix = app_var_prefix plugin.app_name in
let lib_dir = !$ (lib_var ~var_prefix) in
let plugins_dir = !$ (plugins_var ~var_prefix) in
let paths =
[ lib_dir / (Filename.basename plugin.lib_dir)
; plugins_dir / (Filename.basename plugin.plugin_dir)
]
in
List.map call_check_available paths
@ List.map
(fun x -> call_check_lib (lib_dir / (Filename.basename x)))
plugin.dyn_deps
let prompt_for_confirmation =
let open Sh_script in
[ prompt ~question:"Proceed? [y/N]" ~varname:"ans"
; case "ans"
[ {pattern = "[Yy]*"; commands = []}
; {pattern = "*"; commands = [echof "Aborted."; exit 1]}
]
]
let read_arguments =
let open Sh_script in
let check_arg =
if_ (Num_op ("#",Lt,2)) [echof "Option $1 requires an argument"; exit 2] ()
in
while_ (Num_op ("#",Gt,0)) [
case "1" [
{ pattern = "--prefix";
commands = [
check_arg;
shift;
assign ~var:prefix_nv ~value:"$1";
]};
{ pattern = "--help";
commands = [
call_fun "usage" [];
exit 0
]};
{ pattern = "*";
commands = [
call_fun "usage" [];
exit 3
]};
];
shift;
]
let install_script ~installer_name (ic : Installer_config.internal) =
let open Sh_script in
let package = ic.name in
let version = ic.version in
let def_usage =
let open Sh_script in
[
Printf.sprintf "Ocaml Universal Installer for %s.%s"
package version;
"";
"Options:";
Printf.sprintf
" --prefix PREFIX Install bundle in PREFIX (default is %s)"
opt;
Printf.sprintf
" If PREFIX points to a user owned directory \
symlinks and manpage will be put in %s, otherwise (root directory) \
in %s" User.pre Global.pre;
]
|> List.map (echof "%s")
|> def_fun "usage"
in
let install_dir = prefix_v / package in
let set_prefixes =
let set_defaults =
[ assign ~var:prefix_nv ~value:opt
; assign ~var:is_user_install_nv ~value:"false"
] @ set_root_prefixes
in
set_defaults
@ [read_arguments]
@ setup_install_kind ~installer_name ~prefix:prefix_v
in
let plugin_apps =
List.map (fun (p : Installer_config.plugin) -> p.app_name) ic.plugins
|> List.sort_uniq String.compare
in
let all_files =
list_all_files ~install_dir ~bindir:bindir_v ~mandir:mandir_v ic
in
let def_load_conf =
match ic.plugins with
| [] -> []
| _ -> [def_load_conf]
in
let display_install_info =
[ echof "Installing %s.%s to %s" package version install_dir
; echof "The following files and directories will be written to the system:"
]
@ (List.map (echof "- %s") all_files)
in
let display_plugin_install_info =
match (ic.plugins : Installer_config.plugin list) with
| [] -> []
| plugins ->
echof "The following plugins will be installed:" ::
(List.map
(fun (p : Installer_config.plugin) ->
echof "- %s for %s" p.name p.app_name)
plugins)
in
let load_plugin_app_vars = List.map find_and_load_conf plugin_apps in
let check_all_available =
List.map call_check_available all_files
@ List.concat_map check_plugin_available ic.plugins
in
let create_install_dir =
[
create_if_not_found prefix_v;
mkdir ~permissions:755 [install_dir];
]
in
let deffuns = [
def_usage;
def_check_available install_dir;
def_check_lib;
] @
def_load_conf
in
let setup =
deffuns
@ set_prefixes
@ set_install_vars ~install_dir
@ display_install_info
@ display_plugin_install_info
@ load_plugin_app_vars
@ check_all_available
@ prompt_for_confirmation
@ create_install_dir
in
let install_bundle =
Sh_script.copy_all_in ~src:"." ~dst:install_dir ~except:install_script_name
in
let env = ic.environment in
let binaries = ic.exec_files in
let install_binaries =
match binaries with
| [] -> []
| _ ->
create_if_not_found bindir_v ::
List.concat_map (install_binary ~install_dir ~env ~in_:bindir_v) binaries
in
let manpages = Option.value ic.manpages ~default:[] in
let install_manpages = install_manpages ~install_dir ~in_:mandir_v manpages in
let notify_install_complete =
[ echof "Installation complete!"
; echof
"If you want to safely uninstall %s, please run %s/%s."
package install_dir uninstall_script_name
]
in
let install_plugins = List.concat_map (install_plugin ~install_dir) ic.plugins in
let dump_install_conf =
let lines =
List.filter_map (fun x -> x)
[ Some (Printf.sprintf "%s=%s" conf_version ic.version)
; Some (Printf.sprintf "%s=%s" is_user_install_nv is_user_install_v)
; Option.map
(fun (plgdr : Installer_config.plugin_dirs) ->
Printf.sprintf "%s=%s" conf_plugins (install_dir / plgdr.plugins_dir))
ic.plugin_dirs
; Option.map
(fun (plgdr : Installer_config.plugin_dirs) ->
Printf.sprintf "%s=%s" conf_lib (install_dir / plgdr.lib_dir))
ic.plugin_dirs
]
in
let plugin_app_lines =
ListLabels.concat_map plugin_apps
~f:(fun app_name ->
let var_prefix = app_var_prefix app_name in
let lib_var = lib_var ~var_prefix in
let plugins_var = plugins_var ~var_prefix in
[ Printf.sprintf "%s=$%s" lib_var lib_var
; Printf.sprintf "%s=$%s" plugins_var plugins_var
])
in
let install_conf = install_dir / install_conf in
[
Sh_script.write_file install_conf (lines @ plugin_app_lines);
Sh_script.chmod 644 [install_conf];
]
in
setup
@ [install_bundle]
@ install_binaries
@ install_manpages
@ install_plugins
@ dump_install_conf
@ notify_install_complete
let display_plugin (plugin : Installer_config.plugin) =
let open Sh_script in
let b = Filename.basename in
let var_prefix = app_var_prefix plugin.app_name in
let lib_dir = !$ (lib_var ~var_prefix) in
let plugins_dir = !$ (plugins_var ~var_prefix) in
[ echof "- %s/%s" plugins_dir (b plugin.plugin_dir)
; echof "- %s/%s" lib_dir (b plugin.lib_dir)
]
@ List.map (fun x -> echof "- %s/%s" lib_dir (b x)) plugin.dyn_deps
let uninstall_plugin (plugin : Installer_config.plugin) =
let var_prefix = app_var_prefix plugin.app_name in
let lib_dir = !$ (lib_var ~var_prefix) in
let plugins_dir = !$ (plugins_var ~var_prefix) in
[ remove_symlink ~in_:lib_dir plugin.lib_dir
; remove_symlink ~in_:plugins_dir plugin.plugin_dir
]
@ List.map (remove_symlink ~in_:lib_dir) plugin.dyn_deps
let uninstall_script (ic : Installer_config.internal) =
let open Sh_script in
let (/) = Filename.concat in
let package = ic.name in
let install_dir_nv = "INSTALL_DIR" in
let install_dir = !$ install_dir_nv in
let binaries = ic.exec_files in
let load_install_conf ~install_dir =
[ def_load_conf
; call_load_conf (install_dir / install_conf)
]
in
let display_symlinks =
List.filter_map
(fun (binary : Installer_config.exec_file) ->
if binary.symlink then
Some (echof "- %s/%s" bindir_v (Filename.basename binary.path))
else
None
)
binaries
in
let manpages = Option.value ic.manpages ~default:[] in
let display_manpages =
List.concat_map
(fun (section, pages) ->
List.map
(fun page ->
echof "- %s/%s/%s" mandir_v section (Filename.basename page))
pages)
manpages
in
let display_plugins = List.concat_map display_plugin ic.plugins in
let set_prefix_and_install_dir =
[
assign_eval install_dir_nv (dirname "$0");
assign_eval prefix_nv (dirname (!$ install_dir));
]
in
let set_man_and_bin_prefixes =
[ if_ (Str_op (Equal (is_user_install_v, "true")))
set_user_prefixes
~else_:set_root_prefixes
()
]
in
let setup =
set_prefix_and_install_dir
@ load_install_conf ~install_dir
@ set_man_and_bin_prefixes
@ [ echof "About to uninstall %s." package
; echof "The following files and folders will be removed from the system:"
; echof "- %s" install_dir
]
@ display_symlinks
@ display_manpages
@ display_plugins
in
let check_permissions =
[ if_ Is_not_root
[ if_ (Not (Writable_as_user install_dir))
[ echof "Need root permission for %s" install_dir
; echof "Not running as root. Aborting"
; exit 1
]
()
]
()
]
in
let remove_install_folder =
[ if_ (Dir_exists install_dir)
[ echof "Removing %s..." install_dir
; rm_rf [install_dir]
]
()
]
in
let remove_symlinks =
List.filter_map (fun (x : Installer_config.exec_file) ->
if x.symlink then
Some (remove_symlink ~in_:bindir_v x.path)
else
None
) binaries
in
let remove_manpages =
List.concat_map
(fun (section, pages) ->
List.map
(remove_symlink ~name:"manpage" ~in_:(mandir_v / section))
pages)
manpages
in
let remove_plugins = List.concat_map uninstall_plugin ic.plugins in
let notify_uninstall_complete = [echof "Uninstallation complete!"] in
setup
@ prompt_for_confirmation
@ check_permissions
@ remove_install_folder
@ remove_symlinks
@ remove_manpages
@ remove_plugins
@ notify_uninstall_complete
let add_sos_to_bundle ~bundle_dir (binary : Installer_config.exec_file) =
let binary = OpamFilename.Op.(bundle_dir // binary.path) in
let sos = Ldd.get_sos binary in
match sos with
| [] -> ()
| _ ->
let dst_dir = OpamFilename.dirname binary in
List.iter (fun so -> OpamFilename.copy_in so dst_dir) sos;
System.call_unit Patchelf (Set_rpath {rpath = "$ORIGIN"; binary})
let add_sos_to_bundle ~bundle_dir (binary : Installer_config.exec_file) =
if binary.deps then
add_sos_to_bundle ~bundle_dir binary
let update_mtime mtime bundle_dir =
let files =
OpamFilename.rec_files bundle_dir
|> List.map OpamFilename.to_string
in
let dirs =
OpamFilename.rec_dirs bundle_dir
|> List.map OpamFilename.Dir.to_string
in
System.call_list
(List.map (fun file -> System.Touch, {System.mtime; file})
(dirs @ files))
let create_installer ?mtime ?
~(installer_config : Installer_config.internal) ~bundle_dir installer =
check_makeself_installed ();
OpamConsole.formatted_msg "Preparing makeself archive... \n";
List.iter (add_sos_to_bundle ~bundle_dir) installer_config.exec_files;
let installer_name = OpamFilename.(basename installer |> Base.to_string) in
let install_script = install_script ~installer_name installer_config in
let uninstall_script = uninstall_script installer_config in
let install_sh = OpamFilename.Op.(bundle_dir // install_script_name) in
let uninstall_sh = OpamFilename.Op.(bundle_dir // uninstall_script_name) in
Sh_script.save install_script install_sh;
Sh_script.save uninstall_script uninstall_sh;
System.call_unit Chmod (755, install_sh);
System.call_unit Chmod (755, uninstall_sh);
Option.iter (fun mtime -> update_mtime mtime bundle_dir) mtime;
let =
match tar_extra with
| None ->
[
"--numeric-owner";
"--owner=0";
"--group=0";
]
| Some l -> l
in
let args : System.makeself =
{ archive_dir = bundle_dir
; installer
; description = installer_config.name
; startup_script = Format.sprintf "./%s" install_script_name
; tar_extra
}
in
OpamConsole.formatted_msg
"Generating standalone installer %s...\n"
(OpamFilename.to_string installer);
System.call_unit Makeself args;
OpamConsole.formatted_msg "Done.\n"