// file      : bpkg/pkg-build-collect.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BPKG_PKG_BUILD_COLLECT_HXX
#define BPKG_PKG_BUILD_COLLECT_HXX

#include <map>
#include <set>
#include <list>
#include <forward_list>

#include <bpkg/types.hxx>
#include <bpkg/utility.hxx>

#include <bpkg/package.hxx>
#include <bpkg/diagnostics.hxx>

#include <bpkg/common-options.hxx>
#include <bpkg/pkg-build-options.hxx>

#include <bpkg/database.hxx>
#include <bpkg/pkg-configure.hxx>          // find_database_function()
#include <bpkg/package-skeleton.hxx>
#include <bpkg/system-package-manager.hxx>

namespace bpkg
{
  // The current configurations dependents being "repointed" to prerequisites
  // in other configurations, together with their replacement flags. The flag
  // is true for the replacement prerequisites ("new") and false for the
  // prerequisites being replaced ("old"). The unamended prerequisites have no
  // entries.
  //
  using repointed_dependents =
    std::map<package_key, std::map<package_key, bool>>;

  // A "dependency-ordered" list of packages and their prerequisites.
  // That is, every package on the list only possibly depending on the
  // ones after it. In a nutshell, the usage is as follows: we first
  // add one or more packages (the "initial selection"; for example, a
  // list of packages the user wants built). The list then satisfies all
  // the prerequisites of the packages that were added, recursively. At
  // the end of this process we have an ordered list of all the packages
  // that we have to build, from last to first, in order to build our
  // initial selection.
  //
  // This process is split into two phases: satisfaction of all the
  // dependencies (the collect_build() function) and ordering of the list
  // (the order() function).
  //
  // During the satisfaction phase, we collect all the packages, their
  // prerequisites (and so on, recursively) in a map trying to satisfy
  // any version constraints. Specifically, during this step, we may
  // "upgrade" or "downgrade" a package that is already in a map as a
  // result of another package depending on it and, for example, requiring
  // a different version. If that happens, we make sure that the replaced
  // package version doesn't apply constraints and/or configuration to its
  // own dependencies anymore and also that its non-shared dependencies are
  // gone from the map, recursively (see replaced_versions for details).
  // One notable side-effect of this process is that all the packages in the
  // map end up in the list.
  //
  // Note that we don't try to do exhaustive constraint satisfaction (i.e.,
  // there is no backtracking). Specifically, if we have two candidate
  // packages each satisfying a constraint of its dependent package, then if
  // neither of them satisfy both constraints, then we give up and ask the
  // user to resolve this manually by explicitly specifying the version that
  // will satisfy both constraints.
  //
  // Also note that we rule out dependency alternatives with enable constraint
  // that evaluates to false and try to select one satisfactory alternative if
  // there are multiple of them. In the latter case we pick the first
  // alternative with packages that are already used (as a result of being
  // dependencies of other package, requested by the user, or already being
  // present in the configuration) and fail if such an alternative doesn't
  // exist.
  //
  struct build_package
  {
    enum action_type
    {
      // Available package is not NULL.
      //
      build,

      // Selected package is not NULL, available package is NULL.
      //
      drop,

      // Selected package is not NULL, available package is NULL.
      //
      // This is the "only adjustments" action for a selected package.
      // Adjustment flags (see below) are unhold (the package should be
      // treated as a dependency) and reconfigure (dependent package that
      // needs to be reconfigured because its prerequisite is being
      // up/down-graded or reconfigured).
      //
      // Note that this action is "replaceable" with either drop or build
      // action but in the latter case the adjustments must be copied over.
      //
      adjust
    };

    // An object with an absent action is there to "pre-enter" information
    // about a package (constraints and flags) in case it is used.
    //
    optional<action_type> action;

    reference_wrapper<database> db;           // Needs to be move-assignable.

    shared_ptr<selected_package>  selected;   // NULL if not selected.
    shared_ptr<available_package> available;  // Can be NULL, fake/transient.

    // Can be NULL (orphan) or root. If not NULL, then loaded from the
    // repository configuration database, which may differ from the
    // configuration the package is being built in.
    //
    lazy_shared_ptr<bpkg::repository_fragment> repository_fragment;

    const package_name&
    name () const
    {
      return selected != nullptr ? selected->name : available->id.name;
    }

    // If we end up collecting the prerequisite builds for this package, then
    // this member stores copies of the selected dependency alternatives. The
    // dependency alternatives for toolchain build-time dependencies, for
    // dependencies which have all the alternatives disabled, and for inactive
    // constraints are represented as empty dependency alternatives lists,
    // which preserve the original type (dependencies, constraint, etc). If
    // present, it is parallel to the available package's dependencies member.
    //
    // Initially nullopt. Can be filled partially if the package prerequisite
    // builds collection is postponed for any reason (see postponed_packages
    // and postponed_configurations for possible reasons).
    //
    optional<bpkg::dependencies> dependencies;

    // Indexes of the selected dependency alternatives stored in the above
    // dependencies member.
    //
    optional<vector<size_t>> alternatives;

    // If we end up collecting the prerequisite builds for this package, then
    // this member stores the skeleton of the package being built.
    //
    // Initially nullopt. Can potentially be loaded but with the reflection
    // configuration variables collected only partially if the package
    // prerequisite builds collection is postponed for any reason. Can also be
    // unloaded if the package has no conditional dependencies.
    //
    optional<package_skeleton> skeleton;

    // If the package prerequisite builds collection is postponed, then this
    // member stores the references to the enabled alternatives (in available
    // package) of a dependency being the cause of the postponement together
    // with their original indexes in the respective dependency alternatives
    // list. This, in particular, allows us not to re-evaluate conditions
    // multiple times on the re-collection attempts.
    //
    // Note: it shouldn't be very common for a dependency to contain more than
    // two true alternatives.
    //
    using dependency_alternatives_refs =
      small_vector<pair<reference_wrapper<const dependency_alternative>,
                        size_t>,
                   2>;

    optional<dependency_alternatives_refs> postponed_dependency_alternatives;

    // True if the recursive collection of the package has been started or
    // performed.
    //
    // Used by the dependency configuration negotiation machinery which makes
    // sure that its configuration is negotiated between dependents before its
    // recursive collection is started (see postponed_configurations for
    // details).
    //
    // Note that the dependencies member cannot be used for that purpose since
    // it is not always created (think of a system dependency or an existing
    // dependency that doesn't need its prerequisites re-collection). In a
    // sense the recursive collection flag is a barrier for the dependency
    // configuration negotiation.
    //
    bool recursive_collection;

    // Return true if the recursive collection started but has been postponed
    // for any reason.
    //
    bool
    recursive_collection_postponed () const;

    // Hold flags.
    //
    // Note that we only "increase" the hold_package value that is already in
    // the selected package, unless the adjust_unhold flag is set (see below).
    //
    optional<bool> hold_package;

    // Note that it is perfectly valid for the hold_version flag to be false
    // while the command line constraint is present in the constraints list
    // (see below). This may happen if the package build is collected by the
    // unsatisfied dependency constraints resolution logic (see
    // try_replace_dependency() in pkg-build.cxx for details).
    //
    optional<bool> hold_version;

    // Version constraint value plus, normally, the dependent package
    // name/version that placed this constraint but can also be some other
    // name (in which case the version is absent) for the initial selection.
    // Currently, the only valid non-package name is 'command line', which is
    // used when the package version is constrained by the user on the command
    // line. Also, specifies the dependency type.
    //
    // Note that if the dependent is a package name, then this package is
    // expected to be collected (present in the map).
    //
    struct constraint
    {
      // If the dependency type is 'dependency', then the constraint
      // originates from the dependent's depends manifest value (dependent's
      // version is present) or from the build-to-hold command line spec. If
      // the type is 'constraint', then it originates from the constrains
      // manifest value or from the dependency command line spec.
      //
      bpkg::version_constraint version_constraint;

      package_version_key dependent;

      // Note: only used for diagnostics.
      //
      bpkg::dependency_type dependency_type;

      // False for non-packages. Otherwise, indicates whether the constraint
      // comes from the existing rather than the being built dependent.
      //
      // Note that we call an already configured dependent "existing", if it
      // need not be recollected recursively (see query_existing_dependents()
      // for details) and it is not being dropped.
      //
      // Note: only used for diagnostics (see print_constraints() for details).
      //
      bool existing_dependent;

      // Create constraint for a package dependent.
      //
      constraint (bpkg::version_constraint c,
                  database& db,
                  package_name nm,
                  version ver,
                  bpkg::dependency_type t,
                  bool e)
          : version_constraint (move (c)),
            dependent (db, move (nm), move (ver)),
            dependency_type (t),
            existing_dependent (e) {}

      // Create constraint for a non-package dependent.
      //
      constraint (bpkg::version_constraint c,
                  database& db,
                  string nm,
                  bpkg::dependency_type t)
          : version_constraint (move (c)),
            dependent (db, move (nm)),
            dependency_type (t),
            existing_dependent (false) {}

      // Return the constraint string representation.
      //
      std::string
      string (const package_name& dependency) const
      {
        return constraint_string (dependent,
                                  dependency,
                                  dependency_type,
                                  version_constraint);
      }

