aboutsummaryrefslogtreecommitdiff
path: root/bpkg/build.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-12-09 11:32:50 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-12-09 11:32:50 +0200
commitcbd8296c7b86f7fc368d1133da3be3670b7923be (patch)
tree1f8c10aeaf0e19a7d198b06800c2e2a72e760499 /bpkg/build.cxx
parent82a8cf379ee19dbc66c62bfe78b436d4ab6b497a (diff)
Clean up command names, add aliases
Diffstat (limited to 'bpkg/build.cxx')
-rw-r--r--bpkg/build.cxx1224
1 files changed, 0 insertions, 1224 deletions
diff --git a/bpkg/build.cxx b/bpkg/build.cxx
deleted file mode 100644
index 3cb96f4..0000000
--- a/bpkg/build.cxx
+++ /dev/null
@@ -1,1224 +0,0 @@
-// file : bpkg/build.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <bpkg/build>
-
-#include <map>
-#include <list>
-#include <iterator> // make_move_iterator()
-#include <iostream> // cout
-#include <algorithm> // find()
-#include <functional> // reference_wrapper
-
-#include <butl/utility> // reverse_iterate()
-
-#include <bpkg/types>
-#include <bpkg/package>
-#include <bpkg/package-odb>
-#include <bpkg/utility>
-#include <bpkg/database>
-#include <bpkg/diagnostics>
-#include <bpkg/satisfaction>
-#include <bpkg/manifest-utility>
-
-#include <bpkg/common-options>
-
-#include <bpkg/pkg-fetch>
-#include <bpkg/pkg-unpack>
-#include <bpkg/pkg-update>
-#include <bpkg/pkg-verify>
-#include <bpkg/pkg-configure>
-#include <bpkg/pkg-disfigure>
-
-using namespace std;
-using namespace butl;
-
-namespace bpkg
-{
- // @@ TODO
- //
- // - Detect and complain about dependency cycles.
- // - Configuration vars (both passed and preserved)
- //
-
- // Try to find a package that optionally satisfies the specified
- // version constraint. Look in the specified repository, its
- // prerequisite repositories, and their complements, recursively
- // (note: recursivity applies to complements, not prerequisites).
- // Return the package and the repository in which it was found or
- // NULL for both if not found.
- //
- std::pair<shared_ptr<available_package>, shared_ptr<repository>>
- find_available (database& db,
- const string& name,
- const shared_ptr<repository>& r,
- const optional<dependency_constraint>& c)
- {
- using query = query<available_package>;
-
- query q (query::id.name == name);
- const auto& vm (query::id.version);
-
- // If there is a constraint, then translate it to the query. Otherwise,
- // get the latest version.
- //
- bool order (true);
- if (c)
- {
- const version& v (c->version);
-
- // Note that the constraint's version is always rhs (libfoo >= 1.2.3).
- //
- switch (c->operation)
- {
- case comparison::eq: q = q && vm == v; order = false; break;
- case comparison::lt: q = q && vm < v; break;
- case comparison::gt: q = q && vm > v; break;
- case comparison::le: q = q && vm <= v; break;
- case comparison::ge: q = q && vm >= v; break;
- }
- }
-
- if (order)
- q += order_by_version_desc (vm);
-
- // Filter the result based on the repository to which each version
- // belongs.
- //
- return filter_one (r, db.query<available_package> (q));
- }
-
- // Create a transient (or fake, if you prefer) available_package
- // object corresponding to the specified selected object. Note
- // that the package locations list is left empty and that the
- // returned repository could be NULL if the package is an orphan.
- //
- std::pair<shared_ptr<available_package>, shared_ptr<repository>>
- make_available (const common_options& options,
- const dir_path& cd,
- database& db,
- const shared_ptr<selected_package>& sp)
- {
- assert (sp != nullptr && sp->state != package_state::broken);
-
- // First see if we can find its repository.
- //
- shared_ptr<repository> ar (
- db.find<repository> (
- sp->repository.canonical_name ()));
-
- // The package is in at least fetched state, which means we should
- // be able to get its manifest.
- //
- const optional<path>& a (sp->archive);
- const optional<dir_path>& d (sp->src_root);
-
- package_manifest m (
- sp->state == package_state::fetched
- ? pkg_verify (options, a->absolute () ? *a : cd / *a, true)
- : pkg_verify (d->absolute () ? *d : cd / *d, true));
-
- return make_pair (make_shared<available_package> (move (m)), move (ar));
- }
-
- // 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() 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 dependency 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. One notable side-effect of this process is that
- // we may end up with a lot more packages in the map than we will have
- // on the list. This is because some of the prerequisites of "upgraded"
- // or "downgraded" packages may no longer need to be built.
- //
- // Note also 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.
- //
- struct build_package
- {
- shared_ptr<selected_package> selected; // NULL if not selected.
- shared_ptr<available_package> available; // Can be NULL, fake/transient.
- shared_ptr<bpkg::repository> repository; // Can be NULL (orphan) or root.
-
- // Hold flags. Note that we can only "increase" the values that are
- // already in the selected package.
- //
- bool hold_package;
- bool hold_version;
-
- // Constraint value plus, normally, the dependent package name that
- // placed this constraint but can also be some other name for the
- // initial selection (e.g., package version specified by the user
- // on the command line).
- //
- struct constraint_type
- {
- string dependent;
- dependency_constraint value;
-
- constraint_type () = default;
- constraint_type (string d, dependency_constraint v)
- : dependent (move (d)), value (move (v)) {}
- };
-
- vector<constraint_type> constraints;
-
- // True if we need to reconfigure this package. If available package
- // is NULL, then reconfigure must be true (this is a dependent that
- // needs to be reconfigured because its prerequisite is being up/down-
- // graded or reconfigured). 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 true. We only make sure to set it for
- // cases that would otherwise miss the need for the reconfiguration.
- // As a result, use the reconfigure() accessor 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).
- //
- bool reconfigure_;
-
- bool
- reconfigure () const
- {
- return selected != nullptr &&
- selected->state == package_state::configured &&
- (reconfigure_ || // Must be checked first, available could be NULL.
- selected->version != available->version);
- }
- };
-
- struct build_packages: list<reference_wrapper<build_package>>
- {
- // Collect the package. Return true if this package version was,
- // in fact, added to the map and false if it was already there
- // or the existing version was preferred.
- //
- bool
- collect (const common_options& options,
- const dir_path& cd,
- database& db,
- build_package&& pkg)
- {
- using std::swap; // ...and not list::swap().
-
- tracer trace ("collect");
-
- assert (pkg.available != nullptr); // No dependents allowed here.
- auto i (map_.find (pkg.available->id.name));
-
- // If we already have an entry for this package name, then we
- // have to pick one over the other.
- //
- if (i != map_.end ())
- {
- const string& n (i->first);
-
- // At the end we want p1 to point to the object that we keep
- // and p2 to the object whose constraints we should copy.
- //
- build_package* p1 (&i->second.package);
- build_package* p2 (&pkg);
-
- // If versions are the same, then all we have to do is copy the
- // constraint (p1/p2 already point to where we would want them to).
- //
- if (p1->available->version != p2->available->version)
- {
- using constraint_type = build_package::constraint_type;
-
- // If the versions differ, we have to pick one. Start with the
- // newest version since if both satisfy, then that's the one we
- // should prefer. So get the first to try into p1 and the second
- // to try -- into p2.
- //
- if (p2->available->version > p1->available->version)
- swap (p1, p2);
-
- // See if pv's version satisfies pc's constraints. Return the
- // pointer to the unsatisfied constraint or NULL if all are
- // satisfied.
- //
- auto test = [] (build_package* pv, build_package* pc)
- -> const constraint_type*
- {
- for (const constraint_type& c: pc->constraints)
- if (!satisfies (pv->available->version, c.value))
- return &c;
-
- return nullptr;
- };
-
- // First see if p1 satisfies p2's constraints.
- //
- if (auto c2 = test (p1, p2))
- {
- // If not, try the other way around.
- //
- if (auto c1 = test (p2, p1))
- {
- const string& d1 (c1->dependent);
- const string& d2 (c2->dependent);
-
- fail << "unable to satisfy constraints on package " << n <<
- info << d1 << " depends on (" << n << " " << c1->value << ")" <<
- info << d2 << " depends on (" << n << " " << c2->value << ")" <<
- info << "available " << n << " " << p1->available->version <<
- info << "available " << n << " " << p2->available->version <<
- info << "explicitly specify " << n << " version to manually "
- << "satisfy both constraints";
- }
- else
- swap (p1, p2);
- }
-
- level4 ([&]{trace << "pick " << n << " " << p1->available->version
- << " over " << p2->available->version;});
- }
-
- // See if we are replacing the object. If not, then we don't
- // need to collect its prerequisites since that should have
- // already been done. Remember, p1 points to the object we
- // want to keep.
- //
- bool replace (p1 != &i->second.package);
-
- if (replace)
- {
- swap (*p1, *p2);
- swap (p1, p2); // Setup for constraints copying below.
- }
-
- p1->constraints.insert (p1->constraints.end (),
- make_move_iterator (p2->constraints.begin ()),
- make_move_iterator (p2->constraints.end ()));
-
- if (!replace)
- return false;
- }
- else
- {
- string n (pkg.available->id.name); // Note: copy; see emplace() below.
-
- level4 ([&]{trace << "add " << n << " " << pkg.available->version;});
-
- // This is the first time we are adding this package name to the
- // map. If it is already selected, then we need to make sure that
- // packages that already depend on it (called dependents) are ok
- // with the up/downgrade. We will also have to keep doing this
- // every time we choose a new available package above. So what
- // we are going to do is copy the dependents' constrains over to
- // our constraint list; this way they will be automatically taken
- // into account by the rest of the logic.
- //
- const shared_ptr<selected_package>& sp (pkg.selected);
- const shared_ptr<available_package>& ap (pkg.available);
-
- int r;
- if (sp != nullptr &&
- sp->state == package_state::configured &&
- (r = sp->version.compare (ap->version)) != 0)
- {
- using query = query<package_dependent>;
-
- for (const auto& pd: db.query<package_dependent> (query::name == n))
- {
- if (!pd.constraint)
- continue;
-
- const version& v (ap->version);
- const dependency_constraint& c (*pd.constraint);
-
- if (satisfies (v, c))
- {
- pkg.constraints.emplace_back (pd.name, c);
- continue;
- }
-
- fail << "unable to " << (r < 0 ? "up" : "down") << "grade "
- << "package " << n << " " << sp->version << " to " << v <<
- info << pd.name << " depends on (" << n << " " << c << ")" <<
- info << "explicitly specify " << n << " version to manually "
- << "satisfy this constraint";
- }
- }
-
- i = map_.emplace (move (n), data_type {end (), move (pkg)}).first;
- }
-
- // Now collect all the prerequisites recursively. But first "prune"
- // this process if the package 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 in which to
- // search for prerequisites). By skipping the prerequisite check we
- // are able to gracefully handle configured orphans.
- //
- const build_package& p (i->second.package);
- const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
-
- if (sp != nullptr &&
- sp->version == ap->version &&
- sp->state == package_state::configured)
- return true;
-
- // Show how we got here if things go wrong.
- //
- auto g (
- make_exception_guard (
- [&ap] ()
- {
- info << "while satisfying " << ap->id.name << " " << ap->version;
- }));
-
- const shared_ptr<repository>& ar (p.repository);
- const string& name (ap->id.name);
-
- for (const dependency_alternatives& da: ap->dependencies)
- {
- if (da.conditional) // @@ TODO
- fail << "conditional dependencies are not yet supported";
-
- if (da.size () != 1) // @@ TODO
- fail << "multiple dependency alternatives not yet supported";
-
- const dependency& d (da.front ());
-
- // The first step is to always find the available package even
- // if, in the end, it won't be the one we select. If we cannot
- // find the package then that means the repository is broken.
- // And if we have no repository to look in, then that means the
- // package is an orphan (we delay this check until we actually
- // need the repository to allow orphans without prerequisites).
- //
- if (ar == nullptr)
- fail << "package " << name << " " << ap->version << " is orphaned" <<
- info << "explicitly upgrade it to a new version";
-
- auto rp (find_available (db, d.name, ar, d.constraint));
-
- if (rp.first == nullptr)
- {
- diag_record dr;
- dr << fail << "unknown prerequisite " << d << " of package " << name;
-
- if (!ar->location.empty ())
- dr << info << "repository " << ar->location << " appears to "
- << "be broken";
- }
-
- // Next see if this package is already selected. If we already
- // have it in the configuraion and it satisfies our dependency
- // constraint, then we don't want to be forcing its upgrade (or,
- // worse, downgrade).
- //
- bool force (false);
- shared_ptr<selected_package> dsp (db.find<selected_package> (d.name));
- if (dsp != nullptr)
- {
- if (dsp->state == package_state::broken)
- fail << "unable to build broken package " << d.name <<
- info << "use 'pkg-purge --force' to remove";
-
- if (satisfies (dsp->version, d.constraint))
- rp = make_available (options, cd, db, dsp);
- else
- // Remember that we may be forcing up/downgrade; we will deal
- // with it below.
- //
- force = true;
- }
-
- build_package dp {
- dsp,
- rp.first,
- rp.second,
- false, // Hold package.
- false, // Hold version.
- {}, // Constraints.
- false}; // Reconfigure.
-
- // Add our constraint, if we have one.
- //
- if (d.constraint)
- dp.constraints.emplace_back (name, *d.constraint);
-
- // Now collect this prerequisite. If it was actually collected
- // (i.e., it wasn't already there) and we are forcing an upgrade
- // and the version is not held, then warn, unless we are running
- // quiet. Downgrade or upgrade of a held version -- refuse.
- //
- if (collect (options, cd, db, move (dp)) && force)
- {
- const version& sv (dsp->version);
- const version& av (rp.first->version);
-
- bool u (av > sv);
- bool f (dsp->hold_version || !u); // Fail if downgrade or held.
-
- if (verb || f)
- {
- bool c (d.constraint);
- diag_record dr;
-
- (f ? dr << fail : dr << warn)
- << "package " << name << " dependency on "
- << (c ? "(" : "") << d << (c ? ")" : "") << " is forcing "
- << (u ? "up" : "down") << "grade of " << d.name << " " << sv
- << " to " << av;
-
- if (dsp->hold_version)
- dr << info << "package version " << d.name << " " << sv
- << " is held";
-
- if (f)
- dr << info << "explicitly request version "
- << (u ? "up" : "down") << "grade to continue";
- }
- }
- }
-
- return true;
- }
-
- // Order the previously-collected package with the specified name
- // returning its positions. If reorder is true, then reorder this
- // package to be considered as "early" as possible.
- //
- iterator
- order (const string& name, bool reorder = true)
- {
- // Every package that we order should have already been collected.
- //
- auto mi (map_.find (name));
- assert (mi != map_.end ());
-
- // If this package is already in the list, then that would also
- // mean all its prerequisites are in the list and we can just
- // return its position. Unless we want it reordered.
- //
- iterator& pos (mi->second.position);
- if (pos != end ())
- {
- if (reorder)
- erase (pos);
- else
- return pos;
- }
-
- // Order all the prerequisites of this package and compute the
- // position of its "earliest" prerequisite -- this is where it
- // will be inserted.
- //
- build_package& p (mi->second.package);
- const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
-
- assert (ap != nullptr); // No dependents allowed here.
-
- // Unless this package needs something to be before it, add it to
- // the end of the list.
- //
- iterator i (end ());
-
- // Figure out if j is before i, in which case set i to j. The goal
- // here is to find the position of our "earliest" prerequisite.
- //
- auto update = [this, &i] (iterator j)
- {
- for (iterator k (j); i != j && k != end ();)
- if (++k == i)
- i = j;
- };
-
- // Similar to collect(), we can prune if the package is already
- // configured, right? Not so fast. While in collect() we didn't
- // need to add prerequisites of such a package, it doesn't mean
- // that they actually never ended up in the map via another way.
- // For example, some can be a part of the initial selection. And
- // in that case we must order things properly.
- //
- // So here we are going to do things differently depending on
- // whether the package is already configured or not. If it is,
- // then that means we can use its prerequisites list. Otherwise,
- // we use the manifest data.
- //
- if (sp != nullptr &&
- sp->version == ap->version &&
- sp->state == package_state::configured)
- {
- for (const auto& p: sp->prerequisites)
- {
- const string& name (p.first.object_id ());
-
- // The prerequisites may not necessarily be in the map.
- //
- if (map_.find (name) != map_.end ())
- update (order (name, false));
- }
- }
- else
- {
- // We are iterating in reverse so that when we iterate over
- // the dependency list (also in reverse), prerequisites will
- // be built in the order that is as close to the manifest as
- // possible.
- //
- for (const dependency_alternatives& da:
- reverse_iterate (p.available->dependencies))
- {
- assert (!da.conditional && da.size () == 1); // @@ TODO
- const dependency& d (da.front ());
-
- update (order (d.name, false));
- }
- }
-
- return pos = insert (i, p);
- }
-
- // If a configured package is being up/down-graded then that means
- // all its dependents could be affected and we have to reconfigure
- // them. This function examines every package that is already on
- // the list and collects and orders all its dependents.
- //
- // Should we reconfigure just the direct depends or also include
- // indirect, recursively? Consider this plauisible 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.
- //
- void
- collect_order_dependents (database& db)
- {
- // For each package on the list we want to insert all its dependents
- // before it so that they get configured after the package on which
- // they depend is configured (remember, our build order is reverse,
- // with the last package being built first). This applies to both
- // packages that are already on the list as well as the ones that
- // we add, recursively.
- //
- for (auto i (begin ()); i != end (); ++i)
- {
- const build_package& p (*i);
-
- // Prune if this is not a configured package being up/down-graded
- // or reconfigured.
- //
- if (p.reconfigure ())
- collect_order_dependents (db, i);
- }
- }
-
- void
- collect_order_dependents (database& db, iterator pos)
- {
- tracer trace ("collect_order_dependents");
-
- const build_package& p (*pos);
- const string& n (p.selected->name);
-
- using query = query<package_dependent>;
-
- for (auto& pd: db.query<package_dependent> (query::name == n))
- {
- string& dn (pd.name);
-
- // We can have three cases here: the package is already on the
- // list, the package is in the map (but not on the list) and it
- // is in neither.
- //
- auto i (map_.find (dn));
-
- if (i != map_.end ())
- {
- build_package& dp (i->second.package);
-
- // Force reconfiguration in both cases.
- //
- dp.reconfigure_ = true;
-
- if (i->second.position == end ())
- {
- // Clean the build_package object up to make sure we don't
- // inadvertently force up/down-grade.
- //
- dp.available = nullptr;
- dp.repository = nullptr;
-
- i->second.position = insert (pos, dp);
- }
- }
- else
- {
- shared_ptr<selected_package> dsp (db.load<selected_package> (dn));
-
- i = map_.emplace (
- move (dn),
- data_type
- {
- end (),
- build_package {
- move (dsp),
- nullptr,
- nullptr,
- false, // Hold package.
- false, // Hold version.
- {}, // Constraints.
- true} // Reconfigure.
- }).first;
-
- i->second.position = insert (pos, i->second.package);
- }
-
- // Collect our own dependents inserting them before us.
- //
- collect_order_dependents (db, i->second.position);
- }
- }
-
- private:
- struct data_type
- {
- iterator position; // Note: can be end(), see collect().
- build_package package;
- };
-
- map<string, data_type> map_;
- };
-
- int
- build (const build_options& o, cli::scanner& args)
- {
- tracer trace ("build");
-
- const dir_path& c (o.directory ());
- level4 ([&]{trace << "configuration: " << c;});
-
- if (!args.more ())
- fail << "package name argument expected" <<
- info << "run 'bpkg help build' for more information";
-
- database db (open (c, trace));
-
- // Note that the session spans all our transactions. The idea here is
- // that selected_package objects in the build_packages list below will
- // be cached in this session. When subsequent transactions modify any
- // of these objects, they will modify the cached instance, which means
- // our list will always "see" their updated state.
- //
- session s;
-
- // Assemble the list of packages we will need to build.
- //
- build_packages pkgs;
- strings names;
- {
- transaction t (db.begin ());
-
- shared_ptr<repository> root (db.load<repository> (""));
-
- while (args.more ())
- {
- const char* s (args.next ());
-
- // Reduce all the potential variations (archive, directory, package
- // name, package name/version) to a single available_package object.
- //
- string n;
- version v;
-
- shared_ptr<repository> ar;
- shared_ptr<available_package> ap;
-
- // Is this a package archive?
- //
- try
- {
- path a (s);
- if (exists (a))
- {
- package_manifest m (pkg_verify (o, a, true, false));
-
- // This is a package archive (note that we shouldn't throw
- // failed from here on).
- //
- level4 ([&]{trace << "archive " << a;});
- n = m.name;
- v = m.version;
- ar = root;
- ap = make_shared<available_package> (move (m));
- ap->locations.push_back (package_location {root, move (a)});
- }
- }
- catch (const invalid_path&)
- {
- // Not a valid path so cannot be an archive.
- }
- catch (const failed&)
- {
- // Not a valid package archive.
- }
-
- // Is this a package directory?
- //
- try
- {
- dir_path d (s);
- if (exists (d))
- {
- package_manifest m (pkg_verify (d, true, false));
-
- // This is a package directory (note that we shouldn't throw
- // failed from here on).
- //
- level4 ([&]{trace << "directory " << d;});
- n = m.name;
- v = m.version;
- ap = make_shared<available_package> (move (m));
- ar = root;
- ap->locations.push_back (package_location {root, move (d)});
- }
- }
- catch (const invalid_path&)
- {
- // Not a valid path so cannot be an archive.
- }
- catch (const failed&)
- {
- // Not a valid package archive.
- }
-
- // Then it got to be a package name with optional version.
- //
- if (ap == nullptr)
- {
- n = parse_package_name (s);
- v = parse_package_version (s);
- level4 ([&]{trace << "package " << n << "; version " << v;});
-
- // Either get the user-specified version or the latest.
- //
- auto rp (
- v.empty ()
- ? find_available (db, n, root, nullopt)
- : find_available (db, n, root,
- dependency_constraint {comparison::eq, v}));
-
- ap = rp.first;
- ar = rp.second;
- }
-
- // Load the package that may have already been selected and
- // figure out what exactly we need to do here. The end goal
- // is the available_package object corresponding to the actual
- // package that we will be building (which may or may not be
- // the same as the selected package).
- //
- shared_ptr<selected_package> sp (db.find<selected_package> (n));
-
- if (sp != nullptr && sp->state == package_state::broken)
- fail << "unable to build broken package " << n <<
- info << "use 'pkg-purge --force' to remove";
-
- bool found (true);
-
- // If the user asked for a specific version, then that's what
- // we ought to be building.
- //
- if (!v.empty ())
- {
- for (;;)
- {
- if (ap != nullptr) // Must be that version, see above.
- break;
-
- // Otherwise, our only chance is that the already selected
- // object is that exact version.
- //
- if (sp != nullptr && sp->version == v)
- break; // Derive ap from sp below.
-
- found = false;
- break;
- }
- }
- //
- // No explicit version was specified by the user.
- //
- else
- {
- if (ap != nullptr)
- {
- // Even if this package is already in the configuration, should
- // we have a newer version, we treat it as an upgrade request;
- // otherwise, why specify the package in the first place? We just
- // need to check if what we already have is "better" (i.e., newer).
- //
- if (sp != nullptr && ap->id.version < sp->version)
- ap = nullptr; // Derive ap from sp below.
- }
- else
- {
- if (sp == nullptr)
- found = false;
-
- // Otherwise, derive ap from sp below.
- }
- }
-
- if (!found)
- {
- diag_record dr;
-
- dr << fail << "unknown package " << n;
- if (!v.empty ())
- dr << " " << v;
-
- // Let's help the new user out here a bit.
- //
- if (db.query_value<repository_count> () == 0)
- dr << info << "configuration " << c << " has no repositories"
- << info << "use 'bpkg rep-add' to add a repository";
- else if (db.query_value<available_package_count> () == 0)
- dr << info << "configuration " << c << " has no available packages"
- << info << "use 'bpkg rep-fetch' to fetch available packages "
- << "list";
- }
-
- // If the available_package object is still NULL, then it means
- // we need to get one corresponding to the selected package.
- //
- if (ap == nullptr)
- {
- assert (sp != nullptr);
-
- auto rp (make_available (o, c, db, sp));
- ap = rp.first;
- ar = rp.second; // Could be NULL (orphan).
- }
-
- // Finally add this package to the list.
- //
- level4 ([&]{trace << "collect " << ap->id.name << " "
- << ap->version;});
-
- build_package p {
- move (sp),
- move (ap),
- move (ar),
- true, // Hold package.
- !v.empty (), // Hold version.
- {}, // Constraints.
- false}; // Reconfigure.
-
- // "Fix" the version the user asked for by adding the '==' constraint.
- //
- if (!v.empty ())
- p.constraints.emplace_back (
- "command line",
- dependency_constraint {comparison::eq, v});
-
- pkgs.collect (o, c, db, move (p));
- names.push_back (n);
- }
-
- // Now that we have collected all the package versions that we need
- // to build, arrange them in the "dependency order", that is, with
- // every package on the list only possibly depending on the ones
- // after it. Iterate over the names we have collected on the previous
- // step in reverse so that when we iterate over the packages (also in
- // reverse), things will be built as close as possible to the order
- // specified by the user (it may still get altered if there are
- // dependencies between the specified packages).
- //
- for (const string& n: reverse_iterate (names))
- pkgs.order (n);
-
- // Finally, collect and order all the dependents that we will need
- // to reconfigure because of the up/down-grades of packages that
- // are now on the list.
- //
- pkgs.collect_order_dependents (db);
-
- t.commit ();
- }
-
- // Print what we are going to do, then ask for the user's confirmation.
- //
- if (o.print_only () || !o.yes ())
- {
- for (const build_package& p: reverse_iterate (pkgs))
- {
- const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
-
- const char* act;
- string n;
- version v;
-
- if (ap == nullptr)
- {
- // This is a dependent needing reconfiguration.
- //
- assert (sp != nullptr && p.reconfigure ());
-
- n = sp->name;
- act = "reconfigure";
- }
- else
- {
- n = ap->id.name;
- v = ap->version;
-
- // Even if we already have this package selected, we have to
- // make sure it is configured and updated.
- //
- if (sp == nullptr || sp->version == v)
- act = p.reconfigure () ? "reconfigure/build" : "build";
- else
- act = sp->version < v ? "upgrade" : "downgrade";
- }
-
- if (o.print_only ())
- cout << act << " " << n << (v.empty () ? "" : " ") << v << endl;
- else if (verb)
- text << act << " " << n << (v.empty () ? "" : " ") << v;
- }
- }
-
- if (o.print_only ())
- return 0;
-
- // Ask the user if we should continue.
- //
- if (!(o.yes () || yn_prompt ("continue? [Y/n]", 'y')))
- return 1;
-
- // Ok, we have "all systems go". The overall action plan is as follows.
- //
- // 1. disfigure up/down-graded, reconfigured [left to right]
- // 2. purge up/down-graded
- // 3. fetch new, up/down-graded
- // 4. unpack new, up/down-graded
- // 5. configure all [right to left]
- // 6. build user selection [right to left]
- //
- // Note that for some actions, e.g., purge or fetch, the order is not
- // really important. We will, however, do it right to left since that
- // is the order closest to that of the user selection.
- //
- // We are also going to combine purge/fetch/unpack into a single step
- // and use the replace mode so it will become just fetch/unpack.
- //
-
- // disfigure
- //
- for (const build_package& p: pkgs)
- {
- // We are only interested in configured packages that are either
- // up/down-graded or need reconfiguration (e.g., dependents).
- //
- if (!p.reconfigure ())
- continue;
-
- const shared_ptr<selected_package>& sp (p.selected);
-
- // Each package is disfigured in its own transaction, so that we
- // always leave the configuration in a valid state.
- //
- transaction t (db.begin ());
- pkg_disfigure (c, o, t, sp); // Commits the transaction.
- assert (sp->state == package_state::unpacked);
-
- if (verb)
- text << "disfigured " << sp->name << " " << sp->version;
- }
-
- // fetch/unpack
- //
- for (build_package& p: reverse_iterate (pkgs))
- {
- shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
-
- if (ap == nullptr) // Skip dependents.
- continue;
-
- // Fetch if this is a new package or if we are up/down-grading.
- //
- if (sp == nullptr || sp->version != ap->version)
- {
- sp.reset (); // For the directory case below.
-
- // Distinguish between the package and archive/directory cases.
- //
- const package_location& pl (ap->locations[0]); // Got to have one.
-
- if (pl.repository.object_id () != "") // Special root?
- {
- transaction t (db.begin ());
- sp = pkg_fetch (o,
- c,
- t,
- ap->id.name,
- ap->version,
- true); // Replace; commits the transaction.
- }
- else if (exists (pl.location)) // Directory case is handled by unpack.
- {
- transaction t (db.begin ());
- sp = pkg_fetch (o,
- c,
- t,
- pl.location, // Archive path.
- true, // Replace
- false); // Don't purge; commits the transaction.
- }
-
- if (sp != nullptr) // Actually unpacked something?
- {
- assert (sp->state == package_state::fetched);
-
- if (verb)
- text << "fetched " << sp->name << " " << sp->version;
- }
- }
-
- // Unpack. Note that the package can still be NULL if this is the
- // directory case (see the fetch code above).
- //
- if (sp == nullptr || sp->state == package_state::fetched)
- {
- if (sp != nullptr)
- {
- transaction t (db.begin ());
- sp = pkg_unpack (o, c, t, ap->id.name); // Commits the transaction.
- }
- else
- {
- const package_location& pl (ap->locations[0]);
- assert (pl.repository.object_id () == ""); // Special root.
-
- transaction t (db.begin ());
- sp = pkg_unpack (c,
- t,
- path_cast<dir_path> (pl.location),
- true, // Replace.
- false); // Don't purge; commits the transaction.
- }
-
- assert (sp->state == package_state::unpacked);
-
- if (verb)
- text << "unpacked " << sp->name << " " << sp->version;
- }
- }
-
- // configure
- //
- for (const build_package& p: reverse_iterate (pkgs))
- {
- const shared_ptr<selected_package>& sp (p.selected);
-
- assert (sp != nullptr);
-
- // We configure everything that isn't already configured.
- //
- if (sp->state == package_state::configured)
- continue;
-
- transaction t (db.begin ());
- pkg_configure (c, o, t, sp, strings ()); // Commits the transaction.
- assert (sp->state == package_state::configured);
-
- if (verb)
- text << "configured " << sp->name << " " << sp->version;
- }
-
- // Small detour: update the hold state. While we could have tried
- // to "weave" it into one of the previous actions, things there
- // are already convoluted enough.
- //
- for (const build_package& p: reverse_iterate (pkgs))
- {
- const shared_ptr<selected_package>& sp (p.selected);
- assert (sp != nullptr);
-
- // Note that we should only "increase" the hold state.
- //
- bool hp (p.hold_package && sp->hold_package != p.hold_package);
- bool hv (p.hold_version && sp->hold_version != p.hold_version);
-
- if (hp || hv)
- {
- if (hp) sp->hold_package = true;
- if (hv) sp->hold_version = true;
-
- transaction t (db.begin ());
- db.update (sp);
- t.commit ();
-
- if (verb > 1)
- {
- if (hp)
- text << "hold package " << sp->name;
-
- if (hv)
- text << "hold version " << sp->name << " " << sp->version;
- }
- }
- }
-
- if (o.configure_only ())
- return 0;
-
- // update
- //
- for (const build_package& p: reverse_iterate (pkgs))
- {
- const shared_ptr<selected_package>& sp (p.selected);
-
- // Update the user selection only.
- //
- if (find (names.begin (), names.end (), sp->name) == names.end ())
- continue;
-
- pkg_update (c, o, sp);
-
- if (verb)
- text << "updated " << sp->name << " " << sp->version;
- }
-
- return 0;
- }
-}