]> matita.cs.unibo.it Git - helm.git/blob - components/library/librarian.ml
matita now works reasonably well,
[helm.git] / components / library / librarian.ml
1 exception NoRootFor of string
2
3 let absolutize path =
4  let path = 
5    if String.length path > 0 && path.[0] <> '/' then
6      Sys.getcwd () ^ "/" ^ path
7    else 
8      path
9  in
10    HExtlib.normalize_path path
11 ;;
12
13
14 let find_root path =
15   let path = absolutize path in
16   let paths = List.rev (Str.split (Str.regexp "/") path) in
17   let rec build = function
18     | he::tl as l -> ("/" ^ String.concat "/" (List.rev l) ^ "/") :: build tl
19     | [] -> ["/"]
20   in
21   let paths = List.map HExtlib.normalize_path (build paths) in
22   try HExtlib.find_in paths "root"
23   with Failure "find_in" -> 
24     raise (NoRootFor (path ^ " (" ^ String.concat ", " paths ^ ")"))
25 ;;
26   
27 let ensure_trailing_slash s = 
28   if s = "" then "/" else
29   if s.[String.length s-1] <> '/' then s^"/" else s
30 ;;
31
32 let remove_trailing_slash s = 
33   if s = "" then "" else
34   let len = String.length s in
35   if s.[len-1] = '/' then String.sub s 0 (len-1) else s
36 ;;
37
38 let load_root_file rootpath =
39   let data = HExtlib.input_file rootpath in
40   let lines = Str.split (Str.regexp "\n") data in
41   List.map 
42     (fun l -> 
43       match Str.split (Str.regexp "=") l with
44       | [k;v] -> Pcre.replace ~pat:" " k, Pcre.replace ~pat:" " v
45       | _ -> raise (Failure ("Malformed root file: " ^ rootpath)))
46     lines
47 ;;
48
49 let find_root_for ~include_paths file = 
50  let include_paths = "" :: Sys.getcwd () :: include_paths in
51  try 
52    let path = HExtlib.find_in include_paths file in
53    let path = absolutize path in
54    (* HLog.debug ("file "^file^" resolved as "^path); *)
55    let rootpath, root, buri = 
56      try
57        let mburi = Helm_registry.get "matita.baseuri" in
58        match Str.split (Str.regexp " ") mburi with
59        | [root; buri] when HExtlib.is_prefix_of root path -> 
60            ":registry:", root, buri
61        | _ -> raise (Helm_registry.Key_not_found "matita.baseuri")
62      with Helm_registry.Key_not_found "matita.baseuri" -> 
63        let rootpath = find_root path in
64        let buri = List.assoc "baseuri" (load_root_file rootpath) in
65        rootpath, Filename.dirname rootpath, buri
66    in
67    (* HLog.debug ("file "^file^" rooted by "^rootpath^""); *)
68    let uri = Http_getter_misc.strip_trailing_slash buri in
69    if String.length uri < 5 || String.sub uri 0 5 <> "cic:/" then
70      HLog.error (rootpath ^ " sets an incorrect baseuri: " ^ buri);
71    ensure_trailing_slash root, remove_trailing_slash uri, path
72  with Failure "find_in" -> 
73    HLog.error ("Unable to find: "^file^"\nPaths explored:\n");
74    List.iter (fun x -> HLog.error (" - "^x^"\n")) include_paths;
75    raise (NoRootFor file)
76 ;;
77
78 let baseuri_of_script ~include_paths file = 
79   let root, buri, path = find_root_for ~include_paths file in
80   let path = HExtlib.normalize_path path in
81   let root = HExtlib.normalize_path root in
82   let lpath = Str.split (Str.regexp "/") path in
83   let lroot = Str.split (Str.regexp "/") root in
84   let rec substract l1 l2 =
85     match l1, l2 with
86     | h1::tl1,h2::tl2 when h1 = h2 -> substract tl1 tl2
87     | l,[] -> l
88     | _ -> raise (NoRootFor (file ^" "^path^" "^root))
89   in
90   let extra_buri = substract lpath lroot in
91   let chop name = 
92     assert(Filename.check_suffix name ".ma");
93     try Filename.chop_extension name
94     with Invalid_argument "Filename.chop_extension" -> name
95   in
96   let extra = String.concat "/" extra_buri in
97    root,
98    remove_trailing_slash (HExtlib.normalize_path 
99     (buri ^ "/" ^ chop extra)),
100    path,
101    extra
102 ;;
103
104 let find_roots_in_dir dir =
105   HExtlib.find ~test:(fun f ->
106     Filename.basename f = "root" &&
107     try (Unix.stat f).Unix.st_kind = Unix.S_REG 
108     with Unix.Unix_error _ -> false)
109     dir
110 ;;
111
112 (* make *)
113 let load_deps_file f = 
114   let deps = ref [] in
115   let ic = open_in f in
116   try
117     while true do
118       begin
119         let l = input_line ic in
120         match Str.split (Str.regexp " ") l with
121         | [] -> HLog.error ("malformed deps file: " ^ f); exit 1
122         | he::tl -> deps := (he,tl) :: !deps
123       end
124     done; !deps
125     with End_of_file -> !deps
126 ;;
127
128 type options = (string * string) list
129
130 module type Format =
131   sig
132     type source_object
133     type target_object
134     val load_deps_file: string -> (source_object * source_object list) list
135     val target_of: options -> source_object -> target_object
136     val string_of_source_object: source_object -> string
137     val string_of_target_object: target_object -> string
138     val build: options -> source_object -> bool
139     val root_of: options -> source_object -> string option
140     val mtime_of_source_object: source_object -> float option
141     val mtime_of_target_object: target_object -> float option
142   end
143
144 module Make = functor (F:Format) -> struct
145
146   let prerr_endline _ = ();; 
147
148   let younger_s_t a b = 
149     match F.mtime_of_source_object a, F.mtime_of_target_object b with
150     | Some a, Some b -> a < b
151     | _ -> false (* XXX check if correct *)
152   ;;
153   let younger_t_t a b = 
154     match F.mtime_of_target_object a, F.mtime_of_target_object b with
155     | Some a, Some b -> a < b
156     | _ -> false (* XXX check if correct *)
157   ;;
158
159   let is_built opts t = younger_s_t t (F.target_of opts t);;
160
161   let rec needs_build opts deps compiled (t,dependencies) =
162     prerr_endline ("Checking if "^F.string_of_source_object t^" needs to be built");
163     if List.mem t compiled then
164       (prerr_endline "already compiled";
165       false)
166     else
167     if not (is_built opts t) then
168       (prerr_endline (F.string_of_source_object t^
169        " is not built, thus needs to be built");
170       true)
171     else
172     try
173       let unsat =
174         List.find
175           (needs_build opts deps compiled) 
176           (List.map (fun x -> x, List.assoc x deps) dependencies)
177       in
178         prerr_endline 
179          (F.string_of_source_object t^" depends on "^F.string_of_source_object (fst unsat)^
180          " that needs to be built, thus needs to be built");
181         true
182     with Not_found ->
183       try 
184         let unsat = 
185           List.find (younger_t_t (F.target_of opts t)) 
186            (List.map (F.target_of opts) dependencies)
187         in
188           prerr_endline 
189            (F.string_of_source_object t^" depends on "^F.string_of_target_object
190            unsat^" and "^F.string_of_source_object t^".o is younger than "^
191            F.string_of_target_object unsat^", thus needs to be built");
192           true
193       with Not_found -> false
194   ;;
195
196   let is_buildable opts compiled deps (t,dependencies) =
197     prerr_endline ("Checking if "^F.string_of_source_object t^" is buildable");
198     let b = needs_build opts deps compiled (t,dependencies) in
199     if not b then
200       (prerr_endline (F.string_of_source_object t^
201        " does not need to be built, thus it not buildable");
202       false)
203     else
204     try  
205       let unsat =
206         List.find (needs_build opts deps compiled) 
207           (List.map (fun x -> x, List.assoc x deps) dependencies)
208       in
209         prerr_endline 
210           (F.string_of_source_object t^" depends on "^
211           F.string_of_source_object (fst unsat)^
212           " that needs build, thus is not buildable");
213         false
214     with Not_found -> 
215       prerr_endline 
216         ("None of "^F.string_of_source_object t^
217         " dependencies needs to be built, thus it is buildable");
218       true
219   ;;
220
221   let rec purge_unwanted_roots wanted deps =
222     let roots, rest = 
223        List.partition 
224          (fun (t,d) ->
225            not (List.exists (fun (_,d1) -> List.mem t d1) deps))
226          deps
227     in
228     let newroots = List.filter (fun (t,_) -> List.mem t wanted) roots in
229     if newroots = roots then
230       deps
231     else
232       purge_unwanted_roots wanted (newroots @ rest)
233   ;;
234
235
236   let rec make_aux root local_options compiled failed deps = 
237     let todo = List.filter (is_buildable local_options compiled deps) deps in
238     let todo = List.filter (fun (f,_) -> not (List.mem f failed)) todo in
239     if todo <> [] then
240       let compiled, failed = 
241         let todo =
242           let local, remote =
243             List.partition
244               (fun (file,d) -> d<>[] || F.root_of local_options file = Some root)
245               todo
246           in
247           remote @ local
248         in
249         List.fold_left 
250           (fun (c,f) (file,_) ->
251             let froot = F.root_of local_options file in
252             let rc = 
253               match froot with
254               | Some froot when froot = root ->
255                   F.build local_options file 
256               | Some froot ->
257                   make froot [file]
258               | None -> 
259                   HLog.error ("No root for: "^F.string_of_source_object file);            
260                   false
261             in
262             if rc then (file::c,f)
263             else (c,file::f))
264           (compiled,failed) todo
265       in
266         make_aux root local_options compiled failed deps
267     else
268       compiled, failed
269
270   and  make root targets = 
271     let deps = F.load_deps_file (root^"/depends") in
272     let local_options = load_root_file (root^"/root") in
273     HLog.debug ("Entering directory '"^root^"'");
274     let old_root = Sys.getcwd () in
275     Sys.chdir root;
276     let _compiled, failed =
277       if targets = [] then 
278         make_aux root local_options [] [] deps
279       else
280         make_aux root local_options [] [] (purge_unwanted_roots targets deps)
281     in
282     HLog.debug ("Leaving directory '"^root^"'");
283     Sys.chdir old_root;
284     failed = []
285   ;;
286
287 end
288   
289 let write_deps_file root deps =
290   let oc = open_out "depends" in
291   List.iter (fun (t,d) -> output_string oc (t^" "^String.concat " " d^"\n")) deps;
292   close_out oc;
293   HLog.message ("Generated " ^ Sys.getcwd () ^ "/depends")
294 ;;
295