      // Note: ignores the existing_dependent flag.
      //
      bool
      operator== (const constraint& v) const
      {
        return version_constraint == v.version_constraint &&
               dependent          == v.dependent          &&
               dependency_type    == v.dependency_type;
      }
    };

    vector<constraint> constraints;

    // System package indicator. See also a note in the merge() function.
    //
    bool system;

    // Return the system/distribution package status if this is a system
    // package (re-)configuration and the package is being managed by the
    // system package manager (as opposed to user/fallback). Otherwise, return
    // NULL (so can be used as bool).
    //
    // Note on terminology: We call the bpkg package that is being configured
    // as available from the system as "system package" and we call the
    // underlying package managed by the system/distribution package manager
    // as "system/distribution package". See system-package-manager.hxx for
    // background.
    //
    const system_package_status*
    system_status () const;

    // As above but only return the status if the package needs to be
    // installed.
    //
    const system_package_status*
    system_install () const;

    // If this flag is set and the external package is being replaced with an
    // external one, then keep its output directory between upgrades and
    // downgrades.
    //
    bool keep_out;

    // If this flag is set then disfigure the package between upgrades and
    // downgrades effectively causing a from-scratch reconfiguration.
    //
    bool disfigure;

    // If this flag is set, then don't build this package, only configure.
    //
    // Note: use configure_only() to query.
    //
    bool configure_only_;

    // If present, then check out the package into the specified directory
    // rather than into the configuration directory, if it comes from a
    // version control-based repository. Optionally, remove this directory
    // when the package is purged.
    //
    optional<dir_path> checkout_root;
    bool               checkout_purge;

    // Command line configuration variables, not including global overrides
    // which are expected to be saved to skeleton's global_config_vars. Only
    // meaningful for non-system packages. @@ In fact, we may consider project
    // configuration variables for system packages in the future (see
    // skeleton's collect_config() for details).
    //
    strings config_vars;

    // If present, then the package is requested to be upgraded (true) or
    // patched (false). Can only be present if the package is already
    // selected. Can only be false if the selected package version is
    // patchable. Used by the unsatisfied dependency constraints resolution
    // logic (see try_replace_dependency() in pkg-build.cxx for details).
    //
    optional<bool> upgrade;

    // If true, then this package is requested to be deorphaned. Can only be
    // true if the package is already selected and is orphaned. Used by the
    // unsatisfied dependency constraints resolution logic (see
    // try_replace_dependency() in pkg-build.cxx for details). Also, the
    // package deorphaning target versions are normally preferred over the
    // other versions by collect_build().
    //
    bool deorphan;

    // Set of packages (dependents or dependencies but not a mix) that caused
    // this package to be built or adjusted. The 'command line' name signifies
    // user selection and can be present regardless of the
    // required_by_dependents flag value.
    //
    // Note that if this is a package name, then this package is expected to
    // be collected (present in the map), potentially just pre-entered if
    // required_by_dependents is false. If required_by_dependents is true,
    // then the packages in the set are all expected to be collected as builds
    // (action is build, available is not NULL, etc).
    //
    // Also note that if required_by_dependents is true, then all the
    // dependent package versions in the required_by set are expected to be
    // known (the version members are not empty). Otherwise (the required_by
    // set contains dependencies), since it's not always easy to deduce the
    // dependency versions at the time of collecting the dependent build (see
    // collect_repointed_dependents() implementation for details), the
    // dependency package versions are expected to all be unknown.
    //
    // Note: should not be empty, unless this is a drop, unhold, or is
    //       pre-entered.
    //
    // @@ Note that we don't track relations precisely for entries in
    //    required_by. As a result when we print the execution plan, the
    //    constraints and dependencies are indistinguishable and are all
    //    printed with the 'required by ' prefix. The similar problem appears
    //    for the unsatisfied_dependents::diag() function. Probably, the right
    //    way to go is to get rid of the required_by_dependents member and
    //    turn the required_by set into a map of packages to the relations
    //    with each package: 'constrained by', 'required by', 'dependent
    //    of'. For the command line entry the relation will be either
    //    'constrained by' (dependency spec) or 'required by' (build-to-hold
    //    spec).
    //
    //    Then, will be able to print in the plan:
    //
    //    new libbaz/1.0.0 (required by libbar, libbox; constrained by libfoo)
    //
    std::set<package_version_key> required_by;

    // If this flag is true, then required_by contains dependents.
    //
    // We need this because required_by packages have different semantics for
    // different actions: the dependent for regular builds and dependency for
    // adjustments and repointed dependent reconfiguration builds. Mixing them
    // would break prompts/diagnostics.
    //
    bool required_by_dependents;

    // Consider a package as user-selected if it is specified on the command
    // line, is a held package being upgraded via the `pkg-build -u|-p`
    // command form, or is a dependency being upgraded via the recursively
    // upgraded dependent.
    //
    bool
    user_selection () const;

    // Consider a package as user-selected only if it is specified on the
    // command line as build to hold.
    //
    bool
    user_selection (const vector<build_package>& hold_pkgs) const;

    // Return true if the configured package needs to be recollected
    // recursively.
    //
    // This is required if it is being built as a source package and needs to
    // be up/down-graded and/or reconfigured and has some buildfile clauses,
    // it is a repointed dependent, or it is already in the process of being
    // collected. Also configured dependents can be scheduled for recollection
    // explicitly (see postponed_packages and build_recollect flag for
    // details).
    //
    // Note that recursive recollection of a package which has some buildfile
    // clauses is not triggered by the presence of the global configuration
    // overrides unless the package is specified on the command line. In other
    // words, the recollection will only be triggered by an explicit
    // reconfiguration: the presence of global or non-global overrides plus
    // the package is specified on the command line.
    //
    bool
    recollect_recursively (const repointed_dependents&) const;

    // State flags.
    //
    uint16_t flags;

    // Set if we also need to clear the hold package flag.
    //
    static const uint16_t adjust_unhold = 0x0001;

    bool
    unhold () const
    {
      return (flags & adjust_unhold) != 0;
    }

    // Set if we also need to reconfigure this package. Note that in some
    // cases reconfigure is naturally implied. For example, if an already
    // configured package is being up/down-graded. For such cases we don't
    // guarantee that the reconfigure flag is set. We only make sure to set it
    // for cases that would otherwise miss the need for reconfiguration. As a
    // result, use the reconfigure() predicate which detects both explicit and
    // implied cases.
    //
    // At first, it may seem that this flag is redundant and having the
    // available package set to NULL is sufficient. But consider the case
    // where the user asked us to build a package that is already in the
    // configured state (so all we have to do is pkg-update). Next, add to
    // this a prerequisite package that is being upgraded. Now our original
    // package has to be reconfigured. But without this flag we won't know
    // (available for our package won't be NULL).
    //
    static const uint16_t adjust_reconfigure = 0x0002;

    bool
    reconfigure () const;

    // Set if this build action is for repointing of prerequisite.
    //
    static const uint16_t build_repoint = 0x0004;

    // Set if this build action is for re-evaluating of an existing dependent.
    //
    static const uint16_t build_reevaluate = 0x0008;

    // Set if this build action is for recursive re-collecting of an existing
    // dependent due to deviation, detecting merge configuration cycle, etc.
    //
    static const uint16_t build_recollect = 0x0010;

    // Set if this build action is for replacing of an existing package due to
    // deorphaning or rebuilding as an archive or directory.
    //
    // Note that to replace a package we need to re-fetch it from an existing
    // repository fragment, archive, or directory (even if its version doesn't
    // change).
    //
    static const uint16_t build_replace = 0x0020;

    bool
    replace () const
    {
      return (flags & build_replace) != 0;
    }

    bool
    configure_only () const;

    // Return true if the resulting package will be configured as external.
    // Optionally, if the package is external, return its absolute and
    // normalized source root directory path.
    //
    bool
    external (dir_path* = nullptr) const;

    // If the resulting package will be configured as external, then return
    // its absolute and normalized source root directory path and nullopt
    // otherwise.
    //
    optional<dir_path>
    external_dir () const
    {
      dir_path r;
      return external (&r) ? optional<dir_path> (move (r)) : nullopt;
    }

    // Return the version of a being built package.
    //
    const version&
    available_version () const;

    // Return the name, potentially with the 'sys:' prefix, and version of a
    // being built package.
    //
    string
    available_name_version () const
    {
      assert (available != nullptr);
      return package_string (available->id.name, available_version (), system);
    }

    // As above but also append the database string representation, if not
    // empty.
    //
    string
    available_name_version_db () const;

    // As above but for all action types.
    //
    string
    name_version_db () const;

    // Print information that can be useful for debugging.
    //
    void
    print_info (diag_record&) const;

    // Merge constraints, required-by package names, hold_* flags, state
    // flags, and user-specified options/variables.
    //
    void
    merge (build_package&&);

    // Initialize the skeleton of a being built package.
    //
    package_skeleton&
    init_skeleton (const common_options&,
                   bool load_old_dependent_config = true,
                   const shared_ptr<available_package>& override = nullptr);
  };

  using build_package_list = std::list<reference_wrapper<build_package>>;

