]> matita.cs.unibo.it Git - helm.git/blob - components/library/librarian.ml
tagged 0.5.0-rc1
[helm.git] / components / library / librarian.ml
1 let debug = false;;
2
3 exception NoRootFor of string
4
5 let absolutize path =
6  let path = 
7    if String.length path > 0 && path.[0] <> '/' then
8      Sys.getcwd () ^ "/" ^ path
9    else 
10      path
11  in
12    HExtlib.normalize_path path
13 ;;
14
15
16 let find_root path =
17   let path = absolutize path in
18   let paths = List.rev (Str.split (Str.regexp "/") path) in
19   let rec build = function
20     | he::tl as l -> ("/" ^ String.concat "/" (List.rev l) ^ "/") :: build tl
21     | [] -> ["/"]
22   in
23   let paths = List.map HExtlib.normalize_path (build paths) in
24   try HExtlib.find_in paths "root"
25   with Failure "find_in" -> 
26     raise (NoRootFor (path ^ " (" ^ String.concat ", " paths ^ ")"))
27 ;;
28   
29 let ensure_trailing_slash s = 
30   if s = "" then "/" else
31   if s.[String.length s-1] <> '/' then s^"/" else s
32 ;;
33
34 let remove_trailing_slash s = 
35   if s = "" then "" else
36   let len = String.length s in
37   if s.[len-1] = '/' then String.sub s 0 (len-1) else s
38 ;;
39
40 let load_root_file rootpath =
41   let data = HExtlib.input_file rootpath in
42   let lines = Str.split (Str.regexp "\n") data in
43   let clean s = 
44     Pcre.replace ~pat:"[ \t]+" ~templ:" "
45       (Pcre.replace ~pat:"^ *" (Pcre.replace ~pat:" *$" s))
46   in
47   List.map 
48     (fun l -> 
49       match Str.split (Str.regexp "=") l with
50       | [k;v] -> clean k, Http_getter_misc.strip_trailing_slash (clean v)
51       | _ -> raise (Failure ("Malformed root file: " ^ rootpath)))
52     lines
53 ;;
54
55 let find_root_for ~include_paths file = 
56  let include_paths = "" :: Sys.getcwd () :: include_paths in
57  try 
58    let path = HExtlib.find_in include_paths file in
59    let path = absolutize path in
60 (*     HLog.debug ("file "^file^" resolved as "^path);  *)
61    let rootpath, root, buri = 
62      try
63        let mburi = Helm_registry.get "matita.baseuri" in
64        match Str.split (Str.regexp " ") mburi with
65        | [root; buri] when HExtlib.is_prefix_of root path -> 
66            ":registry:", root, buri
67        | _ -> raise (Helm_registry.Key_not_found "matita.baseuri")
68      with Helm_registry.Key_not_found "matita.baseuri" -> 
69        let rootpath = find_root path in
70        let buri = List.assoc "baseuri" (load_root_file rootpath) in
71        rootpath, Filename.dirname rootpath, buri
72    in
73 (*     HLog.debug ("file "^file^" rooted by "^rootpath^"");  *)
74    let uri = Http_getter_misc.strip_trailing_slash buri in
75    if String.length uri < 5 || String.sub uri 0 5 <> "cic:/" then
76      HLog.error (rootpath ^ " sets an incorrect baseuri: " ^ buri);
77    ensure_trailing_slash root, remove_trailing_slash uri, path
78  with Failure "find_in" -> 
79    HLog.error ("We are in: " ^ Sys.getcwd ());
80    HLog.error ("Unable to find: "^file^"\nPaths explored:");
81    List.iter (fun x -> HLog.error (" - "^x)) include_paths;
82    raise (NoRootFor file)
83 ;;
84
85 let mk_baseuri root extra =
86   let chop name = 
87     assert(Filename.check_suffix name ".ma" ||
88       Filename.check_suffix name ".mma");
89     try Filename.chop_extension name
90     with Invalid_argument "Filename.chop_extension" -> name
91   in
92    remove_trailing_slash (HExtlib.normalize_path (root ^ "/" ^ chop extra))
93 ;;
94
95 let baseuri_of_script ~include_paths file = 
96   let root, buri, path = find_root_for ~include_paths file in
97   let path = HExtlib.normalize_path path in
98   let root = HExtlib.normalize_path root in
99   let lpath = Str.split (Str.regexp "/") path in
100   let lroot = Str.split (Str.regexp "/") root in
101   let rec substract l1 l2 =
102     match l1, l2 with
103     | h1::tl1,h2::tl2 when h1 = h2 -> substract tl1 tl2
104     | l,[] -> l
105     | _ -> raise (NoRootFor (file ^" "^path^" "^root))
106   in
107   let extra_buri = substract lpath lroot in
108   let extra = String.concat "/" extra_buri in
109    root,
110    mk_baseuri buri extra,
111    path,
112    extra
113 ;;
114
115 let find_roots_in_dir dir =
116   HExtlib.find ~test:(fun f ->
117     Filename.basename f = "root" &&
118     try (Unix.stat f).Unix.st_kind = Unix.S_REG 
119     with Unix.Unix_error _ -> false)
120     dir
121 ;;
122
123 (* make *)
124 let load_deps_file f = 
125   let deps = ref [] in
126   let ic = open_in f in
127   try
128     while true do
129       begin
130         let l = input_line ic in
131         match Str.split (Str.regexp " ") l with
132         | [] -> 
133             HLog.error ("Malformed deps file: " ^ f); 
134             raise (Failure ("Malformed deps file: " ^ f)) 
135         | he::tl -> deps := (he,tl) :: !deps
136       end
137     done; !deps
138     with End_of_file -> !deps
139 ;;
140
141 type options = (string * string) list
142
143 module type Format =
144   sig
145     type source_object
146     type target_object
147     val load_deps_file: string -> (source_object * source_object list) list
148     val string_of_source_object: source_object -> string
149     val string_of_target_object: target_object -> string
150     val build: options -> source_object -> bool
151     val root_and_target_of: 
152           options -> source_object -> string option * target_object
153     val mtime_of_source_object: source_object -> float option
154     val mtime_of_target_object: target_object -> float option
155     val is_readonly_buri_of: options -> source_object -> bool
156   end
157
158 module Make = functor (F:Format) -> struct
159
160   let say s = if debug then prerr_endline ("make: "^s);; 
161
162   let unopt_or_call x f y = match x with Some _ -> x | None -> f y;;
163
164   let younger_s_t (_,cs,ct) a b = 
165     let a = try Hashtbl.find cs a with Not_found -> assert false in
166     let b = 
167       try
168         match Hashtbl.find ct b with
169         | Some _ as x -> x
170         | None ->
171            match F.mtime_of_target_object b with
172            | Some t as x -> 
173                Hashtbl.remove ct b;
174                Hashtbl.add ct b x; x
175            | x -> x
176       with Not_found -> assert false
177     in
178     match a, b with 
179     | Some a, Some b -> a < b
180     | _ -> false
181   ;;
182
183   let younger_t_t (_,_,ct) a b = 
184     let a = 
185       try
186         match Hashtbl.find ct a with
187         | Some _ as x -> x
188         | None ->
189            match F.mtime_of_target_object a with
190            | Some t as x -> 
191                Hashtbl.remove ct b;
192                Hashtbl.add ct a x; x
193            | x -> x
194       with Not_found -> assert false
195     in
196     let b = 
197       try
198         match Hashtbl.find ct b with
199         | Some _ as x -> x
200         | None ->
201            match F.mtime_of_target_object b with
202            | Some t as x -> 
203                Hashtbl.remove ct b;
204                Hashtbl.add ct b x; x
205            | x -> x
206       with Not_found -> assert false
207     in
208     match a, b with
209     | Some a, Some b -> a < b
210     | _ -> false
211   ;;
212
213   let is_built opts t tgt = 
214     younger_s_t opts t tgt
215   ;;
216
217   let assoc4 l k = List.find (fun (k1,_,_,_) -> k1 = k) l;;
218
219   let fst4 = function (x,_,_,_) -> x;;
220
221   let rec needs_build opts deps compiled (t,dependencies,root,tgt) =
222     say ("Checking if "^F.string_of_source_object t^ " needs to be built");
223     if List.mem t compiled then
224       (say "already compiled"; false)
225     else
226     if not (is_built opts t tgt) then
227       (say(F.string_of_source_object t^" is not built, thus needs to be built");
228       true)
229     else
230     try
231       let unsat =
232         List.find
233           (needs_build opts deps compiled) 
234           (List.map (assoc4 deps) dependencies)
235       in
236         say (F.string_of_source_object t^" depends on "^
237          F.string_of_source_object (fst4 unsat)^
238          " that needs to be built, thus needs to be built");
239         true
240     with Not_found ->
241       try 
242         let _,_,_,unsat = 
243           List.find 
244            (fun (_,_,_,tgt1) -> younger_t_t opts tgt tgt1) 
245            (List.map (assoc4 deps) dependencies)
246         in
247           say 
248            (F.string_of_source_object t^" depends on "^F.string_of_target_object
249            unsat^" and "^F.string_of_source_object t^".o is younger than "^
250            F.string_of_target_object unsat^", thus needs to be built");
251           true
252       with Not_found -> false
253   ;;
254
255   let is_buildable opts compiled deps (t,dependencies,root,tgt as what) =
256     say ("Checking if "^F.string_of_source_object t^" is buildable");
257     let b = needs_build opts deps compiled what in
258     if not b then
259       (say (F.string_of_source_object t^
260        " does not need to be built, thus it not buildable");
261       false)
262     else
263     try  
264       let unsat,_,_,_ =
265         List.find (needs_build opts deps compiled) 
266           (List.map (assoc4 deps) dependencies)
267       in
268         say (F.string_of_source_object t^" depends on "^
269           F.string_of_source_object unsat^
270           " that needs build, thus is not buildable");
271         false
272     with Not_found -> 
273       say 
274         ("None of "^F.string_of_source_object t^
275         " dependencies needs to be built, thus it is buildable");
276       true
277   ;;
278
279   let rec purge_unwanted_roots wanted deps =
280     let roots, rest = 
281        List.partition 
282          (fun (t,d,_,_) ->
283            not (List.exists (fun (_,d1,_,_) -> List.mem t d1) deps))
284          deps
285     in
286     let newroots = List.filter (fun (t,_,_,_) -> List.mem t wanted) roots in
287     if newroots = roots then
288       deps
289     else
290       purge_unwanted_roots wanted (newroots @ rest)
291   ;;
292
293   let is_not_ro (opts,_,_) (f,_,r,_) =
294     match r with
295     | Some root -> not (F.is_readonly_buri_of opts f)
296     | None -> assert false
297   ;;
298
299   let rec make_aux root (lo,_,ct as opts) compiled failed deps = 
300     let todo = List.filter (is_buildable opts compiled deps) deps in
301     let todo = List.filter (fun (f,_,_,_)->not (List.mem f failed)) todo in
302     let todo =
303       let local, remote =
304         List.partition (fun (_,_,froot,_) -> froot = Some root) todo
305       in
306       let local, skipped = List.partition (is_not_ro opts) local in
307       List.iter 
308        (fun x -> 
309         HLog.warn("Read only baseuri for: "^F.string_of_source_object(fst4 x)))
310        skipped;
311       remote @ local
312     in
313     if todo <> [] then
314       let compiled, failed = 
315         List.fold_left 
316           (fun (c,f) (file,_,froot,tgt) ->
317             let rc = 
318               match froot with
319               | Some froot when froot = root -> 
320                   Hashtbl.remove ct tgt;
321                   Hashtbl.add ct tgt None;
322                   F.build lo file 
323               | Some froot -> make froot [file]
324               | None -> 
325                   HLog.error ("No root for: "^F.string_of_source_object file);
326                   false
327             in
328             if rc then (file::c,f)
329             else (c,file::f))
330           (compiled,failed) todo
331       in
332         make_aux root opts compiled failed deps
333     else
334       compiled, failed
335
336   and  make root targets = 
337     HLog.debug ("Entering directory '"^root^"'");
338     let old_root = Sys.getcwd () in
339     Sys.chdir root;
340     let deps = F.load_deps_file (root^"/depends") in
341     let local_options = load_root_file (root^"/root") in
342     let caches,cachet = Hashtbl.create 73, Hashtbl.create 73 in
343     (* deps are enriched with these informations to sped up things later *)
344     let deps = 
345       List.map 
346         (fun (file,d) -> 
347           let r,tgt = F.root_and_target_of local_options file in
348           Hashtbl.add caches file (F.mtime_of_source_object file);
349           Hashtbl.add cachet tgt (F.mtime_of_target_object tgt); 
350           file, d, r, tgt)
351         deps
352     in
353     let opts = local_options, caches, cachet in
354     let _compiled, failed =
355       if targets = [] then 
356         make_aux root opts [] [] deps
357       else
358         make_aux root opts [] [] 
359           (purge_unwanted_roots targets deps)
360     in
361     HLog.debug ("Leaving directory '"^root^"'");
362     Sys.chdir old_root;
363     failed = []
364   ;;
365
366 end
367   
368 let write_deps_file where deps = match where with 
369    | Some root ->
370       let oc = open_out (root ^ "/depends") in
371       let map (t, d) = output_string oc (t^" "^String.concat " " d^"\n") in
372       List.iter map deps; close_out oc;
373       HLog.message ("Generated: " ^ root ^ "/depends")
374    | None -> 
375       print_endline (String.concat " " (List.flatten (List.map snd deps)))
376       
377 (* FG ***********************************************************************)
378
379 (* scheme uri part as defined in URI Generic Syntax (RFC 3986) *)
380 let uri_scheme_rex = Pcre.regexp "^[[:alpha:]][[:alnum:]\-+.]*:"
381
382 let is_uri str =
383    Pcre.pmatch ~rex:uri_scheme_rex str