ocaml/camlp4/build/YaM.ml

846 lines
29 KiB
OCaml

(*
*
* Copyright (C) 2003-2004 Damien Pous
*
* YaM is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* YaM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with YaM; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*)
open Printf
let print_deps = ref false
let print_cmds = ref true
let debug_status = ref false
let debug_deps = ref false
let debug_build = ref false
(* ---- Définition des unités de compilation ---- *)
(* environnement / options *)
type options_t = {
ocaml: string ref;
ocamlc: string ref;
ocamlopt: string ref;
ocamldep: string ref;
ocamldoc: string ref;
ocamlyacc: string ref;
ocamllex: string ref;
ocamlglade: string ref;
ocaml_P4: string ref;
ocaml_P4_opt: string ref;
ocaml_Flags: string ref;
ocaml_OptFlags: string ref;
ocaml_ByteFlags: string ref;
ocaml_LinkFlags: string ref;
ocaml_ForPack: string ref;
ocaml_Includes: string list ref;
ocaml_ExtIncludes: string list ref;
ocaml_ExtLibraries: string list ref;
}
(* options par défaut *)
let getenv n d = try Sys.getenv n with Not_found -> d
let options = ref {
ocaml = ref (getenv "OCAML" "ocaml");
ocamlc = ref (getenv "OCAMLC" "ocamlc.opt");
ocamlopt = ref (getenv "OCAMLOPT" "ocamlopt.opt");
ocamldep = ref (getenv "OCAMLDEP" "ocamldep.opt");
ocamldoc = ref (getenv "OCAMLDOC" "ocamldoc.opt");
ocamlyacc = ref (getenv "OCAMLYACC" "ocamlyacc");
ocamllex = ref (getenv "OCAMLLEX" "ocamllex.opt");
ocamlglade = ref (getenv "OCAMLGLADE" "lablgladecc2 -hide-default");
ocaml_P4 = ref "";
ocaml_P4_opt = ref "";
ocaml_Flags = ref "";
ocaml_OptFlags = ref "";
ocaml_ByteFlags = ref "";
ocaml_LinkFlags = ref "";
ocaml_ForPack = ref "";
ocaml_Includes = ref [];
ocaml_ExtIncludes = ref [];
ocaml_ExtLibraries = ref [];
}
let dir = ref ""
(* calcul d'une valeur dans un nouvel environnement (options) *)
let new_scope v =
let options' = !options in
options := {
ocaml = ref !(options'.ocaml);
ocamlc = ref !(options'.ocamlc);
ocamlopt = ref !(options'.ocamlopt);
ocamldep = ref !(options'.ocamldep);
ocamldoc = ref !(options'.ocamldoc);
ocamlyacc = ref !(options'.ocamlyacc);
ocamllex = ref !(options'.ocamllex);
ocamlglade = ref !(options'.ocamlglade);
ocaml_P4 = ref !(options'.ocaml_P4);
ocaml_P4_opt = ref !(options'.ocaml_P4_opt);
ocaml_Flags = ref !(options'.ocaml_Flags);
ocaml_OptFlags = ref !(options'.ocaml_OptFlags);
ocaml_ByteFlags = ref !(options'.ocaml_ByteFlags);
ocaml_LinkFlags = ref !(options'.ocaml_LinkFlags);
ocaml_ForPack = ref !(options'.ocaml_ForPack);
ocaml_Includes = ref !(options'.ocaml_Includes);
ocaml_ExtIncludes = ref !(options'.ocaml_ExtIncludes);
ocaml_ExtLibraries = ref !(options'.ocaml_ExtLibraries);
};
let v' = Lazy.force v in
options := options';
v'
(* type (interne) des unités *)
type unit_t = {
name: string;
(* ensembles de fichiers : *)
sources: string list; (* sources *)
targets: string list; (* générés ET ciblés *)
pregenerated: string list; (* à créer avant de calculer des dépendances *)
trash: string list; (* générables "à nettoyer" *)
(* cibles automatiques (quand aucune cible n'est spécifiée) *)
auto_targets: string list;
(* éventuelles sous-unités *)
sub_units: unit_t list;
(* objet généré (à lier) *)
objects: (string*string) option; (* natif / bytecode *)
(* dépendances d'une cible f *)
dependencies: native: bool -> string -> string list;
(* fichiers dont dépendent le résultat précédent *)
dep_files: string -> string list;
(* commande pour la compilation d'une cible f
* renvoie (cmd, out)
* - cmd est la commande à exécuter
* - out est l'ensemble des fichiers générés par cette commande
*)
compile_cmd: string -> string * string list;
}
(* ---- Utilitaires ---- *)
let (^=) r s = r := if !r="" then s else !r^" "^s
let (+=) l x = l := x :: !l
let (@=) l x = l := !l @ x
let (^^) s t = if t="" then s else if s="" then t else s^" "^t
let id x = x
let fcons f = fun x q -> f x::q
let rec rev_map_append f l1 l2 = match l1 with
| [] -> l2
| x::q -> rev_map_append f q (f x::l2)
let string_of_list f = List.fold_left (fun acc x -> acc^^(f x)) ""
let flatten = List.fold_left (^^) ""
let select b = if b then fst else snd
let select_set b = if b then (fun (_,y) z -> z,y) else (fun (x,_) z -> x,z)
let rec print_inc = function
| [] -> ""
| x::q -> "-I "^x^^(print_inc q)
let print_p4 = function "" -> "" | s -> "-pp "^s
let oget x = function Some x -> x | _ -> x
let ofold f = List.fold_right (function Some o -> f o | _ -> id)
let omap f l = ofold (fcons f) l []
let otfold f = List.fold_right (function {objects=Some o} -> f o | _ -> id)
let otmap f l = otfold (fcons f) l []
let mtime f = (Unix.stat f).Unix.st_mtime
let file_newer f1 f2 = not (Sys.file_exists f2) || mtime f1 > mtime f2
let exists_file_newer f =
let mtf = mtime f in
List.exists (fun f' -> mtime f' > mtf)
let silent_remove f = try Sys.remove f with Sys_error _ -> ()
let touch_file f = if not (Sys.file_exists f) then close_out (open_out f)
exception CmdError of string
let call = Sys.command
let ecall cmd = if (call cmd) <> 0 then raise (CmdError cmd)
let exitf ?(err=1) x = kprintf (fun msg -> eprintf "%s" msg; exit err) x
let mk_ext e = (fun n -> n^e), (fun n -> Filename.check_suffix n e)
let ml , is_ml = mk_ext ".ml"
let mli , is_mli = mk_ext ".mli"
let mly , is_mly = mk_ext ".mly"
let mll , is_mll = mk_ext ".mll"
let glade, is_glade = mk_ext ".glade"
let cmo , is_cmo = mk_ext ".cmo"
let cmi , is_cmi = mk_ext ".cmi"
let cmx , is_cmx = mk_ext ".cmx"
let cma , is_cma = mk_ext ".cma"
let cmxa , is_cmxa = mk_ext ".cmxa"
let oo , is_o = mk_ext ".o"
let aa , is_a = mk_ext ".a"
let cc , is_c = mk_ext ".c"
let run , is_run = mk_ext ".run"
let opt , is_opt = mk_ext ".opt"
let annot, is_annot = mk_ext ".annot"
let rec iter_units f = function
| [] -> ()
| u::q -> f u; iter_units f u.sub_units; iter_units f q
let rec fold_units f a = function
| [] -> a
| u::q -> fold_units f (f u (fold_units f a u.sub_units)) q
let get_line c =
let s = input_line c in
s, String.length s
(* ---- Outils OCaml (c,dep,opt...) ---- *)
(* parsing de la sortie d'ocamldep *)
let tokenize ?(skip=false) c =
try
if skip then (
(* ignorage du premier bloc (cmo/cmx) *)
let last c = let s = input_line c in s.[String.length s - 1] in
try while last c = '\\' do () done
with End_of_file -> ()
);
let s,ls = get_line c in
let i = String.index s ':' in
let rec aux i acc ((s,ls) as sls) =
if s.[i]='\\' then aux 4 acc (get_line c)
else
let j = String.index_from s i ' ' in
if j+1 = ls then String.sub s i (j-i) :: acc
else let k,sls' = if s.[j+1]='\\' then 4, get_line c else j+1, sls in
aux k (String.sub s i (j-i) :: acc) sls'
in
aux (i+2) [] (s,ls)
with End_of_file -> []
let ocamldep ~native ~depc ~sf =
let nat = if native then "-native " else "" in
let cmd = depc^^nat^^sf in
if !print_deps then printf "%s\n%!" cmd;
let c_in = Unix.open_process_in cmd in
let deps = tokenize ~skip:(native && is_ml sf) c_in in
let deps' =
if native then deps
else List.map (fun f -> if is_cmo f then cmi (Filename.chop_extension f) else f) deps
in
ignore (Unix.close_process_in c_in);
sf::deps'
let ocamldepi ~native ~depc ~f ~n =
ocamldep ~native ~depc ~sf:(if is_cmi f then mli n else ml n)
let ocamlobj ~bytec ~optc ~f ~n =
(select (is_cmx f) (optc, bytec))^" -c "^(ml n), [f; cmi n]
let ocamlobji ~bytec ~optc ~impl_flags ~f ~n =
if is_cmi f then bytec^" -c "^(mli n), [f]
else (select (is_cmx f) (optc, bytec))^^impl_flags^^"-c"^^(ml n), [f]
let for_pack o = if !(o.ocaml_ForPack) = "" then ""
else "-for-pack" ^^ !(o.ocaml_ForPack)
let ocaml_options ?(o= !options) ?(flags="") ?(byte_flags="") ?(opt_flags="") ?pp ?(includes=[]) ?(ext_includes=[]) n =
let flags' = (print_inc !(o.ocaml_Includes))^^(print_inc includes)^^(print_p4 (oget !(o.ocaml_P4) pp)) in
let depc = !(o.ocamldep)^^flags' in
let flags' = !(o.ocaml_Flags)^^flags^^(print_inc !(o.ocaml_ExtIncludes))^^(print_inc ext_includes)^^flags' in
let bytec = !(o.ocamlc) ^^flags'^^byte_flags^^ !(o.ocaml_ByteFlags) in
let opt_flags' = (print_inc !(o.ocaml_Includes))^^(print_inc includes)^^(print_p4 (oget !(o.ocaml_P4_opt) pp)) in
let opt_flags' = !(o.ocaml_Flags)^^flags^^(print_inc !(o.ocaml_ExtIncludes))^^(print_inc ext_includes)^^opt_flags' in
let optc = !(o.ocamlopt)^^(for_pack o)^^opt_flags'^^opt_flags ^^ !(o.ocaml_OptFlags) in
(Filename.concat !dir n), depc, bytec, optc
(* fabrication générique d'unités *)
let generic_unit
~name ?(sources=[]) ~targets ?(trash=targets) ?(auto_targets=[]) ?(sub_units=[]) ?(pregenerated=[])
?objects ~dependencies ?(dep_files=fun _->[]) ~compile_cmd ()
=
{ name=name; sources=sources; targets=targets; objects=objects;
trash=trash; auto_targets=auto_targets; sub_units=sub_units; pregenerated=pregenerated;
dependencies=dependencies; compile_cmd=compile_cmd; dep_files=dep_files;
}
(* ----- Différents types d'unités ----- *)
(* module ocaml, sans interface *)
let ocaml_Module ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n =
let n, depc, bytec, optc =
ocaml_options ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n
in
let ml_n, cmo_n, cmi_n, cmx_n = ml n, cmo n, cmi n, cmx n in
let targets = [cmo_n; cmi_n; cmx_n] in
generic_unit
~name:n
~sources:[ml_n] ~targets ~trash:(oo n :: annot n :: targets) ~objects:(cmx_n, cmo_n)
~dependencies:(fun ~native f -> if is_cmi f then [select native (cmx_n, cmo_n)]
else ocamldep ~native ~depc ~sf:(ml_n))
~compile_cmd: (fun f -> ocamlobj ~bytec ~optc ~f ~n)
~dep_files: (fun f -> if is_cmi f then [] else [ml_n])
()
let generic_ocaml_Module_extension extension command =
fun ?o ?flags ?byte_flags
?opt_flags ?(cmd_flags="")
?pp ?includes ?ext_includes n ->
let n', depc, _, _ =
ocaml_options ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n
in
let ext_n, ml_n = n'^extension, n'^".ml" in
let cmd = command cmd_flags ext_n ml_n in
generic_unit
~name:n
~sources:[ext_n] ~targets:[ml_n] ~trash:[]
~objects:(n'^".cmx", n'^".cmo") ~pregenerated:[ml_n]
~sub_units:[ocaml_Module ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n]
~dependencies:(fun ~native f -> [ext_n])
(* ~dependencies:(fun ~native f -> ocamldep ~native ~depc ~sf:(ext_n)) *)
~compile_cmd: (fun f -> cmd, [f])
()
(* module ocaml, avec interface *)
let ocaml_IModule ?o ?flags ?byte_flags ?opt_flags ?(impl_flags = "") ?pp ?includes ?ext_includes n =
let n, depc, bytec, optc =
ocaml_options ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n
in
let ml_n, mli_n, cmo_n, cmi_n, cmx_n = ml n, mli n, cmo n, cmi n, cmx n in
let targets = [cmo_n; cmi_n; cmx_n] in
generic_unit
~name:n
~sources:[ml_n; mli_n] ~targets ~trash:(oo n :: annot n :: targets) ~objects:(cmx_n, cmo_n)
~dependencies:(fun ~native f -> ocamldepi ~native ~depc ~f ~n)
~compile_cmd: (fun f -> ocamlobji ~bytec ~impl_flags ~optc ~f ~n)
~dep_files: (fun f -> if is_cmi f then [mli_n] else [ml_n])
()
(* interface ocaml pure *)
let ocaml_Interface ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n =
let n, depc, bytec, optc =
ocaml_options ?o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n
in
let mli_n, cmi_n = mli n, cmi n in
let targets = [cmi_n] in
generic_unit
~name:n
~sources:[mli_n] ~targets ~trash:(annot n :: targets)
~dependencies:(fun ~native f -> ocamldep ~native ~depc ~sf:mli_n)
~compile_cmd: (fun f -> bytec^" -c "^(mli_n), [f])
~dep_files: (fun f -> [mli_n])
()
(* objet C *)
let c_Module ?(o= !options) ?(flags="") ?(source_deps=[]) n =
let n = Filename.concat !dir n in
let sources = List.map (Filename.concat !dir) source_deps in
let c_n, o_n = cc n, oo n in
let cc = !(o.ocamlc)^" -c"^^flags^^c_n in
let sources = c_n::sources in
generic_unit
~name:n
~sources ~targets:[o_n]
~dependencies:(fun ~native f -> sources)
~objects:(o_n,o_n)
~compile_cmd: (fun f -> cc, [f])
()
(* lexer ocaml *)
let ocaml_Lexer ?(o= !options) ?flags ?byte_flags ?opt_flags ?(lex_flags="") ?pp ?includes ?ext_includes n =
let n' = Filename.concat !dir n in
let mll_n, ml_n = mll n', ml n' in
let ocamllex = !(o.ocamllex)^^lex_flags^^mll_n in
generic_unit
~name:n
~sources:[mll_n] ~targets:[ml_n]
~objects:(cmx n', cmo n') ~pregenerated:[ml_n]
~sub_units:[ocaml_Module ~o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n]
~dependencies:(fun ~native f -> [mll_n])
~compile_cmd: (fun f -> ocamllex, [f])
()
(* parser ocaml *)
let ocaml_Parser ?(o= !options) ?flags ?byte_flags ?opt_flags ?(yacc_flags="") ?pp ?includes ?ext_includes n =
let n' = Filename.concat !dir n in
let mly_n, ml_n, mli_n = mly n', ml n', mli n' in
let ocamlyacc = !(o.ocamlyacc)^^yacc_flags^^mly_n in
let gen = [ml_n; mli_n] in
generic_unit
~name:n
~sources:[mly_n] ~targets:gen ~trash:((n'^".output") :: gen)
~objects:(cmx n', cmo n') ~pregenerated:gen
~sub_units:[ocaml_IModule ~o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n]
~dependencies:(fun ~native f -> [mly_n] )
~compile_cmd: (fun f -> ocamlyacc, gen)
()
(* interface glade à compiler en ocaml *)
let ocaml_Glade ?(o= !options) ?flags ?byte_flags ?opt_flags ?(glade_flags="") ?pp ?includes ?ext_includes n =
let n' = Filename.concat !dir n in
let glade_n, ml_n = glade n', ml n' in
let ocamlglade = !(o.ocamlglade)^^glade_flags^^glade_n^" > "^ml_n in
generic_unit
~name:n
~sources:[glade_n] ~targets:[ml_n]
~objects:(cmx n', cmo n') ~pregenerated:[ml_n]
~sub_units:[ocaml_Module ~o ?flags ?byte_flags ?opt_flags ?pp ?includes ?ext_includes n]
~dependencies:(fun ~native f -> [glade_n])
~compile_cmd: (fun f -> ocamlglade, [f])
()
(* paquet de modules ocaml *)
let ocaml_Package ?(o= !options) n sub_units =
let n = Filename.concat !dir n in
let ml_n, cmo_n, cmi_n, cmx_n, o_n = ml n, cmo n, cmi n, cmx n, oo n in
let otmap2 f = List.fold_right
(function { objects=None; targets=[x] }
when is_cmi x
&& Sys.file_exists (mli (Filename.chop_extension x))
-> (fun accu -> x :: accu)
| { objects=Some x } -> fcons f x
| _ -> id) sub_units [] in
let objs, sobjs =
let x = otmap2 fst in
let y = otmap2 snd in
let sx = flatten x in
let sy = flatten y in
(x, y), (sx, sy)
in
let targets = [cmo_n; cmi_n; cmx_n] in
generic_unit
~name:n
~targets ~objects:(cmx_n, cmo_n) ~trash:(ml_n::o_n::targets) ~pregenerated:[ml_n] ~sub_units
~dependencies:(fun ~native f ->
if is_cmi f then [select native (cmx_n, cmo_n)]
else
select native objs
)
~compile_cmd: (fun f ->
if is_cmx f then !(o.ocamlopt)^^(for_pack o)^^"-I"^^n^^"-pack -o"^^f^^(fst sobjs), [cmx_n; cmi_n]
else !(o.ocamlc)^^"-pack -o"^^cmo_n^^(snd sobjs), [cmo_n; cmi_n])
()
let add_for_pack o n =
if !(o.ocaml_ForPack) = "" then o.ocaml_ForPack := n
else o.ocaml_ForPack := !(o.ocaml_ForPack)^"."^n
(* paquet de modules regroupés dans un sous répertoire *)
let ocaml_PackageDir ?o n l =
let n' = Filename.concat !dir n in
let dir' = !dir in
dir := Filename.concat n' "";
let l' = new_scope (lazy (!options.ocaml_Includes += n';
add_for_pack !options n; Lazy.force l)) in
dir := dir';
ocaml_Package ?o n l'
(* librairie ocaml *)
let ocaml_Library ?(o= !options) ?flags ?byte_flags ?opt_flags ?includes ?(libraries=[]) ?(default=`Byte) n sub_units =
let n, depc, bytec, optc =
ocaml_options ~o ?flags ?byte_flags ?opt_flags ?includes n
in
let objs b =
let scma = select b (cmxa, cma) in
(* (map scma extlib) @ (map scma libs) @ (otmap (select b) sub_units) *)
List.rev_append
(rev_map_append scma
libraries
(List.rev_map scma !(o.ocaml_ExtLibraries))
)
(otmap (select b) sub_units)
in
let objs, sobjs =
let x, y = objs true, objs false in
let sx = flatten x in
let sy = flatten y in
(x,y), (sx,sy)
in
let cma_n, cmxa_n, a_n = cma n, cmxa n, aa n in
generic_unit
~name:n
~targets:[cma_n; cmxa_n] ~trash:[a_n] ~objects:(cmxa_n, cma_n) ~sub_units
~auto_targets:[if default=`Byte; then cma_n else cmxa_n]
~dependencies:(fun ~native f -> select (is_cmxa f) objs)
~compile_cmd: (fun f ->
if is_cmxa f then optc ^" -a -o "^f^^(fst sobjs), [f]
else bytec^" -a -o "^f^^(snd sobjs), [f])
()
(* exécutable ocaml *)
let ocaml_Program ?(o= !options) ?flags ?byte_flags ?opt_flags ?includes ?(libraries=[]) ?(default=`Byte) n sub_units =
let n, depc, bytec, optc =
ocaml_options ~o ?flags ?byte_flags ?opt_flags ?includes n
in
let objs b =
let scma = select b (cmxa, cma) in
(* (map scma extlib) @ (map scma libs) @ (otmap (select b) sub_units) *)
List.rev_append
(rev_map_append scma
libraries
(List.rev_map scma !(o.ocaml_ExtLibraries))
)
(otmap (select b) sub_units)
in
let objs, sobjs =
let x, y = objs true, objs false in
let sx = flatten x in
let sy = flatten y in
(x,y), (sx,sy)
in
let run_n, opt_n = run n, opt n in
generic_unit
~name:n
~targets:[run_n; opt_n] ~sub_units
~auto_targets:[if default=`Byte; then run_n else opt_n]
~dependencies:(fun ~native f -> select (is_opt f) objs)
~compile_cmd: (fun f ->
if is_opt f then optc ^" -o "^f^^(fst sobjs), [f]
else bytec^" -o "^f^^(snd sobjs), [f])
()
(* cible silencieuse *)
let phony_unit ?(depends=["@FORCE@"]) ?(command="") name =
generic_unit ~targets:[name]
~name
~dependencies:(fun ~native f -> depends)
~compile_cmd: (fun _ -> command,[])
()
let fold_units_sources units f =
let rec fold units accu =
List.fold_left (fun accu u -> f u.name u.sources (fold u.sub_units) accu) accu units
in fold units
(* (\* unité utilisateur *\) *)
(* let user_unit ?trash ~command ~depends name = *)
(* let targets = [Filename.concat !dir name] in *)
(* let trash = oget targets trash in *)
(* generic_unit ~targets ~trash *)
(* ~dependencies:(fun ~native _ -> depends) *)
(* ~compile_cmd: command *)
(* () *)
(* récupération des fichiers sources OCaml *)
let ocaml_sources =
let rec crev_append l1 l2 = match l1 with
| [] -> l2
| x::q when is_ml x || is_mli x -> crev_append q (x::l2)
| x::q -> crev_append q l2
in
fold_units (fun u -> crev_append u.sources) []
(* ---- Statuts des fichiers ---- *)
(* statut d'un fichier *)
type status_t = {
mutable modified: int; (* dernière fois que l'on a _réellement_ été modifié *)
mutable updated: int; (* date de la dernière mise à jour *)
mutable mtime: float; (* mtime lors de la dernière mise à jour *)
mutable digest: Digest.t; (* digest lors de la dernière mise à jour *)
mutable cmd: Digest.t; (* digest de la commande utiliséee lors de la dernière mise à jour *)
mutable depended: int*int; (* date du dernier calcul des dépendances (mode natif et non natif) *)
mutable deps: string list*string list; (* listes des dépendances *)
}
let make_status ~t ~f =
{ modified = if Sys.file_exists f then t else 0; updated = 0;
depended = -1,-1; mtime = 0.0; digest = Digest.string "$*%";
deps = [],[]; cmd = Digest.string "%*$" }
let update_status ?cmdd ~t ~f st =
if !debug_status then printf "UPDATE (%s) : " f;
if Sys.file_exists f then
(if st.updated < t then (
st.updated <- t;
(match cmdd with Some d -> st.cmd <- d | None -> ());
let mt = mtime f in
if mt > st.mtime then (
st.mtime <- mt;
let d = Digest.file f in
if st.digest <> d then (
st.digest <- d;
if !debug_status then printf "modified";
st.modified <- t
)
)
)
) else st.updated <- 0;
if !debug_status then printf "\n"
(* s1 plus récent que s2 *)
let (>>) s1 s2 = s1.modified > s2.updated
(* ---- Gestion des projets ---- *)
(* type des "projets" *)
type project_t = {
units: unit_t list; (* liste des unités *)
get_unit: string -> unit_t; (* obtention de l'unité correspondant à une cible *)
date: int; (* date courante *)
get_status: string -> status_t; (* statut d'un fichier *)
write_cache: unit -> unit; (* sauvegarde du cache des statuts *)
}
exception NoRuleFor of string
(* un fichier donné est-il une cible *)
let is_target p = fun f -> try ignore (p.get_unit f); true with _ -> false
(* récupération des fichiers sources *)
let sources_of_project p =
let rec crev_append l1 l2 = match l1 with
| [] -> l2
| x::q when is_target p x -> crev_append q l2
| x::q -> crev_append q (x::l2)
in
fold_units (fun u -> crev_append u.sources) [] p.units
(* alias pour le type des expressions mises en cache *)
type status_ct = int * (string, status_t) Hashtbl.t
let st_cache = ".cache-status"
(* création d'un projet *)
let project ?(rebuild="ocaml build.ml") ?(deps=["Makefile.ml"]) units =
(* mise à jour éventuelle de YaM *)
let () = if exists_file_newer Sys.executable_name deps then (
let rebuild = ref rebuild in
for i=1 to Array.length Sys.argv -1 do rebuild := !rebuild^" "^Sys.argv.(i) done;
printf "yam is out-dated, rebuilding it (%s)\n%!" !rebuild;
exit (Sys.command !rebuild)
) in
(* construction de la table cible -> unités *)
let get_unit =
let table = Hashtbl.create 23 in
iter_units (fun u ->
List.iter
(fun t -> Hashtbl.add table t u)
u.targets
) units;
(fun x -> try Hashtbl.find table x with Not_found -> raise (NoRuleFor x))
in
(* récupération des statuts *)
let date, get_status, write_cache =
let get_status (d,gt) =
let t = d-1 in
(fun f ->
try Hashtbl.find gt f
with Not_found ->
let s = make_status ~t ~f in
Hashtbl.add gt f s;
s
)
in
let write_cache v =
(fun () ->
let c_out = open_out_bin st_cache in
output_value c_out (v: status_ct);
close_out c_out
)
in
let get ((d,_) as v) = d, get_status v, write_cache v in
if Sys.file_exists st_cache then
let c_in = open_in_bin st_cache in
let d,gt = (input_value c_in: status_ct) in
close_in c_in;
get (d+1, gt)
else
get (1, Hashtbl.create 50)
in
{ units=units; get_unit=get_unit; date=date;
get_status=get_status; write_cache=write_cache }
(* nettoyage d'un projet *)
let clean p =
silent_remove st_cache;
iter_units (fun u -> List.iter silent_remove u.trash) p.units
(* (\* génération de la documentation *\) *)
(* let doc p = *)
(* let cmd = *)
(* let rec crev_append l1 l2 = match l1 with *)
(* | [] -> l2 *)
(* | x::q when is_ml x || is_mli x && not (is_target p x) -> crev_append q (l2^^x) *)
(* | x::q -> crev_append q l2 *)
(* in *)
(* fold_units (fun u -> crev_append u.sources) !(!options.ocamldoc) p.units *)
(* in *)
(* if !print_cmds then printf "%s\n%!" cmd; *)
(* ecall cmd *)
(* ---- Compilation ---- *)
(* compilation d'un projet (ou d'une de ses cibles) *)
let build ?target p =
let date = p.date in
let get_status = p.get_status in
let get_unit = p.get_unit in
(* FIXME unused let is_target = is_target p in *)
(* compilation d'une cible *)
let compile u f stf (cmd,cmdd,out) =
if !print_cmds then printf "%s\n%!" cmd;
ecall cmd;
List.iter (fun f -> update_status ~cmdd ~t:date ~f (get_status f)) out
in
(* calcul de la liste des dépendances de f d'unité u et de statut st *)
let rec dependencies ~native f u st =
let stf = get_status f in
List.iter (fun sf ->
try ignore(get_unit sf); build ~native [sf]
with NoRuleFor _ -> update_status ~t:date ~f:sf (get_status sf)
) u.sources;
if !debug_deps then printf "DEP: `%s' -> " f;
let l = u.dep_files f in
let ddate = select native stf.depended in
let recent df = (get_status df).modified > ddate in
if l=[] || List.exists recent l then
let deps = u.dependencies ~native f in
stf.deps <- select_set native stf.deps deps;
stf.depended <- select_set native stf.depended date;
if !debug_deps then printf "[%s]\n" (flatten deps);
deps
else (
if !debug_deps then printf "cached [%s]\n" (flatten (select native stf.deps));
select native stf.deps
)
(* compilation par continuations d'une pile de cibles *)
and build ?(native=false) ?(k=fun ()->()) = function
| [] -> k()
| f::q ->
let stf = get_status f in
if stf.updated < date then (
if !debug_build then printf "BUILD: `%s'\n" f;
let u = get_unit f in
let native = native || is_cmx f || is_opt f || is_cmxa f in
let cmd, out = u.compile_cmd f in
let deps = dependencies ~native f u stf in
let deps' = List.filter (fun f -> try ignore(get_unit f); true with NoRuleFor _ -> false) deps in
let cmdd = Digest.string cmd in
let k' () =
if stf.cmd <> cmdd ||
List.exists (fun f' -> get_status f' >> stf) deps
then compile u f stf (cmd,cmdd,out)
else update_status ~cmdd ~t:date ~f stf;
build ~native ~k q
in
build ~native ~k:k' deps'
) else
build ~native ~k q
in
(* ensemble des cibles à compiler *)
let targets = match target with
| Some t -> [t]
| _ -> List.fold_left (fun acc u -> List.rev_append u.auto_targets acc) [] p.units
in
(* pré-génération des fichiers nécessaires au calcul des dépendances *)
iter_units (fun u -> List.iter touch_file u.pregenerated) p.units;
(* compilation des cibles *)
try build ~k:p.write_cache targets with
| CmdError _ -> p.write_cache(); exit 1
| NoRuleFor t -> eprintf "No rule to build `%s'\n" t; p.write_cache(); exit 1
(* ---- Point d'entrée par défaut ---- *)
let main ?rebuild ?deps l =
let p = project ?rebuild ?deps l in
let cwd() = Sys.chdir (Filename.dirname Sys.executable_name) in
let targets = ref [] in
let main = ref (fun () ->
match !targets with
| [] -> build p
| l -> List.iter (fun target -> build ~target p) (List.rev l)
)
in
let alone s =
if !targets <> [] || !Arg.current <> Array.length Sys.argv - 1
then Printf.eprintf "Warning: `%s' specified, other arguments are ignored.\n" s
in
let version() = alone "-version"; Printf.printf "YaM version 1.0\n"; exit 0 in
let clean() = alone "-clean"; clean p; exit 0 in
Arg.parse [
"-version", Arg.Unit version, " \tdisplay version information";
"-clean", Arg.Unit clean, " \tremove all generated files";
"-v", Arg.Set print_deps, " \t\tbe verbose: print dependencies commands";
"-q", Arg.Clear print_cmds, " \t\tbe quiet: do not print commands";
"-r", Arg.String Sys.chdir, " <dir>\tset `dir' as root directory";
"-R", Arg.Unit cwd, " \t\tset directory of YaM as root directory";
"-db", Arg.Set debug_build, " \tdebug build";
"-dd", Arg.Set debug_deps, " \tdebug deps";
"-ds", Arg.Set debug_status, " \tdebug status";
] ((+=) targets) "usage: yam {-version, -clean, [options] [targets...]}";
!main ()