  using build_package_refs =
    small_vector<reference_wrapper<const build_package>, 16>;

  // Packages with postponed prerequisites collection, for one of the
  // following reasons:
  //
  // - Postponed due to the inability to find a dependency version satisfying
  //   the pre-entered constraint from repositories available to this
  //   package. The idea is that this constraint could still be satisfied from
  //   a repository fragment of some other package (that we haven't processed
  //   yet) that also depends on this prerequisite.
  //
  // - Postponed due to the inability to choose between two dependency
  //   alternatives, both having dependency packages which are not yet
  //   selected in the configuration nor being built. The idea is that this
  //   ambiguity could still be resolved after some of those dependency
  //   packages get built via some other dependents.
  //
  // - Postponed recollection of configured dependents whose dependencies
  //   up/downgrade causes selection of different dependency alternatives.
  //   This, in particular, may end up in resolving different dependency
  //   packages and affect the dependent and dependency configurations.
  //
  // - Postponed recollection of configured dependents for resolving merge
  //   configuration cycles and as a fallback for missed re-evaluations due to
  //   the shadow-based configuration clusters merge (see
  //   collect_build_prerequisites() for details).
  //
  // For the sake of testing, make sure the order in the set is stable.
  //
  struct compare_build_package
  {
    bool
    operator() (const build_package* x, const build_package* y) const
    {
      const package_name& nx (x->name ());
      const package_name& ny (y->name ());

      if (int d = nx.compare (ny))
        return d < 0;

      return x->db.get () < y->db.get ();
    }
  };
  using postponed_packages = std::set<build_package*, compare_build_package>;

  // Base for exception types that indicate an inability to collect a package
  // build because it was collected prematurely (version needs to be replaced,
  // configuration requires further negotiation, etc).
  //
  struct scratch_collection
  {
    // Only used for tracing.
    //
    const char* description;
    const package_key* package = nullptr; // Could be NULL.

    explicit
    scratch_collection (const char* d): description (d) {}
  };

  // Map of dependency packages whose recursive processing should be postponed
  // because they have dependents with configuration clauses.
  //
  // Note that dependents of such a package that don't have any configuration
  // clauses are processed right away (since the negotiated configuration may
  // not affect them) while those that do are postponed in the same way as
  // those with dependency alternatives (see above).
  //
  // Note that the latter kind of dependent is what eventually causes
  // recursive processing of the dependency packages. Which means we must
  // watch out for bogus entries in this map which we may still end up with
  // (e.g., because postponement caused cross-talk between dependency
  // alternatives). Thus we keep flags that indicate whether we have seen each
  // type of dependent and then just process dependencies that have the first
  // (without config) but not the second (with config).
  //
  // Note that if any of these flags is set to true, then the dependency is
  // expected to be collected (present in the build_packages's map; see below
  // for the class definition).
  //
  struct postponed_dependency
  {
    bool wout_config; // Has dependent without config.
    bool with_config; // Has dependent with config.

    postponed_dependency (bool woc, bool wic)
        : wout_config (woc),
          with_config (wic) {}

    bool
    bogus () const {return wout_config && !with_config;}

    void
    to_checksum (xxh64& cs) const
    {
      cs.append (wout_config);
      cs.append (with_config);
    }
  };

  class postponed_dependencies: public std::map<package_key,
                                                postponed_dependency>
  {
  public:
    bool
    has_bogus () const
    {
      for (const auto& pd: *this)
      {
        if (pd.second.bogus ())
          return true;
      }
      return false;
    }

    // Erase the bogus postponements and throw cancel_postponement, if any.
    //
    struct cancel_postponement: scratch_collection
    {
      cancel_postponement ()
          : scratch_collection (
            "bogus dependency collection postponement cancellation") {}
    };

    void
    cancel_bogus (tracer& trace)
    {
      bool bogus (false);
      for (auto i (begin ()); i != end (); )
      {
        const postponed_dependency& d (i->second);

        if (d.bogus ())
        {
          bogus = true;

          l5 ([&]{trace << "erase bogus postponement " << i->first;});

          i = erase (i);
        }
        else
          ++i;
      }

      if (bogus)
      {
        l5 ([&]{trace << "bogus postponements erased, throwing";});
        throw cancel_postponement ();
      }
    }
  };

  // Map of the dependencies whose recursive collection is postponed until
  // their existing dependents re-collection/re-evaluation to the lists of the
  // respective existing dependents (see collect_build_prerequisites() for
  // details).
  //
  using postponed_existing_dependencies = std::map<package_key,
                                                   vector<package_key>>;

  // Set of dependency alternatives which were found unacceptable by the
  // configuration negotiation machinery and need to be ignored on re-
  // collection.
  //
  // Note that while negotiating the dependency alternative configuration for
  // a dependent it may turn out that the configuration required by other
  // dependents is not acceptable for this dependent. It can also happen that
  // this dependent is involved in a negotiation cycle when two dependents
  // continuously overwrite each other's configuration during re-negotiation.
  // Both situations end up with the failure, unless the dependent has some
  // other reused dependency alternative which can be tried instead. In the
  // latter case, we note the problematic alternative and re-collect from
  // scratch. On re-collection the unacceptable alternatives are ignored,
  // similar to the disabled alternatives.
  //
  struct unacceptable_alternative
  {
    package_key package;
    bpkg::version version;
    pair<size_t, size_t> position; // depends + alternative (1-based)

    unacceptable_alternative (package_key pkg,
                              bpkg::version ver,
                              pair<size_t, size_t> pos)
        : package (move (pkg)), version (move (ver)), position (pos) {}

    bool
    operator< (const unacceptable_alternative& v) const
    {
      if (package != v.package)
        return package < v.package;

      if (int r = version.compare (v.version))
        return r < 0;

      return position < v.position;
    }

    void
    to_checksum (xxh64& cs) const
    {
      package.to_checksum (cs);
      cs.append (version.string ());
      cs.append (position.first);
      cs.append (position.second);
    }
  };

  using unacceptable_alternatives = std::set<unacceptable_alternative>;

  struct unaccept_alternative: scratch_collection
  {
    unaccept_alternative (): scratch_collection ("unacceptable alternative") {}
  };

  // Map of packages which need to be re-collected with the different version
  // and/or system flag or dropped.
  //
  // Note that the initial package version may be adjusted to satisfy
  // constraints of dependents discovered during the packages collection. It
  // may also be dropped if this is a dependency which turns out to be unused.
  // However, it may not always be possible to perform such an adjustment
  // in-place since the intermediate package version could already apply some
  // constraints and/or configuration to its own dependencies. Thus, we may
  // need to note the desired package version information and re-collect from
  // scratch.
  //
  // Also note that during re-collection such a desired version may turn out
  // to not be a final version and the adjustment/re-collection can repeat.
  //
  // And yet, it doesn't seem plausible to ever create a replacement for the
  // drop: replacing one drop with another is meaningless (all drops are the
  // same) and replacing the package drop with a package version build can
  // always been handled in-place. Note, however, that we need to be careful
  // in this case and, potentially, cancel dropping prerequisites of such a
  // package (see cancel_drop_prerequisites() for details).
  //
  // On the first glance, the map entries which have not been used for
  // replacement during the package collection (bogus entries) are harmless
  // and can be ignored. However, the dependency configuration negotiation
  // machinery refers to this map and skips existing dependents with
  // configuration clause which belong to it (see query_existing_dependents()
  // for details). Thus, if after collection of packages some bogus entries
  // are present in the map, then it means that we could have erroneously
  // skipped some existing dependents because of them and so need to erase
  // these entries and re-collect.
  //
  // Also, it is possible that the version constraint, which has triggered a
  // version replacement, doesn't exist anymore (the respective dependent has
  // been upgraded, etc). This, in particular, may result in an unjustified
  // configuration of a lower package version. Thus, after collection of
  // packages we check if for any of the used replacements the constraints
  // contained in the respective build_package object are also satisfied with
  // the replaced (original) package version, this version is greater than the
  // replacement version, and the system flag doesn't change. If such a
  // redundant replacement exists, then we cancel it by erasing its entry from
  // the map and re-collect. Note, however, that we need to beware of yo-yoing
  // and track the replacement cancellations applied to specific package
  // collection states. For an example of a potential cycle think of a
  // situation when a replacement cancellation introduces a new dependency
  // constraint (due to a different dependency alternative selection,
  // dependency configuration, etc) which enforces the replacement back, but
  // disappears when the replacement is applied.
  //
  struct replaced_version
  {
    // NOTE: consider updating to_checksum() in pkg-build.cxx if changing
    //       anything here.

    // Desired package version, repository fragment, system flag, and the
    // original version and system flag.
    //
    // Both are NULL for the replacement with the drop.
    //
    shared_ptr<available_package> available;
    lazy_shared_ptr<bpkg::repository_fragment> repository_fragment;

    // Meaningless for the drop.
    //
    bool system;
    version original_version;
    bool original_system;

    // True if the entry has been inserted or used for the replacement during
    // the current (re-)collection iteration. Used to keep track of "bogus"
    // (no longer relevant) entries.
    //
    bool replaced;

