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 +- doc/buildfile | 1 + doc/cli.sh | 2 +- tests/cfg-unlink.testscript | 275 +++++++++++++++++++++++++++++++++++++++++ tests/cfg-unlink/t7a | 1 + tests/common.testscript | 1 + tests/pkg-drop.testscript | 36 +----- tests/pkg-drop/t7b | 1 - 17 files changed, 816 insertions(+), 61 deletions(-) create mode 100644 bpkg/cfg-unlink.cli create mode 100644 bpkg/cfg-unlink.cxx create mode 100644 bpkg/cfg-unlink.hxx create mode 100644 tests/cfg-unlink.testscript create mode 120000 tests/cfg-unlink/t7a delete mode 120000 tests/pkg-drop/t7b 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); diff --git a/doc/buildfile b/doc/buildfile index 3af1782..d1592aa 100644 --- a/doc/buildfile +++ b/doc/buildfile @@ -5,6 +5,7 @@ cmds = \ bpkg-cfg-create \ bpkg-cfg-info \ bpkg-cfg-link \ +bpkg-cfg-unlink \ bpkg-help \ bpkg-pkg-build \ bpkg-pkg-checkout \ diff --git a/doc/cli.sh b/doc/cli.sh index a2942bb..40e1c1f 100755 --- a/doc/cli.sh +++ b/doc/cli.sh @@ -78,7 +78,7 @@ compile "pkg-build" $o --class-doc bpkg::pkg_build_pkg_options=exclude-base # NOTE: remember to update a similar list in buildfile and bpkg.cli as well as # the help topics sections in bpkg/buildfile and help.cxx. # -pages="cfg-create cfg-info cfg-link help pkg-clean pkg-configure \ +pages="cfg-create cfg-info cfg-link cfg-unlink help pkg-clean pkg-configure \ pkg-disfigure pkg-drop pkg-fetch pkg-checkout pkg-install pkg-purge \ pkg-status pkg-test pkg-uninstall pkg-unpack pkg-update pkg-verify rep-add \ rep-remove rep-list rep-create rep-fetch rep-info repository-signing \ diff --git a/tests/cfg-unlink.testscript b/tests/cfg-unlink.testscript new file mode 100644 index 0000000..b65f4b1 --- /dev/null +++ b/tests/cfg-unlink.testscript @@ -0,0 +1,275 @@ +# file : tests/cfg-link.testscript +# license : MIT; see accompanying LICENSE file + +.include common.testscript remote.testscript + +# Source repository (see pkg-build for details): +# +# cfg-unlink +# `-- t7a + +# Prepare repositories used by tests if running in the local mode. +# ++if! $remote + rep_create += 2>! + + cp -r $src/t7a $out/t7a && $rep_create $out/t7a &$out/t7a/packages.manifest +end + +cfg_create += 2>! +cfg_info += --link +pkg_build += --yes 2>! +pkg_drop += --yes 2>! +rep_add += 2>! +rep_fetch += --trust-yes 2>! + +cfg1_uuid = '18f48b4b-b5d9-4712-b98c-1930df1c4228' +cfg2_uuid ='28f48b4b-b5d9-4712-b98c-1930df1c4228' + ++$cfg_create -d cfg1 --name 'main' --uuid "$cfg1_uuid" &cfg1/*** ++$cfg_create -d cfg2 --name 'shared' --uuid "$cfg2_uuid" --type host &cfg2/*** + ++$cfg_link -d cfg1 cfg2 2>! + +clone_root_cfgs = cp -r $~/cfg1 $~/cfg2 ./ + +: unlink +: +{ + : name-dir + : + { + $clone_root_cfgs; + + $* -d cfg1 cfg2 --name 'host' 2>/'error: both --name and directory argument specified' != 0 + } + + : dir + : + { + $clone_root_cfgs; + + $* -d cfg1 cfg1 2>/"error: no configuration with path $~/cfg1/ is linked with cfg1/" != 0; + + $* -d cfg1 cfg2 2>/"unlinked configuration $~/cfg2/"; + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/"EOO" + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + EOO + } + + : name + : + { + $clone_root_cfgs; + + $* -d cfg1 --name 'target' 2>/"error: no configuration with name 'target' is linked with cfg1/" != 0; + + $* -d cfg1 --name 'shared' 2>/"unlinked configuration $~/cfg2/"; + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/"EOO" + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + EOO + } + + : id + : + { + $clone_root_cfgs; + + $* -d cfg1 --id 2 2>/"error: no configuration with id 2 is linked with cfg1/" != 0; + + $* -d cfg1 --id 1 2>/"unlinked configuration $~/cfg2/"; + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/"EOO" + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + EOO + } + + : uuid + : + { + $clone_root_cfgs; + + $* -d cfg1 --uuid $cfg1_uuid 2>/"error: no configuration with uuid $cfg1_uuid is linked with cfg1/" != 0; + + $* -d cfg1 --uuid $cfg2_uuid 2>/"unlinked configuration $~/cfg2/"; + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/"EOO" + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + EOO + } + + : mutual + : + { + $clone_root_cfgs; + + $cfg_link -d cfg2 cfg1 2>!; + + $* -d cfg1 cfg2 2>>/"EOE"; + info: configurations cfg2/ and cfg1/ are mutually linked, turning the link to cfg2/ into implicit back-link + unlinked configuration $~/cfg2/ + EOE + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/"EOO" + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + } + + : dependency + : + { + $clone_root_cfgs; + + $rep_add -d cfg1 $rep/t7a && $rep_fetch -d cfg1; + + $pkg_build -d cfg1 libbar &cfg2/.bpkg/build2/***; + + $* -d cfg1 cfg2 2>>/EOE != 0; + error: configuration cfg1/ still depends on configuration cfg2/ + info: package foo [cfg2/] has dependents: + info: package libbar on foo ^1.0.0 + EOE + + $pkg_drop -d cfg1 --keep-unused libbar; + + $* -d cfg1 cfg2 2>>/"EOE"; + unlinked configuration $~/cfg2/ + EOE + + $cfg_info -d cfg1 >>/"EOO"; + path: $~/cfg1/ + uuid: $cfg1_uuid + type: target + name: main + EOO + + $cfg_info -d cfg2 >>/~"%EOO%"; + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + + path: $~/cfg2/.bpkg/build2/ + %uuid: .{36}% + type: build2 + name: build2 + EOO + + $pkg_drop -d cfg1 libbaz; + $pkg_drop -d cfg2 foo + } + + : dependency-private + : + { + $clone_root_cfgs; + + $rep_add -d cfg2 $rep/t7a && $rep_fetch -d cfg2; + + $pkg_build -d cfg2 foo; + + $* -d cfg2 --name build2 2>>/EOE != 0; + error: configuration cfg2/ still depends on private configuration cfg2/.bpkg/build2/ + info: package libbuild2-bar [cfg2/.bpkg/build2/] has dependents: + info: package foo on libbuild2-bar ^1.0.0 + EOE + + $pkg_drop -d cfg2 --keep-unused foo; + + test -d cfg2/.bpkg/build2/; + + $* -d cfg2 --name build2 2>>/"EOE"; + unlinked and removed configuration $~/cfg2/.bpkg/build2/ + EOE + + $cfg_info -d cfg2 >>/"EOO"; + path: $~/cfg2/ + uuid: $cfg2_uuid + type: host + name: shared + EOO + + test -d cfg2/.bpkg/build2/ == 1; + + $pkg_drop -d cfg2 libbaz + } +} +: remove-dangling +: +{ + : success + : + { + $clone_root_cfgs; + + mv cfg1 cfg3; + + $* -d cfg2 --dangling 2>'removed 1 dangling implicit back-link(s)'; + $* -d cfg2 --dangling 2>'removed 0 dangling implicit back-link(s)' + } + + : error + : + { + $clone_root_cfgs; + + $* -d cfg1 --dangling --name 'host' 2>'error: both --dangling and --name specified' != 0 + } +} diff --git a/tests/cfg-unlink/t7a b/tests/cfg-unlink/t7a new file mode 120000 index 0000000..d02b5d4 --- /dev/null +++ b/tests/cfg-unlink/t7a @@ -0,0 +1 @@ +../common/linked/t7a \ No newline at end of file diff --git a/tests/common.testscript b/tests/common.testscript index 8af2cc7..25671c6 100644 --- a/tests/common.testscript +++ b/tests/common.testscript @@ -35,6 +35,7 @@ test.options += --default-options $options_guard \ cfg_create = $* cfg-create cfg_info = $* cfg-info cfg_link = $* cfg-link +cfg_unlink = $* cfg-unlink pkg_build = $* pkg-build pkg_checkout = $* pkg-checkout pkg_configure = $* pkg-configure diff --git a/tests/pkg-drop.testscript b/tests/pkg-drop.testscript index 83fa13b..7a93c2d 100644 --- a/tests/pkg-drop.testscript +++ b/tests/pkg-drop.testscript @@ -3,39 +3,14 @@ .include common.testscript config.testscript remote.testscript -# Source repository: +# Source repository (see pkg-build for details): # # pkg-drop # |-- t4a -# | |-- libfoo-1.1.0.tar.gz -# | `-- repositories.manifest -# | -# |-- t4b -> t4a (prerequisite repository) -# | |-- libbar-1.1.0.tar.gz -> libfoo == 1.1.0 -# | `-- repositories.manifest -# | -# |-- t4c -> t4b (prerequisite repository) -# | |-- libbaz-1.1.0.tar.gz -> libfoo, libbar -# | |-- libfoo-1.0.0.tar.gz -# | `-- repositories.manifest -# | -# |-- t4d -> t4c (complement) -# | |-- libbiz-1.0.0.tar.gz -> libfox, libfoo, libbaz -# | |-- libfox-1.0.0.tar.gz -# | `-- repositories.manifest -# | -# |-- t7a -# | |-- libbaz-1.0.0.tar.gz -# | |-- libbuild2-bar-1.0.0.tar.gz -# | |-- foo-1.0.0.tar.gz -> * libbuild2-bar ^1.0.0, libbaz ^1.0.0 -# | |-- libbar-1.0.0.tar.gz -> * foo ^1.0.0, libbaz ^1.0.0 -# | `-- repositories.manifest -# | -# `-- t7b -> t7a (complement repository) -# |-- libbaz-1.1.0.tar.gz -# |-- foo-1.1.0.tar.gz -> libbaz ^1.1.0 -# |-- libbar-1.1.0.tar.gz -> * foo ^1.1.0, libbaz ^1.0.0 -# `-- repositories.manifest +# |-- t4b +# |-- t4c +# |-- t4d +# `-- t7a # Prepare repositories used by tests if running in the local mode. # @@ -47,7 +22,6 @@ cp -r $src/t4c $out/t4c && $rep_create $out/t4c &$out/t4c/packages.manifest cp -r $src/t4d $out/t4d && $rep_create $out/t4d &$out/t4d/packages.manifest cp -r $src/t7a $out/t7a && $rep_create $out/t7a &$out/t7a/packages.manifest - cp -r $src/t7b $out/t7b && $rep_create $out/t7b &$out/t7b/packages.manifest end cfg_create += 2>! diff --git a/tests/pkg-drop/t7b b/tests/pkg-drop/t7b deleted file mode 120000 index 808039d..0000000 --- a/tests/pkg-drop/t7b +++ /dev/null @@ -1 +0,0 @@ -../common/linked/t7b \ No newline at end of file -- cgit v1.1