Plasma GitLab Archive
Projects Blog Knowledge

(* $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
;;

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml