(***********************************************************************) (* *) (* OCaml *) (* *) (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) (* *) (* Copyright 1999 Institut National de Recherche en Informatique et *) (* en Automatique. All rights reserved. This file is distributed *) (* under the terms of the Q Public License version 1.0. *) (* *) (***********************************************************************) (* $Id$ *) open Format open Location open Longident open Parsetree (* Print the dependencies *) type file_kind = ML | MLI;; let load_path = ref ([] : (string * string array) list) let ml_synonyms = ref [".ml"] let mli_synonyms = ref [".mli"] let native_only = ref false let force_slash = ref false let error_occurred = ref false let raw_dependencies = ref false let sort_files = ref false let all_dependencies = ref false let one_line = ref false let files = ref [] (* Fix path to use '/' as directory separator instead of '\'. Only under Windows. *) let fix_slash s = if Sys.os_type = "Unix" then s else begin let r = String.copy s in for i = 0 to String.length r - 1 do if r.[i] = '\\' then r.[i] <- '/' done; r end let add_to_load_path dir = try let dir = Misc.expand_directory Config.standard_library dir in let contents = Sys.readdir dir in load_path := !load_path @ [dir, contents] with Sys_error msg -> fprintf Format.err_formatter "@[Bad -I option: %s@]@." msg; error_occurred := true let add_to_synonym_list synonyms suffix = if (String.length suffix) > 1 && suffix.[0] = '.' then synonyms := suffix :: !synonyms else begin fprintf Format.err_formatter "@[Bad suffix: '%s'@]@." suffix; error_occurred := true end (* Find file 'name' (capitalized) in search path *) let find_file name = let uname = String.uncapitalize name in let rec find_in_array a pos = if pos >= Array.length a then None else begin let s = a.(pos) in if s = name || s = uname then Some s else find_in_array a (pos + 1) end in let rec find_in_path = function [] -> raise Not_found | (dir, contents) :: rem -> match find_in_array contents 0 with Some truename -> if dir = "." then truename else Filename.concat dir truename | None -> find_in_path rem in find_in_path !load_path let rec find_file_in_list = function [] -> raise Not_found | x :: rem -> try find_file x with Not_found -> find_file_in_list rem let find_dependency target_kind modname (byt_deps, opt_deps) = try let candidates = List.map ((^) modname) !mli_synonyms in let filename = find_file_in_list candidates in let basename = Filename.chop_extension filename in let cmi_file = basename ^ ".cmi" in let ml_exists = List.exists (fun ext -> Sys.file_exists (basename ^ ext)) !ml_synonyms in let new_opt_dep = if !all_dependencies then match target_kind with | MLI -> [ cmi_file ] | ML -> cmi_file :: (if ml_exists then [ basename ^ ".cmx"] else []) else (* this is a make-specific hack that makes .cmx to be a 'proxy' target that would force the dependency on .cmi via transitivity *) if ml_exists then [ basename ^ ".cmx" ] else [ cmi_file ] in ( cmi_file :: byt_deps, new_opt_dep @ opt_deps) with Not_found -> try (* "just .ml" case *) let candidates = List.map ((^) modname) !ml_synonyms in let filename = find_file_in_list candidates in let basename = Filename.chop_extension filename in let bytenames = if !all_dependencies then match target_kind with | MLI -> [basename ^ ".cmi"] | ML -> [basename ^ ".cmi"; basename ^ ".cmo"] else (* again, make-specific hack *) [basename ^ (if !native_only then ".cmx" else ".cmo")] in let optnames = if !all_dependencies then match target_kind with | MLI -> [basename ^ ".cmi"] | ML -> [basename ^ ".cmi"; basename ^ ".cmx"] else [ basename ^ ".cmx" ] in (bytenames @ byt_deps, optnames @ opt_deps) with Not_found -> (byt_deps, opt_deps) let (depends_on, escaped_eol) = (":", " \\\n ") let print_filename s = let s = if !force_slash then fix_slash s else s in if not (String.contains s ' ') then begin print_string s; end else begin let rec count n i = if i >= String.length s then n else if s.[i] = ' ' then count (n+1) (i+1) else count n (i+1) in let spaces = count 0 0 in let result = String.create (String.length s + spaces) in let rec loop i j = if i >= String.length s then () else if s.[i] = ' ' then begin result.[j] <- '\\'; result.[j+1] <- ' '; loop (i+1) (j+2); end else begin result.[j] <- s.[i]; loop (i+1) (j+1); end in loop 0 0; print_string result; end ;; let print_dependencies target_files deps = let rec print_items pos = function [] -> print_string "\n" | dep :: rem -> if !one_line || (pos + 1 + String.length dep <= 77) then begin if pos <> 0 then print_string " "; print_filename dep; print_items (pos + String.length dep + 1) rem end else begin print_string escaped_eol; print_filename dep; print_items (String.length dep + 4) rem end in print_items 0 (target_files @ [depends_on] @ deps) let print_raw_dependencies source_file deps = print_filename source_file; print_string depends_on; Depend.StringSet.iter (fun dep -> if (String.length dep > 0) && (match dep.[0] with 'A'..'Z' -> true | _ -> false) then begin print_char ' '; print_string dep end) deps; print_char '\n' (* Optionally preprocess a source file *) let preprocessor = ref None exception Preprocessing_error let preprocess sourcefile = match !preprocessor with None -> sourcefile | Some pp -> flush Pervasives.stdout; let tmpfile = Filename.temp_file "camlpp" "" in let comm = Printf.sprintf "%s %s > %s" pp sourcefile tmpfile in if Sys.command comm <> 0 then begin Misc.remove_file tmpfile; raise Preprocessing_error end; tmpfile let remove_preprocessed inputfile = match !preprocessor with None -> () | Some _ -> Misc.remove_file inputfile (* Parse a file or get a dumped syntax tree in it *) let is_ast_file ic ast_magic = try let buffer = String.create (String.length ast_magic) in really_input ic buffer 0 (String.length ast_magic); if buffer = ast_magic then true else if String.sub buffer 0 9 = String.sub ast_magic 0 9 then failwith "Ocaml and preprocessor have incompatible versions" else false with End_of_file -> false let parse_use_file ic = if is_ast_file ic Config.ast_impl_magic_number then let _source_file = input_value ic in [Ptop_def (input_value ic : Parsetree.structure)] else begin seek_in ic 0; let lb = Lexing.from_channel ic in Parse.use_file lb end let parse_interface ic = if is_ast_file ic Config.ast_intf_magic_number then let _source_file = input_value ic in (input_value ic : Parsetree.signature) else begin seek_in ic 0; let lb = Lexing.from_channel ic in Parse.interface lb end (* Process one file *) let ml_file_dependencies source_file = Depend.free_structure_names := Depend.StringSet.empty; let input_file = preprocess source_file in let ic = open_in_bin input_file in try let ast = parse_use_file ic in Depend.add_use_file Depend.StringSet.empty ast; if !sort_files then files := (source_file, ML, !Depend.free_structure_names) :: !files else if !raw_dependencies then begin print_raw_dependencies source_file !Depend.free_structure_names end else begin let basename = Filename.chop_extension source_file in let byte_targets = if !native_only then [] else [ basename ^ ".cmo" ] in let native_targets = if !all_dependencies then [ basename ^ ".cmx"; basename ^ ".o" ] else [ basename ^ ".cmx" ] in let init_deps = if !all_dependencies then [source_file] else [] in let cmi_name = basename ^ ".cmi" in let init_deps, extra_targets = if List.exists (fun ext -> Sys.file_exists (basename ^ ext)) !mli_synonyms then (cmi_name :: init_deps, cmi_name :: init_deps), [] else (init_deps, init_deps), ( if !all_dependencies then [cmi_name] else [] ) in let (byt_deps, native_deps) = Depend.StringSet.fold (find_dependency ML) !Depend.free_structure_names init_deps in if not !native_only then print_dependencies (byte_targets @ extra_targets) byt_deps; print_dependencies (native_targets @ extra_targets) native_deps; end; close_in ic; remove_preprocessed input_file with x -> close_in ic; remove_preprocessed input_file; raise x let mli_file_dependencies source_file = Depend.free_structure_names := Depend.StringSet.empty; let input_file = preprocess source_file in let ic = open_in_bin input_file in try let ast = parse_interface ic in Depend.add_signature Depend.StringSet.empty ast; if !sort_files then files := (source_file, MLI, !Depend.free_structure_names) :: !files else if !raw_dependencies then begin print_raw_dependencies source_file !Depend.free_structure_names end else begin let basename = Filename.chop_extension source_file in let (byt_deps, opt_deps) = Depend.StringSet.fold (find_dependency MLI) !Depend.free_structure_names ([], []) in print_dependencies [basename ^ ".cmi"] byt_deps end; close_in ic; remove_preprocessed input_file with x -> close_in ic; remove_preprocessed input_file; raise x let file_dependencies_as kind source_file = Location.input_name := source_file; try if Sys.file_exists source_file then begin match kind with | ML -> ml_file_dependencies source_file | MLI -> mli_file_dependencies source_file end with x -> let report_err = function | Lexer.Error(err, range) -> fprintf Format.err_formatter "@[%a%a@]@." Location.print_error range Lexer.report_error err | Syntaxerr.Error err -> fprintf Format.err_formatter "@[%a@]@." Syntaxerr.report_error err | Sys_error msg -> fprintf Format.err_formatter "@[I/O error:@ %s@]@." msg | Preprocessing_error -> fprintf Format.err_formatter "@[Preprocessing error on file %s@]@." source_file | x -> raise x in error_occurred := true; report_err x let file_dependencies source_file = if List.exists (Filename.check_suffix source_file) !ml_synonyms then file_dependencies_as ML source_file else if List.exists (Filename.check_suffix source_file) !mli_synonyms then file_dependencies_as MLI source_file else () let sort_files_by_dependencies files = let h = Hashtbl.create 31 in let worklist = ref [] in (* Init Hashtbl with all defined modules *) let files = List.map (fun (file, file_kind, deps) -> let modname = Filename.chop_extension (Filename.basename file) in modname.[0] <- Char.uppercase modname.[0]; let key = (modname, file_kind) in let new_deps = ref [] in Hashtbl.add h key (file, new_deps); worklist := key :: !worklist; (modname, file_kind, deps, new_deps) ) files in (* Keep only dependencies to defined modules *) List.iter (fun (modname, file_kind, deps, new_deps) -> let add_dep modname kind = new_deps := (modname, kind) :: !new_deps; in Depend.StringSet.iter (fun modname -> match file_kind with ML -> (* ML depends both on ML and MLI *) if Hashtbl.mem h (modname, MLI) then add_dep modname MLI; if Hashtbl.mem h (modname, ML) then add_dep modname ML | MLI -> (* MLI depends on MLI if exists, or ML otherwise *) if Hashtbl.mem h (modname, MLI) then add_dep modname MLI else if Hashtbl.mem h (modname, ML) then add_dep modname ML ) deps; if file_kind = ML then (* add dep from .ml to .mli *) if Hashtbl.mem h (modname, MLI) then add_dep modname MLI ) files; (* Print and remove all files with no remaining dependency. Iterate until all files have been removed (worklist is empty) or no file was removed during a turn (cycle). *) let printed = ref true in while !printed && !worklist <> [] do let files = !worklist in worklist := []; printed := false; List.iter (fun key -> let (file, deps) = Hashtbl.find h key in let set = !deps in deps := []; List.iter (fun key -> if Hashtbl.mem h key then deps := key :: !deps ) set; if !deps = [] then begin printed := true; Printf.printf "%s " file; Hashtbl.remove h key; end else worklist := key :: !worklist ) files done; if !worklist <> [] then begin fprintf Format.err_formatter "@[Warning: cycle in dependencies. End of list is not sorted.@]@."; Hashtbl.iter (fun _ (file, deps) -> fprintf Format.err_formatter "\t@[%s: " file; List.iter (fun (modname, kind) -> fprintf Format.err_formatter "%s.%s " modname (if kind=ML then "ml" else "mli"); ) !deps; fprintf Format.err_formatter "@]@."; Printf.printf "%s@ " file) h; end; Printf.printf "\n%!"; () (* Entry point *) let usage = "Usage: ocamldep [options] \nOptions are:" let print_version () = printf "ocamldep, version %s@." Sys.ocaml_version; exit 0; ;; let print_version_num () = printf "%s@." Sys.ocaml_version; exit 0; ;; let _ = Clflags.classic := false; add_to_load_path Filename.current_dir_name; Arg.parse [ "-I", Arg.String add_to_load_path, " Add to the list of include directories"; "-impl", Arg.String (file_dependencies_as ML), " Process as a .ml file"; "-intf", Arg.String (file_dependencies_as MLI), " Process as a .mli file"; "-ml-synonym", Arg.String(add_to_synonym_list ml_synonyms), " Consider as a synonym of the .ml extension"; "-mli-synonym", Arg.String(add_to_synonym_list mli_synonyms), " Consider as a synonym of the .mli extension"; "-ml-synonym", Arg.String(add_to_synonym_list ml_synonyms), " Consider as a synonym of the .ml extension"; "-mli-synonym", Arg.String(add_to_synonym_list mli_synonyms), " Consider as a synonym of the .mli extension"; "-sort", Arg.Set sort_files, " Sort files according to their dependencies"; "-modules", Arg.Set raw_dependencies, " Print module dependencies in raw form (not suitable for make)"; "-native", Arg.Set native_only, " Generate dependencies for a pure native-code project (no .cmo files)"; "-all", Arg.Set all_dependencies, " Generate dependencies on all files (not accommodating for make shortcomings)"; "-one-line", Arg.Set one_line, " Output one line per file, regardless of the length"; "-pp", Arg.String(fun s -> preprocessor := Some s), " Pipe sources through preprocessor "; "-slash", Arg.Set force_slash, " (Windows) Use forward slash / instead of backslash \\ in file paths"; "-version", Arg.Unit print_version, " Print version and exit"; "-vnum", Arg.Unit print_version_num, " Print version number and exit"; ] file_dependencies usage; if !sort_files then sort_files_by_dependencies !files; exit (if !error_occurred then 2 else 0)