From 74bcab9dfa50647dd6615f261f2c2bc9f5a38951 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 13 Feb 2016 16:30:31 +0200 Subject: 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. --- bpkg/pkg-build.cli | 6 + bpkg/pkg-build.cxx | 37 ++++ bpkg/pkg-drop | 13 ++ bpkg/pkg-drop.cxx | 234 +++++++++++++++------- tests/repository/1/satisfy/libbar-1.2.0.tar.gz | Bin 0 -> 348 bytes tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz | 1 + tests/repository/1/satisfy/t5/repositories | 1 + tests/test.sh | 28 +++ 8 files changed, 251 insertions(+), 69 deletions(-) create mode 100644 tests/repository/1/satisfy/libbar-1.2.0.tar.gz create mode 120000 tests/repository/1/satisfy/t5/libbar-1.2.0.tar.gz create mode 120000 tests/repository/1/satisfy/t5/repositories 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 +#include #include #include #include @@ -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> 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 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 + #include +#include // database, selected_package #include #include @@ -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>&, + 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& lpp (pair.first); - if (map_.find (pn) == map_.end ()) + if (map_.find (lpp.object_id ()) == map_.end ()) { - shared_ptr pp (db.load (pn)); + shared_ptr pp (lpp.load ()); if (!pp->hold_package) // Prune held packages. { @@ -288,6 +288,103 @@ namespace bpkg map 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& 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& 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& 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& 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>& 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& p: prqs) + { + assert (p->state != package_state::broken); - const shared_ptr& 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& 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& 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 Binary files /dev/null and b/tests/repository/1/satisfy/libbar-1.2.0.tar.gz 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 -- cgit v1.1