-
Notifications
You must be signed in to change notification settings - Fork 409
/
duneboot.ml
1198 lines (1075 loc) · 32.5 KB
/
duneboot.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
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(** {2 Command line} *)
let concurrency, verbose, _keep_generated_files, debug, secondary, force_byte_compilation =
let anon s = raise (Arg.Bad (Printf.sprintf "don't know what to do with %s\n" s)) in
let concurrency = ref None in
let verbose = ref false in
let keep_generated_files = ref false in
let prog = Filename.basename Sys.argv.(0) in
let debug = ref false in
let secondary = ref false in
let force_byte_compilation = ref false in
Arg.parse
[ "-j", Int (fun n -> concurrency := Some n), "JOBS Concurrency"
; "--verbose", Set verbose, " Set the display mode"
; "--keep-generated-files", Set keep_generated_files, " Keep generated files"
; "--debug", Set debug, " Enable various debugging options"
; "--secondary", Set secondary, " Use the secondary compiler installation"
; ( "--force-byte-compilation"
, Set force_byte_compilation
, " Force bytecode compilation even if ocamlopt is available" )
]
anon
(Printf.sprintf "Usage: %s <options>\nOptions are:" prog);
( !concurrency
, !verbose
, !keep_generated_files
, !debug
, !secondary
, !force_byte_compilation )
;;
(** {2 General configuration} *)
let build_dir = "_boot"
type task =
{ target : string * string
; external_libraries : string list
; local_libraries : (string * string option * bool * string option) list
}
let task =
{ target = "dune", "bin/main.ml"
; external_libraries = Libs.external_libraries
; local_libraries = Libs.local_libraries
}
;;
(** {2 Utility functions} *)
open StdLabels
open Printf
module StringSet = Set.Make (String)
module StringMap = Map.Make (String)
module List = struct
include List
let rec filter_map l ~f =
match l with
| [] -> []
| x :: l ->
(match f x with
| None -> filter_map l ~f
| Some x -> x :: filter_map l ~f)
;;
end
let ( ^/ ) = Filename.concat
let fatal fmt =
ksprintf
(fun s ->
prerr_endline s;
exit 2)
fmt
;;
module Status_line = struct
let num_jobs = ref 0
let num_jobs_finished = ref 0
let displayed = ref ""
let display_status_line =
Unix.(isatty stdout)
||
match Sys.getenv "INSIDE_EMACS" with
| (_ : string) -> true
| exception Not_found -> false
;;
let update jobs =
if display_status_line && !num_jobs > 0
then (
let new_displayed =
sprintf "Done: %d/%d (jobs: %d)" !num_jobs_finished !num_jobs jobs
in
Printf.printf "\r%*s\r%s%!" (String.length !displayed) "" new_displayed;
displayed := new_displayed)
;;
let () = at_exit (fun () -> Printf.printf "\r%*s\r" (String.length !displayed) "")
end
(* Return list of entries in [path] as [path/entry] *)
let readdir path =
Array.fold_right
~f:(fun entry dir -> (path ^/ entry) :: dir)
~init:[]
(Sys.readdir path)
;;
let open_out file =
if Sys.file_exists file then fatal "%s already exists" file;
open_out file
;;
let input_lines ic =
let rec loop ic acc =
match input_line ic with
| line -> loop ic (line :: acc)
| exception End_of_file -> List.rev acc
in
loop ic []
;;
let read_lines fn =
let ic = open_in fn in
let lines = input_lines ic in
close_in ic;
lines
;;
let read_file fn =
let ic = open_in_bin fn in
let s = really_input_string ic (in_channel_length ic) in
close_in ic;
s
;;
let split_lines s =
let rec loop ~last_is_cr ~acc i j =
if j = String.length s
then (
let acc =
if j = i || (j = i + 1 && last_is_cr)
then acc
else String.sub s ~pos:i ~len:(j - i) :: acc
in
List.rev acc)
else (
match s.[j] with
| '\r' -> loop ~last_is_cr:true ~acc i (j + 1)
| '\n' ->
let line =
let len = if last_is_cr then j - i - 1 else j - i in
String.sub s ~pos:i ~len
in
loop ~acc:(line :: acc) (j + 1) (j + 1) ~last_is_cr:false
| _ -> loop ~acc i (j + 1) ~last_is_cr:false)
in
loop ~acc:[] 0 0 ~last_is_cr:false
;;
(* copy a file - fails if the file exists *)
let copy ?(header = "") directive a b =
if Sys.file_exists b then fatal "%s already exists" b;
let ic = open_in_bin a in
let len = in_channel_length ic in
let s = really_input_string ic len in
close_in ic;
let oc = open_out_bin b in
output_string oc header;
fprintf oc "#%s 1 %S\n" directive a;
output_string oc s;
close_out oc
;;
let path_sep = if Sys.win32 then ';' else ':'
let split_path s =
let rec loop i j =
if j = String.length s
then [ String.sub s ~pos:i ~len:(j - i) ]
else if s.[j] = path_sep
then String.sub s ~pos:i ~len:(j - i) :: loop (j + 1) (j + 1)
else loop i (j + 1)
in
loop 0 0
;;
let path =
match Sys.getenv "PATH" with
| exception Not_found -> []
| s -> split_path s
;;
let find_prog ~f =
let rec search = function
| [] -> None
| dir :: rest ->
(match f dir with
| None -> search rest
| Some fn -> Some (dir, fn))
in
search path
;;
let exe = if Sys.win32 then ".exe" else ""
(** {2 Concurrency level} *)
let concurrency =
let try_run_and_capture_line (prog, args) =
match
find_prog ~f:(fun dir -> if Sys.file_exists (dir ^/ prog) then Some prog else None)
with
| None -> None
| Some (dir, prog) ->
let path = dir ^/ prog in
let args = Array.of_list @@ (path :: args) in
let ic, oc, ec = Unix.open_process_args_full path args (Unix.environment ()) in
let line =
match input_line ic with
| s -> Some s
| exception End_of_file -> None
in
(match Unix.close_process_full (ic, oc, ec), line with
| WEXITED 0, Some s -> Some s
| _ -> None)
in
match concurrency with
| Some n -> n
| None ->
(* If no [-j] was given, try to autodetect the number of processors *)
if Sys.win32
then (
match Sys.getenv_opt "NUMBER_OF_PROCESSORS" with
| None -> 1
| Some s ->
(match int_of_string s with
| exception _ -> 1
| n -> n))
else (
let commands =
[ "nproc", []
; "getconf", [ "_NPROCESSORS_ONLN" ]
; "getconf", [ "NPROCESSORS_ONLN" ]
]
in
let rec loop = function
| [] -> 1
| cmd :: rest ->
(match try_run_and_capture_line cmd with
| None -> loop rest
| Some s ->
(match int_of_string (String.trim s) with
| n -> n
| exception _ -> loop rest))
in
loop commands)
;;
(** {2 Fibers} *)
module Fiber : sig
(** Fibers *)
(** This module is similar to the one in [../src/fiber] except that it is much
less optimised and much easier to understand. You should look at the
documentation of the other module to understand the API. *)
type 'a t
val return : 'a -> 'a t
module O : sig
val ( >>> ) : unit t -> 'a t -> 'a t
val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
val ( >>| ) : 'a t -> ('a -> 'b) -> 'b t
end
module Future : sig
type 'a fiber
type 'a t
val wait : 'a t -> 'a fiber
end
with type 'a fiber := 'a t
val fork : (unit -> 'a t) -> 'a Future.t t
val fork_and_join : (unit -> 'a t) -> (unit -> 'b t) -> ('a * 'b) t
val fork_and_join_unit : (unit -> unit t) -> (unit -> 'a t) -> 'a t
val parallel_map : 'a list -> f:('a -> 'b t) -> 'b list t
val parallel_iter : 'a list -> f:('a -> unit t) -> unit t
module Process : sig
val run : ?cwd:string -> string -> string list -> unit t
val run_and_capture : ?cwd:string -> string -> string list -> string t
val try_run_and_capture : ?cwd:string -> string -> string list -> string option t
end
val run : 'a t -> 'a
end = struct
open MoreLabels
type 'a t = ('a -> unit) -> unit
let return x k = k x
module O = struct
let ( >>> ) a b k = a (fun () -> b k)
let ( >>= ) t f k = t (fun x -> f x k)
let ( >>| ) t f k = t (fun x -> k (f x))
end
open O
let both a b = a >>= fun a -> b >>= fun b -> return (a, b)
module Ivar = struct
type 'a state =
| Full of 'a
| Empty of ('a -> unit) Queue.t
type 'a t = { mutable state : 'a state }
let create () = { state = Empty (Queue.create ()) }
let fill t x =
match t.state with
| Full _ -> failwith "Fiber.Ivar.fill"
| Empty q ->
t.state <- Full x;
Queue.iter (fun f -> f x) q
;;
let read t k =
match t.state with
| Full x -> k x
| Empty q -> Queue.push k q
;;
end
module Future = struct
type 'a t = 'a Ivar.t
let wait = Ivar.read
end
let fork f k =
let ivar = Ivar.create () in
f () (fun x -> Ivar.fill ivar x);
k ivar
;;
let fork_and_join f g =
fork f >>= fun a -> fork g >>= fun b -> both (Future.wait a) (Future.wait b)
;;
let fork_and_join_unit f g =
fork f >>= fun a -> fork g >>= fun b -> Future.wait a >>> Future.wait b
;;
let rec parallel_map l ~f =
match l with
| [] -> return []
| x :: l ->
fork (fun () -> f x)
>>= fun future ->
parallel_map l ~f >>= fun l -> Future.wait future >>= fun x -> return (x :: l)
;;
let rec parallel_iter l ~f =
match l with
| [] -> return ()
| x :: l ->
fork (fun () -> f x)
>>= fun future -> parallel_iter l ~f >>= fun () -> Future.wait future
;;
module Temp = struct
module Files = Set.Make (String)
let tmp_files = ref Files.empty
let () =
at_exit (fun () ->
let fns = !tmp_files in
tmp_files := Files.empty;
Files.iter fns ~f:(fun fn ->
try Sys.remove fn with
| _ -> ()))
;;
let file prefix suffix =
let fn = Filename.temp_file prefix suffix in
tmp_files := Files.add fn !tmp_files;
fn
;;
let destroy_file fn =
(try Sys.remove fn with
| _ -> ());
tmp_files := Files.remove fn !tmp_files
;;
end
module Process = struct
let running = Hashtbl.create concurrency
exception Finished of int * Unix.process_status
let rec wait_win32 () =
match
Hashtbl.iter running ~f:(fun ~key:pid ~data:_ ->
let pid, status = Unix.waitpid [ WNOHANG ] pid in
if pid <> 0 then raise_notrace (Finished (pid, status)))
with
| () ->
ignore (Unix.select [] [] [] 0.001);
wait_win32 ()
| exception Finished (pid, status) -> pid, status
;;
let wait = if Sys.win32 then wait_win32 else Unix.wait
let waiting_for_slot = Queue.create ()
let throttle () =
if Hashtbl.length running >= concurrency
then (
let ivar = Ivar.create () in
Queue.push ivar waiting_for_slot;
Ivar.read ivar)
else return ()
;;
let restart_throttled () =
while
Hashtbl.length running < concurrency && not (Queue.is_empty waiting_for_slot)
do
Ivar.fill (Queue.pop waiting_for_slot) ()
done
;;
let open_temp_file () =
let out = Temp.file "duneboot-" ".output" in
let fd = Unix.openfile out [ O_WRONLY; O_CREAT; O_TRUNC; O_SHARE_DELETE ] 0o666 in
out, fd
;;
let read_temp fn =
let s = read_file fn in
Temp.destroy_file fn;
s
;;
let initial_cwd = Sys.getcwd ()
let run_process ?cwd prog args ~split =
throttle ()
>>= fun () ->
let stdout_fn, stdout_fd = open_temp_file () in
let stderr_fn, stderr_fd =
if split then open_temp_file () else stdout_fn, stdout_fd
in
(match cwd with
| Some x -> Sys.chdir x
| None -> ());
let pid =
Unix.create_process
prog
(Array.of_list (prog :: args))
Unix.stdin
stdout_fd
stderr_fd
in
(match cwd with
| Some _ -> Sys.chdir initial_cwd
| None -> ());
Unix.close stdout_fd;
if split then Unix.close stderr_fd;
let ivar = Ivar.create () in
Hashtbl.add running ~key:pid ~data:ivar;
Ivar.read ivar
>>= fun (status : Unix.process_status) ->
let stdout_s = read_temp stdout_fn in
let stderr_s = if split then read_temp stderr_fn else stdout_s in
if stderr_s <> "" || status <> WEXITED 0 || verbose
then (
let cmdline = String.concat ~sep:" " (prog :: args) in
let cmdline =
match cwd with
| Some x -> sprintf "cd %s && %s" x cmdline
| None -> cmdline
in
prerr_endline cmdline;
prerr_string stderr_s;
flush stderr);
match status with
| WEXITED 0 -> return (Ok stdout_s)
| WEXITED n -> return (Error n)
| WSIGNALED _ -> return (Error 255)
| WSTOPPED _ -> assert false
;;
let run ?cwd prog args =
run_process ?cwd prog args ~split:false
>>| function
| Ok _ -> ()
| Error n -> exit n
;;
let run_and_capture ?cwd prog args =
run_process ?cwd prog args ~split:true
>>| function
| Ok x -> x
| Error n -> exit n
;;
let try_run_and_capture ?cwd prog args =
run_process ?cwd prog args ~split:true
>>| function
| Ok x -> Some x
| Error _ -> None
;;
end
let run t =
let result = ref None in
t (fun x -> result := Some x);
let rec loop () =
if Hashtbl.length Process.running > 0
then (
Status_line.update (Hashtbl.length Process.running);
let pid, status = Process.wait () in
let ivar = Hashtbl.find Process.running pid in
Hashtbl.remove Process.running pid;
Ivar.fill ivar status;
Process.restart_throttled ();
loop ())
else (
match !result with
| Some x -> x
| None -> fatal "bootstrap got stuck!")
in
loop ()
;;
end
open Fiber.O
module Process = Fiber.Process
(** {2 OCaml tools} *)
module Mode = struct
type t =
| Byte
| Native
end
module Config : sig
val compiler : string
val ocamldep : string
val ocamllex : string
val ocamlyacc : string
val mode : Mode.t
val ocaml_archive_ext : string
val ocaml_config : unit -> string StringMap.t Fiber.t
val output_complete_obj_arg : string
val unix_library_flags : string list
end = struct
let ocaml_version = Scanf.sscanf Sys.ocaml_version "%d.%d" (fun a b -> a, b)
let prog_not_found prog = fatal "Program %s not found in PATH" prog
let best_prog dir prog =
let fn = dir ^/ prog ^ ".opt" ^ exe in
if Sys.file_exists fn
then Some fn
else (
let fn = dir ^/ prog ^ exe in
if Sys.file_exists fn then Some fn else None)
;;
let find_prog prog = find_prog ~f:(fun dir -> best_prog dir prog)
let get_prog dir prog =
match best_prog dir prog with
| None -> prog_not_found prog
| Some fn -> fn
;;
let bin_dir, ocamlc =
if secondary
then (
let s =
Fiber.run
(Process.run_and_capture
"ocamlfind"
[ "-toolchain"; "secondary"; "query"; "ocaml" ])
in
match split_lines s with
| [] | _ :: _ :: _ -> fatal "Unexpected output locating secondary compiler"
| [ bin_dir ] ->
(match best_prog bin_dir "ocamlc" with
| None -> fatal "Failed to locate secondary ocamlc"
| Some x -> bin_dir, x))
else (
match find_prog "ocamlc" with
| None -> prog_not_found "ocamlc"
| Some x -> x)
;;
let ocamlyacc = get_prog bin_dir "ocamlyacc"
let ocamllex = get_prog bin_dir "ocamllex"
let ocamldep = get_prog bin_dir "ocamldep"
let compiler, mode, ocaml_archive_ext =
match force_byte_compilation, best_prog bin_dir "ocamlopt" with
| true, _ | _, None -> ocamlc, Mode.Byte, ".cma"
| false, Some path -> path, Mode.Native, ".cmxa"
;;
let ocaml_config () =
Process.run_and_capture ocamlc [ "-config" ]
>>| fun s ->
List.fold_left (split_lines s) ~init:StringMap.empty ~f:(fun acc line ->
match Scanf.sscanf line "%[^:]: %s" (fun k v -> k, v) with
| k, v -> StringMap.add k v acc
| exception _ ->
fatal "invalid line in output of 'ocamlc -config': %s" (String.escaped line))
;;
let output_complete_obj_arg =
if ocaml_version < (4, 10) then "-custom" else "-output-complete-exe"
;;
let unix_library_flags = if ocaml_version >= (5, 0) then [ "-I"; "+unix" ] else []
end
let insert_header fn ~header =
match header with
| "" -> ()
| h ->
let s = read_file fn in
let oc = open_out_bin fn in
output_string oc h;
output_string oc s;
close_out oc
;;
let copy_lexer ~header src dst =
let dst = Filename.remove_extension dst ^ ".ml" in
Process.run Config.ocamllex [ "-q"; "-o"; dst; src ]
>>| fun () -> insert_header dst ~header
;;
let copy_parser ~header src dst =
let dst = Filename.remove_extension dst in
Process.run Config.ocamlyacc [ "-b"; dst; src ]
>>| fun () ->
insert_header (dst ^ ".ml") ~header;
insert_header (dst ^ ".mli") ~header
;;
(** {2 Handling of the dune-build-info library} *)
(** {2 Preparation of library files} *)
module Build_info = struct
let get_version () =
let from_dune_project =
match read_lines "dune-project" with
| exception _ -> None
| lines ->
let rec loop = function
| [] -> None
| line :: lines ->
(match Scanf.sscanf line "(version %[^)])" (fun v -> v) with
| exception _ -> loop lines
| v -> Some v)
in
loop lines
in
match from_dune_project with
| Some _ -> Fiber.return from_dune_project
| None ->
if not (Sys.file_exists ".git")
then Fiber.return None
else
Process.try_run_and_capture
"git"
[ "describe"; "--always"; "--dirty"; "--abbrev=7" ]
>>| (function
| Some s -> Some (String.trim s)
| None -> None)
;;
let gen_data_module oc =
let pr fmt = fprintf oc fmt in
let prlist name l ~f =
match l with
| [] -> pr "let %s = []\n" name
| x :: l ->
pr "let %s =\n" name;
pr " [ ";
f x;
List.iter l ~f:(fun x ->
pr " ; ";
f x);
pr " ]\n"
in
get_version ()
>>| fun version ->
pr
"let version = %s\n"
(match version with
| None -> "None"
| Some v -> sprintf "Some %S" v);
pr "\n";
let libs =
List.map task.local_libraries ~f:(fun (name, _, _, _) -> name, "version")
@ List.map task.external_libraries ~f:(fun name ->
name, {|Some "[distributed with OCaml]"|})
|> List.sort ~cmp:(fun (a, _) (b, _) -> String.compare a b)
in
prlist "statically_linked_libraries" libs ~f:(fun (name, v) -> pr "%S, %s\n" name v)
;;
end
(* module OCaml_file = struct module Kind = struct type t = Impl | Intf end
type t = { kind : Kind.t ; module_name = end *)
module Library = struct
module File_kind = struct
type t =
| Header
| C
| Ml
| Mli
| Mll
| Mly
let analyse fn =
let dn = Filename.dirname fn in
let fn = Filename.basename fn in
let i =
try String.index fn '.' with
| Not_found -> String.length fn
in
match String.sub fn ~pos:i ~len:(String.length fn - i) with
| ".h" -> Some Header
| ".c" -> Some C
| ".ml" -> Some Ml
| ".mli" -> Some Mli
| ".mll" -> Some Mll
| ".mly" -> Some Mly
| ".defaults.ml" ->
let fn' = String.sub fn ~pos:0 ~len:i ^ ".ml" in
if Sys.file_exists (dn ^/ fn') then None else Some Ml
| _ -> None
;;
end
module Wrapper = struct
type t =
{ toplevel_module : string
; alias_module : string
}
let make ~namespace ~modules =
match namespace with
| None -> None
| Some namespace ->
let namespace = String.capitalize_ascii namespace in
if StringSet.equal modules (StringSet.singleton namespace)
then None
else if StringSet.mem namespace modules
then Some { toplevel_module = namespace; alias_module = namespace ^ "__" }
else Some { toplevel_module = namespace; alias_module = namespace }
;;
let mangle_filename t fn (kind : File_kind.t) =
let base =
let fn = Filename.basename fn in
String.sub fn ~pos:0 ~len:(String.index fn '.') |> String.uncapitalize_ascii
in
match kind with
| C -> base ^ ".c"
| Header -> base ^ ".h"
| _ ->
let ext =
match kind with
| Mli -> ".mli"
| _ -> ".ml"
in
(match t with
| None -> base ^ ext
| Some t ->
if String.capitalize_ascii base = t.toplevel_module
then base ^ ext
else (
let base = String.capitalize_ascii base in
String.uncapitalize_ascii t.toplevel_module ^ "__" ^ base ^ ext))
;;
let header t =
match t with
| None -> ""
| Some t -> sprintf "open! %s\n" t.alias_module
;;
let generate_wrapper t modules =
match t with
| None -> None
| Some t ->
let fn = String.uncapitalize_ascii t.alias_module ^ ".ml" in
let oc = open_out (build_dir ^/ fn) in
StringSet.iter
(fun m ->
if m <> t.toplevel_module
then fprintf oc "module %s = %s__%s\n" m t.toplevel_module m)
modules;
close_out oc;
Some fn
;;
end
(* Collect source files *)
let scan ~dir ~scan_subdirs =
let rec loop files acc =
match files with
| [] -> acc
| file :: files ->
let acc =
if Sys.is_directory file
then if scan_subdirs then loop (readdir file) acc else acc
else (
match File_kind.analyse file with
| Some kind -> (file, kind) :: acc
| None -> acc)
in
loop files acc
in
loop (readdir dir) []
;;
let process (dir, namespace, scan_subdirs, build_info_module) =
let files = scan ~dir ~scan_subdirs in
let modules =
List.fold_left files ~init:StringSet.empty ~f:(fun acc (fn, kind) ->
match (kind : File_kind.t) with
| Header | C -> acc
| Ml | Mli | Mll | Mly ->
let module_name =
let fn = Filename.basename fn in
String.sub fn ~pos:0 ~len:(String.index fn '.') |> String.capitalize_ascii
in
StringSet.add module_name acc)
in
let modules =
match build_info_module with
| None -> modules
| Some m -> StringSet.add (String.capitalize_ascii m) modules
in
let wrapper = Wrapper.make ~namespace ~modules in
let header = Wrapper.header wrapper in
Fiber.fork_and_join
(fun () ->
Fiber.parallel_map files ~f:(fun (fn, kind) ->
let mangled = Wrapper.mangle_filename wrapper fn kind in
let dst = build_dir ^/ mangled in
match kind with
| Header | C ->
copy "line" fn dst;
Fiber.return [ mangled ]
| Ml | Mli ->
copy "" fn dst ~header;
Fiber.return [ mangled ]
| Mll -> copy_lexer fn dst ~header >>> Fiber.return [ mangled ]
| Mly -> copy_parser fn dst ~header >>> Fiber.return [ mangled; mangled ^ "i" ]))
(fun () ->
match build_info_module with
| None -> Fiber.return None
| Some m ->
let fn = String.uncapitalize_ascii m ^ ".ml" in
let mangled = Wrapper.mangle_filename wrapper fn Ml in
let oc = open_out (build_dir ^/ mangled) in
Build_info.gen_data_module oc
>>| fun () ->
close_out oc;
Some mangled)
>>| fun (files, build_info_file) ->
let files = List.concat files in
let files =
match build_info_file with
| None -> files
| Some fn -> fn :: files
in
let alias_file = Wrapper.generate_wrapper wrapper modules in
let c_files, ocaml_files =
List.partition files ~f:(fun fn -> Filename.extension fn = ".c")
in
ocaml_files, alias_file, c_files
;;
end
let ocamldep args =
Process.run_and_capture Config.ocamldep ("-modules" :: args) ~cwd:build_dir
>>| fun s ->
List.map (split_lines s) ~f:(fun line ->
let colon = String.index line ':' in
let filename = String.sub line ~pos:0 ~len:colon in
let modules =
if colon = String.length line - 1
then []
else (
let modules =
String.sub line ~pos:(colon + 2) ~len:(String.length line - colon - 2)
in
String.split_on_char ~sep:' ' modules)
in
filename, modules)
|> List.sort ~cmp:compare
;;
let mk_flags arg l = List.map l ~f:(fun m -> [ arg; m ]) |> List.flatten
let convert_dependencies ~all_source_files (file, dependencies) =
let is_mli = Filename.check_suffix file ".mli" in
let convert_module module_name =
let filename = String.uncapitalize_ascii module_name in
if filename = Filename.chop_extension file
then (* Self-reference *)
None
else if StringSet.mem (filename ^ ".mli") all_source_files
then
if (not is_mli) && StringSet.mem (filename ^ ".ml") all_source_files
then
(* We need to build the .ml for inlining info *)
Some [ filename ^ ".mli"; filename ^ ".ml" ]
else (* .mli files never depend on .ml files *)
Some [ filename ^ ".mli" ]
else if StringSet.mem (filename ^ ".ml") all_source_files
then
(* If there's no .mli, then we must always depend on the .ml *)
Some [ filename ^ ".ml" ]
else (* This is a module coming from an external library *)
None
in
let dependencies = List.concat (List.filter_map ~f:convert_module dependencies) in
(* .ml depends on .mli, if it exists *)
let dependencies =
if (not is_mli) && StringSet.mem (file ^ "i") all_source_files
then (file ^ "i") :: dependencies
else dependencies
in
file, dependencies
;;
let write_args file args =
let ch = open_out (build_dir ^/ file) in
output_string ch (String.concat ~sep:"\n" args);
close_out ch
;;
let get_dependencies libraries =
let alias_files =
List.fold_left libraries ~init:[] ~f:(fun acc (_, alias_file, _) ->
match alias_file with
| None -> acc
| Some fn -> fn :: acc)
in
let all_source_files = List.map ~f:(fun (x, _, _) -> x) libraries |> List.concat in
write_args "source_files" all_source_files;
ocamldep (mk_flags "-map" alias_files @ [ "-args"; "source_files" ])
>>| fun dependencies ->
let all_source_files =
List.fold_left
alias_files
~init:(StringSet.of_list all_source_files)
~f:(fun acc fn -> StringSet.add fn acc)
in
let deps =
List.rev_append
((* Alias files have no dependencies *)
List.rev_map alias_files ~f:(fun fn -> fn, []))
(List.rev_map dependencies ~f:(convert_dependencies ~all_source_files))
in
if debug
then (
eprintf "***** Dependencies *****\n";
List.iter deps ~f:(fun (fn, deps) ->
eprintf "%s: %s\n" fn (String.concat deps ~sep:" "));
eprintf "**********\n");
deps
;;
let assemble_libraries { local_libraries; target = _, main; _ } =
let libraries =