1 (***********************************************************************)
4 (* Nicolas Pouillard, Berke Durak, projet Gallium, INRIA Rocquencourt *)
6 (* Copyright 2007 Institut National de Recherche en Informatique et *)
7 (* en Automatique. All rights reserved. This file is distributed *)
8 (* under the terms of the Q Public License version 1.0. *)
10 (***********************************************************************)
13 (* Original author: Nicolas Pouillard *)
17 open Pathname.Operators
19 module Resources
= Set.Make
(Pathname
)
21 let print = Pathname.print
27 if Pathname.is_implicit p
then Pathname.pwd
/p
else invalid_arg
(Printf.sprintf
"in_source_dir: %S" p
)
30 if Pathname.is_relative p
then p
31 else invalid_arg
(Printf.sprintf
"in_build_dir: %S" p
)
33 let clean_up_links entry
=
34 if not
!Options.make_links
then entry
else
35 Slurp.filter
begin fun path name _
->
36 let pathname = in_source_dir (path
/name
) in
37 if Pathname.link_to_dir
pathname !Options.build_dir
then
38 let z = Pathname.readlink
pathname in
39 (* Here is one exception where one can use Sys.file_exists directly *)
40 (if not
(Sys.file_exists
z) then
41 Shell.rm
pathname; false)
45 let clean_up_link_to_build () =
46 Options.entry
:= Some
(clean_up_links (the
!Options.entry
))
48 let source_dir_path_set_without_links_to_build =
50 clean_up_link_to_build ();
51 Slurp.fold
(fun path name _
-> StringSet.add
(path
/name
))
52 (the
!Options.entry
) StringSet.empty
56 if !*My_unix.is_degraded
then
59 ignore
(clean_up_link_to_build ())
61 let exists_in_source_dir p
=
62 if !*My_unix.is_degraded
then sys_file_exists
(in_source_dir p
)
63 else StringSet.mem p
!*source_dir_path_set_without_links_to_build
65 let clean p
= Shell.rm_f p
69 let clean () = Shell.chdir
Pathname.pwd
; Shell.rm_rf
!Options.build_dir
76 type suspension
= (Command.t
* (unit -> unit))
82 | Bsuspension
of suspension
85 { mutable built
: build_status
;
86 mutable changed
: knowledge
;
87 mutable dependencies
: Resources.t
}
90 { built
= Bnot_built_yet
;
92 dependencies
= Resources.empty }
94 let print_knowledge f
=
96 | Yes
-> pp_print_string f
"Yes"
97 | No
-> pp_print_string f
"No"
98 | Unknown
-> pp_print_string f
"Unknown"
100 let print_build_status f
=
102 | Bbuilt
-> pp_print_string f
"Bbuilt"
103 | Bnot_built_yet
-> pp_print_string f
"Bnot_built_yet"
104 | Bcannot_be_built
-> pp_print_string f
"Bcannot_be_built"
105 | Bsuspension
(cmd
, _
) ->
106 fprintf f
"@[<2>Bsuspension(%a,@ (<fun> : unit -> unit))@]" Command.print cmd
108 let print_cache_entry f e
=
109 fprintf f
"@[<2>{ @[<2>built =@ %a@];@ @[<2>changed =@ %a@];@ @[<2>dependencies =@ %a@]@ }@]"
110 print_build_status e
.built
print_knowledge e
.changed
Resources.print e
.dependencies
112 let cache = Hashtbl.create
103
115 try Hashtbl.find
cache r
117 let cache_entry = empty () in
118 Hashtbl.add
cache r
cache_entry; cache_entry
120 let fold_cache f x
= Hashtbl.fold f
cache x
122 let print_cache f
() =
123 fprintf f
"@[<hv0>@[<hv2>{:";
124 fold_cache begin fun k v
() ->
125 fprintf f
"@ @[<2>%a =>@ %a@];" print k
print_cache_entry v
129 let print_graph f
() =
130 fprintf f
"@[<hv0>@[<hv2>{:";
131 fold_cache begin fun k v
() ->
132 if not
(Resources.is_empty v
.dependencies
) then
133 fprintf f
"@ @[<2>%a =>@ %a@];" print k
Resources.print v
.dependencies
137 let resource_changed r
=
138 dprintf
10 "resource_changed:@ %a" print r
;
139 (get r
).changed
<- Yes
141 let external_is_up_to_date absolute_path
=
142 let key = "Resource: " ^ absolute_path
in
143 let digest = Digest.file absolute_path
in
146 let digest'
= Digest_cache.get key in
151 is_up_to_date || (Digest_cache.put
key digest; false)
153 let source_is_up_to_date r_in_source_dir r_in_build_dir
=
154 let key = "Resource: " ^ r_in_source_dir
in
155 let digest = Digest.file r_in_source_dir
in
156 let r_is_up_to_date =
157 Pathname.exists r_in_build_dir
&&
159 let digest'
= Digest_cache.get key in
164 r_is_up_to_date || (Digest_cache.put
key digest; false)
166 let prod_is_up_to_date p
=
167 let x = in_build_dir p
in
168 not
(exists_in_source_dir p
) || Pathname.exists
x && Pathname.same_contents
x (in_source_dir p
)
170 let rec resource_has_changed r
=
171 let cache_entry = get r
in
172 match cache_entry.changed
with
177 match cache_entry.built
with
179 | Bsuspension _
-> assert false
180 | Bcannot_be_built
-> false
181 | Bnot_built_yet
-> not
(prod_is_up_to_date r
) in
182 let () = cache_entry.changed
<- if res then Yes
else No
in res
184 let resource_state r
= (get r
).built
186 let resource_built r
= (get r
).built
<- Bbuilt
188 let resource_failed r
= (get r
).built
<- Bcannot_be_built
190 let import_in_build_dir r
=
191 let cache_entry = get r
in
192 let r_in_build_dir = in_build_dir r
in
193 let r_in_source_dir = in_source_dir r
in
194 if source_is_up_to_date r_in_source_dir r_in_build_dir then begin
195 dprintf
5 "%a exists and up to date" print r
;
197 dprintf
5 "%a exists in source dir -> import it" print r
;
198 Shell.mkdir_p
(Pathname.dirname r
);
199 Pathname.copy
r_in_source_dir r_in_build_dir;
200 cache_entry.changed
<- Yes
;
202 cache_entry.built
<- Bbuilt
204 let suspend_resource r cmd kont prods
=
205 let cache_entry = get r
in
206 match cache_entry.built
with
207 | Bsuspension _
-> ()
209 | Bcannot_be_built
-> assert false
211 let kont = begin fun () ->
213 List.iter
begin fun prod
->
214 (get prod
).built
<- Bbuilt
216 end in cache_entry.built
<- Bsuspension
(cmd
, kont)
218 let resume_suspension (cmd
, kont) =
222 let resume_resource r
=
223 let cache_entry = get r
in
224 match cache_entry.built
with
225 | Bsuspension
(s
) -> resume_suspension s
227 | Bcannot_be_built
-> ()
228 | Bnot_built_yet
-> ()
230 let get_optional_resource_suspension r
=
231 match (get r
).built
with
232 | Bsuspension cmd_kont
-> Some cmd_kont
233 | Bbuilt
| Bcannot_be_built
| Bnot_built_yet
-> None
235 let clear_resource_failed r
= (get r
).built
<- Bnot_built_yet
237 let dependencies r
= (get r
).dependencies
239 let fold_dependencies f
=
240 fold_cache (fun k v
-> Resources.fold
(f k
) v
.dependencies)
242 let add_dependency r s
=
243 let cache_entry = get r
in
244 cache_entry.dependencies <- Resources.add s
cache_entry.dependencies
246 let print_dependencies = print_graph
251 let f = Pathname.to_string
(in_build_dir p
) in
252 let buf = Buffer.create
1024 in
253 Buffer.add_string
buf f;
254 (if sys_file_exists
f then Buffer.add_string
buf (Digest.file
f));
255 Digest.string (Buffer.contents
buf)
257 let exists_in_build_dir p
= Pathname.exists
(in_build_dir p
)
262 let split_percent s =
264 let pos = String.index s '%' in
265 Some (String.before s pos, String.after s (pos + 1))
266 with Not_found -> None
268 let extract prefix suffix s =
269 let lprefix = String.length prefix in
270 let lsuffix = String.length suffix in
271 let ls = String.length s in
272 if lprefix + lsuffix > ls then None else
273 let s' = String.sub s lprefix (ls - lsuffix - lprefix) in
274 if equal (prefix ^ s' ^ suffix) s then Some s' else None
277 match split_percent r1 with
278 | Some (x, y) -> extract x y r2
279 | _ -> if equal r1 r2 then Some "" else None
281 let rec subst percent r =
282 match split_percent r with
283 | Some (x, y) -> x ^ percent ^ y
286 let print_env = pp_print_string
289 (* Should normalize *)
290 let import x = Pathname.normalize
x
292 module MetaPath
: sig
297 val mk
: (bool * string) -> t
298 val matchit : t
-> string -> env
option
299 val subst : env
-> t
-> string
300 val print_env : Format.formatter
-> env
-> unit
305 type atoms
= A
of string | V
of string * Glob.globber
307 type env
= (string * string) list
309 exception No_solution
311 let mk (pattern_allowed
, s) = List.map
begin function
312 | `Var
(var_name
, globber
) -> V
(var_name
, globber
)
314 end (Lexers.path_scheme pattern_allowed
(Lexing.from_string
s))
318 let match_prefix s pos prefix
=
319 match String.contains_string
s pos prefix
with
320 | Some
(pos'
) -> if pos = pos'
then pos'
+ String.length prefix
else raise No_solution
321 | None
-> raise No_solution
324 let sl = String.length
s in
325 let rec loop xs
pos acc delta
=
327 | [] -> if pos = sl then acc
else raise No_solution
328 | A prefix
:: xs
-> loop xs
(match_prefix s pos prefix
) acc
0
329 | V
(var
, patt
) :: A s2
:: xs'
->
330 begin match String.contains_string
s (pos + delta
) s2
with
332 let matched = String.sub
s pos (pos'
- pos) in
333 if Glob.eval patt
matched
335 try loop xs'
(pos'
+ String.length s2
) ((var
, matched) :: acc
) 0
336 with No_solution
-> loop xs
pos acc
(pos'
- pos + 1)
337 else loop xs
pos acc
(pos'
- pos + 1)
338 | None
-> raise No_solution
341 let matched = String.sub
s pos (sl - pos) in
342 if Glob.eval patt
matched then (var
, matched) :: acc
else raise No_solution
343 | V _
:: _
-> assert false
345 try Some
(loop p
0 [] 0)
346 with No_solution
-> None
348 let pp_opt pp_elt
f =
350 | None
-> pp_print_string
f "None"
351 | Some
x -> Format.fprintf
f "Some(%a)" pp_elt
x
353 let print_env f env
=
354 List.iter
begin fun (k
, v
) ->
355 if k
= "" then Format.fprintf
f "%%=%s " v
356 else Format.fprintf
f "%%(%s)=%s " k v
360 let res = matchit p s in
361 Format.eprintf "matchit %S %S = %a@." p s (pp_opt print_env) res;
365 assert (matchit "%(path)lib%(libname).a" "libfoo.a" <> None);
366 assert (matchit "%(path)lib%(libname).a" "path/libfoo.a" <> None);
367 assert (matchit "libfoo.a" "libfoo.a" <> None);
368 assert (matchit "lib%(libname).a" "libfoo.a" <> None);
369 assert (matchit "%(path)libfoo.a" "path/libfoo.a" <> None);
370 assert (matchit "foo%" "foobar" <> None);
375 String.concat
"" begin
376 List.map
begin fun x ->
379 | V
(var
, _) -> List.assoc var env
384 type env
= MetaPath.env
385 type resource_pattern
= (Pathname.t
* MetaPath.t
)
387 let print_pattern f (x, _) = Pathname.print f x
389 let import_pattern x = x, MetaPath.mk (true, x)
390 let matchit (_, p
) x = MetaPath.matchit p
x
392 let subst env
s = MetaPath.subst env
(MetaPath.mk (false, s))
393 let subst_any env
s = MetaPath.subst env
(MetaPath.mk (true, s))
394 let subst_pattern env
(_, p
) = MetaPath.subst env p
396 let print_env = MetaPath.print_env