    // Create replacement with the different version.
    //
    replaced_version (shared_ptr<available_package> a,
                      lazy_shared_ptr<bpkg::repository_fragment> f,
                      bool s,
                      version ov,
                      bool os)
        : available (move (a)),
          repository_fragment (move (f)),
          system (s),
          original_version (move (ov)),
          original_system (os),
          replaced (true) {}

    // Create replacement with the drop.
    //
    replaced_version (): system (false), replaced (true) {}
  };

  struct build_packages;

  class replaced_versions: public std::map<package_key, replaced_version>
  {
  public:
    // Erase the bogus replacements and, if any, throw
    // cancel_bogus_replacement, if requested.
    //
    struct cancel_bogus_replacement: scratch_collection
    {
      cancel_bogus_replacement ()
          : scratch_collection ("bogus version replacement cancellation") {}
    };

    void
    cancel_bogus (tracer&, bool scratch);

    // Erase the redundant replacements and, if any, throw
    // cancel_redundant_replacement.
    //
    struct cancel_redundant_replacement: scratch_collection
    {
      cancel_redundant_replacement ()
          : scratch_collection ("redundant version replacement cancellation") {}
    };

    // Only cancel a redundant replacement if such a cancellation has not been
    // tried earlier for the same collection initial state. Leave the
    // replacement in place if the cancellation has already been tried.
    //
    void
    cancel_redundant (tracer&,
                      const build_packages&,
                      const xxh64& collection_initial_state);

  private:
    set<uint64_t> former_redundant_replacements_;
  };

  // Map of the dependency constraints to their active/inactive states.
  //
  // When we recursively collect the prerequisites for a package build, after
  // the package dependencies we also collect the dependency constraints
  // (originate from the 'constrains' manifest values). This is required to
  // incorporate the constraints into the logic of selecting the "best"
  // satisfactory dependency version, the dependency configuration negotiation
  // machinery, etc. We, however, only need to consider a dependency
  // constraint if the dependency belongs to the dependent's dependency tree.
  // We call the constraint active in this case. The problem is that the
  // dependent's dependency tree may not be finalized at the moment of
  // collecting the constraint, in which case we cannot really know if we need
  // to collect it or not. Also, traversing a dependency tree during
  // collection via the being up/down-graded and configured dependencies would
  // be a real pain. Thus, what we will do is make initial assumptions about
  // the constraints' active/inactive states on the first collection
  // iteration. Then, after the plan execution is simulated, we deduce the
  // actual constraints states, cache them, and check if our assumptions were
  // correct. If any of them was wrong, then we re-collect using the cached
  // states. Then, after the plan execution simulation, we re-check the used
  // states and, if any of them doesn't match the actual state, correct them
  // and re-collect again. Note, however, that we may potentially end up
  // yo-yoing due to the fact that an active constraint can potentially push
  // out the dependency from the dependent's dependency tree by applying the
  // version constraint, reflecting, etc and the now deactivated constraint
  // can allow the dependency back into the tree. We will detect the repeated
  // attempt to apply a specific constraints states change to the same
  // collection initial state, issue diagnostics, and fail.
  //
  // Note, however, that we could try to complete the package collection
  // successfully for some simple constraint cycles. Consider the following
  // case:
  //
  // libfoo:
  //   depends: libbar <= 1.0 | libbaz
  //
  // foo:
  //  depends: libfoo
  //  constrains: libbar > 1.0
  //
  // $ bpkg build foo
  //
  // Here we could potentially configure libbaz as libfoo's dependency instead
  // of failing. However, trying to resolve the cycle in this and similar
  // cases feels too hairy at the moment. Maybe later.
  //
  // Also note, that potentially there can be multiple constraints of the same
  // dependency in the dependent's manifest. We will always assume the same
  // state for all such constraints and will use the same cache entry for
  // them.
  //
  class dependency_constraints
  {
  public:
    struct dependency
    {
      package_name name;
      bool         buildtime;

      dependency (package_name n, bool b): name (move (n)), buildtime (b) {}

      bool
      operator< (const dependency&) const;

      // Return the dependency constraint string representation in the
      // following form:
      //
      // <dependent>/<version> [ <config-dir>] ':' [*] <dependency>
      //
      std::string
      string (const package_version_key& dependent) const;

      void
      to_checksum (xxh64&) const;
    };

    enum class state: uint8_t
    {
      active,
      inactive
    };

    using dependency_states_map = std::map<dependency, state>;
    using dependents_map = std::map<package_version_key, dependency_states_map>;

    dependents_map dependents;

    // Match the cached constraint states against the actual states and
    // correct those which don't match, skipping dependents which are not
    // configured. Return true if any states were corrected. If the deduced
    // states correction have already been applied to the same collection
    // initial state in the past, then assume this is a dependency
    // constraining cycle and fail.
    //
    bool
    correct_states (const xxh64& collection_initial_state);

  private:
    set<uint64_t> former_corrections_;
  };

  // Note: only used for tracing.
  //
  string
  to_string (dependency_constraints::state);

  inline ostream&
  operator<< (ostream& os, const dependency_constraints::state& s)
  {
    return os << to_string (s);
  }

  // Dependents with their ignored dependency constraints and, optionally,
  // with the respective unsatisfactory dependency versions.
  //
  // Note that during the collecting of all the explicitly specified packages
  // and their dependencies for the build, we may discover that a being
  // up/downgraded dependency doesn't satisfy all the being reconfigured,
  // up/downgraded, or newly built dependents. We may also discover that a
  // user-specified dependency constraint does not satisfy constraints imposed
  // by some dependent packages on this dependency. Rather than fail
  // immediately in such cases, we postpone the failure, add the unsatisfied
  // dependents and their respective constraints to the unsatisfied dependents
  // list, and continue the collection/ordering in the hope that these
  // problems will be resolved naturally as a result of the requested
  // recollection from scratch or execution plan refinement (dependents will
  // also be up/downgraded or dropped, dependencies will be up/downgraded to a
  // different versions, etc).
  //
  // Also note that after collecting/ordering of all the explicitly specified
  // packages and their dependencies for the build we also collect/order their
  // existing dependents for reconfiguration, recursively. It may happen that
  // some of the up/downgraded dependencies don't satisfy the version
  // constraints which some of the existing dependents impose on them. Rather
  // than fail immediately in such a case, we postpone the failure, add this
  // dependent and the unsatisfactory dependency to the unsatisfied dependents
  // list, and continue the collection/ordering in the hope that these
  // problems will be resolved naturally as a result of the execution plan
  // refinement.
  //
  // And yet, if these problems do not resolve naturally, then we still try to
  // resolve them by finding dependency versions which satisfy all the imposed
  // constraints or by finding versions of dependents which are all satisfied
  // with the dependency.
  //
  // Specifically, we cache such unsatisfied dependents/constraints, pretend
  // that the dependents don't impose them and proceed with the remaining
  // collecting/ordering, simulating the plan execution, and evaluating the
  // dependency versions. After that, if scratch_collection exception has not
  // been thrown, we check if the execution plan is finalized or a further
  // refinement is required. In the latter case we drop the cache and proceed
  // with the next iteration of the execution plan refinement which may
  // resolve these problems naturally. Otherwise, we pick the first collected
  // unsatisfactory dependency and try to find the best available version,
  // considering all the constraints imposed by the user (explicit version
  // constraint, --patch and/or --deorphan options, etc) as well as by its new
  // and existing dependents. If the search succeeds, we update an existing
  // package spec or add the new one to the command line and recollect from
  // the very beginning. Note that we always add a new spec with the
  // hold_version flag set to false. If the search fails, then, similarily, we
  // try to find the replacement for some of the dependency's dependents,
  // recursively. Note that we track the package build replacements and never
  // repeat a replacement for the same command line state (which we adjust for
  // each replacement). If no replacement is deduced, then we roll back the
  // latest command line adjustment and recollect from the very beginning. If
  // there are no adjustments left to try, then we give up the resolution
  // search and report the first encountered unsatisfied (and ignored)
  // dependency constraint and fail.
  //
  // Note that while we are trying to pick a dependent replacement for the
  // subsequent re-collection, we cannot easily detect if the replacement is
  // satisfied with the currently collected dependencies since that would
  // effectively require to collect the replacement (select dependency
  // alternatives, potentially re-negotiate dependency configurations,
  // etc). Thus, we only verify that the replacement version satisfies its
  // currently collected dependents. To reduce the number of potential
  // dependent replacements to consider, we apply the heuristics and only
  // consider those dependents which have or may have some satisfaction
  // problems (not satisfied with a collected dependency, apply a dependency
  // constraint which is incompatible with other dependents, etc; see
  // try_replace_dependent() for details).
  //
  struct unsatisfied_constraint
  {
    // Note: also contains the unsatisfied dependent information.
    //
    build_package::constraint constraint;

    // Optional available package version which satisfies the above
    // constraint.
    //
    optional<version> available_version;
    bool              available_system;
  };

  struct ignored_constraint
  {
    package_key              dependency;
    bpkg::version_constraint version_constraint;

    // Note: only used for diagnostics.
    //
    bpkg::dependency_type dependency_type;

