(* $Id: frontend.ml 116 2007-11-11 22:40:21Z gerd $
* ----------------------------------------------------------------------
*
*)
open Findlib;;
exception Usage;;
type mode =
M_use | M_query | M_install | M_remove | M_compiler of string | M_dep
| M_printconf | M_list | M_browser | M_call of (string*string)
| M_doc
;;
type psubst =
Const of string
| Percent of string
| Lookup of string
;;
let percent_subst spec lookup s =
(* spec = [ "%c", [ "ctext1"; "ctext2"; ... ];
* "%d", [ "dtext1"; "dtext2"; ... ] ]
* All occurrences of %c in the string s are replaced as specified in spec.
* spec is an association list with the %-notation as keys
* and lists of strings as values. The result is a list of strings containing
* every combination of substituted values.
*
* Support for the %(name) syntax: In this case, the name is taken as
* key for the [lookup] function, which either returns the string value
* or raises Not_found.
*
* Example:
* spec = [ "%a", [ "file1" ] ]
* lookup = function "archive" -> "file2" | _ -> raise Not_found
* Here, %a is substituted by file1, and %(archive) is substituted by
* file2.
*)
let l = String.length s in
let rec preprocess i j =
if j<l then begin
match s.[j] with
'%' ->
if j+1<l then begin
let prev = Const(String.sub s i (j-i)) in
let c = s.[j+1] in
match c with
'%' ->
prev :: Const "%" :: preprocess (j+2) (j+2)
| '(' -> (
try
if j+2>=l then raise Not_found;
let k = String.index_from s (j+2) ')' in
let name = String.sub s (j+2) (k-j-2) in
prev :: Lookup name :: preprocess (k+1) (k+1)
with Not_found ->
failwith "bad format string";
)
| _ ->
let name = "%" ^ String.make 1 c in
prev :: Percent name :: preprocess (j+2) (j+2)
end
else failwith "bad format string"
| _ ->
preprocess i (j+1)
end
else
if i<j then
[Const(String.sub s i (j-i))]
else
[]
in
let rec subst prefix l =
match l with
[] -> [prefix]
| Const s :: l' ->
subst (prefix ^ s) l'
| Percent name :: l' ->
let replacements =
try List.assoc name spec
with Not_found -> failwith "bad format string" in
List.flatten
(List.map
(fun replacement ->
subst (prefix ^ replacement) l')
replacements)
| Lookup name :: l' ->
let replacement =
try lookup name
with Not_found -> "" in
subst (prefix ^ replacement) l'
in
subst "" (preprocess 0 0)
;;
let rec remove_dups l =
match l with
x :: l' ->
if List.mem x l' then remove_dups l' else x::remove_dups l'
| [] -> []
;;
let arg n =
if n < Array.length Sys.argv then Sys.argv.(n) else raise Not_found
;;
let use_package prefix pkgnames =
(* may raise No_such_package *)
let pdirs =
List.map
(fun pname ->
"-I " ^ package_directory pname
)
pkgnames
in
print_endline (prefix ^ String.concat " " pdirs)
;;
let read_ldconf filename =
let lines = ref [] in
let f = open_in filename in
try
while true do
let line = input_line f in
if line <> "" then
lines := line :: !lines
done;
assert false
with
End_of_file ->
close_in f;
List.rev !lines
| other ->
close_in f;
raise other
;;
let write_ldconf filename lines new_lines =
let f = open_out filename in
try
List.iter
(fun line -> output_string f (line ^ "\n"))
(lines @ new_lines);
close_out f;
prerr_endline("Updated " ^ filename);
with
Sys_error e ->
prerr_endline ("ocamlfind: [WARNING] Cannot write " ^ filename);
prerr_endline ("Reason: " ^ e);
prerr_endline ("This file contains the directories with DLLs.");
if new_lines <> [] then begin
prerr_endline ("It is recommended to add the following line(s) to this file:");
List.iter prerr_endline new_lines
end
;;
let is_dll p =
let sfx = Findlib_config.dll_suffix in
sfx <> "" && Filename.check_suffix p sfx
;;
let identify_dir d =
match Sys.os_type with
| "Win32" ->
failwith "identify_dir" (* not available *)
| _ ->
let s = Unix.stat d in
(s.Unix.st_dev, s.Unix.st_ino)
;;
let conflict_report incpath pkglist =
(* Check whether there are several definitions for packages
* in the current path. We remove duplicate directories first.
* Note that all other checks are not sensitive to duplicate directories.
*)
Fl_package_base.package_conflict_report ~identify_dir ();
(* Second check whether there are module conflicts *)
let pkgpath =
List.map Findlib.package_directory pkglist in
Fl_package_base.module_conflict_report ~identify_dir incpath;
(* Finally check whether there are multiple DLLs: *)
(* Note: Only the directories mentioned in ld.conf are checked, but not the
* directories in [incpath], and not the directories in CAML_LD_LIBRARY_PATH.
* The idea of this check is to ensure a proper installation, and not to
* complain about the user's special configuration.
*)
let ldconf = ocaml_ldconf() in
if ldconf <> "ignore" then begin
let dll_dirs = remove_dups (read_ldconf ldconf) in
let dll_pairs =
List.flatten
(List.map
(fun dll_dir ->
let files =
try Array.to_list (Sys.readdir dll_dir)
with _ ->
prerr_endline ("ocamlfind: [WARNING] Cannot read directory " ^
dll_dir ^ " which is mentioned in ld.conf");
[]
in
List.map
(fun file -> (file, dll_dir))
(List.filter is_dll files)
)
dll_dirs
) in
let dll_hash = Hashtbl.create 50 in
List.iter
(fun (file, dll_dir) -> Hashtbl.add dll_hash file dll_dir)
dll_pairs;
Hashtbl.iter
(fun file dll_dir ->
let locations = Hashtbl.find_all dll_hash file in
if List.length locations > 1 then begin
prerr_endline ("ocamlfind: [WARNING] The DLL " ^ file ^
" occurs in multiple directories: " ^ dll_dir)
end
)
dll_hash
end
;;
let check_package_list l =
(* may raise No_such_package *)
List.iter
(fun pkg ->
let _ = package_directory pkg in
()
)
l
;;
let run_command ?filter verbose cmd args =
if verbose then begin
print_string ("+ " ^ cmd ^ " " ^
String.concat " " args ^ "\n");
if filter <> None then
print_string (" (output of this command is filtered by ocamlfind)\n")
end;
flush stdout;
let filter_input, cmd_output =
match filter with
None -> Unix.stdin (* dummy *), Unix.stdout
| Some f -> Unix.pipe()
in
(* Signals: On SIGINT, we wait until the subprocess finishes, and
* die then. This allows us to call interactive commands as subprocesses.
*)
let old_sigint =
Sys.signal Sys.sigint Sys.Signal_ignore in
let need_exe =
List.mem Findlib_config.system [ "win32"; "mingw" ] in
let fixed_cmd =
if need_exe then (
if Filename.check_suffix cmd ".exe" then cmd else cmd ^ ".exe"
)
else
cmd in
let pid =
Unix.create_process
fixed_cmd
(Array.of_list (cmd :: args))
Unix.stdin
cmd_output
Unix.stderr
in
begin match filter with
Some filter_fun ->
begin
Unix.close cmd_output;
let ch = Unix.in_channel_of_descr filter_input in
try
while true do
let line = input_line ch in
match filter_fun line with
None -> () (* Suppress line *)
| Some line' -> print_endline line'
done;
assert false
with
End_of_file ->
close_in ch;
flush stdout
end
| None -> ()
end;
let (_,status) = Unix.waitpid [] pid in
Sys.set_signal Sys.sigint old_sigint;
begin
match status with
Unix.WEXITED 0 -> ()
| Unix.WEXITED n ->
if verbose then
print_string (cmd ^ " returned with exit code " ^ string_of_int n ^ "\n");
exit n
| Unix.WSIGNALED _ ->
print_string (cmd ^ " got signal and exited\n");
exit 2
| Unix.WSTOPPED _ ->
failwith "Your operating system does not work correctly"
end
;;
(************************* format expansion *************************)
let expand predicates eff_packages format =
(* may raise No_such_package *)
(* format:
* %p package name
* %d package directory
* %D description
* %v version
* %a archive file(s)
* %A archive files as single string
* %o link option(s)
* %O link options as single string
*)
List.flatten
(List.map
(fun pkg ->
let dir = package_directory pkg in
(* May raise No_such_package *)
let spec =
[ "%p", [pkg];
"%d", [dir];
"%D", [try package_property predicates pkg "description"
with Not_found -> "[n/a]"];
"%v", [try package_property predicates pkg "version"
with Not_found -> "[unspecified]"];
"%a", Fl_split.in_words
(try package_property predicates pkg "archive"
with Not_found -> "");
"%A", [String.concat " "
(Fl_split.in_words
(try package_property predicates pkg "archive"
with Not_found -> ""))];
"%o", Fl_split.in_words_ws
(try package_property predicates pkg "linkopts"
with Not_found -> "");
"%O", [String.concat " "
(Fl_split.in_words_ws
(try package_property predicates pkg "linkopts"
with Not_found -> ""))];
]
in
let lookup = package_property predicates pkg in
percent_subst spec lookup format)
eff_packages)
;;
(************************** QUERY SUBCOMMAND ***************************)
let query_package () =
let long_format =
"package: %p\ndescription: %D\nversion: %v\narchive(s): %A\nlinkopts: %O\nlocation: %d\n" in
let i_format =
"-I %d" in
let l_format =
if Findlib_config.system = "win32" then
(* Microsoft toolchain *)
"-ccopt \"/link /libpath:%d\""
else
"-ccopt -L%d" in
let a_format =
"%a" in
let o_format =
"%o" in
let p_format =
"%p" in
let predicates = ref [] in
let format = ref "%d" in
let separator = ref "\n" in
let prefix = ref "" in
let suffix = ref "\n" in
let recursive = ref false in
let descendants = ref false in
let packages = ref [] in
let append_predicate s =
let pl = Fl_split.in_words s in
predicates := !predicates @ pl
in
Arg.parse
[ "-predicates", Arg.String append_predicate,
" specifies comma-separated list of assumed predicates";
"-format", Arg.String (fun s -> format := s),
" specifies the output format";
"-separator", Arg.String (fun s -> separator := s),
" specifies the string that separates multiple answers";
"-prefix", Arg.String (fun s -> prefix := s),
" a string printed before the first answer";
"-suffix", Arg.String (fun s -> suffix := s),
" a string printed after the last answer";
"-recursive", Arg.Set recursive,
" select direct and indirect ancestors/descendants, too";
"-r", Arg.Set recursive,
" same as -recursive";
"-descendants", Arg.Unit (fun () -> descendants := true; recursive := true),
" query descendants instead of ancestors; implies -recursive";
"-d", Arg.Unit (fun () -> descendants := true; recursive := true),
" same as -descendants";
"-long-format", Arg.Unit (fun () -> format := long_format),
" specifies long output format";
"-l", Arg.Unit (fun () -> format := long_format),
" same as -long-format";
"-i-format", Arg.Unit (fun () -> format := i_format),
" prints -I options for ocamlc";
"-l-format", Arg.Unit (fun () -> format := l_format),
" prints -ccopt -L options for ocamlc";
"-a-format", Arg.Unit (fun () -> format := a_format),
" prints names of archives to be linked in for ocamlc";
"-o-format", Arg.Unit (fun () -> format := o_format),
" prints link options for ocamlc";
"-p-format", Arg.Unit (fun () -> format := p_format),
" prints package names";
]
(fun p -> packages := !packages @ [p])
"usage: ocamlfind query [ -predicates <p> | -format <f> |
-long-format | -i-format |
-l-format | -a-format |
-o-format | -p-format |
-prefix <p> | -suffix <s> |
-separator <s> |
-descendants | -recursive ] package ...";
let eff_packages =
if !recursive then begin
if !descendants then
Fl_package_base.package_users !predicates !packages
else
package_deep_ancestors !predicates !packages
end
else
!packages
in
let answers = expand !predicates eff_packages !format in
print_string !prefix;
print_string (String.concat !separator answers);
print_string !suffix;
;;
(**************** preprocessor ******************************************)
let process_pp_spec syntax_preds packages pp_opts =
(* Returns: pp_command *)
(* may raise No_such_package *)
(* [packages]: all packages given on the command line. May include
* packages for compilation and for preprocessing.
*
* The difficulty is now that the preprocessor packages may have
* requirements that are non-preprocessor packages. To get exactly
* the preprocessor packages and its requirements, we do:
*
* 1. Determine the subset of [packages] that are preprocessor
* packages by checking whether they have an "archive" for
* [syntax_preds], i.e. the preprocessor packages mentioned
* on the command line = [cl_pp_packages].
*
* 2. Add their requirements = [pp_packages]
*
* Because the packages are now mixed, we must evaluate for
* [syntax_preds] + "byte".
*)
let cl_pp_packages =
if syntax_preds = [] then
(* No syntax predicates, no preprocessor! *)
[]
else
List.filter
(fun pkg ->
let al = try package_property syntax_preds pkg "archive"
with Not_found -> "" in
let w = Fl_split.in_words al in
w <> []
)
packages in
let pp_packages =
package_deep_ancestors syntax_preds cl_pp_packages in
(* One packages must now have the variable "preprocessor", usually camlp4 *)
let preprocessor_cmds =
List.flatten
(List.map (fun pname ->
try
[ pname,
package_property syntax_preds pname "preprocessor"
]
with
Not_found -> []
)
pp_packages
)
in
let preprocessor_cmd =
if syntax_preds <> [] then
match preprocessor_cmds with
[] ->
failwith("When using -syntax, the META variable 'preprocessor' must be set")
| [_, cmd] -> Some cmd
| _ ->
failwith("No unique value for the META variable 'preprocessor': " ^
String.concat ", "
(List.map
(fun (n,v) ->
"package " ^ n ^ " defines `" ^ v ^ "'")
preprocessor_cmds
)
)
else
None
in
let pp_i_options =
List.flatten
(List.map
(fun pkg ->
let pkgdir = package_directory pkg in
[ "-I"; pkgdir ]
)
pp_packages) in
let pp_archives =
if preprocessor_cmd = None then
[]
else
List.flatten
(List.map
(fun pkg ->
let al =
try package_property ("byte" :: syntax_preds) pkg "archive"
with Not_found -> "" in
Fl_split.in_words al
)
pp_packages) in
match preprocessor_cmd with
None -> []
| Some cmd ->
["-pp";
cmd ^ " " ^
String.concat " " (List.map Filename.quote pp_i_options) ^ " " ^
String.concat " " (List.map Filename.quote pp_archives) ^ " " ^
String.concat " " (List.map Filename.quote pp_opts)]
;;
(**************** Generic argument processing *************************)
let merge_native_arguments native_spec f_unit f_string f_special_list =
List.map
(fun (switch_name, switch_has_arg, help_text) ->
let f =
try
List.assoc switch_name f_special_list
with
Not_found ->
if switch_has_arg then
f_string switch_name
else
f_unit switch_name in
(switch_name, f, help_text)
)
native_spec
;;
(**************** OCAMLC/OCAMLMKTOP/OCAMLOPT subcommands ****************)
type pass_file_t =
Pass of string
| Impl of string (* Forces module implementation: -impl <file> *)
| Intf of string (* Forces module interface: -intf <file> *)
| Cclib of string (* Option for the C linker: -cclib <opt> *)
;;
let ocamlc which () =
let destdir = ref (default_location()) in
let switches = ref [] in
let pass_options = ref [] in
let pass_files = ref [] in
let incpath = ref [] in
let dll_pkgs = ref [] in
let dll_pkgs_all = ref false in
let linkpkg = ref false in
let packages = ref [] in
let predicates = ref [] in
let dontlink = ref [] in
let syntax_preds = ref [] in
let pp_opts = ref [] in
let pp_specified = ref false in
let type_of_threads =
try package_property [] "threads" "type_of_threads"
with Not_found -> "ignore"
in
let threads_default =
match type_of_threads with
"posix" -> `POSIX_threads
| "vm" -> `VM_threads
| _ -> `None
in
let threads = ref `None in
let add_switch name =
Arg.Unit (fun () ->
switches := name :: !switches;
pass_options := !pass_options @ [name]) in
let add_spec_fn name s =
pass_options := !pass_options @ [name; s] in
let add_spec name = Arg.String (add_spec_fn name) in
let add_pkg =
Arg.String (fun s -> packages := !packages @ (Fl_split.in_words s)) in
let add_pred =
Arg.String (fun s -> predicates := !predicates @ (Fl_split.in_words s)) in
let add_dontlink =
Arg.String (fun s -> dontlink := !dontlink @ (Fl_split.in_words s)) in
let add_syntax_pred =
Arg.String (fun s -> syntax_preds := !syntax_preds @ (Fl_split.in_words s)) in
let add_pp_opt =
Arg.String (fun s -> pp_opts := !pp_opts @ [s]) in
let add_dll_pkg =
Arg.String (fun s -> dll_pkgs := !dll_pkgs @ (Fl_split.in_words s)) in
let ignore_error = ref false in
let native_spec_opt =
match which with
| "ocamlc" -> Ocaml_args.ocamlc_spec
| "ocamlcp" -> Ocaml_args.ocamlcp_spec
| "ocamlmktop" -> Ocaml_args.ocamlmktop_spec
| "ocamlopt" -> Ocaml_args.ocamlopt_spec
| _ -> None in
let native_spec =
match native_spec_opt with
| None -> failwith ("Not supported in your configuration: " ^ which)
| Some s -> s in
Arg.parse
(List.flatten
[ [
"-package", add_pkg,
" <name> Refer to package when compiling";
"-linkpkg", Arg.Set linkpkg,
" Link the packages in";
"-predicates", add_pred,
" <p> Add predicate <p> when resolving package properties";
"-dontlink", add_dontlink,
" <name> Do not link in package <name> and its ancestors";
"-syntax", add_syntax_pred,
" <p> Use preprocessor with predicate <p>";
"-ppopt", add_pp_opt,
" <opt> Append option <opt> to preprocessor invocation";
"-dllpath-pkg", add_dll_pkg,
"<pkg> Add -dllpath for this package";
"-dllpath-all", Arg.Set dll_pkgs_all,
" Add -dllpath for all linked packages";
"-ignore-error", Arg.Set ignore_error,
" Ignore the 'error' directive in META files";
"-passopt", Arg.String (fun s -> pass_options := !pass_options @ [s]),
" <opt> Pass option <opt> directly to ocamlc/opt/mktop\nSTANDARD OPTIONS:";
];
merge_native_arguments
native_spec
add_switch
add_spec
[ "-cclib",
Arg.String (fun s -> pass_files := !pass_files @ [ Cclib s ]);
"-I", (Arg.String
(fun s ->
let s = resolve_path s in
if Sys.file_exists s then incpath := s :: !incpath; (* reverted below *)
add_spec_fn "-I" s ));
"-impl",
Arg.String (fun s -> pass_files := !pass_files @ [ Impl s ]);
"-intf",
Arg.String (fun s -> pass_files := !pass_files @ [ Intf s ]);
"-pp",
Arg.String (fun s -> pp_specified := true; add_spec_fn "-pp" s);
"-thread",
Arg.Unit (fun _ -> threads := threads_default);
"-vmthread",
Arg.Unit (fun _ -> threads := `VM_threads);
"-",
Arg.String (fun s -> pass_files := !pass_files @ [ Pass s ]);
]
])
(fun s -> pass_files := !pass_files @ [ Pass s])
("usage: ocamlfind " ^ which ^ " [options] file ...");
(* ---- Start requirements analysis ---- *)
begin match which with
"ocamlc" -> predicates := "byte" :: !predicates;
| "ocamlcp" -> predicates := "byte" :: !predicates;
| "ocamlmktop" -> predicates := "byte" :: "create_toploop" :: !predicates;
| "ocamlopt" -> predicates := "native" :: !predicates;
| _ -> failwith "unsupported backend"
end;
incpath := List.rev !incpath;
( match !threads with
`None ->
()
| `VM_threads ->
if which = "ocamlopt" then
failwith "ocamlopt does not support multi-threaded programs for your configuration";
pass_options := !pass_options @ [ "-vmthread" ];
predicates := "mt" :: "mt_vm" :: !predicates;
| `POSIX_threads ->
pass_options := !pass_options @ [ "-thread" ];
predicates := "mt" :: "mt_posix" :: !predicates;
);
if List.mem "-p" !switches then
predicates := "gprof" :: !predicates;
if Findlib_config.ocaml_has_autolinking &&
not (List.mem "-noautolink" !switches)
then
predicates := "autolink" :: !predicates;
if !syntax_preds <> [] then begin
predicates := "syntax" :: !predicates;
syntax_preds := "preprocessor" :: "syntax" :: !syntax_preds;
end;
let verbose = List.mem "-verbose" !switches in
if !pp_specified && !syntax_preds <> [] then
prerr_endline("ocamlfind: [WARNING] -pp overrides the effect of -syntax partly");
(* check packages: *)
check_package_list !packages;
check_package_list !dontlink;
let eff_packages =
package_deep_ancestors !predicates !packages in
let eff_dontlink =
package_deep_ancestors !predicates !dontlink in
let eff_link =
List.flatten
(List.map
(fun pkg -> if List.mem pkg !dontlink then [] else [pkg])
eff_packages) in
let eff_packages_dl =
remove_dups (List.map package_directory eff_packages) in
let eff_link_dl =
remove_dups (List.map package_directory eff_link) in
(* Conflict report: *)
conflict_report (!incpath @ ["."; Findlib.ocaml_stdlib() ]) eff_packages;
(* ---- End of requirements analysis ---- *)
(* Add the pkg_<name> predicates: *)
predicates := List.map (fun pkg -> "pkg_" ^ pkg) eff_packages @ !predicates;
(* Check on [error] directives: *)
List.iter
(fun pkg ->
try
let error = package_property !predicates pkg "error" in
if !ignore_error then
prerr_endline("ocamlfind: [WARNING] Package `" ^ pkg ^
"' signals error: " ^ error)
else
failwith ("Error from package `" ^ pkg ^ "': " ^ error)
with
Not_found -> ()
)
eff_packages;
if verbose then begin
if !syntax_preds <> [] then
print_string ("Effective set of preprocessor predicates: " ^
String.concat "," !syntax_preds ^ "\n");
print_string ("Effective set of compiler predicates: " ^
String.concat "," !predicates ^ "\n");
end;
let stdlibdir = Fl_split.norm_dir (Findlib.ocaml_stdlib()) in
let threads_dir = Filename.concat stdlibdir "threads" in
let vmthreads_dir = Filename.concat stdlibdir "vmthreads" in
let initl_file_needed =
List.mem "create_toploop" !predicates && List.mem "findlib" eff_link in
let initl_file_name =
if initl_file_needed then
Filename.temp_file "findlib_initl" ".ml"
else
""
in
(* initl_file_name: the initialization code inserted at the end of
* the cma/cmo list (initl = init last)
*)
if initl_file_needed then begin
(* Generate initializer for "findlib_top.cma" *)
let initl = open_out_gen
[Open_wronly; Open_trunc; Open_text]
0o777
initl_file_name in
try
output_string initl
("Topfind.don't_load [" ^
String.concat ";"
(List.map
(fun pkg -> "\"" ^ String.escaped pkg ^ "\"")
eff_link) ^
"];;\n");
output_string initl
("Topfind.predicates := [" ^
String.concat ";"
(List.map
(fun pred -> "\"" ^ String.escaped pred ^ "\"")
("toploop" ::
(List.filter (fun p -> p <> "create_toploop") !predicates))) ^
"];;\n");
close_out initl;
with
any ->
close_out initl;
Sys.remove initl_file_name;
raise any
end;
if initl_file_needed then
at_exit
(fun () ->
let tr f x = try f x with _ -> () in
tr Sys.remove initl_file_name;
tr Sys.remove (Filename.chop_extension initl_file_name ^ ".cmi");
tr Sys.remove (Filename.chop_extension initl_file_name ^ ".cmo");
);
let exclude_list = [ stdlibdir; threads_dir; vmthreads_dir ] in
(* Don't generate -I options for these directories because there is
* also some magic in ocamlc/ocamlopt that would not work otherwise
*)
let i_options =
List.flatten
(List.map
(fun pkgdir ->
let npkgdir = Fl_split.norm_dir pkgdir in
if List.mem npkgdir exclude_list then
[]
else
[ "-I"; pkgdir;
"-ccopt"; "-I" ^ pkgdir; ])
eff_packages_dl) in
let l_options =
List.flatten
(List.map
(fun pkgdir ->
let npkgdir = Fl_split.norm_dir pkgdir in
if List.mem npkgdir exclude_list then
[]
else
if Findlib_config.system = "win32" then
(* Microsoft toolchain *)
[ "-ccopt"; "/link /libpath:" ^ pkgdir ]
else
[ "-ccopt"; "-L" ^ pkgdir; ])
eff_link_dl) in
let archives =
List.flatten
(List.map
(fun pkg ->
let al = try package_property !predicates pkg "archive"
with Not_found -> "" in
let pkg_dir =
if pkg = "threads" then (* MAGIC *)
match !threads with
`None -> stdlibdir
| `VM_threads -> vmthreads_dir
| `POSIX_threads -> threads_dir
else
package_directory pkg in
List.map
(fun arch ->
resolve_path ~base:pkg_dir arch)
(Fl_split.in_words al)
)
eff_link)
@
(if initl_file_needed then
[ initl_file_name ]
else
[]
)
in
let linkopts =
List.flatten
(List.map
(fun pkg ->
let ol = try package_property !predicates pkg "linkopts"
with Not_found -> "" in
Fl_split.in_words_ws ol)
(List.rev eff_link)) in
let pp_command =
if !pp_specified then
[]
else
process_pp_spec !syntax_preds !packages !pp_opts
in
let pass_files' =
List.flatten
(List.map
(function
Pass s ->
if s.[0] = '-'
then [ "-"; String.sub s 1 (String.length s - 1) ]
else [ resolve_path s ]
| Impl s ->
[ "-impl"; resolve_path s ]
| Intf s ->
[ "-intf"; resolve_path s ]
| Cclib s ->
[ "-cclib"; s ]
)
!pass_files)
in
let dll_dirs =
remove_dups
((List.map package_directory !dll_pkgs) @ (* XXX *)
(if !dll_pkgs_all then eff_link_dl else [])) in
let dll_options =
List.flatten
(List.map
(fun pkg -> ["-dllpath"; pkg] )
dll_dirs) in
let arguments =
!pass_options @ (* other options from the command line *)
i_options @ (* Generated -I options from package analysis *)
pp_command @ (* Optional preprocessor command *)
(if !linkpkg then l_options else []) @ (* Generated -ccopt -L options *)
(if !linkpkg then archives else []) @ (* Gen file names to link *)
pass_files' @ (* File names from cmd line *)
(if !linkpkg then linkopts else []) @ (* Generated link options *)
dll_options (* Generated -dllpath options *)
in
let actual_command =
match which with
"ocamlc" -> Findlib.command `ocamlc
| "ocamlopt" -> Findlib.command `ocamlopt
| "ocamlcp" -> Findlib.command `ocamlcp
| "ocamlmktop" -> Findlib.command `ocamlmktop
| _ -> assert false
in
run_command verbose actual_command arguments
;;
(************************************************************************)
let ocamldoc() =
let packages = ref [] in
let predicates = ref [] in
let syntax_preds = ref [] in
let pp_opts = ref [] in
let pp_specified = ref false in
let verbose = ref false in
let options = ref [] in
let native_spec =
match Ocaml_args.ocamldoc_spec with
| None -> failwith "Not supported in your configuration: ocamldoc"
| Some s -> s in
Arg.parse
( [ "-package",
Arg.String (fun s ->
packages := Fl_split.in_words s @ !packages),
"<name> Add this package to the search path";
"-predicates",
Arg.String (fun s ->
predicates := Fl_split.in_words s @ !predicates),
"<p> Add predicate <p> when calculating dependencies";
"-syntax",
Arg.String (fun s ->
syntax_preds := Fl_split.in_words s @ !syntax_preds),
"<p> Use preprocessor with predicate <p>";
"-ppopt",
Arg.String (fun s -> pp_opts := s :: !pp_opts),
"<opt> Append option <opt> to preprocessor invocation";
"-verbose",
Arg.Set verbose,
" Be verbose\nSTANDARD OPTIONS:";
]
@
( merge_native_arguments
native_spec
(fun s ->
Arg.Unit (fun () ->
options := !options @ [s]))
(fun s ->
Arg.String (fun arg ->
options := !options @ [s; arg]))
[ "-v", Arg.Set verbose;
"-pp", Arg.String (fun s ->
pp_specified := true;
options := !options @ ["-pp"; s]);
]
)
)
(fun s -> options := !options @ [s])
"usage: ocamlfind ocamldoc <options> <files>...";
check_package_list !packages;
if !syntax_preds <> [] then (
predicates := "syntax" :: !predicates;
syntax_preds := "preprocessor" :: "syntax" :: !syntax_preds;
);
if !verbose then begin
if !syntax_preds <> [] then
print_string ("Effective set of preprocessor predicates: " ^
String.concat "," !syntax_preds ^ "\n");
print_string ("Effective set of compiler predicates: " ^
String.concat "," !predicates ^ "\n");
end;
if !pp_specified && !syntax_preds <> [] then
prerr_endline("Warning: -pp overrides the effect of -syntax partly");
let pp_command =
if !pp_specified then
[]
else
process_pp_spec !syntax_preds !packages !pp_opts
in
let eff_packages =
package_deep_ancestors !predicates !packages in
let eff_packages_dl =
remove_dups (List.map package_directory eff_packages) in
let arguments =
(List.flatten (List.map (fun d -> [ "-I"; d ]) eff_packages_dl)) @
pp_command @
!options in
let actual_command = Findlib.command `ocamldoc in
run_command !verbose actual_command arguments
;;
(************************************************************************)
(* From ocamldep source code: *)
let depends_on_char, continuation_char =
match Sys.os_type with
| "Unix" | "Win32" | "Cygwin" -> ':', '\\'
| "MacOS" -> '\196', '\182'
| _ -> assert false
;;
let suppress_targets suffix =
(* If [line] begins with "target: dependencies ...", and [target] is a
* file name ending in [suffix], this line is suppressed, and all
* follow-up lines.
*)
let do_suppress = ref false in
fun line ->
let target =
try
let k = String.index_from line 0 depends_on_char in (* or Not_found *)
let target_string = String.sub line 0 k in
if String.contains target_string ' ' then raise Not_found;
Some target_string
with
Not_found -> None
in
begin match target with
Some target_string ->
do_suppress := Filename.check_suffix target_string suffix;
| None ->
()
end;
if !do_suppress then
None
else
Some line
;;
let ocamldep () =
let switches = ref [] in
let pass_options = ref [] in
let pass_files = ref [] in
let packages = ref [] in
let syntax_preds = ref [] in
let pp_opts = ref [] in
let pp_specified = ref false in
let verbose = ref false in
let native_filter = ref false in
let bytecode_filter = ref false in
let add_switch name =
Arg.Unit (fun () ->
switches := name :: !switches;
pass_options := !pass_options @ [name]) in
let add_spec_fn name s =
pass_options := !pass_options @ [name; s] in
let add_spec name = Arg.String (add_spec_fn name) in
let add_syntax_pred =
Arg.String (fun s -> syntax_preds := !syntax_preds @ (Fl_split.in_words s)) in
let add_pp_opt =
Arg.String (fun s -> pp_opts := !pp_opts @ [s]) in
let add_pkg =
Arg.String (fun s -> packages := !packages @ (Fl_split.in_words s)) in
let native_spec =
match Ocaml_args.ocamldep_spec with
| None -> failwith "Not supported in your configuration: ocamldep"
| Some s -> s in
Arg.parse
( [
"-syntax", add_syntax_pred,
" <p> Use preprocessor with predicate <p>";
"-package", add_pkg,
" <p> Add preprocessor package <p>";
"-ppopt", add_pp_opt,
" <opt> Append option <opt> to preprocessor invocation";
"-passopt", Arg.String (fun s -> pass_options := !pass_options @ [s]),
" <opt> Pass option <opt> directly to ocamlc/opt/mktop";
"-native-filter", Arg.Set native_filter,
" Output only dependencies for native code (implies -native)";
"-bytecode-filter", Arg.Set bytecode_filter,
" Output only dependencies for bytecode";
"-verbose", Arg.Set verbose,
" Print calls to external commands\nSTANDARD OPTIONS:";
]
@
( merge_native_arguments
native_spec
add_switch
add_spec
[ "-I",
Arg.String (fun s -> add_spec_fn "-I" (resolve_path s));
"-pp", Arg.String (fun s -> pp_specified := true;
add_spec_fn "-pp" s);
]
)
)
(fun s -> pass_files := !pass_files @ [ s])
("usage: ocamlfind ocamldep [options] file ...");
check_package_list !packages;
if !native_filter && !bytecode_filter then
failwith "The options -native-filter and -bytecode-filter are incompatible";
if !native_filter && not (List.mem "-native" !switches) then
pass_options := "-native" :: !pass_options;
if !syntax_preds <> [] then
syntax_preds := "preprocessor" :: "syntax" :: !syntax_preds;
if !verbose && !syntax_preds <> [] then
print_string ("Effective set of preprocessor predicates: " ^
String.concat "," !syntax_preds ^ "\n");
if !pp_specified && !syntax_preds <> [] then
prerr_endline("Warning: -pp overrides the effect of -syntax partly");
let pp_command =
if !pp_specified then
[]
else
process_pp_spec !syntax_preds !packages !pp_opts
in
let arguments =
!pass_options @
pp_command @
!pass_files
in
let actual_command = Findlib.command `ocamldep in
let filter =
if !native_filter then
(* Suppress when target is ".cmo": *)
Some (suppress_targets ".cmo")
else
if !bytecode_filter then
(* Suppress when target is ".cmx": *)
Some (suppress_targets ".cmx")
else
None
in
run_command ?filter !verbose actual_command arguments
;;
(************************************************************************)
let ocamlbrowser () =
let switches = ref [] in
let pass_options = ref [] in
let add_all = ref false in
let packages = ref [] in
let add_switch name =
Arg.Unit (fun () ->
switches := name :: !switches;
pass_options := !pass_options @ [name]) in
let add_spec_fn name s =
pass_options := !pass_options @ [name; s] in
let add_spec name = Arg.String (add_spec_fn name) in
let add_pkg =
Arg.String (fun s -> packages := !packages @ (Fl_split.in_words s)) in
Arg.parse
[
"-I", Arg.String (fun s -> add_spec_fn "-I" (resolve_path s)),
" <dir> Add <dir> to the list of include directories";
"-all", Arg.Set add_all,
" Add all packages to include path";
"-package", add_pkg,
" <p> Add package <p> to include path";
"-passopt", Arg.String (fun s -> pass_options := !pass_options @ [s]),
" <opt> Pass option <opt> directly to ocamlbrowser";
]
(fun s -> raise (Arg.Bad ("Unexpected argument: " ^ s)))
("usage: ocamlfind ocamlbrowser [options] file ...");
if !add_all then packages := Fl_package_base.list_packages();
check_package_list !packages;
let arguments =
!pass_options @
(List.flatten
(List.map
(fun pkg ->
let dir = Findlib.package_directory pkg in
[ "-I"; dir ]
)
!packages
)
)
in
let actual_command = Findlib.command `ocamlbrowser in
run_command false actual_command arguments
;;
(************************************************************************)
let copy_file ?(rename = (fun name -> name)) ?(append = "") src dstdir =
(* A system-independent function to copy the file src to dstdir *)
let outname = rename (Filename.basename src) in
let ch_in = open_in_bin src in
(* Determine the permissions of the file: the permissions of the
* user bits are extended to all groups (user, group, world bits),
* and the umask is applied to the result.
* Furthermore, the mtime of the file is preserved. This seems to be
* important for BSD-style archives (otherwise the system is confused
* and wants that ranlib is run again). For simplicity, the atime is
* set to the mtime, too.
*)
let s = Unix.stat src in
let perm = s.Unix.st_perm in
let user_perm = (perm land 0o700) lsr 6 in
let perm' = user_perm lor (user_perm lsl 3) lor (user_perm lsl 6) in
try
let outpath = Filename.concat dstdir outname in
if Sys.file_exists outpath then
prerr_endline ("ocamlfind: [WARNING] Overwriting file " ^ outpath);
let ch_out = open_out_gen
[Open_wronly; Open_creat; Open_trunc; Open_binary]
perm'
outpath in
try
let buflen = 4096 in
let buf = String.create buflen in
let pos = ref 0 in
let len = ref (input ch_in buf 0 buflen) in
while !len > 0 do
output ch_out buf !pos !len;
len := input ch_in buf !pos buflen;
done;
output_string ch_out append;
close_out ch_out;
close_in ch_in;
Unix.utimes outpath s.Unix.st_mtime s.Unix.st_mtime;
prerr_endline("Installed " ^ outpath);
with
exc -> close_out ch_out; raise exc
with
exc -> close_in ch_in; raise exc
;;
let install_create_directory pkgname dstdir =
try
Unix.mkdir dstdir 0o777
with
Unix.Unix_error(Unix.EEXIST,_,_) ->
()
| Unix.Unix_error(Unix.ENOENT,_,_)
| Unix.Unix_error(Unix.ENOTDIR,_,_) ->
failwith ("Bad configuration: Cannot mkdir " ^ dstdir ^ " because a path component does not exist or is not a directory")
| Unix.Unix_error(e,_,_) ->
failwith ("Cannot mkdir " ^ dstdir ^ ": " ^
Unix.error_message e)
;;
let create_owner_file pkg file =
let outpath = file ^ ".owner" in
let f = open_out outpath in
try
output_string f (pkg ^ "\n");
close_out f;
prerr_endline("Installed " ^ outpath);
with
exc -> close_out f; raise exc
;;
let find_owned_files pkg dir =
let files = Array.to_list(Sys.readdir dir) in
List.filter
(fun file ->
let owner_file =
if Filename.check_suffix file ".owner" then
file
else
file ^ ".owner" in
(List.mem owner_file files) && (
let f = open_in (Filename.concat dir owner_file) in
try
let line = input_line f in
let is_my_file = (line = pkg) in
close_in f;
is_my_file
with
exc -> close_in f; raise exc
)
)
files
;;
exception Missing_archives of Fl_metascanner.pkg_expr
let rec patch_archives pkgdir pkg =
(* First remove all missing files from archive variables: *)
let defs' =
List.map
(fun def ->
if def.Fl_metascanner.def_var = "archive" then (
let files = Fl_split.in_words def.Fl_metascanner.def_value in
let files' =
List.filter
(fun file ->
let p = Findlib.resolve_path ~base:pkgdir file in
Sys.file_exists p)
files in
{ def with
Fl_metascanner.def_value = String.concat " " files'
}
)
else def
)
pkg.Fl_metascanner.pkg_defs in
(* Remove empty archive variables: *)
let defs'' =
List.filter
(fun def ->
def.Fl_metascanner.def_var <> "archive" ||
Fl_split.in_words def.Fl_metascanner.def_value <> []
)
defs' in
(* Return the package or raise Not_found if all archives vanished: *)
let children =
(* Recursive patch, remove all Not_found packages: *)
List.flatten
(List.map
(fun (name, child) ->
try [ name, patch_archives pkgdir child ]
with Missing_archives _ -> []
)
pkg.Fl_metascanner.pkg_children) in
let pkg' =
{ Fl_metascanner.pkg_defs = defs'';
pkg_children = children
} in
if List.exists (fun def -> def.Fl_metascanner.def_var = "archive") defs'' then
pkg'
else
raise (Missing_archives pkg')
;;
let rec patch_pkg pkgdir pkg patches =
match patches with
| [] -> pkg
| (`Version v) :: patches' ->
let def =
{ Fl_metascanner.def_var = "version";
def_flav = `BaseDef;
def_preds = [];
def_value = v
} in
let defs =
List.filter
(fun d -> d.Fl_metascanner.def_var <> "version")
pkg.Fl_metascanner.pkg_defs in
let pkg' =
{ pkg with
Fl_metascanner.pkg_defs = def :: defs
} in
patch_pkg pkgdir pkg' patches'
| (`Rmpkg n) :: patches' ->
let children =
List.filter
(fun (name,_) -> name <> n)
pkg.Fl_metascanner.pkg_children in
let pkg' =
{ pkg with
Fl_metascanner.pkg_children = children
} in
patch_pkg pkgdir pkg' patches'
| `Archives :: patches' ->
let pkg' =
try patch_archives pkgdir pkg
with
Missing_archives p -> p in
patch_pkg pkgdir pkg' patches'
;;
exception Skip_file;;
type which = Auto | Dll | No_dll;;
let install_package () =
let destdir = ref (default_location()) in
let metadir = ref (meta_directory()) in
let ldconf = ref (ocaml_ldconf()) in
let don't_add_directory_directive = ref false in
let pkgname = ref "" in
let auto_files = ref [] in
let dll_files = ref [] in
let nodll_files = ref [] in
let which = ref Auto in
let optional = ref false in
let patches = ref [] in
let keywords =
[ "-destdir", (Arg.String (fun s -> destdir := s)),
("<path> Set the destination directory (default: " ^
!destdir ^ ")");
"-metadir", (Arg.String (fun s -> metadir := s)),
("<path> Install the META file into this directory (default: "^
(if !metadir = "" then "none" else !metadir) ^ ")");
"-ldconf", (Arg.String (fun s -> ldconf := s)),
("<path> Update this ld.conf file (default: " ^ !ldconf ^ ")");
"-dont-add-directory-directive", (Arg.Set don't_add_directory_directive),
" never append directory='...' to META";
"-dll", Arg.Unit (fun () -> which := Dll),
" The following files are DLLs";
"-nodll", Arg.Unit (fun () -> which := No_dll),
" The following files are not DLLs";
"-optional", Arg.Set optional,
" The following files are optional";
"-patch-version", Arg.String (fun s -> patches := !patches @ [`Version s]),
"<v> Set the package version to <v>";
"-patch-rmpkg", Arg.String (fun s -> patches := !patches @ [`Rmpkg s]),
"<n> Remove the subpackage <n>";
"-patch-archives", Arg.Unit (fun () -> patches := !patches @ [`Archives]),
" Remove non-existing archives";
] in
let errmsg = "usage: ocamlfind install [options] <package_name> <file> ..." in
Arg.parse
keywords
(fun s ->
if !pkgname = ""
then pkgname := s
else
if not !optional || Sys.file_exists s then
match !which with
Auto -> auto_files := s :: !auto_files
| Dll -> dll_files := s :: !dll_files
| No_dll -> nodll_files := s :: !nodll_files
)
errmsg;
if !pkgname = "" then (Arg.usage keywords errmsg; exit 1);
if not (Fl_split.is_valid_package_name !pkgname) then
failwith "Package names must not contain the character '.'!";
let pkgdir = Filename.concat !destdir !pkgname in
let dlldir = Filename.concat !destdir Findlib_config.libexec_name in
let has_metadir = !metadir <> "" in
let meta_dot_pkg = "META." ^ !pkgname in
(* The list of all files to install: *)
let full_list = !auto_files @ !dll_files @ !nodll_files in
(* Check whether there are DLLs: *)
let (l1,l2) = List.partition is_dll !auto_files in
let dll_list = l1 @ !dll_files in
let nodll_list = l2 @ !nodll_files in
let have_libexec = Sys.file_exists dlldir in
let pkgdir_list = if have_libexec then nodll_list else full_list in
let pkgdir_eff_list =
(* The files that will be placed into pkgdir: *)
List.map
(fun f ->
if f = meta_dot_pkg then "META" else f)
(List.filter
(fun f ->
not has_metadir ||
(f <> "META" && f <> meta_dot_pkg))
pkgdir_list) in
(* Check whether META exists: (And check syntax) *)
let meta_name =
try
List.find
(fun p ->
let b = Filename.basename p in
b = "META" || b = meta_dot_pkg)
nodll_list
with
| Not_found ->
failwith "The META file is missing" in
let meta_pkg =
let f = open_in meta_name in
try
let pkg = Fl_metascanner.parse f in
close_in f;
pkg
with
| Failure s
| Stream.Error s ->
close_in f;
failwith ("Cannot parse '" ^ meta_name ^ "': " ^ s)
in
(* Check for frequent reasons why installation can go wrong *)
if Sys.file_exists (Filename.concat !metadir meta_dot_pkg) then
failwith ("Package " ^ !pkgname ^ " is already installed\n - (file " ^ Filename.concat !metadir meta_dot_pkg ^ " already exists)");
if Sys.file_exists (Filename.concat pkgdir "META") then
failwith ("Package " ^ !pkgname ^ " is already installed\n - (file " ^ pkgdir ^ "/META already exists)");
List.iter
(fun f ->
let f' = Filename.concat pkgdir f in
if Sys.file_exists f' then
failwith ("Conflict with file: " ^ f'))
pkgdir_eff_list;
if have_libexec then begin
List.iter
(fun dll ->
let b = Filename.basename dll in
if Sys.file_exists (Filename.concat dlldir b) then
failwith ("Conflict with another package: Library " ^ b ^
" is already installed");
)
dll_list
end;
(* Create the package directory: *)
install_create_directory !pkgname pkgdir;
(* Now copy the files into the package directory: *)
List.iter
(fun p ->
try
copy_file
~rename: (fun f ->
if f = "META" || f = meta_dot_pkg then
raise Skip_file
else
f)
p
pkgdir
with
Skip_file -> ()
)
pkgdir_list;
(* Now write the META file: *)
let write_meta append_directory dir name =
(* If there are patches, write the patched META, else copy the file: *)
if !patches = [] then
copy_file
~rename:(fun _ -> name)
?append:(if append_directory then
Some("\ndirectory=\"" ^ pkgdir ^
"\" # auto-added by ocamlfind\n")
else
None)
meta_name
dir
else (
let p = Filename.concat dir name in
let patched_pkg = patch_pkg pkgdir meta_pkg !patches in
let out = open_out p in
Fl_metascanner.print out patched_pkg;
if append_directory then
output_string out ("\ndirectory=\"" ^ pkgdir ^
"\" # auto-added by ocamlfind\n");
close_out out;
prerr_endline ("Installed " ^ p);
)
in
if has_metadir then
write_meta true !metadir meta_dot_pkg
else
write_meta false pkgdir "META";
(* Copy the DLLs into the libexec directory if necessary *)
if have_libexec then begin
List.iter
(fun p ->
copy_file p dlldir;
create_owner_file !pkgname
(Filename.concat dlldir (Filename.basename p))
)
dll_list
end;
(* Extend ld.conf if necessary: *)
if dll_list <> [] && !ldconf <> "ignore" && not have_libexec then begin
if Sys.file_exists !ldconf then
begin
let lines = read_ldconf !ldconf in
write_ldconf !ldconf lines [ pkgdir ]
end
else
prerr_endline("ocamlfind: [WARNING] You have installed DLLs but there is no ld.conf")
end;
if dll_list <> [] && have_libexec && !ldconf <> "ignore" then begin
(* Check whether libexec is mentioned in ldconf *)
let lines = read_ldconf !ldconf in
let dlldir_norm = Fl_split.norm_dir dlldir in
if not (List.exists
(fun d -> Fl_split.norm_dir d = dlldir_norm)
lines) then
prerr_endline("ocamlfind: [WARNING] You have installed DLLs but the directory " ^ dlldir_norm ^ " is not mentioned in ld.conf");
end;
(* Check if there is a postinstall script: *)
let postinstall = Filename.concat !destdir "postinstall" in
if Sys.file_exists postinstall then
run_command true postinstall [ !destdir; !pkgname ]
;;
let reserved_names = [ Findlib_config.libexec_name; "postinstall"; "postremove" ];;
let remove_package () =
let destdir = ref (default_location()) in
let destdir_set = ref false in
let metadir = ref (meta_directory()) in
let ldconf = ref (ocaml_ldconf()) in
let pkgname = ref "" in
let keywords =
[ "-destdir", (Arg.String (fun s -> destdir := s; destdir_set := true)),
("<path> Set the destination directory (default: " ^
!destdir ^ ")");
"-metadir", (Arg.String (fun s -> metadir := s)),
("<path> Remove the META file from this directory (default: " ^
(if !metadir = "" then "none" else !metadir) ^ ")");
"-ldconf", (Arg.String (fun s -> ldconf := s)),
("<path> Update this ld.conf file (default: " ^ !ldconf ^ ")");
] in
let errmsg = "usage: ocamlfind remove [options] <package_name>" in
Arg.parse
keywords
(fun s ->
if !pkgname = ""
then pkgname := s
else raise (Arg.Bad "too many arguments")
)
errmsg;
if !pkgname = "" then (Arg.usage keywords errmsg; exit 1);
if List.mem !pkgname reserved_names then
failwith ("You are not allowed to remove this thing by ocamlfind!");
if not (Fl_split.is_valid_package_name !pkgname) then
failwith "Package names must not contain the character '.'!";
let meta_dot_pkg = "META." ^ !pkgname in
let has_metadir = !metadir <> "" in
let pkgdir = Filename.concat !destdir !pkgname in
let dlldir = Filename.concat !destdir Findlib_config.libexec_name in
let have_libexec = Sys.file_exists dlldir in
(* Warn if there is another package with the same name: *)
let other_pkgdir =
try Findlib.package_directory !pkgname with No_such_package _ -> "" in
if other_pkgdir <> "" && not !destdir_set then begin
(* Is pkgdir = other_pkgdir? - We check physical identity: *)
try
let s_other_pkgdir = Unix.stat other_pkgdir in
try
let s_pkgdir = Unix.stat pkgdir in
if (s_pkgdir.Unix.st_dev <> s_other_pkgdir.Unix.st_dev) ||
(s_pkgdir.Unix.st_ino <> s_other_pkgdir.Unix.st_ino)
then
prerr_endline("ocamlfind: [WARNING] You are removing the package from " ^ pkgdir ^ " but the currently visible package is at " ^ other_pkgdir ^ "; you may want to specify the -destdir option");
with
Unix.Unix_error(Unix.ENOENT,_,_) ->
prerr_endline("ocamlfind: [WARNING] You are trying to remove the package from " ^ pkgdir ^ " but the currently visible package is at " ^ other_pkgdir ^ "; you may want to specify the -destdir option");
with
Unix.Unix_error(_,_,_) -> () (* ignore, it's only a warning *)
end;
(* If there is a metadir, remove the META file from it: *)
if has_metadir then begin
let f = Filename.concat !metadir meta_dot_pkg in
if Sys.file_exists f then begin
Sys.remove f;
prerr_endline ("Removed " ^ f);
end
else
prerr_endline ("ocamlfind: [WARNING] No such file: " ^ f)
end;
(* Remove files from libexec directory: *)
if have_libexec then begin
let dll_files = find_owned_files !pkgname dlldir in
List.iter
(fun file ->
let absfile = Filename.concat dlldir file in
Sys.remove absfile;
prerr_endline ("Removed " ^ absfile)
)
dll_files
end;
(* Remove the files from the package directory: *)
if Sys.file_exists pkgdir then begin
let files = Sys.readdir pkgdir in
Array.iter (fun f -> Sys.remove (Filename.concat pkgdir f)) files;
Unix.rmdir pkgdir;
prerr_endline ("Removed " ^ pkgdir)
end
else
prerr_endline("ocamlfind: [WARNING] No such directory: " ^ pkgdir);
(* Modify ld.conf *)
if !ldconf <> "ignore" then begin
if Sys.file_exists !ldconf then
begin
let lines = read_ldconf !ldconf in
let d = Fl_split.norm_dir pkgdir in
let exists = List.exists (fun p -> Fl_split.norm_dir p = d) lines in
if exists then begin
let lines' = List.filter (fun p -> Fl_split.norm_dir p <> d) lines in
write_ldconf !ldconf lines' []
end
end
end;
(* Check if there is a postremove script: *)
let postremove = Filename.concat !destdir "postremove" in
if Sys.file_exists postremove then
run_command true postremove [ !destdir; !pkgname ]
;;
let list_packages() =
let descr = ref false in
let keywords =
[ "-describe", Arg.Set descr,
" Output package descriptions";
] in
let errmsg = "usage: ocamlfind list [options]" in
Arg.parse
keywords
(fun _ -> Arg.usage keywords errmsg; exit 1)
errmsg;
Findlib.list_packages ~descr:!descr stdout;
Fl_package_base.package_conflict_report ~identify_dir ()
;;
let print_configuration() =
let dir s =
if Sys.file_exists s then
s
else
s ^ " (not found)"
in
let var = ref None in
let errmsg = "usage: ocamlfind printconf (conf|path|destdir|metadir|stdlib|ldconf)" in
Arg.parse
[]
(fun s ->
if !var <> None then raise(Arg.Bad "Unexpected argument");
match s with
("conf" | "path" | "destdir" | "metadir" | "stdlib" | "ldconf") ->
var := Some s
| _ ->
raise(Arg.Bad "Bad argument");
)
errmsg;
match !var with
None ->
print_endline "Effective configuration:";
Printf.printf "Configuration file:\n %s\n"
(dir Findlib_config.config_file);
Printf.printf "Search path:\n";
List.iter
(fun p -> Printf.printf " %s\n" (dir p))
(Findlib.search_path());
Printf.printf "Packages will be installed in/removed from:\n %s\n"
(dir (Findlib.default_location()));
Printf.printf "META files will be installed in/removed from:\n %s\n"
(let md = Findlib.meta_directory() in
if md = "" then "the corresponding package directories" else dir md
);
Printf.printf "The standard library is assumed to reside in:\n %s\n"
(Findlib.ocaml_stdlib());
Printf.printf "The ld.conf file can be found here:\n %s\n"
(Findlib.ocaml_ldconf());
flush stdout
| Some "conf" ->
print_endline Findlib_config.config_file
| Some "path" ->
List.iter print_endline (Findlib.search_path())
| Some "destdir" ->
print_endline (Findlib.default_location())
| Some "metadir" ->
print_endline (Findlib.meta_directory())
| Some "stdlib" ->
print_endline (Findlib.ocaml_stdlib())
| Some "ldconf" ->
print_endline (Findlib.ocaml_ldconf())
| _ ->
assert false
;;
let ocamlcall pkg cmd =
let dir = package_directory pkg in
let path = Filename.concat dir cmd in
begin
try Unix.access path [ Unix.X_OK ]
with
Unix.Unix_error (Unix.ENOENT, _, _) ->
failwith ("Cannot find command: " ^ path)
| Unix.Unix_error (Unix.EACCES, _, _) ->
failwith ("Cannot execute: " ^ path)
| other ->
Unix.handle_unix_error (fun () -> raise other) ()
end;
let args = Array.to_list (Array.sub Sys.argv 2 (Array.length Sys.argv -2)) in
run_command false path args
;;
let rec select_mode () =
let k = !Arg.current in
let m_string = try arg (k+1) with Not_found -> raise Usage in
let m =
match m_string with
("use"|"-use") -> incr Arg.current; M_use
| ("query"|"-query") -> incr Arg.current; M_query
| ("install"|"-install") -> incr Arg.current; M_install
| ("remove"|"-remove") -> incr Arg.current; M_remove
| ("ocamlc"|"-ocamlc"|"c") -> incr Arg.current; M_compiler "ocamlc"
| ("ocamlcp"|"-ocamlcp"|"cp") -> incr Arg.current; M_compiler "ocamlcp"
| ("ocamlmktop"|"-ocamlmktop"|"mktop") -> incr Arg.current; M_compiler "ocamlmktop"
| ("ocamlopt"|"-ocamlopt"|"opt") -> incr Arg.current; M_compiler "ocamlopt"
| ("ocamldep"|"-ocamldep"|"dep") -> incr Arg.current; M_dep
| ("ocamlbrowser"|"-ocamlbrowser"|"browser") -> incr Arg.current; M_browser
| ("ocamldoc"|"-ocamldoc"|"doc") -> incr Arg.current; M_doc
| ("printconf"|"-printconf") -> incr Arg.current; M_printconf
| ("list"|"-list") -> incr Arg.current; M_list
| "-toolchain" ->
let t = try arg (k+2) with Not_found -> raise Usage in
Findlib.init ~toolchain:t ();
Arg.current := k+2;
select_mode()
| s when String.contains m_string '/' ->
incr Arg.current;
let k = String.index m_string '/' in
let pkg = String.sub m_string 0 k in
let cmd = String.sub m_string (k+1) (String.length m_string - k - 1) in
M_call(pkg,cmd)
| _ -> raise Usage
in
m
;;
let main() =
try
let m = select_mode() in
let l = Array.length Sys.argv in
let k = !Arg.current in
let rest = Array.sub Sys.argv (k+1) (l-k-1) in
match m with
M_use -> if rest = [| |] then raise Usage;
if rest.(0) = "-p" then begin
if l<4 then raise Usage;
use_package rest.(1)
(List.tl(List.tl(Array.to_list rest)))
end
else
use_package "" (Array.to_list rest)
| M_query -> query_package ()
| M_install -> install_package()
| M_remove -> remove_package ()
| M_printconf -> print_configuration ()
| M_list -> list_packages()
| M_compiler which -> ocamlc which ()
| M_dep -> ocamldep()
| M_browser -> ocamlbrowser()
| M_doc -> ocamldoc()
| M_call(pkg,cmd) -> ocamlcall pkg cmd
with
Usage ->
prerr_endline "Usage: ocamlfind query [-help | other options] <package_name> ...";
prerr_endline " or: ocamlfind ocamlc [-help | other options] <file> ...";
prerr_endline " or: ocamlfind ocamlcp [-help | other options] <file> ...";
prerr_endline " or: ocamlfind ocamlmktop [-help | other options] <file> ...";
prerr_endline " or: ocamlfind ocamlopt [-help | other options] <file> ...";
prerr_endline " or: ocamlfind ocamldep [-help | other options] <file> ...";
prerr_endline " or: ocamlfind ocamlbrowser [-help | other options]";
prerr_endline " or: ocamlfind ocamldoc [-help | other options] <file> ...";
prerr_endline " or: ocamlfind install [-help | other options] <package_name> <file> ...";
prerr_endline " or: ocamlfind remove [-help | other options] <package_name>";
prerr_endline " or: ocamlfind printconf [-help] [variable]";
prerr_endline " or: ocamlfind list";
prerr_endline " or: ocamlfind pkg/cmd arg ...";
prerr_endline "Select toolchain with:";
prerr_endline " ocamlfind -toolchain <t> <command>";
prerr_endline "Abbreviations:";
prerr_endline " e.g. ocamlfind opt instead of ocamlfind ocamlopt";
exit 2
| Failure f ->
prerr_endline ("ocamlfind: " ^ f);
exit 2
| Sys_error f ->
prerr_endline ("ocamlfind: " ^ f);
exit 2
| Findlib.No_such_package(pkg,info) ->
prerr_endline ("ocamlfind: Package `" ^ pkg ^ "' not found" ^
(if info <> "" then " - " ^ info else ""));
exit 2
| Findlib.Package_loop pkg ->
prerr_endline ("ocamlfind: Package `" ^ pkg ^ "' requires itself");
exit 2
;;
try
Sys.catch_break true;
main()
with
any ->
prerr_endline ("Uncaught exception: " ^ Printexc.to_string any);
let raise_again =
try ignore(Sys.getenv "OCAMLFIND_DEBUG"); true
with Not_found -> false
in
if raise_again then raise any;
exit 3
;;