forked from ocaml/dune
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgen_rules.ml
2017 lines (1843 loc) · 68.9 KB
/
gen_rules.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
open Import
open Jbuild_types
open Build.O
(* +-----------------------------------------------------------------+
| Utils |
+-----------------------------------------------------------------+ *)
let g () =
if !Clflags.g then
["-g"]
else
[]
module Ocaml_flags = struct
let default_ocamlc_flags = g
let default_ocamlopt_flags = g
let dev_mode_warnings =
"@a" ^
String.concat ~sep:""
(List.map ~f:(sprintf "-%d")
[ 4
; 29
; 40
; 41
; 42
; 44
; 45
; 48
; 58
; 59
])
let default_flags () =
if !Clflags.dev_mode then
[ "-w"; dev_mode_warnings ^ !Clflags.warnings
; "-strict-sequence"
; "-strict-formats"
]
else
[ "-w"; !Clflags.warnings ]
type t =
{ common : string list
; specific : string list Mode.Dict.t
}
let make { Buildable. flags; ocamlc_flags; ocamlopt_flags; _ } =
let eval = Ordered_set_lang.eval_with_standard in
{ common = eval flags ~standard:(default_flags ())
; specific =
{ byte = eval ocamlc_flags ~standard:(default_ocamlc_flags ())
; native = eval ocamlopt_flags ~standard:(default_ocamlopt_flags ())
}
}
let get t mode = Arg_spec.As (t.common @ Mode.Dict.get t.specific mode)
let get_for_cm t ~cm_kind = get t (Mode.of_cm_kind cm_kind)
let default () =
{ common = default_flags ()
; specific =
{ byte = default_ocamlc_flags ()
; native = default_ocamlopt_flags ()
}
}
end
let default_c_flags = g ()
let default_cxx_flags = g ()
let cm_files modules ~dir ~cm_kind =
List.map modules ~f:(fun (m : Module.t) -> Module.cm_file m ~dir cm_kind)
let find_module ~dir modules name =
String_map.find_exn name modules
~string_of_key:(sprintf "%S")
~desc:(fun _ ->
sprintf "<module name to module info in %s>" (Path.to_string dir))
let find_deps ~dir dep_graph name =
String_map.find_exn name dep_graph
~string_of_key:(sprintf "%S")
~desc:(fun _ -> sprintf "<dependency graph in %s>" (Path.to_string dir))
let modules_of_names ~dir ~modules names =
List.map names ~f:(find_module ~dir modules)
let obj_name_of_basename fn =
match String.index fn '.' with
| None -> fn
| Some i -> String.sub fn ~pos:0 ~len:i
module type Params = sig
val context : Context.t
val file_tree : File_tree.t
val stanzas : (Path.t * Stanza.t list) list
val packages : Package.t String_map.t
val filter_out_optional_stanzas_with_missing_deps : bool
val alias_store : Alias.Store.t
val dirs_with_dot_opam_files : Path.Set.t
end
module Gen(P : Params) = struct
type dir =
{ src_dir : Path.t
; ctx_dir : Path.t
; stanzas : Stanza.t list
}
module P = struct
include P
let stanzas =
List.map stanzas
~f:(fun (dir, stanzas) ->
{ src_dir = dir
; ctx_dir = Path.append context.build_dir dir
; stanzas
})
let internal_libraries =
List.concat_map stanzas ~f:(fun { ctx_dir; stanzas; _ } ->
List.filter_map stanzas ~f:(fun stanza ->
match (stanza : Stanza.t) with
| Library lib -> Some (ctx_dir, lib)
| _ -> None))
let dirs_with_dot_opam_files =
Path.Set.elements dirs_with_dot_opam_files
|> List.map ~f:(Path.append context.build_dir)
|> Path.Set.of_list
end
let ctx = P.context
let findlib = ctx.findlib
module Mode = struct
include Mode
let choose byte native = function
| Byte -> byte
| Native -> native
let compiler t = choose (Some ctx.ocamlc) ctx.ocamlopt t
let best =
match ctx.ocamlopt with
| Some _ -> Native
| None -> Byte
end
module Cm_kind = struct
include Cm_kind
let compiler = function
| Cmi | Cmo -> Some ctx.ocamlc
| Cmx -> ctx.ocamlopt
end
module Lib_db = struct
open Lib_db
let t =
create findlib P.internal_libraries
~dirs_with_dot_opam_files:P.dirs_with_dot_opam_files
let find ~from name = find t ~from name
module Libs_vfile =
Vfile_kind.Make_full
(struct type t = Lib.t list end)
(struct
open Sexp.To_sexp
let t _dir l = list string (List.map l ~f:Lib.best_name)
end)
(struct
open Sexp.Of_sexp
let t dir sexp =
List.map (list string sexp) ~f:(Lib_db.find_exn t ~from:dir)
end)
let vrequires ~dir ~item =
let fn = Path.relative dir (item ^ ".requires.sexp") in
Build.Vspec.T (fn, (module Libs_vfile))
let load_requires ~dir ~item =
Build.vpath (vrequires ~dir ~item)
let vruntime_deps ~dir ~item =
let fn = Path.relative dir (item ^ ".runtime-deps.sexp") in
Build.Vspec.T (fn, (module Libs_vfile))
let load_runtime_deps ~dir ~item =
Build.vpath (vruntime_deps ~dir ~item)
let with_fail ~fail build =
match fail with
| None -> build
| Some f -> Build.fail f >>> build
let closure ~dir ~dep_kind lib_deps =
let internals, externals, fail = Lib_db.interpret_lib_deps t ~dir lib_deps in
with_fail ~fail
(Build.record_lib_deps ~dir ~kind:dep_kind lib_deps
>>>
Build.all
(List.map internals ~f:(fun ((dir, lib) : Lib.Internal.t) ->
load_requires ~dir ~item:lib.name))
>>^ (fun internal_deps ->
let externals =
List.map (Findlib.closure externals) ~f:(fun pkg ->
Lib.External pkg)
in
Lib.remove_dups_preserve_order
(List.concat (externals :: internal_deps) @
List.map internals ~f:(fun x -> Lib.Internal x))))
let closed_ppx_runtime_deps_of ~dir ~dep_kind lib_deps =
let internals, externals, fail = Lib_db.interpret_lib_deps t ~dir lib_deps in
with_fail ~fail
(Build.record_lib_deps ~dir ~kind:dep_kind lib_deps
>>>
Build.all
(List.map internals ~f:(fun ((dir, lib) : Lib.Internal.t) ->
load_runtime_deps ~dir ~item:lib.name))
>>^ (fun libs ->
let externals =
List.map (Findlib.closed_ppx_runtime_deps_of externals)
~f:(fun pkg -> Lib.External pkg)
in
Lib.remove_dups_preserve_order (List.concat (externals :: libs))))
let internal_libs_without_non_installable_optional_ones =
internal_libs_without_non_installable_optional_ones t
let select_rules ~dir lib_deps =
List.map (Lib_db.resolve_selects t ~from:dir lib_deps) ~f:(fun { dst_fn; src_fn } ->
let src = Path.relative dir src_fn in
let dst = Path.relative dir dst_fn in
Build.path src
>>>
Build.action ~targets:[dst]
(Copy_and_add_line_directive (src, dst)))
(* Hides [t] so that we don't resolve things statically *)
let t = ()
let _ = t
end
module Artifacts = struct
open Artifacts
let t = create ctx (List.map P.stanzas ~f:(fun d -> (d.ctx_dir, d.stanzas)))
let binary name = binary t name
let file_of_lib ?use_provides ~dir name =
let lib, file =
match String.lsplit2 name ~on:':' with
| None ->
Loc.fail (Loc.in_file (Path.to_string (Path.relative dir "jbuild")))
"invalid ${lib:...} form: %s" name
| Some x -> x
in
(lib, file_of_lib t ~lib ~file ?use_provides)
(* Hides [t] so that we don't resolve things statically *)
let t = ()
let _ = t
end
(* Hides [findlib] so that we don't resolve things statically *)
let findlib = ()
let _ = findlib
module Build = struct
include Build
[@@@warning "-32"]
let run ?(dir=ctx.build_dir) ?stdout_to ?extra_targets prog args =
Build.run ~dir ?stdout_to ~context:ctx ?extra_targets prog args
let bash ?dir ?stdout_to ?extra_targets cmd =
run (Dep (Path.absolute "/bin/bash")) ?dir ?stdout_to ?extra_targets
[ As ["-e"; "-u"; "-o"; "pipefail"; "-c"; cmd] ]
let system ?dir ?stdout_to ?extra_targets cmd ~needed_to =
let path, arg, fail = Utils.system_shell ~needed_to in
let build =
run (Dep path) ?dir ?stdout_to ?extra_targets
[ As [arg; cmd] ]
in
match fail with
| None -> build
| Some fail -> Build.fail fail >>> build
let action ?dir ~targets action =
Build.action ?dir ~context:ctx ~targets action
let action_context_independent ?dir ~targets shexp =
Build.action ?dir ~targets shexp
end
module Alias = struct
include Alias
let add_deps t deps = add_deps P.alias_store t deps
end
let all_rules = ref []
let known_targets_by_src_dir_so_far = ref Path.Map.empty
let add_rule build =
let rule = Build_interpret.Rule.make build in
all_rules := rule :: !all_rules;
known_targets_by_src_dir_so_far :=
List.fold_left rule.targets ~init:!known_targets_by_src_dir_so_far
~f:(fun acc target ->
match Path.extract_build_context (Build_interpret.Target.path target) with
| None -> acc
| Some (_, path) ->
let dir = Path.parent path in
let fn = Path.basename path in
let files =
match Path.Map.find dir acc with
| None -> String_set.singleton fn
| Some set -> String_set.add fn set
in
Path.Map.add acc ~key:dir ~data:files)
let sources_and_targets_known_so_far ~src_path =
let sources =
match File_tree.find_dir P.file_tree src_path with
| None -> String_set.empty
| Some dir -> File_tree.Dir.files dir
in
match Path.Map.find src_path !known_targets_by_src_dir_so_far with
| None -> sources
| Some set -> String_set.union sources set
(* +-----------------------------------------------------------------+
| User variables |
+-----------------------------------------------------------------+ *)
let cxx_compiler, cxx_flags =
match String.split_words ctx.bytecomp_c_compiler with
| [] -> assert false
| prog :: flags ->
let comp =
if Filename.is_relative prog then
match Bin.which prog with
| None -> Path.of_string "g++"
| Some p -> p
else
Path.of_string prog
in
let flags =
List.filter flags ~f:(fun s -> not (String.is_prefix s ~prefix:"-std="))
in
(comp, flags)
(* Expand some $-vars within action strings of rules defined in jbuild files *)
let dollar_var_map =
let ocamlopt =
match ctx.ocamlopt with
| None -> Path.relative ctx.ocaml_bin "ocamlopt"
| Some p -> p
in
let make =
match Bin.make with
| None -> "make"
| Some p -> Path.to_string p
in
[ "-verbose" , "" (*"-verbose";*)
; "CPP" , ctx.bytecomp_c_compiler ^ " -E"
; "PA_CPP" , ctx.bytecomp_c_compiler ^ " -undef -traditional -x c -E"
; "CC" , ctx.bytecomp_c_compiler
; "CXX" , String.concat ~sep:" " (Path.to_string cxx_compiler :: cxx_flags)
; "ocaml_bin" , Path.to_string ctx.ocaml_bin
; "OCAML" , Path.to_string ctx.ocaml
; "OCAMLC" , Path.to_string ctx.ocamlc
; "OCAMLOPT" , Path.to_string ocamlopt
; "ocaml_version" , ctx.version
; "ocaml_where" , Path.to_string ctx.stdlib_dir
; "ARCH_SIXTYFOUR" , string_of_bool ctx.arch_sixtyfour
; "PORTABLE_INT63" , "true"
; "MAKE" , make
; "null" , Path.to_string Config.dev_null
] |> String_map.of_alist
|> function
| Ok x -> x
| Error _ -> assert false
let root_var_lookup ~dir var_name =
match var_name with
| "ROOT" -> Some (Path.reach ~from:dir ctx.build_dir)
| _ -> String_map.find var_name dollar_var_map
let expand_vars ~dir s =
String_with_vars.expand s ~f:(root_var_lookup ~dir)
(* +-----------------------------------------------------------------+
| User deps |
+-----------------------------------------------------------------+ *)
module Dep_conf_interpret = struct
include Dep_conf
let dep ~dir = function
| File s -> Build.path (Path.relative dir (expand_vars ~dir s))
| Alias s -> Build.path (Alias.file (Alias.make ~dir (expand_vars ~dir s)))
| Glob_files s -> begin
let path = Path.relative dir (expand_vars ~dir s) in
let dir = Path.parent path in
let s = Path.basename path in
match Glob_lexer.parse_string s with
| Ok re ->
Build.paths_glob ~dir (Re.compile re)
| Error (_pos, msg) ->
die "invalid glob in %s/jbuild: %s" (Path.to_string dir) msg
end
| Files_recursively_in s ->
let path = Path.relative dir (expand_vars ~dir s) in
Build.files_recursively_in ~dir:path
let dep_of_list ~dir ts =
let rec loop acc = function
| [] -> acc
| t :: ts ->
loop (acc >>> dep ~dir t) ts
in
loop (Build.return ()) ts
let only_plain_file ~dir = function
| File s -> Some (Path.relative dir (expand_vars ~dir s))
| Alias _ -> None
| Glob_files _ -> None
| Files_recursively_in _ -> None
let only_plain_files ~dir ts = List.map ts ~f:(only_plain_file ~dir)
end
(* +-----------------------------------------------------------------+
| ocamldep stuff |
+-----------------------------------------------------------------+ *)
let parse_deps ~dir lines ~modules ~alias_module =
List.map lines ~f:(fun line ->
match String.index line ':' with
| None -> die "`ocamldep` in %s returned invalid line: %S" (Path.to_string dir) line
| Some i ->
let unit =
let basename =
String.sub line ~pos:0 ~len:i
|> Filename.basename
in
let module_basename =
match String.index basename '.' with
| None -> basename
| Some i -> String.sub basename ~pos:0 ~len:i
in
String.capitalize_ascii module_basename
in
let deps =
String.split_words (String.sub line ~pos:(i + 1)
~len:(String.length line - (i + 1)))
|> List.filter ~f:(fun m -> m <> unit && String_map.mem m modules)
in
let deps =
match alias_module with
| None -> deps
| Some (m : Module.t) -> m.name :: deps
in
(unit, deps))
|> String_map.of_alist
|> function
| Ok x -> begin
match alias_module with
| None -> x
| Some m -> String_map.add x ~key:m.name ~data:[]
end
| Error (unit, _, _) ->
die
"`ocamldep` in %s returned %s several times" (Path.to_string dir) unit
module Ocamldep_vfile =
Vfile_kind.Make
(struct type t = string list String_map.t end)
(functor (C : Sexp.Combinators) -> struct
open C
let t = string_map (list string)
end)
let ocamldep_rules ~ml_kind ~dir ~item ~modules ~alias_module =
let suffix = Ml_kind.suffix ml_kind in
let vdepends =
let fn = Path.relative dir (sprintf "%s.depends%s.sexp" item suffix) in
Build.Vspec.T (fn, (module Ocamldep_vfile))
in
let files =
List.filter_map (String_map.values modules) ~f:(fun m -> Module.file ~dir m ml_kind)
|> List.map ~f:(fun fn ->
match ml_kind, Filename.ext (Path.to_string fn) with
| Impl, Some ".ml" -> Arg_spec.Dep fn
| Intf, Some ".mli" -> Dep fn
| Impl, _ -> S [A "-impl"; Dep fn]
| Intf, _ -> S [A "-intf"; Dep fn])
in
let ocamldep_output =
Path.relative dir (sprintf "%s.depends%s.ocamldep-output" item suffix)
in
add_rule
(Build.run (Dep ctx.ocamldep) [A "-modules"; S files] ~stdout_to:ocamldep_output);
add_rule
(Build.lines_of ocamldep_output
>>^ parse_deps ~dir ~modules ~alias_module
>>> Build.store_vfile vdepends);
Build.vpath vdepends
module Dep_closure =
Top_closure.Make(String)(struct
type t = string
type graph = Path.t * t list String_map.t
let key t = t
let deps t (dir, map) = find_deps ~dir map t
end)
let dep_closure ~dir dep_graph names =
match Dep_closure.top_closure (dir, dep_graph) names with
| Ok names -> names
| Error cycle ->
die "dependency cycle between modules in %s:\n %s" (Path.to_string dir)
(String.concat cycle ~sep:"\n-> ")
let names_to_top_closed_cm_files ~dir ~dep_graph ~modules ~mode names =
dep_closure ~dir dep_graph names
|> modules_of_names ~dir ~modules
|> cm_files ~dir ~cm_kind:(Mode.cm_kind mode)
let ocamldep_rules ~dir ~item ~modules ~alias_module =
Ml_kind.Dict.of_func (ocamldep_rules ~dir ~item ~modules ~alias_module)
(* +-----------------------------------------------------------------+
| User actions |
+-----------------------------------------------------------------+ *)
module Action_interpret : sig
val run
: Action.Mini_shexp.Unexpanded.t
-> dir:Path.t
-> dep_kind:Build.lib_dep_kind
-> targets:Path.t list
-> deps:Path.t option list
-> (unit, Action.t) Build.t
end = struct
module U = Action.Mini_shexp.Unexpanded
type resolved_forms =
{ (* Mapping from ${...} forms to their resolutions *)
artifacts : Path.t String_map.t
; (* Failed resolutions *)
failures : fail list
; (* All "name" for ${lib:name:...} forms *)
lib_deps : String_set.t
}
let add_artifact ?lib_dep acc ~var result =
let lib_deps =
match lib_dep with
| None -> acc.lib_deps
| Some lib -> String_set.add lib acc.lib_deps
in
match result with
| Ok path ->
{ acc with
artifacts = String_map.add acc.artifacts ~key:var ~data:path
; lib_deps
}
| Error fail ->
{ acc with
failures = fail :: acc.failures
; lib_deps
}
let extract_artifacts ~dir t =
let init =
{ artifacts = String_map.empty
; failures = []
; lib_deps = String_set.empty
}
in
U.fold_vars t ~init ~f:(fun acc var ->
let module A = Artifacts in
match String.lsplit2 var ~on:':' with
| Some ("exe" , s) -> add_artifact acc ~var (Ok (Path.relative dir s))
| Some ("path" , s) -> add_artifact acc ~var (Ok (Path.relative dir s))
| Some ("bin" , s) -> add_artifact acc ~var (A.binary s)
| Some ("lib" , s)
| Some ("libexec" , s) ->
let lib_dep, res = A.file_of_lib ~dir s in
add_artifact acc ~var ~lib_dep res
(* CR-someday jdimino: allow this only for (jbuild_version jane_street) *)
| Some ("findlib" , s) ->
let lib_dep, res = A.file_of_lib ~dir s ~use_provides:true in
add_artifact acc ~var ~lib_dep res
| _ -> acc)
let expand_var =
let dep_exn name = function
| Some dep -> dep
| None -> die "cannot use ${%s} with files_recursively_in" name
in
fun ~artifacts ~targets ~deps var_name ->
match String_map.find var_name artifacts with
| Some path -> Action.Path path
| None ->
match var_name with
| "@" -> Paths targets
| "<" -> (match deps with
| [] -> Str ""
| dep1 :: _ -> Path (dep_exn var_name dep1))
| "^" ->
Paths (List.map deps ~f:(dep_exn var_name))
| "ROOT" -> Path ctx.build_dir
| _ ->
match String_map.find var_name dollar_var_map with
| Some s -> Str s
| _ -> Not_found
let run t ~dir ~dep_kind ~targets ~deps =
let forms = extract_artifacts ~dir t in
let build =
match
U.expand ctx dir t
~f:(expand_var ~artifacts:forms.artifacts ~targets ~deps)
with
| t ->
Build.paths (String_map.values forms.artifacts)
>>>
Build.action t ~dir ~targets
| exception e ->
Build.fail ~targets { fail = fun () -> raise e }
in
let build =
Build.record_lib_deps ~dir ~kind:dep_kind
(String_set.elements forms.lib_deps
|> List.map ~f:(fun s -> Lib_dep.Direct s))
>>>
build
in
match forms.failures with
| [] -> build
| fail :: _ -> Build.fail fail >>> build
end
(* +-----------------------------------------------------------------+
| Preprocessing stuff |
+-----------------------------------------------------------------+ *)
let pp_fname fn =
match Filename.split_ext fn with
| None -> fn ^ ".pp"
| Some (fn, ext) ->
(* We need to to put the .pp before the .ml so that the compiler realises that
[foo.pp.mli] is the interface for [foo.pp.ml] *)
fn ^ ".pp" ^ ext
let pped_module ~dir (m : Module.t) ~f =
let ml_pp_fname = pp_fname m.ml_fname in
f Ml_kind.Impl (Path.relative dir m.ml_fname) (Path.relative dir ml_pp_fname);
let mli_pp_fname =
Option.map m.mli_fname ~f:(fun fname ->
let pp_fname = pp_fname fname in
f Intf (Path.relative dir fname) (Path.relative dir pp_fname);
pp_fname)
in
{ m with
ml_fname = ml_pp_fname
; mli_fname = mli_pp_fname
}
let ppx_drivers = Hashtbl.create 32
let migrate_driver_main = "ocaml-migrate-parsetree.driver-main"
let build_ppx_driver ~dir ~dep_kind ~target pp_names ~driver =
let mode = Mode.best in
let compiler = Option.value_exn (Mode.compiler mode) in
let pp_names = pp_names @ [migrate_driver_main] in
let libs =
Lib_db.closure ~dir ~dep_kind (List.map pp_names ~f:Lib_dep.direct)
in
let libs =
(* Put the driver back at the end, just before migrate_driver_main *)
match driver with
| None -> libs
| Some driver ->
libs >>^ fun libs ->
let is_driver name = name = driver || name = migrate_driver_main in
let libs, drivers =
List.partition_map libs ~f:(fun lib ->
if (match lib with
| External pkg -> is_driver pkg.name
| Internal (_, lib) ->
is_driver lib.name ||
match lib.public with
| None -> false
| Some { name; _ } -> is_driver name)
then
Inr lib
else
Inl lib)
in
let user_driver, migrate_driver =
List.partition_map drivers ~f:(fun lib ->
if Lib.best_name lib = migrate_driver_main then
Inr lib
else
Inl lib)
in
libs @ user_driver @ migrate_driver
in
(* Provide a better error for migrate_driver_main given that this is an implicit
dependency *)
let libs =
match Lib_db.find ~from:dir migrate_driver_main with
| None ->
Build.fail { fail = fun () ->
die "@{<error>Error@}: I couldn't find '%s'.\n\
I need this library in order to use ppx rewriters.\n\
See the manual for details.\n\
Hint: opam install ocaml-migrate-parsetree"
migrate_driver_main
}
>>>
libs
| Some _ ->
libs
in
add_rule
(libs
>>>
Build.dyn_paths (Build.arr (Lib.archive_files ~mode ~ext_lib:ctx.ext_lib))
>>>
Build.run (Dep compiler)
[ A "-o" ; Target target
; Dyn (Lib.link_flags ~mode)
]);
libs
let ppx_dir = Path.of_string (sprintf "_build/.ppx/%s" ctx.name)
let get_ppx_driver pps ~dir ~dep_kind =
let driver, names =
match List.rev_map pps ~f:Pp.to_string with
| [] -> (None, [])
| driver :: rest ->
(Some driver, List.sort rest ~cmp:String.compare @ [driver])
in
let key =
match names with
| [] -> "+none+"
| _ -> String.concat names ~sep:"+"
in
match Hashtbl.find ppx_drivers key with
| Some x -> x
| None ->
let ppx_dir = Path.relative ppx_dir key in
let exe = Path.relative ppx_dir "ppx.exe" in
let libs =
build_ppx_driver names ~dir ~dep_kind ~target:exe ~driver
in
Hashtbl.add ppx_drivers ~key ~data:(exe, libs);
(exe, libs)
let specific_args_for_ppx_rewriters ~dir ~lib_name ~for_merlin (libs : Lib.t list) =
let uses_inline_test = ref false in
let uses_inline_bench = ref false in
let uses_here = ref false in
let uses_libname = ref false in
let uses_ppx_driver = ref false in
List.iter libs ~f:(fun lib ->
match Lib.best_name lib with
| "ppx_here" | "ppx_assert" -> uses_here := true
| "ppx_inline_test.libname" -> uses_libname := true
| "ppx_expect" -> uses_inline_test := true; uses_here := true
| "ppx_inline_test" -> uses_inline_test := true
| "ppx_bench" -> uses_inline_bench := true
| "ppx_driver.runner" -> uses_ppx_driver := true
| _ -> ());
Arg_spec.S
[ S (if !uses_here
then [A "-dirname"; Path dir]
else [])
; S (match lib_name with
| Some name when !uses_libname ->
[ A "-inline-test-lib"; A name ]
| _ -> [])
; S (if !uses_inline_test(* && drop_test*)
then [ A "-inline-test-drop-with-deadcode" ]
else [])
; S (if !uses_inline_bench (*&& drop_bench*)
then [ A "-bench-drop-with-deadcode" ]
else [])
; S (if !uses_ppx_driver && not for_merlin
then [ A "-embed-errors"; A "false" ]
else [])
]
let target_var = String_with_vars.of_string "${@}"
let root_var = String_with_vars.of_string "${ROOT}"
(* Generate rules to build the .pp files and return a new module map where all filenames
point to the .pp files *)
let pped_modules ~dir ~dep_kind ~modules ~preprocess ~preprocessor_deps ~lib_name =
let preprocessor_deps = Dep_conf_interpret.dep_of_list ~dir preprocessor_deps in
String_map.map modules ~f:(fun (m : Module.t) ->
match Preprocess_map.find m.name preprocess with
| No_preprocessing -> m
| Action action ->
pped_module m ~dir ~f:(fun _kind src dst ->
add_rule
(preprocessor_deps
>>>
Build.path src
>>>
Action_interpret.run
(Redirect
(Stdout,
target_var,
Chdir (root_var,
action)))
~dir
~dep_kind
~targets:[dst]
~deps:[Some src]))
| Pps { pps; flags } ->
let ppx_exe, libs = get_ppx_driver pps ~dir ~dep_kind in
pped_module m ~dir ~f:(fun kind src dst ->
add_rule
(preprocessor_deps
>>>
libs
>>>
Build.run
(Dep ppx_exe)
[ Dyn (specific_args_for_ppx_rewriters ~dir ~lib_name ~for_merlin:false)
; As flags
; A "--dump-ast"
; A "-o"; Target dst
; Ml_kind.ppx_driver_flag kind; Dep src
])
)
)
let real_requires ~dir ~dep_kind ~item ~libraries ~preprocess ~virtual_deps =
let all_pps =
List.map (Preprocess_map.pps preprocess) ~f:Pp.to_string
in
let vrequires = Lib_db.vrequires ~dir ~item in
add_rule
(Build.record_lib_deps ~dir ~kind:dep_kind (List.map virtual_deps ~f:Lib_dep.direct)
>>>
Build.fanout
(Lib_db.closure ~dir ~dep_kind libraries)
(Lib_db.closed_ppx_runtime_deps_of ~dir ~dep_kind
(List.map all_pps ~f:Lib_dep.direct))
>>>
Build.arr (fun (libs, rt_deps) ->
Lib.remove_dups_preserve_order (libs @ rt_deps))
>>>
Build.store_vfile vrequires);
Build.vpath vrequires
let requires ~dir ~dep_kind ~item ~libraries ~preprocess ~virtual_deps =
let real_requires =
real_requires ~dir ~dep_kind ~item ~libraries ~preprocess ~virtual_deps
in
let requires =
if ctx.merlin then
(* We don't depend on the dot_merlin directly, otherwise
everytime it changes we would have to rebuild everything.
.merlin-exists depends on the .merlin and is an empty
file. Depending on it forces the generation of the .merlin
but not recompilation when it changes. Maybe one day we
should add [Build.path_exists] to do the same in
general. *)
Build.path (Path.relative dir ".merlin-exists")
>>>
real_requires
else
real_requires
in
(requires, real_requires)
module Merlin = struct
type t =
{ requires : (unit, Lib.t list) Build.t
; flags : string list
; preprocess : Preprocess.t
; libname : string option
}
let ppx_flags ~dir ~src_dir { preprocess; libname; _ } =
match preprocess with
| Pps { pps; flags } ->
let exe, libs = get_ppx_driver pps ~dir ~dep_kind:Optional in
libs >>^ fun libs ->
let specific_flags, _ =
Arg_spec.expand ~dir:src_dir
[specific_args_for_ppx_rewriters ~dir ~lib_name:libname libs ~for_merlin:true]
()
in
let command =
List.map (Path.reach exe ~from:src_dir :: "--as-ppx" :: specific_flags @ flags)
~f:quote_for_shell
|> String.concat ~sep:" "
in
[sprintf "FLG -ppx \"%s\"" command]
| _ -> Build.return []
let dot_merlin ~dir ({ requires; flags; _ } as t) =
if ctx.merlin then
match Path.extract_build_context dir with
| Some (_, remaindir) ->
let path = Path.relative remaindir ".merlin" in
add_rule
(Build.path path
>>>
Build.update_file (Path.relative dir ".merlin-exists") "");
add_rule (
Build.fanout requires (ppx_flags ~dir ~src_dir:remaindir t)
>>^ (fun (libs, ppx_flags) ->
let internals, externals =
List.partition_map libs ~f:(function
| Lib.Internal (path, _) ->
let path = Path.reach path ~from:remaindir in
Inl ("B " ^ path)
| Lib.External pkg ->
Inr ("PKG " ^ pkg.name))
in
let flags =
match flags with
| [] -> []
| _ -> ["FLG " ^ String.concat flags ~sep:" "]
in
let dot_merlin =
List.concat
[ [ "S ."
; "B " ^ (Path.reach dir ~from:remaindir)
]
; internals
; externals
; flags
; ppx_flags
]
in
dot_merlin
|> String_set.of_list
|> String_set.elements
|> List.map ~f:(Printf.sprintf "%s\n")
|> String.concat ~sep:"")
>>>
Build.update_file_dyn path
)
| _ ->
()
let merge_two a b =
{ requires =
(Build.fanout a.requires b.requires
>>^ fun (x, y) ->
Lib.remove_dups_preserve_order (x @ y))
; flags = a.flags @ b.flags
; preprocess =
if a.preprocess = b.preprocess then
a.preprocess
else
No_preprocessing
; libname =
match a.libname with
| Some _ as x -> x
| None -> b.libname
}
let gen ~dir ts =
if ctx.merlin then
match ts with
| [] -> ()
| t :: ts -> dot_merlin ~dir (List.fold_left ts ~init:t ~f:merge_two)
end
let setup_runtime_deps ~dir ~dep_kind ~item ~libraries ~ppx_runtime_libraries =
let vruntime_deps = Lib_db.vruntime_deps ~dir ~item in