From d1fa0047be1db658b165514dc429ce494517b39c Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 11 Aug 2021 20:17:12 +0300 Subject: Add support for cfg-unlink --- bpkg/bpkg.cli | 7 +- bpkg/bpkg.cxx | 4 +- bpkg/buildfile | 2 + bpkg/cfg-link.cli | 6 +- bpkg/cfg-unlink.cli | 81 +++++++++++++++ bpkg/cfg-unlink.cxx | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++++ bpkg/cfg-unlink.hxx | 18 ++++ bpkg/database.cxx | 66 +++++++++--- bpkg/database.hxx | 82 +++++++++++++-- bpkg/pkg-build.cxx | 2 +- 10 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 bpkg/cfg-unlink.cli create mode 100644 bpkg/cfg-unlink.cxx create mode 100644 bpkg/cfg-unlink.hxx (limited to 'bpkg') diff --git a/bpkg/bpkg.cli b/bpkg/bpkg.cli index caa33c0..17ac927 100644 --- a/bpkg/bpkg.cli +++ b/bpkg/bpkg.cli @@ -177,11 +177,16 @@ namespace bpkg "\l{bpkg-cfg-info(1)} \- print configuration information" } - bool cfg-link + bool cfg-link|link { "\l{bpkg-cfg-link(1)} \- link configuration" } + bool cfg-unlink|unlink + { + "\l{bpkg-cfg-unlink(1)} \- unlink configuration" + } + bool rep-info { "\l{bpkg-rep-info(1)} \- print repository information" diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index 9488509..04aa798 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -22,9 +22,10 @@ // #include +#include #include #include -#include +#include #include #include @@ -530,6 +531,7 @@ try CFG_COMMAND (create, false); // Temp dir initialized manually. CFG_COMMAND (info, true); CFG_COMMAND (link, true); + CFG_COMMAND (unlink, true); // pkg-* commands // diff --git a/bpkg/buildfile b/bpkg/buildfile index d32980b..3ae522d 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -18,6 +18,7 @@ bpkg-options \ cfg-create-options \ cfg-info-options \ cfg-link-options \ +cfg-unlink-options \ common-options \ configuration-options \ help-options \ @@ -154,6 +155,7 @@ if $cli.configured cli.cxx{cfg-create-options}: cli{cfg-create} cli.cxx{cfg-info-options}: cli{cfg-info} cli.cxx{cfg-link-options}: cli{cfg-link} + cli.cxx{cfg-unlink-options}: cli{cfg-unlink} # rep-* command. # diff --git a/bpkg/cfg-link.cli b/bpkg/cfg-link.cli index 49f17c8..562d4c1 100644 --- a/bpkg/cfg-link.cli +++ b/bpkg/cfg-link.cli @@ -19,8 +19,10 @@ namespace bpkg \h|DESCRIPTION| The \cb{cfg-link} command links the specified \cb{bpkg} configuration - with the current configuration. See \l{bpkg-cfg-create(1)} for background - on linked configurations. + with the current configuration. Note that it also establishes an implicit + back-link from the specified to the current configuration. See + \l{bpkg-cfg-create(1)} for background on linked configurations. To unlink + previously linked configurations use \l{bpkg-cfg-unlink(1)}. The linked configurations are normally referred to using names when specified on the \cb{bpkg} command line. Unless overridden with the diff --git a/bpkg/cfg-unlink.cli b/bpkg/cfg-unlink.cli new file mode 100644 index 0000000..ade3373 --- /dev/null +++ b/bpkg/cfg-unlink.cli @@ -0,0 +1,81 @@ +// file : bpkg/cfg-unlink.cli +// license : MIT; see accompanying LICENSE file + +include ; + +"\section=1" +"\name=bpkg-cfg-unlink" +"\summary=unlink configuration" + +namespace bpkg +{ + { + " ", + + "\h|SYNOPSIS| + + \c{\b{bpkg cfg-unlink} [] []\n + \b{bpkg cfg-unlink} [] \b{--dangling}} + + \h|DESCRIPTION| + + The \cb{cfg-unlink} command unlinks the specified \cb{bpkg} configuration + from the current configuration (the first form) or removes dangling + implicit back-links (the second form). See \l{bpkg-cfg-create(1)} for + background on linked configurations. + + In the first form the configuration to unlink can be specified either as + configuration directory (), name (\cb{--name}), id (\cb{--id}), or + UUID (\cb{--uuid}). + " + } + + class cfg_unlink_options: configuration_options + { + "\h|CFG-UNLINK OPTIONS|" + + string --name + { + "", + "Name of the configuration to unlink." + } + + uint64_t --id + { + "", + "Numeric id of the configuration to unlink." + } + + uuid_type --uuid + { + "", + "UUID of the configuration to unlink." + } + + bool --dangling + { + "Remove dangling implicit back-links." + } + }; + + " + \h|DEFAULT OPTIONS FILES| + + See \l{bpkg-default-options-files(1)} for an overview of the default + options files. For the \cb{cfg-unlink} command the search start directory + is the configuration directory. The following options files are searched + for in each directory and, if found, loaded in the order listed: + + \ + bpkg.options + bpkg-cfg-unlink.options + \ + + The following \cb{cfg-unlink} command options cannot be specified in the + default options files: + + \ + --directory|-d + \ + " +} diff --git a/bpkg/cfg-unlink.cxx b/bpkg/cfg-unlink.cxx new file mode 100644 index 0000000..e29d949 --- /dev/null +++ b/bpkg/cfg-unlink.cxx @@ -0,0 +1,292 @@ +// file : bpkg/cfg-unlink.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace bpkg +{ + static int + cfg_unlink_config (const cfg_unlink_options& o, cli::scanner& args) + try + { + tracer trace ("cfg_unlink_config"); + + dir_path c (o.directory ()); + l4 ([&]{trace << "configuration: " << c;}); + + database mdb (c, trace, true /* pre_attach */); + transaction t (mdb); + + // Find the configuration to be unlinked. + // + // Note that we exclude the current configuration from the search. + // + database& udb (o.name_specified () ? mdb.find_attached (o.name (), false) : + o.id_specified () ? mdb.find_attached (o.id (), false) : + o.uuid_specified () ? mdb.find_attached (o.uuid (), false) : + mdb.find_attached ( + normalize (dir_path (args.next ()), + "specified linked configuration"), + false)); + + l4 ([&]{trace << "unlink configuration: " << udb.config;}); + + bool priv (udb.private_ ()); + + // If the configuration being unlinked contains any prerequisites of + // packages in other configurations, make sure that they will stay + // resolvable for their dependents after the configuration is unlinked + // (see _selected_package_ref::to_ptr() for the resolution details). + // + // Specifically, if the configuration being unlinked is private, make sure + // it doesn't contain any prerequisites of any dependents in any other + // configurations (since we will remove it). Otherwise, do not consider + // those dependent configurations which will still be linked with the + // unlinked configuration (directly or indirectly through some different + // path). + // + // So, for example, for the following link chain where cfg1 contains a + // dependent of a prerequisite in cfg3, unlinking cfg3 from cfg2 will + // result with the "cfg3 still depends on cfg1" error. + // + // cfg1 (target) -> cfg2 (target) -> cfg3 (host) + // + { + // Note: needs to come before the subsequent unlinking. + // + // Also note that this call also verifies integrity of the implicit + // links of the configuration being unlinked, which we rely upon below. + // + linked_databases dcs (udb.dependent_configs ()); + + // Unlink the configuration in the in-memory model, so we can evaluate + // if the dependent configurations are still linked with it. + // + // Note that we don't remove the back-link here, since this is not + // required for the check. + // + if (!priv) + { + linked_configs& ls (mdb.explicit_links ()); + + auto i (find_if (ls.begin (), ls.end (), + [&udb] (const linked_config& lc) + { + return lc.db == udb; + })); + + assert (i != ls.end ()); // By definition. + + ls.erase (i); + } + + // Now go through the packages configured in the unlinked configuration + // and check it they have some dependents in other configurations which + // now unable to resolve them as prerequisites. Issue diagnostics and + // fail if that's the case. + // + using query = query; + + for (shared_ptr sp: + pointer_result ( + udb.query (query::state == "configured"))) + { + for (auto i (dcs.begin_linked ()); i != dcs.end (); ++i) + { + database& db (*i); + + odb::result ds ( + query_dependents (db, sp->name, udb)); + + // Skip the dependent configuration if it doesn't contain any + // dependents of the package. + // + if (ds.empty ()) + continue; + + // Skip the dependent configuration if it is still (potentially + // indirectly) linked with the unlinked configuration. + // + if (!priv) + { + linked_databases cs (db.dependency_configs ()); + + if (find_if (cs.begin (), cs.end (), + [&udb] (const database& db) + { + return db == udb; + }) != cs.end ()) + continue; + } + + diag_record dr (fail); + + dr << "configuration " << db.config_orig + << " still depends on " << (priv ? "private " : "") + << "configuration " << udb.config_orig << + info << "package " << sp->name << udb << " has dependents:"; + + for (const package_dependent& pd: ds) + { + dr << info << "package " << pd.name << db; + + if (pd.constraint) + dr << " on " << sp->name << " " << *pd.constraint; + } + } + } + } + + // Now unlink the configuration for real, in the database. + // + // Specifically, load the current and the being unlinked configurations + // and remove their respective explicit and implicit links. + // + { + using query = query; + + // Explicit link. + // + shared_ptr uc ( + mdb.query_one (query::uuid == udb.uuid.string ())); + + // The integrity of the current configuration explicit links is verified + // by the database constructor. + // + assert (uc != nullptr); + + // Implicit back-link. + // + shared_ptr cc ( + udb.query_one (query::uuid == mdb.uuid.string ())); + + // The integrity of the implicit links of the configuration being + // unlinked is verified by the above dependent_configs() call. + // + assert (cc != nullptr); + + // If the back-link turns out to be explicit, then, unless the + // configuration being unlinked is private, we just turn the explicit + // link into an implicit one rather then remove the direct and back + // links. + // + if (cc->expl && !priv) + { + info << "configurations " << udb.config_orig << " and " + << mdb.config_orig << " are mutually linked, turning the link " + << "to " << udb.config_orig << " into implicit back-link"; + + uc->expl = false; + mdb.update (uc); + } + else + { + mdb.erase (uc); + udb.erase (cc); + } + } + + t.commit (); + + // If the unlinked configuration is private, then detach its database and + // remove its directory. But first, stash the directory path for the + // subsequent removal and diagnostics. + // + dir_path ud (udb.config); + + if (priv) + { + mdb.detach_all (); + rm_r (ud); + } + + if (verb && !o.no_result ()) + text << "unlinked " << (priv ? "and removed " : "") << "configuration " + << ud; + + return 0; + } + catch (const invalid_path& e) + { + fail << "invalid path: '" << e.path << "'" << endf; + } + + static int + cfg_unlink_dangling (const cfg_unlink_options& o, cli::scanner&) + { + tracer trace ("cfg_unlink_dangling"); + + dir_path c (o.directory ()); + l4 ([&]{trace << "configuration: " << c;}); + + database db (c, trace, false /* pre_attach */); + transaction t (db); + + using query = query; + + size_t count (0); + for (auto& c: db.query (query::id != 0 && !query::expl)) + { + if (!exists (c.effective_path (db.config))) + { + if (verb > 1) + text << "removing dangling implicit back-link " << c.path; + + db.erase (c); + ++count; + } + } + + t.commit (); + + if (verb && !o.no_result ()) + text << "removed " << count << " dangling implicit back-link(s)"; + + return 0; + } + + int + cfg_unlink (const cfg_unlink_options& o, cli::scanner& args) + { + // Verify that the unlink mode is specified unambiguously. + // + // Points to the mode, if any is specified and NULL otherwise. + // + const char* mode (nullptr); + + // If the mode is specified, then check that it hasn't been specified yet + // and set it, if that's the case, or fail otherwise. + // + auto verify = [&mode] (const char* m, bool specified) + { + if (specified) + { + if (mode == nullptr) + mode = m; + else + fail << "both " << mode << " and " << m << " specified"; + } + }; + + verify ("--dangling", o.dangling ()); + verify ("--name", o.name_specified ()); + verify ("--id", o.id_specified ()); + verify ("--uuid", o.uuid_specified ()); + verify ("directory argument", args.more ()); + + if (mode == nullptr) + fail << "expected configuration to unlink or --dangling option" << + info << "run 'bpkg help cfg-unlink' for more information"; + + return o.dangling () + ? cfg_unlink_dangling (o, args) + : cfg_unlink_config (o, args); + } +} diff --git a/bpkg/cfg-unlink.hxx b/bpkg/cfg-unlink.hxx new file mode 100644 index 0000000..50256f3 --- /dev/null +++ b/bpkg/cfg-unlink.hxx @@ -0,0 +1,18 @@ +// file : bpkg/cfg-unlink.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_CFG_UNLINK_HXX +#define BPKG_CFG_UNLINK_HXX + +#include +#include + +#include + +namespace bpkg +{ + int + cfg_unlink (const cfg_unlink_options&, cli::scanner& args); +} + +#endif // BPKG_CFG_UNLINK_HXX diff --git a/bpkg/database.cxx b/bpkg/database.cxx index 22cd61a..3d83de8 100644 --- a/bpkg/database.cxx +++ b/bpkg/database.cxx @@ -577,7 +577,13 @@ namespace bpkg // Skip the dangling implicit link. // if (!lc.expl && !exists (d)) + { + if (verb > 1) + info << "skipping dangling implicit back-link " << lc.path << + info << "use 'cfg-unlink --dangling' to clean up"; + continue; + } database& db (attach (d, sys_rep)); @@ -657,9 +663,7 @@ namespace bpkg // const std::string& nbt (db.type == bt ? bt : empty_string); - // Skip the self-link. - // - for (auto i (lds.begin () + 1); i != lds.end (); ++i) + for (auto i (lds.begin_linked ()); i != lds.end (); ++i) { database& ldb (*i); add (ldb, db.type, nbt, add); @@ -763,9 +767,7 @@ namespace bpkg const linked_configs& lcs (db.explicit_links ()); - // Skip the self-link. - // - for (auto i (lcs.begin () + 1); i != lcs.end (); ++i) + for (auto i (lcs.begin_linked ()); i != lcs.end (); ++i) add (i->db, db.type, add); // If this is a private host configuration, then also add the parent's @@ -775,7 +777,7 @@ namespace bpkg { const linked_configs& lcs (db.parent_config ().explicit_links ()); - for (auto i (lcs.begin () + 1); i != lcs.end (); ++i) + for (auto i (lcs.begin_linked ()); i != lcs.end (); ++i) { database& ldb (i->db); if (ldb.type == build2_config_type) @@ -805,7 +807,7 @@ namespace bpkg } database& database:: - find_attached (uint64_t id) + find_attached (uint64_t id, bool s) { assert (!explicit_links_.empty ()); @@ -818,7 +820,7 @@ namespace bpkg return lc.id == id; })); - if (r == explicit_links_.end ()) + if (r == explicit_links_.end () || (!s && r == explicit_links_.begin ())) fail << "no configuration with id " << id << " is linked with " << config_orig; @@ -826,7 +828,7 @@ namespace bpkg } database& database:: - find_attached (const std::string& name) + find_attached (const std::string& name, bool s) { assert (!explicit_links_.empty ()); @@ -836,7 +838,7 @@ namespace bpkg return lc.name && *lc.name == name; })); - if (r == explicit_links_.end ()) + if (r == explicit_links_.end () || (!s && r == explicit_links_.begin ())) fail << "no configuration with name '" << name << "' is linked with " << config_orig; @@ -844,6 +846,42 @@ namespace bpkg } database& database:: + find_attached (const uuid_type& uid, bool s) + { + assert (!explicit_links_.empty ()); + + auto r (find_if (explicit_links_.begin (), explicit_links_.end (), + [&uid] (const linked_config& lc) + { + return lc.db.get ().uuid == uid; + })); + + if (r == explicit_links_.end () || (!s && r == explicit_links_.begin ())) + fail << "no configuration with uuid " << uid << " is linked with " + << config_orig; + + return r->db; + } + + database& database:: + find_attached (const dir_path& d, bool s) + { + assert (!explicit_links_.empty ()); + + auto r (find_if (explicit_links_.begin (), explicit_links_.end (), + [&d] (const linked_config& lc) + { + return lc.db.get ().config == d; + })); + + if (r == explicit_links_.end () || (!s && r == explicit_links_.begin ())) + fail << "no configuration with path " << d << " is linked with " + << config_orig; + + return r->db; + } + + database& database:: find_dependency_config (const uuid_type& uid) { for (database& ldb: dependency_configs ()) @@ -864,9 +902,7 @@ namespace bpkg dir_path pd (config.directory ().directory ()); // Parent configuration. const linked_databases& lds (implicit_links (true /* attach */, sys_rep)); - // Skip the self-link. - // - for (auto i (lds.begin () + 1); i != lds.end (); ++i) + for (auto i (lds.begin_linked ()); i != lds.end (); ++i) { if (i->get ().config == pd) return *i; @@ -884,7 +920,7 @@ namespace bpkg { assert (!explicit_links_.empty ()); - auto r (find_if (explicit_links_.begin () + 1, explicit_links_.end (), + auto r (find_if (explicit_links_.begin_linked (), explicit_links_.end (), [&type] (const linked_config& lc) { database& db (lc.db); diff --git a/bpkg/database.hxx b/bpkg/database.hxx index 32169bb..1961272 100644 --- a/bpkg/database.hxx +++ b/bpkg/database.hxx @@ -35,15 +35,60 @@ namespace bpkg }; // Used for the immediate explicit links which are normally not many (one - // entry for the self-link). + // entry for the self-link, which normally comes first). // - using linked_configs = small_vector; + class linked_configs: public small_vector + { + public: + using base_type = small_vector; + + using base_type::base_type; - // In particular, is used for implicit links which can potentially be many. - // Think of a dependency in a shared configuration with dependents in - // multiple implicitly linked configurations. + // Skip the self-link. + // + const_iterator + begin_linked () const + { + assert (!empty ()); + return begin () + 1; + } + + iterator + begin_linked () + { + assert (!empty ()); + return begin () + 1; + } + }; + + // In particular, is used for implicit links which can potentially be many + // (with the self-link which normally comes first). Think of a dependency in + // a shared configuration with dependents in multiple implicitly linked + // configurations. // - using linked_databases = small_vector, 16>; + class linked_databases: public small_vector, 16> + { + public: + using base_type = small_vector, 16>; + + using base_type::base_type; + + // Skip the self-link. + // + const_iterator + begin_linked () const + { + assert (!empty ()); + return begin () + 1; + } + + iterator + begin_linked () + { + assert (!empty ()); + return begin () + 1; + } + }; // Derive a custom database class that handles attaching/detaching // additional configurations. @@ -236,12 +281,16 @@ namespace bpkg // created with the pre_attach flag set to true. // + // The following find_attached() overloads include the self reference into + // the search by default and skip it if requested. + // + // Return the self reference if the id is 0. Otherwise, return the // database of an explicitly linked configuration with the specified link // id and issue diagnostics and fail if no link is found. // database& - find_attached (uint64_t id); + find_attached (uint64_t id, bool self = true); // Return the self reference if this is the current configuration // name. Otherwise, return the database of an explicitly linked @@ -249,7 +298,24 @@ namespace bpkg // no link is found. // database& - find_attached (const std::string& name); + find_attached (const std::string& name, bool self = true); + + // Return the self reference if this is the current configuration + // uuid. Otherwise, return the database of an explicitly linked + // configuration with the specified uuid and issue diagnostics and fail if + // no link is found. + // + database& + find_attached (const uuid_type&, bool self = true); + + // Return the self reference if this is the current configuration + // path. Otherwise, return the database of an explicitly linked + // configuration with the specified path and issue diagnostics and fail if + // no link is found. The configuration directory should be absolute and + // normalized. + // + database& + find_attached (const dir_path&, bool self = true); // Return the dependency configuration with the specified uuid and issue // diagnostics and fail if not found. diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index e20909b..d8e5cda 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -1173,7 +1173,7 @@ namespace bpkg // Skip the self-link. // const linked_configs& lcs (sdb.explicit_links ()); - for (auto i (lcs.begin () + 1); i != lcs.end (); ++i) + for (auto i (lcs.begin_linked ()); i != lcs.end (); ++i) { database& ldb (i->db); -- cgit v1.1