aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bpkg/bpkg.cli7
-rw-r--r--bpkg/bpkg.cxx4
-rw-r--r--bpkg/buildfile2
-rw-r--r--bpkg/cfg-link.cli6
-rw-r--r--bpkg/cfg-unlink.cli81
-rw-r--r--bpkg/cfg-unlink.cxx292
-rw-r--r--bpkg/cfg-unlink.hxx18
-rw-r--r--bpkg/database.cxx66
-rw-r--r--bpkg/database.hxx82
-rw-r--r--bpkg/pkg-build.cxx2
-rw-r--r--doc/buildfile1
-rwxr-xr-xdoc/cli.sh2
-rw-r--r--tests/cfg-unlink.testscript275
l---------tests/cfg-unlink/t7a1
-rw-r--r--tests/common.testscript1
-rw-r--r--tests/pkg-drop.testscript36
l---------tests/pkg-drop/t7b1
17 files changed, 816 insertions, 61 deletions
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 <bpkg/help.hxx>
+#include <bpkg/cfg-create.hxx>
#include <bpkg/cfg-info.hxx>
#include <bpkg/cfg-link.hxx>
-#include <bpkg/cfg-create.hxx>
+#include <bpkg/cfg-unlink.hxx>
#include <bpkg/pkg-build.hxx>
#include <bpkg/pkg-checkout.hxx>
@@ -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 <bpkg/configuration.cli>;
+
+"\section=1"
+"\name=bpkg-cfg-unlink"
+"\summary=unlink configuration"
+
+namespace bpkg
+{
+ {
+ "<options> <dir>",
+
+ "\h|SYNOPSIS|
+
+ \c{\b{bpkg cfg-unlink} [<options>] [<dir>]\n
+ \b{bpkg cfg-unlink} [<options>] \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 (<dir>), name (\cb{--name}), id (\cb{--id}), or
+ UUID (\cb{--uuid}).
+ "
+ }
+
+ class cfg_unlink_options: configuration_options
+ {
+ "\h|CFG-UNLINK OPTIONS|"
+
+ string --name
+ {
+ "<name>",
+ "Name of the configuration to unlink."
+ }
+
+ uint64_t --id
+ {
+ "<id>",
+ "Numeric id of the configuration to unlink."
+ }
+
+ uuid_type --uuid
+ {
+ "<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 <bpkg/cfg-unlink.hxx>
+
+#include <bpkg/package.hxx>
+#include <bpkg/package-odb.hxx>
+#include <bpkg/database.hxx>
+#include <bpkg/diagnostics.hxx>
+
+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<selected_package>;
+
+ for (shared_ptr<selected_package> sp:
+ pointer_result (
+ udb.query<selected_package> (query::state == "configured")))
+ {
+ for (auto i (dcs.begin_linked ()); i != dcs.end (); ++i)
+ {
+ database& db (*i);
+
+ odb::result<package_dependent> 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<configuration>;
+
+ // Explicit link.
+ //
+ shared_ptr<configuration> uc (
+ mdb.query_one<configuration> (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<configuration> cc (
+ udb.query_one<configuration> (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<configuration>;
+
+ size_t count (0);
+ for (auto& c: db.query<configuration> (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 <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/cfg-unlink-options.hxx>
+
+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<linked_config, 2>;
+ class linked_configs: public small_vector<linked_config, 2>
+ {
+ public:
+ using base_type = small_vector<linked_config, 2>;
+
+ 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<reference_wrapper<database>, 16>;
+ class linked_databases: public small_vector<reference_wrapper<database>, 16>
+ {
+ public:
+ using base_type = small_vector<reference_wrapper<database>, 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