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
|
(***********************************************************************)
(* ocamlbuild *)
(* *)
(* Nicolas Pouillard, Berke Durak, projet Gallium, INRIA Rocquencourt *)
(* *)
(* Copyright 2007 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the Q Public License version 1.0. *)
(* *)
(***********************************************************************)
(* Original author: Nicolas Pouillard *)
open My_std
open Format
open Log
open Outcome
module Resources = Resource.Resources
exception Exit_rule_error of string
exception Failed
type env = Pathname.t -> Pathname.t
type builder = Pathname.t list list -> (Pathname.t, exn) Outcome.t list
type action = env -> builder -> Command.t
type digest_command = { digest : string; command : Command.t }
type 'a gen_rule =
{ name : string;
tags : Tags.t;
deps : Pathname.t list; (* These pathnames must be normalized *)
prods : 'a list; (* Note that prods also contains stamp *)
stamp : 'a option;
code : env -> builder -> digest_command }
type rule = Pathname.t gen_rule
type rule_scheme = Resource.resource_pattern gen_rule
let name_of_rule r = r.name
let deps_of_rule r = r.deps
let prods_of_rule r = r.prods
let stamp_of_rule r = r.stamp
type 'a rule_printer = (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a gen_rule -> unit
let compare _ _ = assert false
let print_rule_name f r = pp_print_string f r.name
let print_resource_list = List.print Resource.print
let print_rule_contents ppelt f r =
fprintf f "@[<v2>{@ @[<2>name =@ %S@];@ @[<2>tags =@ %a@];@ @[<2>deps =@ %a@];@ @[<2>prods = %a@];@ @[<2>code = <fun>@]@]@ }"
r.name Tags.print r.tags print_resource_list r.deps (List.print ppelt) r.prods
let pretty_print ppelt f r =
fprintf f "@[<hv2>rule@ %S@ ~deps:%a@ ~prods:%a@ <fun>@]"
r.name print_resource_list r.deps (List.print ppelt) r.prods
let print = print_rule_name
let subst env rule =
let subst_resources = List.map (Resource.subst env) in
let subst_resource_patterns = List.map (Resource.subst_pattern env) in
let finder next_finder p = next_finder (Resource.subst_any env p) in
let stamp = match rule.stamp with None -> None | Some x -> Some (Resource.subst_pattern env x) in
let prods = subst_resource_patterns rule.prods in
{ (rule) with name = sbprintf "%s (%a)" rule.name Resource.print_env env;
prods = prods;
deps = subst_resources rule.deps; (* The substition should preserve normalization of pathnames *)
stamp = stamp;
code = (fun env -> rule.code (finder env)) }
exception Can_produce of rule
let can_produce target rule =
try
List.iter begin fun resource ->
match Resource.matchit resource target with
| Some env -> raise (Can_produce (subst env rule))
| None -> ()
end rule.prods; None
with Can_produce r -> Some r
(* let tags_matches tags r = if Tags.does_match tags r.tags then Some r else None *)
let digest_prods r =
List.fold_right begin fun p acc ->
let f = Pathname.to_string (Resource.in_build_dir p) in
if sys_file_exists f then (f, Digest.file f) :: acc else acc
end r.prods []
let digest_deps r dyndeps =
let buf = Buffer.create 1024 in
let add_resource r = Buffer.add_string buf (Digest.to_hex (Resource.digest r)) in
Buffer.add_string buf "deps:";
List.iter add_resource r.deps;
Buffer.add_string buf "dyndeps:";
Resources.iter add_resource dyndeps;
Digest.to_hex (Digest.string (Buffer.contents buf))
let digest_rule r dyndeps action =
let buf = Buffer.create 1024 in
Buffer.add_string buf action.digest;
let add_resource r = Buffer.add_string buf (Resource.digest r) in
Buffer.add_string buf "prods:";
List.iter add_resource r.prods;
Buffer.add_string buf "deps:";
List.iter add_resource r.deps;
Buffer.add_string buf "dyndeps:";
Resources.iter add_resource dyndeps;
Digest.string (Buffer.contents buf)
let cached_digest r =
try Some (Digest_cache.get ("Rule: " ^ r.name))
with Not_found -> None
let store_digest r digest = Digest_cache.put ("Rule: " ^ r.name) digest
let print_digest f x = pp_print_string f (Digest.to_hex x)
let exists2 find p rs =
try Some (find p rs) with Not_found -> None
let build_deps_of_tags builder tags =
match Command.deps_of_tags tags with
| [] -> []
| deps -> List.map Outcome.good (builder (List.map (fun x -> [x]) deps))
let build_deps_of_tags_on_cmd builder =
Command.iter_tags begin fun tags ->
match Command.deps_of_tags tags with
| [] -> ()
| deps -> List.iter ignore_good (builder (List.map (fun x -> [x]) deps))
end
let call builder r =
let dyndeps = ref Resources.empty in
let builder rs =
let results = builder rs in
List.map begin fun res ->
match res with
| Good res' ->
let () = dprintf 10 "new dyndep for %S(%a): %S" r.name print_resource_list r.prods res' in
dyndeps := Resources.add res' !dyndeps;
List.iter (fun x -> Resource.Cache.add_dependency x res') r.prods;
res
| Bad _ -> res
end results in
let () = dprintf 5 "start rule %a" print r in
let action = r.code (fun x -> x) builder in
build_deps_of_tags_on_cmd builder action.command;
let dyndeps = !dyndeps in
let () = dprintf 10 "dyndeps: %a" Resources.print dyndeps in
let (reason, cached) =
match exists2 List.find (fun r -> not (Resource.exists_in_build_dir r)) r.prods with
| Some r -> (`cache_miss_missing_prod r, false)
| _ ->
begin match exists2 List.find Resource.Cache.resource_has_changed r.deps with
| Some r -> (`cache_miss_changed_dep r, false)
| _ ->
begin match exists2 Resources.find Resource.Cache.resource_has_changed dyndeps with
| Some r -> (`cache_miss_changed_dyn_dep r, false)
| _ ->
begin match cached_digest r with
| None -> (`cache_miss_no_digest, false)
| Some d ->
let rule_digest = digest_rule r dyndeps action in
if d = rule_digest then (`cache_hit, true)
else (`cache_miss_digest_changed(d, rule_digest), false)
end
end
end
in
let explain_reason l =
raw_dprintf (l+1) "mid rule %a: " print r;
match reason with
| `cache_miss_missing_prod r ->
dprintf l "cache miss: a product is not in build dir (%a)" Resource.print r
| `cache_miss_changed_dep r ->
dprintf l "cache miss: a dependency has changed (%a)" Resource.print r
| `cache_miss_changed_dyn_dep r ->
dprintf l "cache miss: a dynamic dependency has changed (%a)" Resource.print r
| `cache_miss_no_digest ->
dprintf l "cache miss: no digest found for %S (the command, a dependency, or a product)"
r.name
| `cache_hit -> dprintf (l+1) "cache hit"
| `cache_miss_digest_changed(old_d, new_d) ->
dprintf l "cache miss: the digest has changed for %S (the command, a dependency, or a product: %a <> %a)"
r.name print_digest old_d print_digest new_d
in
let prod_digests = digest_prods r in
(if not cached then List.iter Resource.clean r.prods);
(if !Options.nothing_should_be_rebuilt && not cached then
(explain_reason (-1);
let msg = sbprintf "Need to rebuild %a through the rule `%a'" print_resource_list r.prods print r in
raise (Exit_rule_error msg)));
explain_reason 3;
let thunk () =
try
if cached then Command.execute ~pretend:true action.command
else
begin match r.stamp with
| Some stamp ->
reset_filesys_cache ();
let digest_deps = digest_deps r dyndeps in
with_output_file stamp (fun oc -> output_string oc digest_deps)
| None -> ()
end;
List.iter (fun r -> Resource.Cache.resource_built r) r.prods;
(if not cached then
let new_rule_digest = digest_rule r dyndeps action in
let new_prod_digests = digest_prods r in
let () = store_digest r new_rule_digest in
List.iter begin fun p ->
let f = Pathname.to_string (Resource.in_build_dir p) in
(try let digest = List.assoc f prod_digests in
let new_digest = List.assoc f new_prod_digests in
if digest <> new_digest then raise Not_found
with Not_found -> Resource.Cache.resource_changed p)
end r.prods);
dprintf 5 "end rule %a" print r
with exn -> (List.iter Resource.clean r.prods; raise exn)
in
if cached
then thunk ()
else List.iter (fun x -> Resource.Cache.suspend_resource x action.command thunk r.prods) r.prods
let (get_rules, add_rule) =
let rules = ref [] in
(fun () -> !rules),
begin fun pos r ->
try
let _ = List.find (fun x -> x.name = r.name) !rules in
raise (Exit_rule_error (sbprintf "Rule.add_rule: already exists: (%a)" print r))
with Not_found ->
match pos with
| `bottom -> rules := !rules @ [r]
| `top -> rules := r :: !rules
| `after s ->
rules :=
List.fold_right begin fun x acc ->
if x.name = s then x :: r :: acc else x :: acc
end !rules []
| `before s ->
rules :=
List.fold_right begin fun x acc ->
if x.name = s then r :: x :: acc else x :: acc
end !rules []
end
let rule name ?(tags=[]) ?(prods=[]) ?(deps=[]) ?prod ?dep ?stamp ?(insert = `bottom) code =
let res_add import xs xopt =
let init =
match xopt with
| None -> []
| Some r -> [import r]
in
List.fold_right begin fun x acc ->
let r = import x in
if List.mem r acc then
failwith (sprintf "in rule %s, multiple occurences of the resource %s" name x)
else r :: acc
end xs init
in
if prods = [] && prod = None && stamp = None then raise (Exit_rule_error "Can't make a rule that produce nothing");
let stamp, prods =
match stamp with
| None -> None, prods
| Some stamp ->
Some (Resource.import_pattern stamp), stamp :: prods
in
let prods = res_add Resource.import_pattern prods prod in
let code env build =
let cmd = code env build in
{ digest = Command.digest cmd
; command = cmd }
in
add_rule insert
{ name = name;
tags = List.fold_right Tags.add tags Tags.empty;
deps = res_add Resource.import (* should normalize *) deps dep;
stamp = stamp;
prods = prods;
code = code }
module Common_commands = struct
open Command
let mv src dest = Cmd (S [A"mv"; P src; Px dest])
let cp src dest = Cmd (S [A"cp"; P src; Px dest])
let cp_p src dest = Cmd (S [A"cp"; A"-p"; P src; Px dest])
let ln_f pointed pointer = Cmd (S [A"ln"; A"-f"; P pointed; Px pointer])
let ln_s pointed pointer = Cmd (S[A"ln"; A"-s"; P pointed; Px pointer])
let rm_f x = Cmd (S [A"rm"; A"-f"; Px x])
let chmod opts file = Cmd (S[A"chmod"; opts; Px file])
let cmp a b = Cmd (S[A"cmp"; P a; Px b])
end
open Common_commands
let copy_rule name ?insert src dest =
rule name ?insert ~prod:dest ~dep:src
begin fun env _ ->
let src = env src and dest = env dest in
Shell.mkdir_p (Pathname.dirname dest);
cp_p src dest
end
|