aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bpkg/pkg-build.cxx144
1 files changed, 96 insertions, 48 deletions
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index d6102a7..48875be 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -39,6 +39,13 @@ using namespace butl;
namespace bpkg
{
+ // @@ Overall TODO:
+ //
+ // - Detect and complain about dependency cycles.
+ //
+ // - Configuration vars (both passed and preserved)
+ //
+
// Query the available packages that optionally satisfy the specified version
// version constraint and return them in the version descending order. Note
// that a stub satisfies any constraint.
@@ -119,12 +126,6 @@ namespace bpkg
return db.query<available_package> (q);
}
- // @@ 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
@@ -1089,6 +1090,12 @@ namespace bpkg
map_.clear ();
}
+ void
+ clear_order ()
+ {
+ build_package_list::clear ();
+ }
+
private:
struct data_type
{
@@ -1099,12 +1106,19 @@ namespace bpkg
map<string, data_type> map_;
};
+ // Unused dependencies to drop. This list is ordered by construction during
+ // the plan simulation/refinment process below (i.e., we only add a package
+ // to this list if/when nothing else depends on it).
+ //
struct drop_package
{
shared_ptr<selected_package> selected;
};
using drop_package_list = vector<drop_package>;
+ // @@ TODO: try to move back inside pkg_build() and get rid of functions. Or
+ // need to change the name to something more appropriate for a scope.
+ //
using pkg_options = pkg_build_pkg_options;
struct pkg_arg
@@ -1539,8 +1553,8 @@ namespace bpkg
string () /* reason for "fetching ..." */);
}
- // Expand the package specs into the package args, parsing them into the
- // package scheme, name and version components.
+ // Expand the package specs into individual package args, parsing them
+ // into the package scheme, name and version components.
//
// Note that the package specs that have no scheme and location can not be
// unambiguously distinguished from the package archive and directory
@@ -1556,7 +1570,7 @@ namespace bpkg
if (ps.location.empty ())
{
// Parse if it is clear that this is the package name/version,
- // otherwise unparsed add unparsed.
+ // otherwise add unparsed.
//
const char* s (ps.packages.c_str ());
package_scheme h (parse_package_scheme (s));
@@ -2086,14 +2100,53 @@ namespace bpkg
t.commit ();
}
- // Assemble the list of packages we will need to build and drop.
+ // Assemble the list of packages we will need to build-to-hold, still used
+ // dependencies to up/down-grade, and unused dependencies to drop. We call
+ // this the plan.
+ //
+ // The way we do it is tricky: we first create the plan based on build-to-
+ // holds (i.e., the user selected). Next, to decide whether we need to
+ // up/down-grade or drop any dependecies we need to take into account an
+ // existing state of the package database plus the changes that would be
+ // made to it once we executed the plan (think about it: when the user
+ // says "I want to upgrade a package and all its dependencies", they want
+ // to upgrade dependencies that are still used after upgrading the
+ // package, not those that were used before by the old version).
+ //
+ // As you can probably imagine, figuring out the desired state of the
+ // dependencies based on the current package database and to-be-executed
+ // plan won't be an easy task. So instead what we are going to do is
+ // simulate the plan execution by only applying it to the package database
+ // (but not to the filesystem/packages themselves). We then use this
+ // simulated database as the sole (and convenient) source of the
+ // dependency information (i.e., which packages are still used and by
+ // whom) to decide which dependencies we need to upgrade, downgrade, or
+ // drop. Once this is done, we rollback the database (and reload any
+ // in-memory objects that might have changed during the simulation) and
+ // add the up/down-grades and drops to the plan.
+ //
+ // Of course, adding dependecy up/down-grades to the plan can change the
+ // plan. For example, a new version of a dependency we are upgrading may
+ // force an upgrade of one of the packages from the user selected. And
+ // that, in turn, can pretty much rewrite the plan entirely (including
+ // rendering our earlier decisions about up/down-grades/drops of other
+ // dependencies invalid).
+ //
+ // So what we have to do is refine the plan over several iterations.
+ // Specifically, if we added a new up/down-grade/drop, then we need to
+ // re-simulate this plan and (1) re-example if any new dependencies now
+ // need up/down-grade/drop and, this one is tricky, (2) that none of the
+ // already made decisions have changed. If we detect (2), then we need to
+ // cancel all such decisions and also rebuild the plan from scratch. The
+ // intuitive feeling here is that this process will discover an up/down-
+ // grade order where any subsequent entry does not affect the decision of
+ // the previous ones.
+ //
+ // Package managers are an easy, already solved problem, right?
//
build_packages build_pkgs;
drop_package_list drop_pkgs;
-
{
- // Iteratively refine the plan with dependency up/down-grades/drops.
- //
struct dep_pkg
{
string name;
@@ -2101,6 +2154,8 @@ namespace bpkg
};
vector<dep_pkg> dep_pkgs;
+ // Iteratively refine the plan with dependency up/down-grades/drops.
+ //
for (bool refine (true), scratch (true); refine; )
{
transaction t (db);
@@ -2124,18 +2179,16 @@ namespace bpkg
scratch = false;
}
+ else
+ {
+ // Only clear the ordered lists.
+ //
+ build_pkgs.clear_order ();
+ drop_pkgs.clear ();
+ }
- // Add dependencies to upgrade/downgrade/drop that were discovered on
- // the previous iterations.
- //
- // Looks like keeping them as build_package objects would
- // be natural? BUT: what if the selected_package is from
- // the temporary changes/session?! So maybe not...
- //
- //@@ TODO: use empty version in build_package to indicate drop?
- //@@ TODO: always put drops at the back of dep_pkgs so that they
- // appear in the plan last (could also do it as a post-
- // collection step if less hairy).
+ // Add to the plan dependencies to upgrade/downgrade/drop that were
+ // discovered on the previous iterations.
//
for (const dep_pkg& p: dep_pkgs)
{
@@ -2143,7 +2196,7 @@ namespace bpkg
db.load<selected_package> (p.name));
if (p.version.empty ())
- drop_pkgs.push_back ({move (sp)});
+ drop_pkgs.push_back (drop_package {move (sp)});
else
{
shared_ptr<available_package> ap (
@@ -2181,9 +2234,6 @@ namespace bpkg
// deterministic. We, however, do them before hold_pkgs so that they
// appear (e.g., on the plan) last.
//
-
- //@@ TODO: need to clear the list on subsequent iterations.
-
for (const dep_pkg& p: dep_pkgs)
build_pkgs.order (p.name);
@@ -2199,10 +2249,9 @@ namespace bpkg
// We are about to execute the plan on the database (but not on the
// filesystem / actual packages). Save the session state for the
// selected_package objects so that we can restore it later (see
- // below)
+ // below for details).
//
using selected_packages = session::object_map<selected_package>;
-
auto selected_packages_session = [&db, &ses] () -> selected_packages*
{
auto& m (ses.map ()[&db]);
@@ -2213,23 +2262,22 @@ namespace bpkg
};
selected_packages old_sp;
-
if (const selected_packages* sp = selected_packages_session ())
old_sp = *sp;
- // We also need to perform the execution on the copy of the
- // build/drop_package objects to preserve the original ones. Note that
- // the selected package objects will still be changed so we will
- // reload them afterwards (see below).
+ // Note that we need to perform the execution on the copies of the
+ // build/drop_package objects to preserve the original ones. The
+ // selected_package objects will still be changed so we will reload
+ // them afterwards (see below).
//
{
- vector<build_package> bs (build_pkgs.begin (), build_pkgs.end ());
- vector<drop_package> ds (drop_pkgs.begin (), drop_pkgs.end ());
+ vector<build_package> tmp (build_pkgs.begin (), build_pkgs.end ());
- set<shared_ptr<selected_package>> dummy;
- build_package_list pl (bs.begin (), bs.end ());
+ build_package_list bl (tmp.begin (), tmp.end ());
+ drop_package_list dl (drop_pkgs.begin (), drop_pkgs.end ());
- execute_plan (o, c, db, pl, ds, true /* simulate */, dummy);
+ set<shared_ptr<selected_package>> dummy;
+ execute_plan (o, c, db, bl, dl, true /* simulate */, dummy);
}
// Verify that none of the previously-made upgrade/downgrade/drop
@@ -2275,7 +2323,7 @@ namespace bpkg
}
// Rollback the changes to the database and reload the changed
- // objects.
+ // selected_package objects.
//
t.rollback ();
{
@@ -2286,8 +2334,9 @@ namespace bpkg
// plan). And in case of drop the object is removed from the session
// so we need to bring it back.
//
- // Note: we use the original pkgs lists since the executed one may
- // contain newly created (but now gone) selected_package objects.
+ // Note: we use the original *_pkgs lists since the executed ones
+ // may contain newly created (but now gone) selected_package
+ // objects.
//
for (build_package& p: build_pkgs)
{
@@ -2303,9 +2352,9 @@ namespace bpkg
db.reload (*p.selected);
}
- // Now drop all the newly created selected_package objects. The
- // tricky part is to distinguish newly created ones from newly
- // loaded (and potentially cached).
+ // Now remove all the newly created selected_package objects from
+ // the session. The tricky part is to distinguish newly created ones
+ // from newly loaded (and potentially cached).
//
if (selected_packages* sp = selected_packages_session ())
{
@@ -2319,10 +2368,9 @@ namespace bpkg
{
if (i->second.use_count () == 1)
{
- sp->erase (i);
-
// This might cause another object's use count to drop.
//
+ sp->erase (i);
rescan = true;
}
}