-
Notifications
You must be signed in to change notification settings - Fork 410
/
build_system.ml
1666 lines (1509 loc) · 52.5 KB
/
build_system.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! Stdune
open Import
open Fiber.O
module Vspec = Build.Vspec
(* Where we store stamp files for aliases *)
let alias_dir = Path.(relative build_dir) ".aliases"
(* Where we store stamp files for [stamp_file_for_files_of] *)
let misc_dir = Path.(relative build_dir) ".misc"
module Promoted_to_delete = struct
module P = Utils.Persistent(struct
type t = Path.Set.t
let name = "PROMOTED-TO-DELETE"
let version = 1
end)
let db = ref Path.Set.empty
let fn = Path.relative Path.build_dir ".to-delete-in-source-tree"
let add p = db := Path.Set.add !db p
let load () =
Option.value ~default:Path.Set.empty (P.load fn)
let dump () =
if Path.build_dir_exists () then
load ()
|> Path.Set.union !db
|> P.dump fn
end
let files_in_source_tree_to_delete () =
Promoted_to_delete.load ()
module Exec_status = struct
type rule_evaluation = (Action.t * Deps.t) Fiber.Future.t
type rule_execution = unit Fiber.Future.t
type eval_rule = unit -> (Action.t * Deps.t) Fiber.t
type exec_rule = rule_evaluation -> unit Fiber.t
module Evaluating_rule = struct
type t =
{ rule_evaluation : rule_evaluation
; exec_rule : exec_rule
}
end
module Running = struct
type t =
{ rule_evaluation : rule_evaluation
; rule_execution : rule_execution
}
end
module Not_started = struct
type t =
{ eval_rule : eval_rule
; exec_rule : exec_rule
}
end
type t =
| Not_started of Not_started.t
| Evaluating_rule of Evaluating_rule.t
| Running of Running.t
end
let rule_loc ~file_tree ~loc ~dir =
match loc with
| Some loc -> loc
| None ->
let dir = Path.drop_optional_build_context dir in
let file =
match
Option.bind (File_tree.find_dir file_tree dir)
~f:File_tree.Dir.dune_file
with
| Some file -> File_tree.Dune_file.path file
| None -> Path.relative dir "_unknown_"
in
Loc.in_file (Path.to_string file)
module Internal_rule = struct
module Id : sig
type t
val to_int : t -> int
val compare : t -> t -> Ordering.t
val gen : unit -> t
module Set : Set.S with type elt = t
module Top_closure : Top_closure.S with type key := t
end = struct
module M = struct
type t = int
let compare (x : int) y = compare x y
end
include M
module Set = Set.Make(M)
module Top_closure = Top_closure.Make(Set)
let to_int x = x
let counter = ref 0
let gen () =
let n = !counter in
counter := n + 1;
n
end
type t =
{ id : Id.t
; static_deps : Static_deps.t Lazy.t
; targets : Path.Set.t
; context : Context.t option
; build : (unit, Action.t) Build.t
; mode : Dune_file.Rule.Mode.t
; loc : Loc.t option
; dir : Path.t
; mutable exec : Exec_status.t
; (* Reverse dependencies discovered so far, labelled by the
requested target *)
mutable rev_deps : (Path.t * t) list
; (* Transitive reverse dependencies discovered so far. *)
mutable transitive_rev_deps : Id.Set.t
}
let compare a b = Id.compare a.id b.id
let loc ~file_tree ~dir t = rule_loc ~file_tree ~dir ~loc:t.loc
let lib_deps t =
(* Forcing this lazy ensures that the various globs and
[if_file_exists] are resolved inside the [Build.t] value. *)
ignore (Lazy.force t.static_deps : Static_deps.t);
Build_interpret.lib_deps t.build
(* Represent the build goal given by the user. This rule is never
actually executed and is only used starting point of all
dependency paths. *)
let root =
{ id = Id.gen ()
; static_deps = lazy Static_deps.empty
; targets = Path.Set.empty
; context = None
; build = Build.return (Action.Progn [])
; mode = Standard
; loc = None
; dir = Path.root
; exec = Not_started { eval_rule = (fun _ -> assert false)
; exec_rule = (fun _ -> assert false)
}
; rev_deps = []
; transitive_rev_deps = Id.Set.empty
}
let dependency_cycle ~last ~last_rev_dep ~last_requested_file =
let rec build_loop acc t =
if t.id = last.id then
last_requested_file :: acc
else
let requested_file, rev_dep =
List.find_exn
t.rev_deps
~f:(fun (_, t) -> Id.Set.mem t.transitive_rev_deps last.id)
in
build_loop (requested_file :: acc) rev_dep
in
let loop = build_loop [last_requested_file] last_rev_dep in
die "Dependency cycle between the following files:\n %s"
(String.concat ~sep:"\n--> "
(List.map loop ~f:Path.to_string))
let dep_path_var = Fiber.Var.create ()
let push_rev_dep t requested_file ~f =
Fiber.Var.get dep_path_var >>= fun x ->
let rev_dep = Option.value x ~default:root in
if Id.Set.mem rev_dep.transitive_rev_deps t.id then
dependency_cycle ~last:t ~last_rev_dep:rev_dep
~last_requested_file:requested_file;
t.rev_deps <- (requested_file, rev_dep) :: t.rev_deps;
t.transitive_rev_deps <-
Id.Set.union t.transitive_rev_deps rev_dep.transitive_rev_deps;
let on_error exn =
Dep_path.reraise exn (Path requested_file)
in
Fiber.Var.set dep_path_var t (Fiber.with_error_handler f ~on_error)
end
module File_kind = struct
type 'a t =
| Ignore_contents : unit t
| Sexp_file : 'a Vfile_kind.t -> 'a t
let eq : type a b. a t -> b t -> (a, b) Type_eq.t option = fun a b ->
match a, b with
| Ignore_contents, Ignore_contents -> Some Type_eq.T
| Sexp_file a , Sexp_file b -> Vfile_kind.eq a b
| _ -> None
let eq_exn a b = Option.value_exn (eq a b)
end
module File_spec = struct
type 'a t =
{ rule : Internal_rule.t (* Rule which produces it *)
; mutable kind : 'a File_kind.t
; mutable data : 'a option
}
type packed = T : _ t -> packed
let create rule kind =
T { rule; kind; data = None }
end
module Alias0 = struct
module T : sig
type t = private
{ dir : Path.t
; name : string
}
val make : string -> dir:Path.t -> t
val of_user_written_path : loc:Loc.t -> Path.t -> t
end = struct
type t =
{ dir : Path.t
; name : string
}
let make name ~dir =
if not (Path.is_in_build_dir dir) || String.contains name '/' then
Exn.code_error "Alias0.make: Invalid alias"
[ "name", Sexp.Encoder.string name
; "dir", Path.to_sexp dir
];
{ dir; name }
let of_user_written_path ~loc path =
if not (Path.is_in_build_dir path) then
Errors.fail loc "Invalid alias!\n\
Tried to reference path outside build dir: %S"
(Path.to_string_maybe_quoted path);
{ dir = Path.parent_exn path
; name = Path.basename path
}
end
include T
let pp fmt t = Path.pp fmt (Path.relative t.dir t.name)
let suffix = "-" ^ String.make 32 '0'
let name t = t.name
let dir t = t.dir
let fully_qualified_name t = Path.relative t.dir t.name
let stamp_file t =
Path.relative (Path.insert_after_build_dir_exn t.dir ".aliases")
(t.name ^ suffix)
let dep t = Build.path (stamp_file t)
let find_dir_specified_on_command_line ~dir ~file_tree =
match File_tree.find_dir file_tree dir with
| None ->
die "From the command line:\n\
@{<error>Error@}: Don't know about directory %s!"
(Path.to_string_maybe_quoted dir)
| Some dir -> dir
let dep_multi_contexts ~dir ~name ~file_tree ~contexts =
ignore
(find_dir_specified_on_command_line ~dir ~file_tree : File_tree.Dir.t);
Build.paths (List.map contexts ~f:(fun ctx ->
let dir = Path.append (Path.(relative build_dir) ctx) dir in
stamp_file (make ~dir name)))
let standard_aliases = Hashtbl.create 7
let is_standard = Hashtbl.mem standard_aliases
let make_standard name =
Hashtbl.add standard_aliases name ();
make name
open Build.O
let dep_rec_internal ~name ~dir ~ctx_dir =
Build.lazy_no_targets (lazy (
File_tree.Dir.fold dir ~traverse_ignored_dirs:false
~init:(Build.return true)
~f:(fun dir acc ->
let path = Path.append ctx_dir (File_tree.Dir.path dir) in
let fn = stamp_file (make ~dir:path name) in
acc
>>>
Build.if_file_exists fn
~then_:(Build.path fn >>^ fun _ -> false)
~else_:(Build.arr (fun x -> x)))))
let dep_rec t ~loc ~file_tree =
let ctx_dir, src_dir =
Path.extract_build_context_dir t.dir |> Option.value_exn
in
match File_tree.find_dir file_tree src_dir with
| None ->
Build.fail { fail = fun () ->
Errors.fail loc "Don't know about directory %s!"
(Path.to_string_maybe_quoted src_dir) }
| Some dir ->
dep_rec_internal ~name:t.name ~dir ~ctx_dir
>>^ fun is_empty ->
if is_empty && not (is_standard t.name) then
Errors.fail loc
"This alias is empty.\n\
Alias %S is not defined in %s or any of its descendants."
t.name (Path.to_string_maybe_quoted src_dir)
let dep_rec_multi_contexts ~dir:src_dir ~name ~file_tree ~contexts =
let open Build.O in
let dir = find_dir_specified_on_command_line ~dir:src_dir ~file_tree in
Build.all (List.map contexts ~f:(fun ctx ->
let ctx_dir = Path.(relative build_dir) ctx in
dep_rec_internal ~name ~dir ~ctx_dir))
>>^ fun is_empty_list ->
let is_empty = List.for_all is_empty_list ~f:(fun x -> x) in
if is_empty && not (is_standard name) then
die "From the command line:\n\
@{<error>Error@}: Alias %S is empty.\n\
It is not defined in %s or any of its descendants."
name (Path.to_string_maybe_quoted src_dir)
let default = make_standard "default"
let runtest = make_standard "runtest"
let install = make_standard "install"
let doc = make_standard "doc"
let private_doc = make_standard "doc-private"
let lint = make_standard "lint"
let all = make_standard "all"
let check = make_standard "check"
let package_install ~(context : Context.t) ~pkg =
make (sprintf ".%s-files" (Package.Name.to_string pkg))
~dir:context.build_dir
end
module Dir_status = struct
type waiting_for_load_dir =
{ mutable lazy_generators : (unit -> unit) list }
type collection_stage =
| Loading
| Pending of waiting_for_load_dir
type alias_action =
{ stamp : Digest.t
; action : (unit, Action.t) Build.t
; locks : Path.t list
; context : Context.t
; loc : Loc.t option
}
type alias =
{ mutable deps : Path.Set.t
; mutable dyn_deps : (unit, Path.Set.t) Build.t
; mutable actions : alias_action list
}
type rules_collector =
{ mutable rules : Build_interpret.Rule.t list
; mutable aliases : alias String.Map.t
; mutable stage : collection_stage
}
type t =
| Collecting_rules of rules_collector
| Loaded of Path.Set.t (* set of targets in the directory *)
| Forward of Path.t (* Load this directory first *)
| Failed_to_load
end
module Files_of = struct
type t =
{ files_by_ext : Path.t list String.Map.t
; dir_hash : string
; mutable stamps : Path.t String.Map.t
}
end
module Trace = struct
module Entry = struct
type t =
{ rule_digest : Digest.t
; targets_digest : Digest.t
}
end
(* Keyed by the first target *)
type t = Entry.t Path.Table.t
let file = Path.relative Path.build_dir ".db"
module P = Utils.Persistent(struct
type nonrec t = t
let name = "INCREMENTAL-DB"
let version = 2
end)
let dump t =
if Path.build_dir_exists () then P.dump file t
let load () =
match P.load file with
| Some t -> t
| None -> Path.Table.create 1024
end
type extra_sub_directories_to_keep =
| All
| These of String.Set.t
type hook =
| Rule_started
| Rule_completed
type t =
{ (* File specification by targets *)
files : File_spec.packed Path.Table.t
; contexts : Context.t String.Map.t
; (* Table from target to digest of
[(deps (filename + contents), targets (filename only), action)] *)
trace : Trace.t
; file_tree : File_tree.t
; mutable local_mkdirs : Path.Set.t
; mutable dirs : Dir_status.t Path.Table.t
; mutable gen_rules :
(dir:Path.t -> string list -> extra_sub_directories_to_keep) String.Map.t
; mutable load_dir_stack : Path.t list
; (* Set of directories under _build that have at least one rule and
all their ancestors. *)
mutable build_dirs_to_keep : Path.Set.t
; files_of : Files_of.t Path.Table.t
; mutable prefix : (unit, unit) Build.t option
; hook : hook -> unit
; (* Package files are part of *)
packages : Package.Name.t Path.Table.t
}
let string_of_paths set =
Path.Set.to_list set
|> List.map ~f:(fun p -> sprintf "- %s"
(Path.to_string_maybe_quoted
(Path.drop_optional_build_context p)))
|> String.concat ~sep:"\n"
let set_rule_generators t generators =
assert (String.Map.keys generators = String.Map.keys t.contexts);
t.gen_rules <- generators
let get_dir_status t ~dir =
Path.Table.find_or_add t.dirs dir ~f:(fun _ ->
if Path.is_in_source_tree dir then
Dir_status.Loaded (File_tree.files_of t.file_tree dir)
else if Path.equal dir Path.build_dir then
(* Not allowed to look here *)
Dir_status.Loaded Path.Set.empty
else if not (Path.is_managed dir) then
Dir_status.Loaded
(match Path.readdir_unsorted dir with
| exception _ -> Path.Set.empty
| files ->
Path.Set.of_list (List.map files ~f:(Path.relative dir)))
else begin
let (ctx, sub_dir) = Option.value_exn (Path.extract_build_context dir) in
if ctx = ".aliases" then
Forward (Path.(append build_dir) sub_dir)
else if ctx <> "install" && not (String.Map.mem t.contexts ctx) then
Dir_status.Loaded Path.Set.empty
else
Collecting_rules
{ rules = []
; aliases = String.Map.empty
; stage = Pending { lazy_generators = [] }
}
end)
let entry_point t ~f =
(match t.load_dir_stack with
| [] ->
()
| stack ->
Exn.code_error
"Build_system.entry_point: called inside the rule generator callback"
["stack", Sexp.Encoder.list Path.to_sexp stack]
);
f ()
module Target = Build_interpret.Target
module Pre_rule = Build_interpret.Rule
let get_file : type a. t -> Path.t -> a File_kind.t -> a File_spec.t = fun t fn kind ->
match Path.Table.find t.files fn with
| None -> die "no rule found for %s" (Path.to_string fn)
| Some (File_spec.T file) ->
let Type_eq.T = File_kind.eq_exn kind file.kind in
file
let vfile_to_string (type a) (module K : Vfile_kind.S with type t = a) _fn x =
K.to_string x
module Build_exec = struct
open Build.Repr
let exec bs t x =
let rec exec
: type a b. Deps.t ref -> (a, b) t -> a -> b = fun dyn_deps t x ->
match t with
| Arr f -> f x
| Targets _ -> x
| Store_vfile (Vspec.T (fn, kind)) ->
let file = get_file bs fn (Sexp_file kind) in
file.data <- Some x;
Write_file (fn, vfile_to_string kind fn x)
| Compose (a, b) ->
exec dyn_deps a x |> exec dyn_deps b
| First t ->
let x, y = x in
(exec dyn_deps t x, y)
| Second t ->
let x, y = x in
(x, exec dyn_deps t y)
| Split (a, b) ->
let x, y = x in
let x = exec dyn_deps a x in
let y = exec dyn_deps b y in
(x, y)
| Fanout (a, b) ->
let a = exec dyn_deps a x in
let b = exec dyn_deps b x in
(a, b)
| Paths _ -> x
| Paths_for_rule _ -> x
| Paths_glob state -> get_glob_result_exn state
| Contents p -> Io.read_file p
| Lines_of p -> Io.lines_of_file p
| Vpath (Vspec.T (fn, kind)) ->
let file : b File_spec.t = get_file bs fn (Sexp_file kind) in
Option.value_exn file.data
| Dyn_paths t ->
let fns = exec dyn_deps t x in
dyn_deps := Deps.add_paths !dyn_deps fns;
x
| Record_lib_deps _ -> x
| Fail { fail } -> fail ()
| If_file_exists (_, state) ->
exec dyn_deps (get_if_file_exists_exn state) x
| Catch (t, on_error) -> begin
try
exec dyn_deps t x
with exn ->
on_error exn
end
| Lazy_no_targets t ->
exec dyn_deps (Lazy.force t) x
| Env_var _ ->
x
| Memo m ->
match m.state with
| Evaluated (x, deps) ->
dyn_deps := Deps.union !dyn_deps deps;
x
| Evaluating ->
die "Dependency cycle evaluating memoized build arrow %s" m.name
| Unevaluated ->
m.state <- Evaluating;
let dyn_deps' = ref Deps.empty in
match exec dyn_deps' m.t x with
| x ->
m.state <- Evaluated (x, !dyn_deps');
dyn_deps := Deps.union !dyn_deps !dyn_deps';
x
| exception exn ->
m.state <- Unevaluated;
reraise exn
in
let dyn_deps = ref Deps.empty in
let result = exec dyn_deps (Build.repr t) x in
(result, !dyn_deps)
end
(* [copy_source] is [true] for rules copying files from the source directory *)
let add_spec t fn spec ~copy_source =
match Path.Table.find t.files fn with
| None ->
Path.Table.add t.files fn spec
| Some (File_spec.T { rule; _ }) ->
match copy_source, rule.mode with
| true, (Standard | Not_a_rule_stanza) ->
Errors.warn (Internal_rule.loc rule ~dir:(Path.parent_exn fn)
~file_tree:t.file_tree)
"File %s is both generated by a rule and present in the source tree.\n\
As a result, the rule is currently ignored, however this will become an error \
in the future.\n\
%t"
(String.maybe_quoted (Path.basename fn))
(fun ppf ->
match rule.mode with
| Not_a_rule_stanza ->
Format.fprintf ppf "Delete file %s to get rid of this warning."
(Path.to_string_maybe_quoted (Path.drop_optional_build_context fn))
| Standard ->
Format.fprintf ppf
"To keep the current behavior and get rid of this warning, add a field \
(fallback) to the rule."
| _ -> assert false);
Path.Table.add t.files fn spec
| _ ->
let (File_spec.T { rule = rule2; _ }) = spec in
let string_of_loc = function
| None -> "<internal location>"
| Some { Loc.start; _ } ->
start.pos_fname ^ ":" ^ string_of_int start.pos_lnum
in
die "Multiple rules generated for %s:\n\
- %s\n\
- %s"
(Path.to_string_maybe_quoted fn)
(if copy_source then
"<internal copy rule>"
else
string_of_loc rule.loc)
(string_of_loc rule2.loc)
let create_file_specs t targets rule ~copy_source =
List.iter targets ~f:(function
| Target.Normal fn ->
add_spec t fn (File_spec.create rule Ignore_contents) ~copy_source
| Target.Vfile (Vspec.T (fn, kind)) ->
add_spec t fn (File_spec.create rule (Sexp_file kind)) ~copy_source)
(* This contains the targets of the actions that are being executed. On exit, we need to
delete them as they might contain garbage *)
let pending_targets = ref Path.Set.empty
let () =
Hooks.End_of_build.always (fun () ->
let fns = !pending_targets in
pending_targets := Path.Set.empty;
Path.Set.iter fns ~f:Path.unlink_no_err)
let compute_targets_digest_after_rule_execution targets =
let good, bad =
List.partition_map targets ~f:(fun fn ->
match Utils.Cached_digest.refresh fn with
| digest -> Left digest
| exception (Unix.Unix_error _ | Sys_error _) -> Right fn)
in
match bad with
| [] -> Digest.string (Marshal.to_string good [])
| missing ->
die "@{<error>Error@}: Rule failed to generate the following targets:\n%s"
(string_of_paths (Path.Set.of_list missing))
let make_local_dir t fn =
if not (Path.Set.mem t.local_mkdirs fn) then begin
Path.mkdir_p fn;
t.local_mkdirs <- Path.Set.add t.local_mkdirs fn
end
let make_local_dirs t paths =
Path.Set.iter paths ~f:(make_local_dir t)
let make_local_parent_dirs_for t ~map_path path =
let path = map_path path in
if Path.is_managed path then
Option.iter (Path.parent path) ~f:(make_local_dir t)
let make_local_parent_dirs t paths ~map_path =
Path.Set.iter paths ~f:(make_local_parent_dirs_for t ~map_path)
let sandbox_dir = Path.relative Path.build_dir ".sandbox"
let locks : (Path.t, Fiber.Mutex.t) Hashtbl.t = Hashtbl.create 32
let rec with_locks mutexes ~f =
match mutexes with
| [] -> f ()
| m :: mutexes ->
Fiber.Mutex.with_lock
(Hashtbl.find_or_add locks m ~f:(fun _ -> Fiber.Mutex.create ()))
(fun () -> with_locks mutexes ~f)
let remove_old_artifacts t ~dir ~subdirs_to_keep =
if not (Path.is_in_build_dir dir) ||
Path.Table.mem t.files (Path.relative dir Config.dune_keep_fname) then
()
else
match Path.readdir_unsorted dir with
| exception _ -> ()
| files ->
List.iter files ~f:(fun fn ->
let path = Path.relative dir fn in
match Unix.lstat (Path.to_string path) with
| { st_kind = S_DIR; _ } -> begin
match subdirs_to_keep with
| All -> ()
| These set ->
if String.Set.mem set fn ||
Path.Set.mem t.build_dirs_to_keep path then
()
else
Path.rm_rf path
end
| exception _ ->
if not (Path.Table.mem t.files path) then Path.unlink path
| _ ->
if not (Path.Table.mem t.files path) then Path.unlink path)
let no_rule_found =
let fail fn ~loc =
Errors.fail_opt loc "No rule found for %s" (Utils.describe_target fn)
in
fun t ~loc fn ->
match Utils.analyse_target fn with
| Other _ -> fail fn ~loc
| Regular (ctx, _) ->
if String.Map.mem t.contexts ctx then
fail fn ~loc
else
die "Trying to build %s but build context %s doesn't exist.%s"
(Path.to_string_maybe_quoted fn)
ctx
(hint ctx (String.Map.keys t.contexts))
| Alias (ctx, fn') ->
if String.Map.mem t.contexts ctx then
fail fn ~loc
else
let fn = Path.append (Path.relative Path.build_dir ctx) fn' in
die "Trying to build alias %s but build context %s doesn't exist.%s"
(Path.to_string_maybe_quoted fn)
ctx
(hint ctx (String.Map.keys t.contexts))
let parallel_iter_paths paths ~f =
Fiber.parallel_iter (Path.Set.to_list paths) ~f
let parallel_iter_deps deps ~f =
parallel_iter_paths (Deps.paths deps) ~f
let rec compile_rule t ?(copy_source=false) pre_rule =
let { Pre_rule.
context
; build
; targets = target_specs
; sandbox
; mode
; locks
; loc
; dir
} =
pre_rule
in
let targets = Target.paths target_specs in
let static_deps =
lazy (Build_interpret.static_deps build ~all_targets:(targets_of t)
~file_tree:t.file_tree)
in
let eval_rule () =
t.hook Rule_started;
let static_deps = Lazy.force static_deps in
wait_for_deps t (Static_deps.rule_deps static_deps) ~loc
>>| fun () ->
Build_exec.exec t build ()
in
let exec_rule (rule_evaluation : Exec_status.rule_evaluation) =
let static_deps = Lazy.force static_deps in
let static_deps = Static_deps.action_deps static_deps in
Fiber.fork_and_join_unit
(fun () ->
wait_for_deps ~loc t static_deps)
(fun () ->
Fiber.Future.wait rule_evaluation >>= fun (action, dyn_deps) ->
wait_for_path_deps ~loc t (Deps.path_diff dyn_deps static_deps)
>>| fun () ->
(action, dyn_deps))
>>= fun (action, dyn_deps) ->
make_local_dir t dir;
let all_deps = Deps.union static_deps dyn_deps in
let targets_as_list = Path.Set.to_list targets in
let env =
match context with
| None -> Env.empty
| Some c -> c.env
in
let head_target = List.hd targets_as_list in
let prev_trace = Path.Table.find t.trace head_target in
let rule_digest =
let trace =
( Deps.trace all_deps env,
List.map targets_as_list ~f:Path.to_string,
Option.map context ~f:(fun c -> c.name),
Action.for_shell action)
in
Digest.string (Marshal.to_string trace [])
in
let targets_digest =
match List.map targets_as_list ~f:Utils.Cached_digest.file with
| l -> Some (Digest.string (Marshal.to_string l []))
| exception (Unix.Unix_error _ | Sys_error _) -> None
in
let sandbox_dir =
if sandbox then
Some (Path.relative sandbox_dir (Digest.to_hex rule_digest))
else
None
in
let force =
!Clflags.force &&
List.exists targets_as_list ~f:Path.is_alias_stamp_file
in
let something_changed =
match prev_trace, targets_digest with
| Some prev_trace, Some targets_digest ->
prev_trace.rule_digest <> rule_digest ||
prev_trace.targets_digest <> targets_digest
| _ -> true
in
begin
if force || something_changed then begin
List.iter targets_as_list ~f:Path.unlink_no_err;
pending_targets := Path.Set.union targets !pending_targets;
let action =
match sandbox_dir with
| Some sandbox_dir ->
Path.rm_rf sandbox_dir;
let sandboxed path = Path.sandbox_managed_paths ~sandbox_dir path in
make_local_parent_dirs t (Deps.paths all_deps) ~map_path:sandboxed;
make_local_dir t (sandboxed dir);
Action.sandbox action
~sandboxed
~deps:all_deps
~targets:targets_as_list
| None ->
action
in
make_local_dirs t (Action.chdirs action);
with_locks locks ~f:(fun () ->
Action_exec.exec ~context ~targets action) >>| fun () ->
Option.iter sandbox_dir ~f:Path.rm_rf;
(* All went well, these targets are no longer pending *)
pending_targets := Path.Set.diff !pending_targets targets;
let targets_digest =
compute_targets_digest_after_rule_execution targets_as_list
in
Path.Table.replace t.trace ~key:head_target
~data:{ rule_digest; targets_digest }
end else
Fiber.return ()
end >>| fun () ->
begin
match mode with
| Standard | Fallback | Not_a_rule_stanza | Ignore_source_files -> ()
| Promote | Promote_but_delete_on_clean ->
Path.Set.iter targets ~f:(fun path ->
let in_source_tree = Option.value_exn (Path.drop_build_context path) in
if not (Path.exists in_source_tree) ||
(Utils.Cached_digest.file path <>
Utils.Cached_digest.file in_source_tree) then begin
if mode = Promote_but_delete_on_clean then
Promoted_to_delete.add in_source_tree;
Scheduler.ignore_for_watch in_source_tree;
Io.copy_file ~src:path ~dst:in_source_tree ()
end)
end;
t.hook Rule_completed
in
let rule =
let id = Internal_rule.Id.gen () in
{ Internal_rule.
id
; static_deps
; targets
; build
; context
; exec = Not_started { eval_rule; exec_rule }
; mode
; loc
; dir
; transitive_rev_deps = Internal_rule.Id.Set.singleton id
; rev_deps = []
}
in
create_file_specs t target_specs rule ~copy_source
and setup_copy_rules t ~ctx_dir ~non_target_source_files =
Path.Set.iter non_target_source_files ~f:(fun path ->
let ctx_path = Path.append ctx_dir path in
let build = Build.copy ~src:path ~dst:ctx_path in
(* We temporarily allow overrides while setting up copy rules from
the source directory so that artifact that are already present
in the source directory are not re-computed.
This allows to keep generated files in tarballs. Maybe we
should allow it on a case-by-case basis though. *)
compile_rule t (Pre_rule.make build ~context:None) ~copy_source:true)
and load_dir t ~dir = ignore (load_dir_and_get_targets t ~dir : Path.Set.t)
and targets_of t ~dir = load_dir_and_get_targets t ~dir
and load_dir_and_get_targets t ~dir =
match get_dir_status t ~dir with
| Failed_to_load -> raise Already_reported
| Loaded targets -> targets
| Forward dir' ->
load_dir t ~dir:dir';
begin match get_dir_status t ~dir with
| Loaded targets -> targets
| _ -> assert false
end
| Collecting_rules collector ->
let lazy_generators =
match collector.stage with
| Loading ->
die "recursive dependency between directories:\n %s"
(String.concat ~sep:"\n--> "
(List.map t.load_dir_stack ~f:Utils.describe_target))
| Pending { lazy_generators } ->
collector.stage <- Loading;
lazy_generators
in
collector.stage <- Loading;
t.load_dir_stack <- dir :: t.load_dir_stack;
try
load_dir_step2_exn t ~dir ~collector ~lazy_generators
with exn ->
(match Path.Table.find t.dirs dir with
| Some (Loaded _) -> ()
| _ ->
(match t.load_dir_stack with
| [] -> assert false
| x :: l ->
t.load_dir_stack <- l;
assert (Path.equal x dir)));
Path.Table.replace t.dirs ~key:dir ~data:Failed_to_load;
reraise exn
and load_dir_step2_exn t ~dir ~collector ~lazy_generators =
List.iter lazy_generators ~f:(fun f -> f ());
let context_name, sub_dir = Option.value_exn (Path.extract_build_context dir) in
(* Load all the rules *)
let extra_subdirs_to_keep =
if context_name = "install" then
These String.Set.empty
else
let gen_rules = Option.value_exn (String.Map.find t.gen_rules context_name) in
gen_rules ~dir (Option.value_exn (Path.explode sub_dir))
in
let rules = collector.rules in
(* Compute alias rules *)
let alias_dir = Path.append (Path.relative alias_dir context_name) sub_dir in
let alias_rules, alias_stamp_files =
let open Build.O in
let aliases = collector.aliases in
let aliases =
if String.Map.mem collector.aliases "default" then
aliases
else
match Path.extract_build_context_dir dir with
| None -> aliases
| Some (ctx_dir, src_dir) ->
match File_tree.find_dir t.file_tree src_dir with
| None -> aliases
| Some dir ->
String.Map.add aliases "default"
{ deps = Path.Set.empty
; dyn_deps =
(Alias0.dep_rec_internal ~name:"install" ~dir ~ctx_dir
>>^ fun (_ : bool) ->
Path.Set.empty)
; actions = []
}
in
String.Map.foldi aliases ~init:([], Path.Set.empty)
~f:(fun name { Dir_status. deps; dyn_deps; actions } (rules, alias_stamp_files) ->
let base_path = Path.relative alias_dir name in
let rules, deps =
List.fold_left actions ~init:(rules, deps)
~f:(fun (rules, deps)