X-Git-Url: http://matita.cs.unibo.it/gitweb/?p=helm.git;a=blobdiff_plain;f=components%2Flibrary%2Flibrarian.ml;fp=components%2Flibrary%2Flibrarian.ml;h=919edb36c4f9104eb88ec5be974016b12637533c;hp=0000000000000000000000000000000000000000;hb=f61af501fb4608cc4fb062a0864c774e677f0d76;hpb=58ae1809c352e71e7b5530dc41e2bfc834e1aef1 diff --git a/components/library/librarian.ml b/components/library/librarian.ml new file mode 100644 index 000000000..919edb36c --- /dev/null +++ b/components/library/librarian.ml @@ -0,0 +1,383 @@ +let debug = false;; + +exception NoRootFor of string + +let absolutize path = + let path = + if String.length path > 0 && path.[0] <> '/' then + Sys.getcwd () ^ "/" ^ path + else + path + in + HExtlib.normalize_path path +;; + + +let find_root path = + let path = absolutize path in + let paths = List.rev (Str.split (Str.regexp "/") path) in + let rec build = function + | he::tl as l -> ("/" ^ String.concat "/" (List.rev l) ^ "/") :: build tl + | [] -> ["/"] + in + let paths = List.map HExtlib.normalize_path (build paths) in + try HExtlib.find_in paths "root" + with Failure "find_in" -> + raise (NoRootFor (path ^ " (" ^ String.concat ", " paths ^ ")")) +;; + +let ensure_trailing_slash s = + if s = "" then "/" else + if s.[String.length s-1] <> '/' then s^"/" else s +;; + +let remove_trailing_slash s = + if s = "" then "" else + let len = String.length s in + if s.[len-1] = '/' then String.sub s 0 (len-1) else s +;; + +let load_root_file rootpath = + let data = HExtlib.input_file rootpath in + let lines = Str.split (Str.regexp "\n") data in + let clean s = + Pcre.replace ~pat:"[ \t]+" ~templ:" " + (Pcre.replace ~pat:"^ *" (Pcre.replace ~pat:" *$" s)) + in + List.map + (fun l -> + match Str.split (Str.regexp "=") l with + | [k;v] -> clean k, Http_getter_misc.strip_trailing_slash (clean v) + | _ -> raise (Failure ("Malformed root file: " ^ rootpath))) + lines +;; + +let find_root_for ~include_paths file = + let include_paths = "" :: Sys.getcwd () :: include_paths in + try + let path = HExtlib.find_in include_paths file in + let path = absolutize path in +(* HLog.debug ("file "^file^" resolved as "^path); *) + let rootpath, root, buri = + try + let mburi = Helm_registry.get "matita.baseuri" in + match Str.split (Str.regexp " ") mburi with + | [root; buri] when HExtlib.is_prefix_of root path -> + ":registry:", root, buri + | _ -> raise (Helm_registry.Key_not_found "matita.baseuri") + with Helm_registry.Key_not_found "matita.baseuri" -> + let rootpath = find_root path in + let buri = List.assoc "baseuri" (load_root_file rootpath) in + rootpath, Filename.dirname rootpath, buri + in +(* HLog.debug ("file "^file^" rooted by "^rootpath^""); *) + let uri = Http_getter_misc.strip_trailing_slash buri in + if String.length uri < 5 || String.sub uri 0 5 <> "cic:/" then + HLog.error (rootpath ^ " sets an incorrect baseuri: " ^ buri); + ensure_trailing_slash root, remove_trailing_slash uri, path + with Failure "find_in" -> + HLog.error ("We are in: " ^ Sys.getcwd ()); + HLog.error ("Unable to find: "^file^"\nPaths explored:"); + List.iter (fun x -> HLog.error (" - "^x)) include_paths; + raise (NoRootFor file) +;; + +let mk_baseuri root extra = + let chop name = + assert(Filename.check_suffix name ".ma" || + Filename.check_suffix name ".mma"); + try Filename.chop_extension name + with Invalid_argument "Filename.chop_extension" -> name + in + remove_trailing_slash (HExtlib.normalize_path (root ^ "/" ^ chop extra)) +;; + +let baseuri_of_script ~include_paths file = + let root, buri, path = find_root_for ~include_paths file in + let path = HExtlib.normalize_path path in + let root = HExtlib.normalize_path root in + let lpath = Str.split (Str.regexp "/") path in + let lroot = Str.split (Str.regexp "/") root in + let rec substract l1 l2 = + match l1, l2 with + | h1::tl1,h2::tl2 when h1 = h2 -> substract tl1 tl2 + | l,[] -> l + | _ -> raise (NoRootFor (file ^" "^path^" "^root)) + in + let extra_buri = substract lpath lroot in + let extra = String.concat "/" extra_buri in + root, + mk_baseuri buri extra, + path, + extra +;; + +let find_roots_in_dir dir = + HExtlib.find ~test:(fun f -> + Filename.basename f = "root" && + try (Unix.stat f).Unix.st_kind = Unix.S_REG + with Unix.Unix_error _ -> false) + dir +;; + +(* make *) +let load_deps_file f = + let deps = ref [] in + let ic = open_in f in + try + while true do + begin + let l = input_line ic in + match Str.split (Str.regexp " ") l with + | [] -> + HLog.error ("Malformed deps file: " ^ f); + raise (Failure ("Malformed deps file: " ^ f)) + | he::tl -> deps := (he,tl) :: !deps + end + done; !deps + with End_of_file -> !deps +;; + +type options = (string * string) list + +module type Format = + sig + type source_object + type target_object + val load_deps_file: string -> (source_object * source_object list) list + val string_of_source_object: source_object -> string + val string_of_target_object: target_object -> string + val build: options -> source_object -> bool + val root_and_target_of: + options -> source_object -> string option * target_object + val mtime_of_source_object: source_object -> float option + val mtime_of_target_object: target_object -> float option + val is_readonly_buri_of: options -> source_object -> bool + end + +module Make = functor (F:Format) -> struct + + let say s = if debug then prerr_endline ("make: "^s);; + + let unopt_or_call x f y = match x with Some _ -> x | None -> f y;; + + let younger_s_t (_,cs,ct) a b = + let a = try Hashtbl.find cs a with Not_found -> assert false in + let b = + try + match Hashtbl.find ct b with + | Some _ as x -> x + | None -> + match F.mtime_of_target_object b with + | Some t as x -> + Hashtbl.remove ct b; + Hashtbl.add ct b x; x + | x -> x + with Not_found -> assert false + in + match a, b with + | Some a, Some b -> a < b + | _ -> false + ;; + + let younger_t_t (_,_,ct) a b = + let a = + try + match Hashtbl.find ct a with + | Some _ as x -> x + | None -> + match F.mtime_of_target_object a with + | Some t as x -> + Hashtbl.remove ct b; + Hashtbl.add ct a x; x + | x -> x + with Not_found -> assert false + in + let b = + try + match Hashtbl.find ct b with + | Some _ as x -> x + | None -> + match F.mtime_of_target_object b with + | Some t as x -> + Hashtbl.remove ct b; + Hashtbl.add ct b x; x + | x -> x + with Not_found -> assert false + in + match a, b with + | Some a, Some b -> a < b + | _ -> false + ;; + + let is_built opts t tgt = + younger_s_t opts t tgt + ;; + + let assoc4 l k = List.find (fun (k1,_,_,_) -> k1 = k) l;; + + let fst4 = function (x,_,_,_) -> x;; + + let rec needs_build opts deps compiled (t,dependencies,root,tgt) = + say ("Checking if "^F.string_of_source_object t^ " needs to be built"); + if List.mem t compiled then + (say "already compiled"; false) + else + if not (is_built opts t tgt) then + (say(F.string_of_source_object t^" is not built, thus needs to be built"); + true) + else + try + let unsat = + List.find + (needs_build opts deps compiled) + (List.map (assoc4 deps) dependencies) + in + say (F.string_of_source_object t^" depends on "^ + F.string_of_source_object (fst4 unsat)^ + " that needs to be built, thus needs to be built"); + true + with Not_found -> + try + let _,_,_,unsat = + List.find + (fun (_,_,_,tgt1) -> younger_t_t opts tgt tgt1) + (List.map (assoc4 deps) dependencies) + in + say + (F.string_of_source_object t^" depends on "^F.string_of_target_object + unsat^" and "^F.string_of_source_object t^".o is younger than "^ + F.string_of_target_object unsat^", thus needs to be built"); + true + with Not_found -> false + ;; + + let is_buildable opts compiled deps (t,dependencies,root,tgt as what) = + say ("Checking if "^F.string_of_source_object t^" is buildable"); + let b = needs_build opts deps compiled what in + if not b then + (say (F.string_of_source_object t^ + " does not need to be built, thus it not buildable"); + false) + else + try + let unsat,_,_,_ = + List.find (needs_build opts deps compiled) + (List.map (assoc4 deps) dependencies) + in + say (F.string_of_source_object t^" depends on "^ + F.string_of_source_object unsat^ + " that needs build, thus is not buildable"); + false + with Not_found -> + say + ("None of "^F.string_of_source_object t^ + " dependencies needs to be built, thus it is buildable"); + true + ;; + + let rec purge_unwanted_roots wanted deps = + let roots, rest = + List.partition + (fun (t,d,_,_) -> + not (List.exists (fun (_,d1,_,_) -> List.mem t d1) deps)) + deps + in + let newroots = List.filter (fun (t,_,_,_) -> List.mem t wanted) roots in + if newroots = roots then + deps + else + purge_unwanted_roots wanted (newroots @ rest) + ;; + + let is_not_ro (opts,_,_) (f,_,r,_) = + match r with + | Some root -> not (F.is_readonly_buri_of opts f) + | None -> assert false + ;; + + let rec make_aux root (lo,_,ct as opts) compiled failed deps = + let todo = List.filter (is_buildable opts compiled deps) deps in + let todo = List.filter (fun (f,_,_,_)->not (List.mem f failed)) todo in + let todo = + let local, remote = + List.partition (fun (_,_,froot,_) -> froot = Some root) todo + in + let local, skipped = List.partition (is_not_ro opts) local in + List.iter + (fun x -> + HLog.warn("Read only baseuri for: "^F.string_of_source_object(fst4 x))) + skipped; + remote @ local + in + if todo <> [] then + let compiled, failed = + List.fold_left + (fun (c,f) (file,_,froot,tgt) -> + let rc = + match froot with + | Some froot when froot = root -> + Hashtbl.remove ct tgt; + Hashtbl.add ct tgt None; + F.build lo file + | Some froot -> make froot [file] + | None -> + HLog.error ("No root for: "^F.string_of_source_object file); + false + in + if rc then (file::c,f) + else (c,file::f)) + (compiled,failed) todo + in + make_aux root opts compiled failed deps + else + compiled, failed + + and make root targets = + HLog.debug ("Entering directory '"^root^"'"); + let old_root = Sys.getcwd () in + Sys.chdir root; + let deps = F.load_deps_file (root^"/depends") in + let local_options = load_root_file (root^"/root") in + let caches,cachet = Hashtbl.create 73, Hashtbl.create 73 in + (* deps are enriched with these informations to sped up things later *) + let deps = + List.map + (fun (file,d) -> + let r,tgt = F.root_and_target_of local_options file in + Hashtbl.add caches file (F.mtime_of_source_object file); + Hashtbl.add cachet tgt (F.mtime_of_target_object tgt); + file, d, r, tgt) + deps + in + let opts = local_options, caches, cachet in + let _compiled, failed = + if targets = [] then + make_aux root opts [] [] deps + else + make_aux root opts [] [] + (purge_unwanted_roots targets deps) + in + HLog.debug ("Leaving directory '"^root^"'"); + Sys.chdir old_root; + failed = [] + ;; + +end + +let write_deps_file where deps = match where with + | Some root -> + let oc = open_out (root ^ "/depends") in + let map (t, d) = output_string oc (t^" "^String.concat " " d^"\n") in + List.iter map deps; close_out oc; + HLog.message ("Generated: " ^ root ^ "/depends") + | None -> + print_endline (String.concat " " (List.flatten (List.map snd deps))) + +(* FG ***********************************************************************) + +(* scheme uri part as defined in URI Generic Syntax (RFC 3986) *) +let uri_scheme_rex = Pcre.regexp "^[[:alpha:]][[:alnum:]\-+.]*:" + +let is_uri str = + Pcre.pmatch ~rex:uri_scheme_rex str