diff options
Diffstat (limited to 'bpkg/pkg-drop.cxx')
-rw-r--r-- | bpkg/pkg-drop.cxx | 366 |
1 files changed, 265 insertions, 101 deletions
diff --git a/bpkg/pkg-drop.cxx b/bpkg/pkg-drop.cxx index 6ea6769..d8fa4ea 100644 --- a/bpkg/pkg-drop.cxx +++ b/bpkg/pkg-drop.cxx @@ -7,6 +7,8 @@ #include <list> #include <iostream> // cout +#include <libbutl/path-pattern.hxx> + #include <bpkg/package.hxx> #include <bpkg/package-odb.hxx> #include <bpkg/database.hxx> @@ -33,6 +35,7 @@ namespace bpkg struct drop_package { + database& db; shared_ptr<selected_package> package; drop_reason reason; }; @@ -41,45 +44,50 @@ namespace bpkg // struct dependent_name { + database& db; package_name name; + database& prq_db; package_name prq_name; // Prerequisite package name. }; using dependent_names = vector<dependent_name>; - // 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 - // the packages specified by the user (the "user selection"). We then - // collect all the dependent packages of the user selection, if any. - // These will either have to be dropped as well or we cannot continue. - // If the user gave the go ahead to drop the dependents, then, for our - // purposes, this list of dependents can from now own be treated as if - // it was a part of the user selection. The next step is to collect all - // the non-held prerequisites of the user selection with the goal of - // figuring out which ones will no longer be needed and offering to - // drop them as well. This part is a bit tricky and has to be done in - // three steps: We first collect all the prerequisites that we could - // possibly be dropping. We then order all the packages. And, finally, - // we filter out prerequisites that we cannot drop. See the comment to - // the call to collect_prerequisites() for details on why it has to be - // done this way. + // 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 the packages + // specified by the user (the "user selection"). We then collect all the + // dependent packages of the user selection, if any. These will either have + // to be dropped as well or we cannot continue and need to either issue + // diagnostics and fail or exit with the specified (via --dependent-exit) + // code. If the user gave the go ahead to drop the dependents, then, for our + // purposes, this list of dependents can from now own be treated as if it + // was a part of the user selection. The next step is to collect all the + // non-held prerequisites of the user selection with the goal of figuring + // out which ones will no longer be needed and offering to drop them as + // well. This part is a bit tricky and has to be done in three steps: We + // first collect all the prerequisites that we could possibly be dropping. + // We then order all the packages. And, finally, we filter out prerequisites + // that we cannot drop. See the comment to the call to + // collect_prerequisites() for details on why it has to be done this way. // struct drop_packages: list<reference_wrapper<drop_package>> { // Collect a package to be dropped, by default, as a user selection. // bool - collect (shared_ptr<selected_package> p, drop_reason r = drop_reason::user) + collect (database& db, + shared_ptr<selected_package> p, + drop_reason r = drop_reason::user) { package_name n (p->name); // Because of move(p) below. - return map_.emplace (move (n), data_type {end (), {move (p), r}}).second; + return map_.emplace (package_key {db, move (n)}, + data_type {end (), {db, move (p), r}}).second; } - // Collect all the dependets of the user selection returning the list + // Collect all the dependents of the user selection returning the list // of their names. Dependents of dependents are collected recursively. // dependent_names - collect_dependents (database& db) + collect_dependents () { dependent_names dns; @@ -91,7 +99,7 @@ namespace bpkg // if (dp.reason != drop_reason::dependent && dp.package->state == package_state::configured) - collect_dependents (db, dns, dp.package); + collect_dependents (pr.first.db, dp.package, dns); } return dns; @@ -99,21 +107,22 @@ namespace bpkg void collect_dependents (database& db, - dependent_names& dns, - const shared_ptr<selected_package>& p) + const shared_ptr<selected_package>& p, + dependent_names& dns) { - using query = query<package_dependent>; - - for (auto& pd: db.query<package_dependent> (query::name == p->name)) + for (database& ddb: db.dependent_configs ()) { - const package_name& dn (pd.name); - - if (map_.find (dn) == map_.end ()) + for (auto& pd: query_dependents_cache (ddb, p->name, db)) { - shared_ptr<selected_package> dp (db.load<selected_package> (dn)); - dns.push_back (dependent_name {dn, p->name}); - collect (dp, drop_reason::dependent); - collect_dependents (db, dns, dp); + const package_name& dn (pd.name); + + if (map_.find (ddb, dn) == map_.end ()) + { + shared_ptr<selected_package> dp (ddb.load<selected_package> (dn)); + dns.push_back (dependent_name {ddb, dn, db, p->name}); + collect (ddb, dp, drop_reason::dependent); + collect_dependents (ddb, dp, dns); + } } } } @@ -123,7 +132,7 @@ namespace bpkg // are collected recursively. // bool - collect_prerequisites (database& db) + collect_prerequisites () { bool r (false); @@ -136,29 +145,30 @@ namespace bpkg if ((dp.reason == drop_reason::user || dp.reason == drop_reason::dependent) && dp.package->state == package_state::configured) - r = collect_prerequisites (db, dp.package) || r; + r = collect_prerequisites (dp.package) || r; } return r; } bool - collect_prerequisites (database& db, const shared_ptr<selected_package>& p) + collect_prerequisites (const shared_ptr<selected_package>& p) { bool r (false); for (const auto& pair: p->prerequisites) { const lazy_shared_ptr<selected_package>& lpp (pair.first); + database& pdb (lpp.database ()); - if (map_.find (lpp.object_id ()) == map_.end ()) + if (map_.find (pdb, lpp.object_id ()) == map_.end ()) { shared_ptr<selected_package> pp (lpp.load ()); if (!pp->hold_package) // Prune held packages. { - collect (pp, drop_reason::prerequisite); - collect_prerequisites (db, pp); + collect (pdb, pp, drop_reason::prerequisite); + collect_prerequisites (pp); r = true; } } @@ -171,11 +181,11 @@ namespace bpkg // returning its positions. // iterator - order (const package_name& name) + order (database& db, const package_name& name) { // Every package that we order should have already been collected. // - auto mi (map_.find (name)); + auto mi (map_.find (db, name)); assert (mi != map_.end ()); // If this package is already in the list, then that would also @@ -214,13 +224,14 @@ namespace bpkg { for (const auto& pair: p->prerequisites) { + database& pdb (pair.first.database ()); const package_name& pn (pair.first.object_id ()); // The prerequisites may not necessarily be in the map (e.g., // a held package that we prunned). // - if (map_.find (pn) != map_.end ()) - update (order (pn)); + if (map_.find (pdb, pn) != map_.end ()) + update (order (pdb, pn)); } } @@ -231,7 +242,7 @@ namespace bpkg // true if any remain. // bool - filter_prerequisites (database& db) + filter_prerequisites () { bool r (false); @@ -244,27 +255,32 @@ namespace bpkg if (dp.reason == drop_reason::prerequisite) { const shared_ptr<selected_package>& p (dp.package); + database& db (dp.db); bool keep (true); // Get our dependents (which, BTW, could only have been before us // on the list). If they are all in the map, then we can be dropped. // - using query = query<package_dependent>; - - for (auto& pd: db.query<package_dependent> (query::name == p->name)) + for (database& ddb: db.dependent_configs ()) { - if (map_.find (pd.name) == map_.end ()) + for (auto& pd: query_dependents (ddb, p->name, db)) { - keep = false; - break; + if (map_.find (ddb, pd.name) == map_.end ()) + { + keep = false; + break; + } } + + if (!keep) + break; } if (!keep) { i = erase (i); - map_.erase (p->name); + map_.erase (package_key {db, p->name}); continue; } @@ -284,15 +300,24 @@ namespace bpkg drop_package package; }; - map<package_name, data_type> map_; + class package_map: public map<package_key, data_type> + { + public: + using base_type = map<package_key, data_type>; + + iterator + find (database& db, const package_name& pn) + { + return base_type::find (package_key {db, pn}); + } + }; + package_map map_; }; // Drop ordered list of packages. // static int - pkg_drop (const dir_path& c, - const pkg_drop_options& o, - database& db, + pkg_drop (const pkg_drop_options& o, const drop_packages& pkgs, bool drop_prq, bool need_prompt) @@ -330,11 +355,11 @@ namespace bpkg } if (o.print_only ()) - cout << "drop " << p->name << endl; + cout << "drop " << p->name << dp.db << endl; else if (verb) // Print indented for better visual separation. // - text << " drop " << p->name; + text << " drop " << p->name << dp.db; } if (o.print_only ()) @@ -347,24 +372,50 @@ namespace bpkg !(o.yes () || !need_prompt || yn_prompt ("continue? [Y/n]", 'y'))) return 1; + bool result (verb && !o.no_result ()); + bool progress (!result && + ((verb == 1 && !o.no_progress () && stderr_term) || + o.progress ())); + + size_t prog_i, prog_n, prog_percent; + // 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) + // Note: similar code in pkg-build. + // + auto disfigure_pred = [drop_prq] (const drop_package& dp) { // Skip prerequisites if we weren't instructed to drop them. // if (dp.reason == drop_reason::prerequisite && !drop_prq) - continue; + return false; - const shared_ptr<selected_package>& p (dp.package); + if (dp.package->state != package_state::configured) + return false; + + return true; + }; + + if (progress) + { + prog_i = 0; + prog_n = static_cast<size_t> (count_if (pkgs.begin (), pkgs.end (), + disfigure_pred)); + prog_percent = 100; + } - if (p->state != package_state::configured) + for (const drop_package& dp: pkgs) + { + if (!disfigure_pred (dp)) continue; + database& db (dp.db); + const shared_ptr<selected_package>& p (dp.package); + // Each package is disfigured in its own transaction, so that we always // leave the configuration in a valid state. // @@ -372,15 +423,46 @@ namespace bpkg // Commits the transaction. // - pkg_disfigure (c, o, t, p, true /* clean */, false /* simulate */); + pkg_disfigure (o, db, t, + p, + true /* clean */, + true /* disfigure */, + false /* simulate */); assert (p->state == package_state::unpacked || p->state == package_state::transient); - if (verb && !o.no_result ()) - text << (p->state == package_state::transient - ? "purged " - : "disfigured ") << p->name; + if (result || progress) + { + const char* what (p->state == package_state::transient + ? "purged" + : "disfigured"); + if (result) + text << what << ' ' << p->name << db; + else if (progress) + { + size_t p ((++prog_i * 100) / prog_n); + + if (prog_percent != p) + { + prog_percent = p; + + diag_progress_lock pl; + diag_progress = ' '; + diag_progress += to_string (p); + diag_progress += "% of packages "; + diag_progress += what; + } + } + } + } + + // Clear the progress if shown. + // + if (progress) + { + diag_progress_lock pl; + diag_progress.clear (); } if (o.disfigure_only ()) @@ -403,14 +485,16 @@ namespace bpkg assert (p->state == package_state::fetched || p->state == package_state::unpacked); + database& db (dp.db); + transaction t (db); // Commits the transaction, p is now transient. // - pkg_purge (c, t, p, false /* simulate */); + pkg_purge (db, t, p, false /* simulate */); - if (verb && !o.no_result ()) - text << "purged " << p->name; + if (result) + text << "purged " << p->name << db; } return 0; @@ -424,19 +508,52 @@ namespace bpkg const dir_path& c (o.directory ()); l4 ([&]{trace << "configuration: " << c;}); - if (o.yes () && o.no ()) - fail << "both --yes|-y and --no|-n specified"; + { + diag_record dr; + + if (o.yes () && o.no ()) + { + dr << fail << "both --yes|-y and --no|-n specified"; + } + else if (o.drop_dependent () && o.keep_dependent ()) + { + dr << fail << "both --drop-dependent and --keep-dependent|-K " + << "specified"; + } + else if (o.drop_dependent () && o.dependent_exit_specified ()) + { + dr << fail << "both --drop-dependent and --dependent-exit " + << "specified"; + } + else if (o.keep_dependent () && o.dependent_exit_specified ()) + { + dr << fail << "both --keep-dependent|-K and --dependent-exit " + << "specified"; + } + else if (o.all ()) + { + if (o.all_pattern_specified ()) + dr << fail << "both --all|-a and --all-pattern specified"; + + if (args.more ()) + dr << fail << "both --all|-a and package argument specified"; + } + else if (o.all_pattern_specified ()) + { + if (args.more ()) + dr << fail << "both --all-pattern and package argument specified"; + } + else if (!args.more ()) + { + dr << fail << "package name argument expected"; + } - if (o.drop_dependent () && o.keep_dependent ()) - fail << "both --drop-dependent and --keep-dependent|-K " - << "specified" << - info << "run 'bpkg help pkg-drop' for more information"; + if (!dr.empty ()) + dr << info << "run 'bpkg help pkg-drop' for more information"; + } - if (!args.more ()) - fail << "package name argument expected" << - info << "run 'bpkg help pkg-drop' for more information"; - database db (open (c, trace)); + database db (c, trace, true /* pre_attach */); // Note that the session spans all our transactions. The idea here is // that drop_package objects in the drop_packages list below will be @@ -464,33 +581,79 @@ namespace bpkg // by the user. // vector<package_name> names; - while (args.more ()) - { - package_name n (parse_package_name (args.next (), - false /* allow_version */)); - - l4 ([&]{trace << "package " << n;}); - - shared_ptr<selected_package> p (db.find<selected_package> (n)); - if (p == nullptr) - fail << "package " << n << " does not exist in configuration " << c; + auto add = [&names, &pkgs, &db] (shared_ptr<selected_package>&& p) + { + package_name n (p->name); if (p->state == package_state::broken) fail << "unable to drop broken package " << n << info << "use 'pkg-purge --force' to remove"; - if (pkgs.collect (move (p))) + if (pkgs.collect (db, move (p))) names.push_back (move (n)); + }; + + if (o.all () || o.all_pattern_specified ()) + { + using query = query<selected_package>; + + for (shared_ptr<selected_package> p: + pointer_result ( + db.query<selected_package> (query::hold_package))) + { + l4 ([&]{trace << *p;}); + + if (o.all_pattern_specified ()) + { + for (const string& pat: o.all_pattern ()) + { + if (path_match (p->name.string (), pat)) + { + add (move (p)); + break; + } + } + } + else // --all + add (move (p)); + } + + if (names.empty ()) + info << "nothing to drop"; + } + else + { + while (args.more ()) + { + package_name n (parse_package_name (args.next (), + false /* allow_version */)); + + l4 ([&]{trace << "package " << n;}); + + shared_ptr<selected_package> p (db.find<selected_package> (n)); + + if (p == nullptr) + fail << "package " << n << " does not exist in configuration " << c; + + add (move (p)); + } } // The next step is to see if there are any dependents that are not // already on the list. We will either have to drop those as well or - // abort. + // issue diagnostics and fail or silently indicate that with an exit + // code. // - dependent_names dnames (pkgs.collect_dependents (db)); + dependent_names dnames (pkgs.collect_dependents ()); if (!dnames.empty () && !o.drop_dependent ()) { + if (o.dependent_exit_specified ()) + { + t.commit (); + return o.dependent_exit (); + } + { diag_record dr; @@ -503,7 +666,8 @@ namespace bpkg << "as well:"; for (const dependent_name& dn: dnames) - dr << text << dn.name << " (requires " << dn.prq_name << ")"; + dr << text << dn.name << dn.db << " (requires " << dn.prq_name + << dn.prq_db << ")"; } if (o.yes ()) @@ -526,7 +690,7 @@ namespace bpkg // on the latter and, if that's the case and "more" cannot be dropped, // then neither can "less". // - pkgs.collect_prerequisites (db); + pkgs.collect_prerequisites (); // Now that we have collected all the packages we could possibly be // dropping, arrange them in the "dependency order", that is, with @@ -540,17 +704,17 @@ namespace bpkg // on which it depends. // for (const package_name& n: names) - pkgs.order (n); + pkgs.order (db, n); for (const dependent_name& dn: dnames) - pkgs.order (dn.name); + pkgs.order (dn.db, dn.name); // Filter out prerequisites that we cannot possibly drop (e.g., they // have dependents other than the ones we are dropping). If there are // some that we can drop, ask the user for confirmation. // - if (pkgs.filter_prerequisites (db) && - !o.keep_unused () && + if (pkgs.filter_prerequisites () && + !o.keep_unused () && !(drop_prq = o.yes ()) && !o.no ()) { { @@ -563,7 +727,7 @@ namespace bpkg { if (dp.reason == drop_reason::prerequisite) dr << text << (dp.package->system () ? "sys:" : "") - << dp.package->name; + << dp.package->name << dp.db; } } @@ -576,6 +740,6 @@ namespace bpkg t.commit (); } - return pkg_drop (c, o, db, pkgs, drop_prq, need_prompt); + return pkg_drop (o, pkgs, drop_prq, need_prompt); } } |