aboutsummaryrefslogtreecommitdiff
path: root/bpkg/pkg-build-collect.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/pkg-build-collect.hxx')
-rw-r--r--bpkg/pkg-build-collect.hxx1882
1 files changed, 1882 insertions, 0 deletions
diff --git a/bpkg/pkg-build-collect.hxx b/bpkg/pkg-build-collect.hxx
new file mode 100644
index 0000000..f84c86f
--- /dev/null
+++ b/bpkg/pkg-build-collect.hxx
@@ -0,0 +1,1882 @@
+// 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 and for
+ // dependencies which have all the alternatives disabled are represented
+ // as empty dependency alternatives lists. 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;
+
+ // 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.
+ //
+ // Note that if the dependent is a package name, then this package is
+ // expected to be collected (present in the map).
+ //
+ struct constraint_type
+ {
+ version_constraint value;
+
+ package_version_key dependent;
+
+ // False for non-packages. Otherwise, indicates whether the constraint
+ // comes from the selected dependent or not.
+ //
+ bool selected_dependent;
+
+ // Create constraint for a package dependent.
+ //
+ constraint_type (version_constraint v,
+ database& db,
+ package_name nm,
+ version ver,
+ bool s)
+ : value (move (v)),
+ dependent (db, move (nm), move (ver)),
+ selected_dependent (s) {}
+
+ // Create constraint for a non-package dependent.
+ //
+ constraint_type (version_constraint v, database& db, string nm)
+ : value (move (v)),
+ dependent (db, move (nm)),
+ selected_dependent (false) {}
+ };
+
+ vector<constraint_type> 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. Only meaningful for non-system
+ // packages.
+ //
+ 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).
+ //
+ 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.
+ //
+ 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).
+ //
+ 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;
+ }
+
+ const version&
+ available_version () const;
+
+ string
+ available_name_version () const
+ {
+ assert (available != nullptr);
+ return package_string (available->id.name, available_version (), system);
+ }
+
+ string
+ available_name_version_db () 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;}
+ };
+
+ 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;
+ }
+ };
+
+ 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.
+ //
+ // 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.
+ //
+ struct replaced_version
+ {
+ // Desired package version, repository fragment, 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;
+ bool system; // Meaningless for the drop.
+
+ // 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)
+ : available (move (a)),
+ repository_fragment (move (f)),
+ system (s),
+ replaced (true) {}
+
+ // Create replacement with the drop.
+ //
+ replaced_version (): system (false), replaced (true) {}
+ };
+
+ class replaced_versions: public std::map<package_key, replaced_version>
+ {
+ public:
+ // Erase the bogus replacements and, if any, throw cancel_replacement, if
+ // requested.
+ //
+ struct cancel_replacement: scratch_collection
+ {
+ cancel_replacement ()
+ : scratch_collection ("bogus version replacement cancellation") {}
+ };
+
+ void
+ cancel_bogus (tracer&, bool scratch);
+ };
+
+
+ // Dependents with their unsatisfactory dependencies and the respective
+ // ignored constraints.
+ //
+ // 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. Rather than fail immediately in
+ // such a case, 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.
+ //
+ // 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_type constraint;
+
+ // Available package version which satisfies the above constraint.
+ //
+ version available_version;
+ bool available_system;
+ };
+
+ struct ignored_constraint
+ {
+ package_key dependency;
+ version_constraint constraint;
+
+ // 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 version_constraint& c,
+ vector<unsatisfied_constraint>&& ucs = {},
+ vector<package_key>&& dc = {})
+ : dependency (d),
+ constraint (c),
+ unsatisfied_constraints (move (ucs)),
+ dependency_chain (move (dc)) {}
+ };
+
+ struct unsatisfied_dependent
+ {
+ package_key dependent;
+ vector<ignored_constraint> ignored_constraints;
+ };
+
+ struct build_packages;
+
+ 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&,
+ vector<unsatisfied_constraint>&& ucs = {},
+ vector<package_key>&& dc = {});
+
+ // 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_type&) 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,
+ 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&,
+ 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&,
+ 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&,
+ 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>&);
+
+ // 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&);
+
+ // Collect the package being unheld.
+ //
+ void
+ collect_unhold (database&, const shared_ptr<selected_package>&);
+
+ void
+ collect_build_postponed (const pkg_build_options&,
+ replaced_versions&,
+ 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. 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.
+ //
+ std::set<package_key>
+ collect_dependents (const repointed_dependents&, unsatisfied_dependents&);
+
+ // 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 ();
+
+ // 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 selected 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> selected_dependent = nullopt) const;
+
+ void
+ print_constraints (diag_record&,
+ const package_key&,
+ string& indent,
+ std::set<package_key>& printed,
+ optional<bool> selected_dependent = nullopt) const;
+
+ // Verify that builds ordering is consistent across all the data
+ // structures and the ordering expectations are fulfilled (real build
+ // actions are all ordered, etc).
+ //
+ void
+ verify_ordering () 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).
+ //
+ // 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&,
+ bool add_required_by);
+
+ // 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&);
+ };
+ 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