    // Only specified when the failure is postponed during the collection of
    // the explicitly specified packages and their dependencies.
    //
    vector<unsatisfied_constraint> unsatisfied_constraints;
    vector<package_key> dependency_chain;

    ignored_constraint (const package_key& d,
                        const bpkg::version_constraint& c,
                        bpkg::dependency_type t,
                        vector<unsatisfied_constraint>&& ucs = {},
                        vector<package_key>&& dc = {})
        : dependency (d),
          version_constraint (c),
          dependency_type (t),
          unsatisfied_constraints (move (ucs)),
          dependency_chain (move (dc)) {}
  };

  struct unsatisfied_dependent
  {
    package_key dependent;
    vector<ignored_constraint> ignored_constraints;
  };

  class unsatisfied_dependents: public vector<unsatisfied_dependent>
  {
  public:
    // Add a dependent together with the ignored dependency constraint and,
    // potentially, with the unsatisfied constraints and the dependency chain.
    //
    void
    add (const package_key& dependent,
         const package_key& dependency,
         const version_constraint&,
         dependency_type,
         vector<unsatisfied_constraint>&& ucs = {},
         vector<package_key>&& dc = {});

    void
    add (unsatisfied_dependent&&);

    // Try to find the dependent entry and return NULL if not found.
    //
    unsatisfied_dependent*
    find_dependent (const package_key&);

    // Issue the diagnostics for the first unsatisfied (and ignored)
    // dependency constraint and throw failed.
    //
    [[noreturn]] void
    diag (const build_packages&);
  };

  // List of dependency groups whose recursive processing should be postponed
  // due to dependents with configuration clauses, together with these
  // dependents (we will call them package clusters).
  //
  // The idea is that configuration for the dependencies in the cluster needs
  // to be negotiated between the dependents in the cluster. Note that at any
  // given time during collection a dependency can only belong to a single
  // cluster. For example, the following dependent/dependencies with
  // configuration clauses:
  //
  // foo: depends: libfoo
  // bar: depends: libfoo
  //      depends: libbar
  // baz: depends: libbaz
  //
  // End up in the following clusters (see string() below for the cluster
  // representation):
  //
  // {foo bar | libfoo->{foo/1,1 bar/1,1}}
  // {bar     | libbar->{bar/2,1}}
  // {baz     | libbaz->{baz/1,1}}
  //
  // Or, another example:
  //
  // foo: depends: libfoo
  // bar: depends: libfoo libbar
  // baz: depends: libbaz
  //
  // {foo bar | libfoo->{foo/1,1 bar/1,1} libbar->{bar/1,1}}
  // {baz     | libbaz->{baz/1,1}}
  //
  // Note that a dependent can belong to any given non-negotiated cluster with
  // only one `depends` position. However, if some dependency configuration is
  // up-negotiated for a dependent, then multiple `depends` positions will
  // correspond to this dependent in the same cluster. Naturally, such
  // clusters are always (being) negotiated.
  //
  // Note that adding new dependent/dependencies to the postponed
  // configurations can result in merging some of the existing clusters if the
  // dependencies being added intersect with multiple clusters. For example,
  // adding:
  //
  // fox: depends: libbar libbaz
  //
  // to the clusters in the second example will merge them into a single
  // cluster:
  //
  // {foo bar baz fox | libfoo->{foo/1,1 bar/1,1} libbar->{bar/1,1 fox/1,1}
  //                    libbaz->{baz/1,1 fox/1,1}}
  //
  // Also note that we keep track of packages which turn out to be
  // dependencies of existing (configured) dependents with configuration
  // clauses. The recursive processing of such packages should be postponed
  // until negotiation between all the existing and new dependents which may
  // or may not be present.
  //
  class postponed_configuration
  {
  public:
    // The id of the cluster plus the ids of all the clusters that have been
    // merged into it directly or as their components.
    //
    size_t id;
    small_vector<size_t, 1> merged_ids;

    using packages = small_vector<package_key, 1>;

    class dependency: public packages
    {
    public:
      pair<size_t, size_t> position; // depends + alternative (1-based)

      // If true, then another dependency alternative is present and that can
      // potentially be considered instead of this one (see
      // unacceptable_alternatives for details).
      //
      // Initially nullopt for existing dependents until they are re-evaluated.
      //
      optional<bool> has_alternative;

      dependency (const pair<size_t, size_t>& pos,
                  packages deps,
                  optional<bool> ha)
          : packages (move (deps)), position (pos), has_alternative (ha) {}
    };

    class dependent_info
    {
    public:
      bool existing;
      small_vector<dependency, 1> dependencies;

      dependency*
      find_dependency (pair<size_t, size_t> pos);

      void
      add (dependency&&);
    };

    using dependents_map = std::map<package_key, dependent_info>;

    dependents_map dependents;
    packages       dependencies;

    // Dependency configuration.
    //
    // Note that this container may not yet contain some entries that are
    // already in the dependencies member above. And it may already contain
    // entries that are not yet in dependencies due to the retry_configuration
    // logic.
    //
    package_configurations dependency_configurations;

    // Shadow clusters.
    //
    // See the collect lambda in collect_build_prerequisites() for details.
    //
    using positions = small_vector<pair<size_t, size_t>, 1>;
    using shadow_dependents_map = std::map<package_key, positions>;

    shadow_dependents_map shadow_cluster;

    // Absent -- not negotiated yet, false -- being negotiated, true -- has
    // been negotiated.
    //
    optional<bool> negotiated;

    // The depth of the negotiating recursion (see collect_build_postponed()
    // for details).
    //
    // Note that non-zero depth for an absent negotiated member indicates that
    // the cluster is in the existing dependents re-evaluation or
    // configuration refinment phases.
    //
    size_t depth = 0;

    // Add dependencies of a new dependent.
    //
    postponed_configuration (size_t i,
                             package_key&& dependent,
                             bool existing,
                             pair<size_t, size_t> position,
                             packages&& deps,
                             optional<bool> has_alternative)
        : id (i)
    {
      add (move (dependent),
           existing,
           position,
           move (deps),
           has_alternative);
    }

    // Add dependency of an existing dependent.
    //
    postponed_configuration (size_t i,
                             package_key&& dependent,
                             pair<size_t, size_t> position,
                             package_key&& dep)
        : id (i)
    {
      add (move (dependent),
           true /* existing */,
           position,
           packages ({move (dep)}),
           nullopt /* has_alternative */);
    }

    // Add dependencies of a dependent.
    //
    // Note: adds the specified dependencies to the end of the configuration
    // dependencies list suppressing duplicates.
    //
    void
    add (package_key&& dependent,
         bool existing,
         pair<size_t, size_t> position,
         packages&& deps,
         optional<bool> has_alternative);

    // Return true if any of the configuration's dependents depend on the
    // specified package.
    //
    bool
    contains_dependency (const package_key& d) const
    {
      return find (dependencies.begin (), dependencies.end (), d) !=
             dependencies.end ();
    }

    // Return true if this configuration contains any of the specified
    // dependencies.
    //
    bool
    contains_dependency (const packages&) const;

    // Return true if this and specified configurations contain any common
    // dependencies.
    //
    bool
    contains_dependency (const postponed_configuration&) const;

    // Notes:
    //
    // - Adds dependencies of the being merged from configuration to the end
    //   of the current configuration dependencies list suppressing
    //   duplicates.
    //
    // - Doesn't change the negotiate member of this configuration.
    //
    void
    merge (postponed_configuration&&);

    void
    set_shadow_cluster (postponed_configuration&&);

    bool
    is_shadow_cluster (const postponed_configuration&);

    bool
    contains_in_shadow_cluster (package_key dependent,
                                pair<size_t, size_t> pos) const;

    // Return the postponed configuration string representation in the form:
    //
    // {<dependent>[ <dependent>]* | <dependency>[ <dependency>]*}['!'|'?']
    //
    // <dependent>  = <package>['^']
    // <dependency> = <package>->{<dependent>/<position>[ <dependent>/<position>]*}
    //
    // The potential trailing '!' or '?' of the configuration representation
    // indicates that the configuration is negotiated or is being negotiated,
    // respectively.
    //
    // '^' character that may follow a dependent indicates that this is an
    // existing dependent.
    //
    // <position> = <depends-index>','<alternative-index>
    //
    // <depends-index> and <alternative-index> are the 1-based serial numbers
    // of the respective depends value and the dependency alternative in the
    // dependent's manifest.
    //
    // See package_key for details on <package>.
    //
    // For example:
    //
    // {foo^ bar | libfoo->{foo/2,3 bar/1,1} libbar->{bar/1,1}}!
    //
    std::string
    string () const;

  private:
    // Add the specified packages to the end of the dependencies list
    // suppressing duplicates.
    //
    void
    add_dependencies (packages&&);

    void
    add_dependencies (const packages&);
  };

  inline ostream&
  operator<< (ostream& os, const postponed_configuration& c)
  {
    return os << c.string ();
  }

