aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-02-13 16:30:31 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-02-13 16:30:31 +0200
commit74bcab9dfa50647dd6615f261f2c2bc9f5a38951 (patch)
tree3cd60a784ff8a16b58ddd2ec4bb12003e9d11719
parent4fd0df2573341824eea5edfaf45be33997ce56ce (diff)
Offer in pkg-build to drop prerequisite packages that are no longer necessary
This can happen if a package that is being upgraded/downgraded changes its dependencies.
-rw-r--r--bpkg/pkg-build.cli6
-rw-r--r--bpkg/pkg-build.cxx37
-rw-r--r--bpkg/pkg-drop13
-rw-r--r--bpkg/pkg-drop.cxx234
-rw-r--r--tests/repository/1/satisfy/libbar-1.2.0.tar.gzbin0 -> 348 bytes
l---------tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz1
l---------tests/repository/1/satisfy/t5/repositories1
-rwxr-xr-xtests/test.sh28
8 files changed, 251 insertions, 69 deletions
diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli
index aa16f58..2b0d24a 100644
--- a/bpkg/pkg-build.cli
+++ b/bpkg/pkg-build.cli
@@ -52,6 +52,12 @@ namespace bpkg
"Assume the answer to all prompts is \cb{yes}."
}
+ bool --keep-prerequisite
+ {
+ "Don't offer to drop prerequsite packages that were automatically built
+ and will no longer be necessary."
+ }
+
bool --print-only|-p
{
"Print to \cb{STDOUT} what would be done without actually doing
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index 6138317..06281ff 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -19,6 +19,7 @@
#include <bpkg/common-options>
+#include <bpkg/pkg-drop>
#include <bpkg/pkg-fetch>
#include <bpkg/pkg-unpack>
#include <bpkg/pkg-update>
@@ -1133,6 +1134,22 @@ namespace bpkg
// 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.
//
+ // Almost forgot, there is one more thing: when we upgrade or downgrade a
+ // package, it may change the list of its prerequisites. Which means we
+ // may end up with packages that are no longer necessary and it would be
+ // nice to offer to drop those. This, howeve, is a tricky business and is
+ // the domain of pkg_drop(). For example, a prerequisite may still have
+ // other dependents (so it looks like we shouldn't be dropping it) but
+ // they are all from the "drop set" (so we should offer to drop it after
+ // all); pkg_drop() knows how to deal with all this.
+ //
+ // So what we are going to do is this: before disfiguring packages we will
+ // collect all their old prerequisites. This will be the "potentially to
+ // drop" list. Then, after configuration, when the new dependencies are
+ // established, we will pass them to pkg_drop() whose job will be to
+ // figure out which ones can be dropped, prompt the user, etc.
+ //
+ set<shared_ptr<selected_package>> drop_pkgs;
// disfigure
//
@@ -1150,6 +1167,20 @@ namespace bpkg
// always leave the configuration in a valid state.
//
transaction t (db.begin ());
+
+ // Collect prerequisites to be potentially dropped.
+ //
+ if (!o.keep_prerequisite ())
+ {
+ for (const auto& pair: sp->prerequisites)
+ {
+ shared_ptr<selected_package> pp (pair.first.load ());
+
+ if (!pp->hold_package)
+ drop_pkgs.insert (move (pp));
+ }
+ }
+
pkg_disfigure (c, o, t, sp); // Commits the transaction.
assert (sp->state == package_state::unpacked);
@@ -1292,6 +1323,12 @@ namespace bpkg
}
}
+ // Now that we have the final dependency state, see if we need to drop
+ // packages that are no longer necessary.
+ //
+ if (!drop_pkgs.empty ())
+ pkg_drop (c, o, db, drop_pkgs, !o.yes ());
+
if (o.configure_only ())
return 0;
diff --git a/bpkg/pkg-drop b/bpkg/pkg-drop
index ea19e11..4b7931e 100644
--- a/bpkg/pkg-drop
+++ b/bpkg/pkg-drop
@@ -5,7 +5,10 @@
#ifndef BPKG_PKG_DROP
#define BPKG_PKG_DROP
+#include <set>
+
#include <bpkg/types>
+#include <bpkg/forward> // database, selected_package
#include <bpkg/utility>
#include <bpkg/pkg-drop-options>
@@ -14,6 +17,16 @@ namespace bpkg
{
int
pkg_drop (const pkg_drop_options&, cli::scanner& args);
+
+ // Examine the list of prerequisite packages and drop those that don't
+ // have any dependents. Note that it should be called in session.
+ //
+ void
+ pkg_drop (const dir_path& configuration,
+ const common_options&,
+ database&,
+ const std::set<shared_ptr<selected_package>>&,
+ bool prompt);
}
#endif // BPKG_PKG_DROP
diff --git a/bpkg/pkg-drop.cxx b/bpkg/pkg-drop.cxx
index 8c979d2..66b9382 100644
--- a/bpkg/pkg-drop.cxx
+++ b/bpkg/pkg-drop.cxx
@@ -76,7 +76,7 @@ namespace bpkg
return map_.emplace (move (n), data_type {end (), {move (p), r}}).second;
}
- // Collect all the dependets of the user selection retutning the list
+ // Collect all the dependets of the user selection returning the list
// of their names. Dependents of dependents are collected recursively.
//
dependent_names
@@ -90,7 +90,7 @@ namespace bpkg
// Unconfigured package cannot have any dependents.
//
- if (dp.reason == drop_reason::user &&
+ if (dp.reason != drop_reason::dependent &&
dp.package->state == package_state::configured)
collect_dependents (db, dns, dp.package);
}
@@ -150,11 +150,11 @@ namespace bpkg
for (const auto& pair: p->prerequisites)
{
- const string& pn (pair.first.object_id ());
+ const lazy_shared_ptr<selected_package>& lpp (pair.first);
- if (map_.find (pn) == map_.end ())
+ if (map_.find (lpp.object_id ()) == map_.end ())
{
- shared_ptr<selected_package> pp (db.load<selected_package> (pn));
+ shared_ptr<selected_package> pp (lpp.load ());
if (!pp->hold_package) // Prune held packages.
{
@@ -288,6 +288,103 @@ namespace bpkg
map<string, data_type> map_;
};
+ // Drop ordered list of packages.
+ //
+ static int
+ pkg_drop (const dir_path& c,
+ const common_options& o,
+ database& db,
+ const drop_packages& pkgs,
+ bool drop_prq,
+ bool print_only,
+ bool disfigure_only,
+ bool yes,
+ bool no)
+ {
+ // Print what we are going to do, then ask for the user's confirmation.
+ //
+ if (print_only || !(yes || no))
+ {
+ for (const drop_package& dp: pkgs)
+ {
+ // Skip prerequisites if we weren't instructed to drop them.
+ //
+ if (dp.reason == drop_reason::prerequisite && !drop_prq)
+ continue;
+
+ const shared_ptr<selected_package>& p (dp.package);
+
+ if (print_only)
+ cout << "drop " << p->name << endl;
+ else if (verb)
+ text << "drop " << p->name;
+ }
+
+ if (print_only)
+ return 0;
+ }
+
+ // Ask the user if we should continue.
+ //
+ if (no || !(yes || yn_prompt ("continue? [Y/n]", 'y')))
+ return 1;
+
+ // All that's left to do is first disfigure configured packages and
+ // then purge all of them. We do both left to right (i.e., from more
+ // dependent to less dependent). For disfigure this order is required.
+ // For purge, it will be the order closest to the one specified by the
+ // user.
+ //
+ for (const drop_package& dp: pkgs)
+ {
+ // Skip prerequisites if we weren't instructed to drop them.
+ //
+ if (dp.reason == drop_reason::prerequisite && !drop_prq)
+ continue;
+
+ const shared_ptr<selected_package>& p (dp.package);
+
+ if (p->state != package_state::configured)
+ continue;
+
+ // 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, p); // Commits the transaction.
+ assert (p->state == package_state::unpacked);
+
+ if (verb)
+ text << "disfigured " << p->name;
+ }
+
+ if (disfigure_only)
+ return 0;
+
+ // Purge.
+ //
+ for (const drop_package& dp: pkgs)
+ {
+ // Skip prerequisites if we weren't instructed to drop them.
+ //
+ if (dp.reason == drop_reason::prerequisite && !drop_prq)
+ continue;
+
+ const shared_ptr<selected_package>& p (dp.package);
+
+ assert (p->state == package_state::fetched ||
+ p->state == package_state::unpacked);
+
+ transaction t (db.begin ());
+ pkg_purge (c, t, p); // Commits the transaction, p is now transient.
+
+ if (verb)
+ text << "purged " << p->name;
+ }
+
+ return 0;
+ }
+
int
pkg_drop (const pkg_drop_options& o, cli::scanner& args)
{
@@ -421,87 +518,86 @@ namespace bpkg
t.commit ();
}
- // Print what we are going to do, then ask for the user's confirmation.
- //
- if (o.print_only () || !(o.yes () || o.no ()))
- {
- for (const drop_package& dp: pkgs)
- {
- // Skip prerequisites if we weren't instructed to drop them.
- //
- if (dp.reason == drop_reason::prerequisite && !drop_prq)
- continue;
-
- const shared_ptr<selected_package>& p (dp.package);
-
- if (o.print_only ())
- cout << "drop " << p->name << endl;
- else if (verb)
- text << "drop " << p->name;
- }
-
- if (o.print_only ())
- return 0;
- }
+ return pkg_drop (c,
+ o,
+ db,
+ pkgs,
+ drop_prq,
+ o.print_only (),
+ o.disfigure_only (),
+ o.yes (),
+ o.no ());
+ }
- // Ask the user if we should continue.
- //
- if (o.no () || !(o.yes () || yn_prompt ("continue? [Y/n]", 'y')))
- return 1;
+ // Examine the list of prerequsite packages and drop those that don't
+ // have any dependents.
+ //
+ void
+ pkg_drop (const dir_path& c,
+ const common_options& o,
+ database& db,
+ const set<shared_ptr<selected_package>>& prqs,
+ bool prompt)
+ {
+ assert (session::has_current ());
- // All that's left to do is first disfigure configured packages and
- // then purge all of them. We do both left to right (i.e., from more
- // dependent to less dependent). For disfigure this order is required.
- // For purge, it will be the order closest to the one specified by the
- // user.
+ // Assemble the list of packages we will be dropping.
//
- for (const drop_package& dp: pkgs)
+ drop_packages pkgs;
{
- // Skip prerequisites if we weren't instructed to drop them.
+ transaction t (db.begin ());
+
+ // First add all the "caller selection" of packages to the list and
+ // collect their prerequisites (these will be the candidates to drop
+ // as well).
//
- if (dp.reason == drop_reason::prerequisite && !drop_prq)
- continue;
+ for (const shared_ptr<selected_package>& p: prqs)
+ {
+ assert (p->state != package_state::broken);
- const shared_ptr<selected_package>& p (dp.package);
+ if (pkgs.collect (p, drop_reason::prerequisite))
+ pkgs.collect_prerequisites (db, p);
+ }
- if (p->state != package_state::configured)
- continue;
+ // Now arrange them (and their prerequisites) in the dependency order.
+ //
+ for (const shared_ptr<selected_package>& p: prqs)
+ pkgs.order (p->name);
- // Each package is disfigured in its own transaction, so that we
- // always leave the configuration in a valid state.
+ // Finally filter out those that we cannot drop.
//
- transaction t (db.begin ());
- pkg_disfigure (c, o, t, p); // Commits the transaction.
- assert (p->state == package_state::unpacked);
+ bool r (pkgs.filter_prerequisites (db));
- if (verb)
- text << "disfigured " << p->name;
- }
+ t.commit ();
- if (o.disfigure_only ())
- return 0;
+ if (!r)
+ return; // Nothing can be dropped.
+ }
- // Purge.
- //
- for (const drop_package& dp: pkgs)
+ if (prompt)
{
- // Skip prerequisites if we weren't instructed to drop them.
- //
- if (dp.reason == drop_reason::prerequisite && !drop_prq)
- continue;
-
- const shared_ptr<selected_package>& p (dp.package);
+ {
+ diag_record dr (text);
- assert (p->state == package_state::fetched ||
- p->state == package_state::unpacked);
+ dr << "following prerequisite packages were automatically "
+ << "built and will no longer be necessary:";
- transaction t (db.begin ());
- pkg_purge (c, t, p); // Commits the transaction, p is now transient.
+ for (const drop_package& dp: pkgs)
+ dr << text << dp.package->name;
+ }
- if (verb)
- text << "purged " << p->name;
+ if (!yn_prompt ("drop prerequisite packages? [Y/n]", 'y'))
+ return;
}
- return 0;
+ pkg_drop (c,
+ o,
+ db,
+ pkgs,
+ true, // Drop prerequisites (that's what we are here for).
+ false, // Print-only (too late for that).
+ false, // Disfigure-only (could be an option).
+ true, // Yes (don't print the plan or prompt).
+ false); // No (we already said yes).
}
}
diff --git a/tests/repository/1/satisfy/libbar-1.2.0.tar.gz b/tests/repository/1/satisfy/libbar-1.2.0.tar.gz
new file mode 100644
index 0000000..4572395
--- /dev/null
+++ b/tests/repository/1/satisfy/libbar-1.2.0.tar.gz
Binary files differ
diff --git a/tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz b/tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz
new file mode 120000
index 0000000..b4a7773
--- /dev/null
+++ b/tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz
@@ -0,0 +1 @@
+../libbar-1.2.0.tar.gz \ No newline at end of file
diff --git a/tests/repository/1/satisfy/t5/repositories b/tests/repository/1/satisfy/t5/repositories
new file mode 120000
index 0000000..d965b15
--- /dev/null
+++ b/tests/repository/1/satisfy/t5/repositories
@@ -0,0 +1 @@
+../repositories \ No newline at end of file
diff --git a/tests/test.sh b/tests/test.sh
index 2dcd6f7..e4d962b 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -1204,6 +1204,34 @@ test cfg-fetch
test pkg-build -y libbaz
stat libfoo "configured 1.1.0"
+# drop prerequisites on downgrade
+#
+test rep-create repository/1/satisfy/t5
+test cfg-create --wipe
+test cfg-add $rep/satisfy/t2
+test cfg-fetch
+
+test pkg-build -y libbar
+stat libfoo "configured 1.0.0"
+stat libbar "configured 1.0.0 hold_package"
+
+test cfg-add $rep/satisfy/t5
+test cfg-fetch
+
+test pkg-build -y libbar
+stat libfoo "available 1.0.0"
+stat libbar "configured 1.2.0 hold_package"
+
+#@@test pkg-build -y libbar/1.0.0 libfoo
+test pkg-build -y libbar/1.0.0
+test pkg-build -y libfoo
+stat libfoo "configured 1.0.0 hold_package"
+stat libbar "configured 1.0.0 hold_package hold_version; available 1.2.0"
+
+test pkg-build -y libbar
+stat libfoo "configured 1.0.0 hold_package"
+#@@stat libbar "configured 1.2.0 hold_package"
+stat libbar "configured 1.2.0 hold_package hold_version"
##
## pkg-drop