]> matita.cs.unibo.it Git - helm.git/blobdiff - components/library/librarian.ml
branch for universe
[helm.git] / components / library / librarian.ml
diff --git a/components/library/librarian.ml b/components/library/librarian.ml
new file mode 100644 (file)
index 0000000..919edb3
--- /dev/null
@@ -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