(* $Id$
 * ----------------------------------------------------------------------
 *
 *)

(** Direct access to the package graph and package files *)

type package =
    { package_name : string;
        (** The fully qualified package name, i.e. for subpackages the
	 * names of the containing packages are prepended and the name
	 * components are separated by '.'
	 *)
      package_dir : string;
        (** The directory where to lookup package files *)
      package_meta : string;
        (** The path to the META file *)
      package_defs : Fl_metascanner.pkg_definition list;
        (** The definitions in the META file *)
      package_priv : package_priv;
        (** Private part of the definition *)
    }
    (** The definition of a package *)

and package_priv


val init : string list -> string -> string list -> unit
  (** This function must be called before [Fl_package_base] can be used.
   * The first string corresponds to the [OCAMLPATH] setting, the second
   * string is the location of the standard library. The second is the
   * list of directories with ignored duplicate cmi files.
   *
   * This function is called by {!Findlib.init} and {!Findlib.init_manually},
   * so it is already sufficient to initialize the [Findlib] module.
   *)


(** {1 The package graph} *)

(** The functions in this section operate on a representation of the
 * package graph in memory. The graph is usually only partially available,
 * as only packages are loaded that are queried for.
 *)


exception No_such_package of string * string
  (** First arg is the package name not found, second arg contains additional
   * info for the user. - This is the same exception as in [Findlib].
   *)

exception Package_loop of string
  (** A package is required by itself. The arg is the name of the 
   * package. - This is the same exception as in [Findlib].
   *)

val query : string -> package
  (** Returns the [package] definition for the fully-qualified package name,
   * or raises [No_such_package]. It is allowed to query for subpackages.
   *
   * This function loads package definitions into the graph kept in memory.
   *)

val requires : preds:string list -> string -> string list
  (** Analyzes the direct requirements of the package whose name is passed as
   * second argument under the assumption that the predicates [preds]
   * hold. The function returns the names of the required packages.
   * It is checked whether these packages exist.
   *
   * If there is the "mt" predicate, missing dependencies on "threads"
   * are silently added.
   *
   * The function may raise [No_such_package] or [Package_loop].
   *
   * This function loads package definitions into the graph kept in memory.
   *)

val requires_deeply : preds:string list -> string list -> string list
  (** Analyzes the direct or indirect requirements of the packages whose names
   * are passed as second argument under the assumption that the predicates
   * [preds] hold. The function returns the names of the required packages.
   * It is checked whether these packages exist.
   *
   * If there is the "mt" predicate, missing dependencies on "threads"
   * are silently added.
   *
   * The function may raise [No_such_package] or [Package_loop].
   *
   * This function loads package definitions into the graph kept in memory.
   *)

val package_conflict_report : 
      ?identify_dir:(string -> 'a) -> unit -> unit
  (** Checks whether there are several META files for the same main
   * packages. Complaints are printed to stderr.
   *
   * Only packages in the loaded part of the package graph are checked (i.e.
   * packages for which there was a query).
   *
   * It is recommended to pass the ~identify_dir function whose task
   * it is to return a unique value for every existing directory.
   * For example, 
   *   {[ fun d -> 
   *        let s = Unix.stat d in
   *        (s.Unix.st_dev, s.Unix.st_ino)
   *   ]}
   * could be an implementation for this function. The default is
   * the identity (and not this nice implementation to avoid dependencies
   * on the Unix module).
   *)

val module_conflict_report : ?identify_dir:(string -> 'a) -> string list -> unit
  (** Checks whether there are cmi files for the same modules. The
   * directories passed as first argument are checked. (Note:
   * Neither the '+' nor the '@' notation are recognized.)
   * Complaints about double cmi files are printed to stderr.
   *
   * @param identify_dir See [package_conflict_report].
   *)

val load_base : ?prefix:string -> unit -> unit
  (** Ensures that the complete package graph is loaded into memory.
   * This is a time-consuming operation. Warnings may be printed to
   * stderr.
   *
   * @param prefix Limit to the packages that starts with it. Default: unlimited
   *)

val list_packages : ?prefix:string -> unit -> string list
  (** Ensures that the complete package graph is loaded into memory
   * (like [load_base]), and returns the (unsorted) list of all
   * packages.
   *
   * @param prefix Limit to the packages that starts with it. Default: unlimited
   *)

val package_users : preds:string list -> string list -> string list
  (** Ensures that the complete package graph is loaded into memory
   * (like [load_base]), and determines the packages using one of
   * the packages passed as second argument. The [preds] are assumed
   * for the evaluation of the [requires] directives.
   * The returned list is sorted in ascending order.
   *
   * If there is the "mt" predicate, missing dependencies on "threads"
   * are silently added.
   *
   * Raises [No_such_package] if one of the passed packages cannot
   * be found.
   *)


(** {1 Parsing META files} *)

(** The functions in this section access directly files and directories.
 * The package graph is unknown.
 *)

val packages_in_meta_file : 
      ?directory_required:bool ->
      name:string -> dir:string -> meta_file:string -> unit -> package list
  (** Parses the META file whose name is [meta_file]. In [name], the
   * name of the main package must be passed. [dir] is the
   * directory associated with the package by default (i.e. before
   * it is overridden by the "directory" directive).
   *
   * Returns the package records found in this file. The "directory"
   * directive is already applied.
   *
   * @param directory_required If true, it is checked whether there is a
   * "directory" directive in the main package. If this directive is missing,
   * the function will fail.
   *)

val package_definitions : search_path:string list -> string -> string list
  (** Return all META files defining this package that occur in the 
   * directories mentioned in [search_path]. The package name must be
   * fully-qualified. For simplicity, however, only the name of the main
   * package is taken into account (so it is a good idea to call this
   * function only for main packages).
   *)