  // Note that we could be adding new/merging existing entries while
  // processing an entry. Thus we use a list.
  //
  class postponed_configurations:
    public std::forward_list<postponed_configuration>
  {
  public:
    // Return the configuration the dependent is added to (after all the
    // potential configuration merges, etc).
    //
    // Also return in second absent if the merge happened due to the shadow
    // cluster logic (in which case the cluster was/is being negotiated),
    // false if any non-negotiated or being negotiated clusters has been
    // merged in, and true otherwise.
    //
    // If some configurations needs to be merged and this involves the (being)
    // negotiated configurations, then merge into the outermost-depth
    // negotiated configuration (with minimum non-zero depth).
    //
    pair<postponed_configuration&, optional<bool>>
    add (package_key dependent,
         bool existing,
         pair<size_t, size_t> position,
         postponed_configuration::packages dependencies,
         optional<bool> has_alternative);

    // Add new postponed configuration cluster with a single dependency of an
    // existing dependent.
    //
    // Note that it's the caller's responsibility to make sure that the
    // dependency doesn't already belong to any existing cluster.
    //
    void
    add (package_key dependent,
         pair<size_t, size_t> position,
         package_key dependency);

    postponed_configuration*
    find (size_t id);

    // Return address of the cluster the dependency belongs to and NULL if it
    // doesn't belong to any cluster.
    //
    const postponed_configuration*
    find_dependency (const package_key& d) const;

    // Return true if all the configurations have been negotiated.
    //
    bool
    negotiated () const;

    // Translate index to iterator and return the referenced configuration.
    //
    postponed_configuration&
    operator[] (size_t);

    size_t
    size () const;

  private:
    size_t next_id_ = 1;
  };

  struct build_packages: build_package_list
  {
    build_packages () = default;

    // Copy-constructible and move-assignable (used for snapshoting).
    //
    build_packages (const build_packages&);

    build_packages (build_packages&&) = delete;

    build_packages& operator= (const build_packages&) = delete;

    build_packages&
    operator= (build_packages&&) noexcept (false);

    // Pre-enter a build_package without an action. No entry for this package
    // may already exists.
    //
    void
    enter (package_name, build_package);

    // Return the package pointer if it is already in the map and NULL
    // otherwise (so can be used as bool).
    //
    build_package*
    entered_build (database& db, const package_name& name)
    {
      auto i (map_.find (db, name));
      return i != map_.end () ? &i->second.package : nullptr;
    }

    build_package*
    entered_build (const package_key& p)
    {
      return entered_build (p.db, p.name);
    }

    const build_package*
    entered_build (database& db, const package_name& name) const
    {
      auto i (map_.find (db, name));
      return i != map_.end () ? &i->second.package : nullptr;
    }

    const build_package*
    entered_build (const package_key& p) const
    {
      return entered_build (p.db, p.name);
    }

    // Return NULL if the dependent in the constraint is not a package name
    // (command line, etc; see build_package::constraint_type for details).
    // Otherwise, return the dependent package build which is expected to be
    // collected.
    //
    const build_package*
    dependent_build (const build_package::constraint&) const;

    // Collect the package being built. Return its pointer if this package
    // version was, in fact, added to the map and NULL if it was already there
    // and the existing version was preferred or if the package build has been
    // replaced with the drop. So can be used as bool.
    //
    // Consult replaced_vers for an existing version replacement entry and
    // follow it, if present, potentially collecting the package drop instead.
    // Ignore the entry if its version doesn't satisfy the specified
    // dependency constraints or the entry is a package drop and the specified
    // required-by package names have the "required by dependents" semantics.
    // In this case it's likely that this replacement will be applied for some
    // later collect_build() call but can potentially turn out bogus. Note
    // that a version replacement for a specific package may only be applied
    // once during the collection iteration.
    //
    // Add entry to replaced_vers and throw replace_version if the
    // existing version needs to be replaced but the new version cannot be
    // re-collected recursively in-place (see replaced_versions for details).
    //
    // Optionally, pass the function which verifies the chosen package
    // version. It is called before replace_version is potentially thrown or
    // the recursive collection is performed. The scratch argument is true if
    // the package version needs to be replaced but in-place replacement is
    // not possible (see replaced_versions for details).
    //
    // Also, in the recursive mode (find database function is not NULL):
    //
    // - Use the custom search function to find the package dependency
    //   databases.
    //
    // - For the repointed dependents collect the prerequisite replacements
    //   rather than prerequisites being replaced.
    //
    // - Call add_priv_cfg_function callback for the created private
    //   configurations.
    //
    // Note that postponed_* arguments must all be either specified or not.
    // The dep_chain argument can be specified in the non-recursive mode (for
    // the sake of the diagnostics) and must be specified in the recursive
    // mode.
    //
    struct replace_version: scratch_collection
    {
      replace_version (): scratch_collection ("package version replacement") {}
    };

    using add_priv_cfg_function = void (database&, dir_path&&);

    using verify_package_build_function = void (const build_package&,
                                                bool scratch);

    build_package*
    collect_build (const pkg_build_options&,
                   build_package,
                   replaced_versions&,
                   postponed_configurations&,
                   unsatisfied_dependents&,
                   build_package_refs* dep_chain = nullptr,
                   const function<find_database_function>& = nullptr,
                   const function<add_priv_cfg_function>& = nullptr,
                   const repointed_dependents* = nullptr,
                   dependency_constraints* = nullptr,
                   postponed_packages* postponed_repo = nullptr,
                   postponed_packages* postponed_alts = nullptr,
                   postponed_packages* postponed_recs = nullptr,
                   postponed_existing_dependencies* = nullptr,
                   postponed_dependencies* = nullptr,
                   unacceptable_alternatives* = nullptr,
                   const function<verify_package_build_function>& = nullptr);

    // Collect prerequisites of the package being built recursively. Return
    // nullopt, unless in the pre-reevaluation mode (see below).
    //
    // But first "prune" this process if the package we build is a system one
    // or is already configured, since that would mean all its prerequisites
    // are configured as well. Note that this is not merely an optimization:
    // the package could be an orphan in which case the below logic will fail
    // (no repository fragment in which to search for prerequisites). By
    // skipping the prerequisite check we are able to gracefully handle
    // configured orphans.
    //
    // There are, however, some cases when we still need to re-collect
    // prerequisites of a configured package:
    //
    // - For the repointed dependent we still need to collect its prerequisite
    //   replacements to make sure its dependency constraints are satisfied.
    //
    // - If configuration variables are specified for the dependent which has
    //   any buildfile clauses in the dependencies, then we need to
    //   re-evaluate them. This can result in a different set of dependencies
    //   required by this dependent (due to conditional dependencies, etc)
    //   and, potentially, for its reconfigured existing prerequisites,
    //   recursively.
    //
    // - For an existing dependent being re-evaluated to the specific
    //   dependency position (reeval_pos argument is specified and is not
    //   {0,0}).
    //
    // - For an existing dependent being pre-reevaluated (reeval_pos argument
    //   is {0,0}).
    //
    // - For an existing dependent being re-collected due to the selected
    //   dependency alternatives deviation, etc which may be caused by its
    //   dependency up/downgrade (see postponed_packages and
    //   build_package::build_recollect flag for details).
    //
    // Note that for these cases, as it was said above, we can potentially
    // fail if the dependent is an orphan, but this is exactly what we need to
    // do in that case, since we won't be able to re-collect its dependencies.
    //
    // Only a single true dependency alternative can be selected per function
    // call, unless we are (pre-)re-evaluating. Such an alternative can only
    // be selected if its index in the postponed alternatives list is less
    // than the specified maximum (used by the heuristics that determines in
    // which order to process packages with alternatives; if 0 is passed, then
    // no true alternative will be selected).
    //
    // The idea here is to postpone the true alternatives selection till the
    // end of the packages collection and then try to optimize the overall
    // resulting selection (over all the dependents) by selecting alternatives
    // with the lower indexes first (see collect_build_postponed() for
    // details).
    //
    // Always postpone recursive collection of dependencies for a dependent
    // with configuration clauses, recording them together with the dependent
    // in postponed_cfgs (see postponed_configurations for details). If it
    // turns out that some dependency of such a dependent has already been
    // collected via some other dependent without configuration clauses, then
    // record it in postponed_deps and throw the postpone_dependency
    // exception. This exception is handled via re-collecting packages from
    // scratch, but now with the knowledge about premature dependency
    // collection. If some dependency already belongs to some non or being
    // negotiated cluster then throw merge_configuration. If some dependencies
    // have existing dependents with config clauses which have not been
    // considered for the configuration negotiation yet, then throw
    // recollect_existing_dependents exception to re-collect these dependents.
    // If configuration has already been negotiated between some other
    // dependents, then up-negotiate the configuration and throw
    // retry_configuration exception so that the configuration refinement can
    // be performed. See the collect lambda implementation for details on the
    // configuration refinement machinery.
    //
    // If the reeval_pos argument is specified and is not {0,0}, then
    // re-evaluate the package to the specified position. In this mode perform
    // the regular dependency alternative selection and non-recursive
    // dependency collection. When the specified position is reached, postpone
    // the collection by recording the dependent together with the
    // dependencies at that position in postponed_cfgs (see
    // postponed_configurations for details). If the dependent/dependencies
    // are added to an already negotiated cluster, then throw
    // merge_configuration, similar to the regular collection mode (see
    // above). Also check for the merge configuration cycles (see the function
    // implementation for details) and throw the merge_configuration_cycle
    // exception if such a cycle is detected.
    //
    // If {0,0} is specified as the reeval_pos argument, then perform the
    // pre-reevaluation of an existing dependent, requested due to the
    // specific dependency up/down-grade or reconfiguration (must be passed as
    // the orig_dep; we call it originating dependency). The main purpose of
    // this read-only mode is to obtain the position of the earliest selected
    // dependency alternative with the config clause, if any, which the
    // re-evaluation needs to be performed to and to determine if such a
    // re-evaluation is optional (see pre_reevaluate_result for the full
    // information being retrieved). The re-evaluation is considered to be
    // optional if the existing dependent has no config clause for the
    // originating dependency and the enable and reflect clauses do not refer
    // to any of the dependency configuration variables (which can only be
    // those which the dependent has the configuration clauses for; see the
    // bpkg manual for details). The thinking here is that such an existing
    // dependent may not change any configuration it applies to its
    // dependencies and thus it doesn't call for any negotiations (note: if
    // there are config clauses for the upgraded originating dependency, then
    // the potentially different defaults for its config variables may affect
    // the configuration this dependent applies to its dependencies). Such a
    // dependent can also be reconfigured without pre-selection of its
    // dependency alternatives since pkg-configure is capable of doing that on
    // its own for such a simple case (see pkg_configure_prerequisites() for
    // details). Also look for any deviation in the dependency alternatives
    // selection and throw reevaluation_deviated exception if such a deviation
    // is detected. Return nullopt if no dependency alternative with the
    // config clause is selected.
    //
    // If the package is a dependency of configured dependents and needs to be
    // reconfigured (being upgraded, has configuration specified, etc), then
    // do the following for each such dependent prior to collecting its own
    // prerequisites:
    //
    // - If the dependent is not already being built/dropped, expected to be
    //   built/dropped, and doesn't apply constraints which the dependency
    //   doesn't satisfy anymore, then pre-reevaluate the dependent.
    //
    // - If the dependency alternative with configuration clause has been
    //   encountered during the pre-reevaluation, then record it in
    //   postponed_cfgs as a single-dependency cluster with an existing
    //   dependent (see postponed_configurations for details). If the index of
    //   the encountered depends clause is equal/less than the index of the
    //   depends clause the dependency belongs to, then postpone the recursive
    //   collection of this dependency assuming that it will be collected
    //   later, during/after its existing dependent re-evaluation.
    //
    // - If the dependency alternatives selection has deviated, then record
    //   the dependent in postponed_recs (so that it can be re-collected
    //   later) and postpone recursive collection of this dependency assuming
    //   that it will be collected later, during its existing dependent
    //   re-collection. Also record this dependency in the postponed existing
    //   dependencies map (postponed_existing_dependencies argument). This way
    //   the caller can track if the postponed dependencies have never been
    //   collected recursively (deviations are too large, etc) and handle this
    //   situation (currently just fail).
    //
    // If a dependency alternative configuration cannot be negotiated between
    // all the dependents, then unaccept_alternative can be thrown (see
    // unacceptable_alternatives for details).
    //
    struct postpone_dependency: scratch_collection
    {
      package_key package;

      explicit
      postpone_dependency (package_key p)
          : scratch_collection ("prematurely collected dependency"),
            package (move (p))
      {
        scratch_collection::package = &package;
      }
    };

    struct retry_configuration
    {
      size_t      depth;
      package_key dependent;
    };

    struct merge_configuration
    {
      size_t depth;
    };

    struct merge_configuration_cycle
    {
      size_t depth;
    };

    struct reevaluation_deviated {};

    struct pre_reevaluate_result
    {
      using packages = postponed_configuration::packages;

      pair<size_t, size_t> reevaluation_position;
      packages             reevaluation_dependencies;
      bool                 reevaluation_optional = true;
      pair<size_t, size_t> originating_dependency_position;
    };

    optional<pre_reevaluate_result>
    collect_build_prerequisites (const pkg_build_options&,
                                 build_package&,
                                 build_package_refs& dep_chain,
                                 const function<find_database_function>&,
                                 const function<add_priv_cfg_function>&,
                                 const repointed_dependents&,
                                 replaced_versions&,
                                 dependency_constraints&,
                                 postponed_packages* postponed_repo,
                                 postponed_packages* postponed_alts,
                                 size_t max_alt_index,
                                 postponed_packages& postponed_recs,
                                 postponed_existing_dependencies&,
                                 postponed_dependencies&,
                                 postponed_configurations&,
                                 unacceptable_alternatives&,
                                 unsatisfied_dependents&,
                                 optional<pair<size_t, size_t>> reeval_pos = nullopt,
                                 const optional<package_key>& orig_dep = nullopt);

    void
    collect_build_prerequisites (const pkg_build_options&,
                                 database&,
                                 const package_name&,
                                 const function<find_database_function>&,
                                 const function<add_priv_cfg_function>&,
                                 const repointed_dependents&,
                                 replaced_versions&,
                                 dependency_constraints&,
                                 postponed_packages& postponed_repo,
                                 postponed_packages& postponed_alts,
                                 size_t max_alt_index,
                                 postponed_packages& postponed_recs,
                                 postponed_existing_dependencies&,
                                 postponed_dependencies&,
                                 postponed_configurations&,
                                 unacceptable_alternatives&,
                                 unsatisfied_dependents&);

    // Collect the repointed dependents and their replaced prerequisites,
    // recursively.
    //
    // If a repointed dependent is already pre-entered or collected with an
    // action other than adjustment, then just mark it for reconfiguration
    // unless it is already implied. Otherwise, collect the package build with
    // the repoint sub-action and reconfigure adjustment flag.
    //
    void
    collect_repointed_dependents (const pkg_build_options&,
                                  const repointed_dependents&,
                                  replaced_versions&,
                                  dependency_constraints&,
                                  postponed_packages& postponed_repo,
                                  postponed_packages& postponed_alts,
                                  postponed_packages& postponed_recs,
                                  postponed_existing_dependencies&,
                                  postponed_dependencies&,
                                  postponed_configurations&,
                                  unacceptable_alternatives&,
                                  unsatisfied_dependents&,
                                  const function<find_database_function>&,
                                  const function<add_priv_cfg_function>&);

    // Schedule for re-collection all the configured constraining dependents
    // (have the 'constrains' value in their manifests), whose potentially
    // indirect prerequisites are being reconfigured.
    //
    // Note that re-configuration of any indirect dependency of a constraining
    // dependent can potentially push out any constrained dependency from or,
    // instead, bring any constrained dependency to the dependent's dependency
    // tree. Trying to precisely detect if that happens (or not) to a specific
    // constraining dependent during the packages collection feels too
    // complicated at the moment. Thus, we just recollect all the potentially
    // affected dependents.
    //
    void
    collect_constraining_dependents (const pkg_build_options&,
                                     const repointed_dependents&,
                                     replaced_versions&,
                                     postponed_packages& postponed_recs,
                                     postponed_configurations&,
                                     unsatisfied_dependents&);

    // Collect the package being dropped. Noop if the specified package is
    // already being built and its required-by package names have the
    // "required by dependents" semantics.
    //
    // Add entry to replaced_vers and throw replace_version if the existing
    // version needs to be dropped but this can't be done in-place (see
    // replaced_versions for details).
    //
    void
    collect_drop (const pkg_build_options&,
                  database&,
                  shared_ptr<selected_package>,
                  replaced_versions&);

    // Cancel dropping of the selected package prerequisites, recursively.
    //
    // Note that at some iteration of the dependency refinement loop we may
    // decide that a dependency is unused and collect it as a drop,
    // potentially together with some of its now unused recursive
    // prerequisites. However, on some later refinement iteration some new
    // dependent may be introduced for this dependency. This will result in
    // collecting this dependency as a build, overwriting the current
    // collection as a drop. If this dependency is not collected recursively
    // (not being up/down-graded, etc), then dropping of its own recursive
    // prerequisites, if any, needs to be canceled as well.
    //
    // Note: the database argument is only used for tracing.
    //
    void
    cancel_drop_prerequisites (const selected_package&, database&);

    // Collect the package being unheld.
    //
    void
    collect_unhold (database&, const shared_ptr<selected_package>&);

    void
    collect_build_postponed (const pkg_build_options&,
                             replaced_versions&,
                             dependency_constraints&,
                             postponed_packages& postponed_repo,
                             postponed_packages& postponed_alts,
                             postponed_packages& postponed_recs,
                             postponed_existing_dependencies&,
                             postponed_dependencies&,
                             postponed_configurations&,
                             strings& postponed_cfgs_history,
                             unacceptable_alternatives&,
                             unsatisfied_dependents&,
                             const function<find_database_function>&,
                             const repointed_dependents&,
                             const function<add_priv_cfg_function>&,
                             postponed_configuration* = nullptr);

    // If a configured package is being up/down-graded or reconfigured then
    // that means all its configured dependents could be affected and we have
    // to reconfigure them. By default, this function examines every such a
    // package that is already in the map and collects all its configured
    // dependents. We also need to make sure the dependents are ok with the
    // up/downgrade. If some dependency constraints are not satisfied, then
    // cache them and proceed further as if no problematic constraints are
    // imposed (see unsatisfied_dependents for details). Return the set of the
    // collected dependents.
    //
    // Should we reconfigure just the direct depends or also include indirect,
    // recursively? Consider this plausible scenario as an example: We are
    // upgrading a package to a version that provides an additional API. When
    // its direct dependent gets reconfigured, it notices this new API and
    // exposes its own extra functionality that is based on it. Now it would
    // make sense to let its own dependents (which would be our original
    // package's indirect ones) to also notice this.
    //
    // If reconfigured_packages is false, then collect configured dependents
    // of all the configured packages present in the map, which are not being
    // up/down-graded or reconfigured. In other words, collect configured
    // dependents of all the configured packages, which have been skipped by
    // this function call in the default mode. Such a collection is normally
    // performed by the unsatisfied dependency constraints resolution logic to
    // make sure that when the replacement versions for a configured
    // unsatisfactory dependency are considered, the version constraints
    // imposed by all its dependents (new and existing) are taken into account
    // (see try_replace_dependency() in pkg-build.cxx for details).
    //
    std::set<package_key>
    collect_dependents (const repointed_dependents&,
                        unsatisfied_dependents&,
                        bool reconfigured_packages = true);

    // Order the previously-collected package with the specified name and
    // configuration returning its position.
    //
    // Recursively order the collected package dependencies, failing if a
    // dependency cycle is detected. If reorder is true, then reorder this
    // package to be considered as "early" as possible.
    //
    iterator
    order (database&,
           const package_name&,
           const function<find_database_function>&,
           bool reorder = true);

    void
    clear ();

    void
    clear_order ();

    // Append the collection state to the specified XXH64 checksum object.
    //
    // Specifically, calculate checksum over the names, versions,
    // configuration paths, system and adjustment flags of the being built,
    // dropped, and adjusted packages.
    //
    // Note that this checksum doesn't cover configuration variables nor the
    // command-line originated flags (hold package/version, etc). Thus, it may
    // not be used as a state identifier across multiple bpkg-build runs.
    //
    void
    state (xxh64&) const;

    // Print all the version constraints (one per line) applied to this
    // package and its dependents, recursively. The specified package is
    // expected to be collected (present in the map). Don't print the version
    // constraints for the same package twice, printing "..." instead. Noop if
    // there are no constraints for this package.
    //
    // Optionally, only print constraints from the existing or being built
    // dependents (see build_package::constraint_type for details).
    //
    void
    print_constraints (diag_record&,
                       const build_package&,
                       string& indent,
                       std::set<package_key>& printed,
                       optional<bool> existing_dependent = nullopt) const;

    void
    print_constraints (diag_record&,
                       const package_key&,
                       string& indent,
                       std::set<package_key>& printed,
                       optional<bool> existing_dependent = nullopt) const;

    // Verify the data consistency as follows:
    //
    // - Verify that builds ordering is consistent.
    //
    //   Specifically, verify that the real build actions are all ordered and
    //   the pre-entered builds are not.
    //
    // - Verify version constraints.
    //
    //   Specifically, verify that if a dependency is constrained with a
    //   dependent package, then the dependent is being built or reconfigured
    //   as source.
    //
    // - Verify dependency constraints.
    //
    //   Specifically, for the being built from scratch or reconfigured as
    //   source dependents which has the 'constrains' value in their
    //   manifests, verify that they are present in the constraints cache, are
    //   collected recursively, and the states of the enabled constraints
    //   match the cached states.
    //
    //   Note, however, that in the current implementation an inactive
    //   constraint is indistinguishable from the disabled one for a collected
    //   package. Thus, we only perform the state verification for the enabled
    //   active constraints.
    //
    // Note that the build of an existing package doesn't imply its
    // reconfiguration and the package reconfiguration may or may not be
    // accompanied with the package replacement (see
    // build_package::reconfigure() for details).
    //
    void
    verify_consistency (const dependency_constraints&) const;

  private:
    // Return the list of existing dependents that has a configuration clause
    // for any of the selected alternatives together with the dependencies for
    // the earliest such an alternative and the originating dependency (for
    // which the function is called for) position. Return absent dependency
    // for those dependents which dependency alternatives selection has
    // deviated (normally due to the dependency up/downgrade). Skip dependents
    // which are being built and require recursive recollection or dropped
    // (present in the map) or expected to be built or dropped (present in
    // rpt_depts or replaced_vers). Also skip dependents which impose the
    // version constraint on this dependency and the dependency doesn't
    // satisfy this constraint. Optionally, skip the existing dependents for
    // which re-evaluation is considered optional (exclude_optional argument;
    // see pre-reevaluation mode of collect_build_prerequisites() for
    // details).
    //
    // Note that the originating dependency is expected to be collected
    // (present in the map).
    //
    struct existing_dependent
    {
      // Dependent.
      //
      reference_wrapper<database>  db;
      shared_ptr<selected_package> selected;

      // Earliest dependency with config clause.
      //
      optional<package_key>        dependency;
      pair<size_t, size_t>         dependency_position;

      // Originating dependency passed to the function call.
      //
      package_key                  originating_dependency;
      pair<size_t, size_t>         originating_dependency_position;
    };

    // This exception is thrown by collect_build_prerequisites() and
    // collect_build_postponed() to resolve different kinds of existing
    // dependent re-evaluation related cycles by re-collecting the problematic
    // dependents from scratch.
    //
    struct recollect_existing_dependents
    {
      size_t depth;
      vector<existing_dependent> dependents;
    };

    vector<existing_dependent>
    query_existing_dependents (
      tracer&,
      const pkg_build_options&,
      database&,
      const package_name&,
      bool exclude_optional,
      const function<find_database_function>&,
      const repointed_dependents&,
      const replaced_versions&);

    // Non-recursively collect the dependency of an existing dependent
    // previously returned by the query_existing_dependents() function call
    // with the build_package::build_reevaluate flag.
    //
    const build_package*
    collect_existing_dependent_dependency (
      const pkg_build_options&,
      const existing_dependent&,
      replaced_versions&,
      postponed_configurations&,
      unsatisfied_dependents&);

    // Non-recursively collect an existing non-deviated dependent previously
    // returned by the query_existing_dependents() function call for the
    // subsequent re-evaluation.
    //
    void
    collect_existing_dependent (
      const pkg_build_options&,
      const existing_dependent&,
      postponed_configuration::packages&& dependencies,
      replaced_versions&,
      postponed_configurations&,
      unsatisfied_dependents&);

    // Non-recursively collect an existing dependent previously returned by
    // the query_existing_dependents() function call with the
    // build_package::build_recollect flag and add it to the postponed package
    // recollections list. Also add the build_package::adjust_reconfigure flag
    // for the deviated dependents (existing_dependent::dependency is absent).
    // Add the originating dependency to the dependent's required-by list (for
    // this reason the dependency is expected to be collected).
    //
    // Note that after this function call the existing dependent may not be
    // returned as a result by the query_existing_dependents() function
    // anymore (due to the build_package::build_recollect flag presence).
    //
    void
    recollect_existing_dependent (const pkg_build_options&,
                                  const existing_dependent&,
                                  replaced_versions&,
                                  postponed_packages& postponed_recs,
                                  postponed_configurations&,
                                  unsatisfied_dependents&);

    void
    collect_constraining_dependents (const pkg_build_options&,
                                     const package_key&,
                                     const repointed_dependents&,
                                     replaced_versions&,
                                     postponed_packages& postponed_recs,
                                     postponed_configurations&,
                                     unsatisfied_dependents&,
                                     set<package_key>& visited_deps);

    // Skip the dependents collection for the specified dependency if that has
    // already been done.
    //
    // Note that if this function has already been called for this dependency,
    // then all its dependents are already in the map and their dependency
    // constraints have been checked.
    //
    void
    collect_dependents (build_package&,
                        const repointed_dependents&,
                        unsatisfied_dependents&,
                        std::set<const build_package*>& visited_deps,
                        std::set<package_key>& result);

    struct package_ref
    {
      database& db;
      const package_name& name;

      bool
      operator== (const package_ref&) const;
    };
    using package_refs = small_vector<package_ref, 16>;

    iterator
    order (database&,
           const package_name&,
           package_refs& chain,
           const function<find_database_function>&,
           bool reorder);

  private:
    struct data_type
    {
      iterator position;         // Note: can be end(), see collect_build().
      build_package package;
    };

    class package_map: public std::map<package_key, data_type>
    {
    public:
      using base_type = std::map<package_key, data_type>;

      using base_type::find;

      iterator
      find (database& db, const package_name& pn)
      {
        return find (package_key {db, pn});
      }

      const_iterator
      find (database& db, const package_name& pn) const
      {
        return find (package_key {db, pn});
      }

      // Try to find a package build in the dependency configurations (see
      // database::dependency_configs() for details). Return the end iterator
      // if no build is found and issue diagnostics and fail if multiple
      // builds (in multiple configurations) are found.
      //
      iterator
      find_dependency (database&, const package_name&, bool buildtime);
    };
    package_map map_;
  };
}

#endif // BPKG_PKG_BUILD_COLLECT_HXX
