aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2021-04-22 21:57:13 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2021-05-14 16:00:50 +0300
commit87d743624a1d5593770bc8ac3c6d79423baf3ca1 (patch)
tree252f0695027b11c780c7aa31f63eaf22d2f8920f
parent9e33f6a048c9ef56c3950e4c2631f806ecbfdb21 (diff)
Add support for associated configurationsassoc-configs-1
-rw-r--r--bpkg/auth.cxx9
-rw-r--r--bpkg/auth.hxx1
-rw-r--r--bpkg/bpkg.cli10
-rw-r--r--bpkg/bpkg.cxx2
-rw-r--r--bpkg/buildfile2
-rw-r--r--bpkg/cfg-add.cli79
-rw-r--r--bpkg/cfg-add.cxx182
-rw-r--r--bpkg/cfg-add.hxx18
-rw-r--r--bpkg/cfg-create.cli40
-rw-r--r--bpkg/cfg-create.cxx23
-rw-r--r--bpkg/database.cxx475
-rw-r--r--bpkg/database.hxx228
-rw-r--r--bpkg/fetch-git.cxx1
-rw-r--r--bpkg/forward.hxx6
-rw-r--r--bpkg/package.cxx156
-rw-r--r--bpkg/package.hxx222
-rw-r--r--bpkg/package.xml21
-rw-r--r--bpkg/pkg-build.cli17
-rw-r--r--bpkg/pkg-build.cxx1167
-rw-r--r--bpkg/pkg-checkout.cxx41
-rw-r--r--bpkg/pkg-checkout.hxx4
-rw-r--r--bpkg/pkg-command.cxx19
-rw-r--r--bpkg/pkg-command.hxx4
-rw-r--r--bpkg/pkg-configure.cxx107
-rw-r--r--bpkg/pkg-configure.hxx10
-rw-r--r--bpkg/pkg-disfigure.cxx46
-rw-r--r--bpkg/pkg-disfigure.hxx4
-rw-r--r--bpkg/pkg-drop.cxx59
-rw-r--r--bpkg/pkg-fetch.cxx51
-rw-r--r--bpkg/pkg-fetch.hxx4
-rw-r--r--bpkg/pkg-purge.cxx18
-rw-r--r--bpkg/pkg-purge.hxx4
-rw-r--r--bpkg/pkg-status.cxx2
-rw-r--r--bpkg/pkg-unpack.cxx66
-rw-r--r--bpkg/pkg-unpack.hxx6
-rw-r--r--bpkg/pkg-update.hxx5
-rw-r--r--bpkg/rep-add.cxx8
-rw-r--r--bpkg/rep-add.hxx5
-rw-r--r--bpkg/rep-fetch.cxx62
-rw-r--r--bpkg/rep-fetch.hxx1
-rw-r--r--bpkg/rep-list.cxx2
-rw-r--r--bpkg/rep-remove.cxx36
-rw-r--r--bpkg/rep-remove.hxx15
-rw-r--r--bpkg/system-repository.cxx4
-rw-r--r--bpkg/system-repository.hxx4
-rw-r--r--bpkg/types-parsers.cxx25
-rw-r--r--bpkg/types-parsers.hxx10
-rw-r--r--bpkg/types.hxx5
-rw-r--r--bpkg/utility.hxx5
-rw-r--r--doc/buildfile1
-rwxr-xr-xdoc/cli.sh2
-rw-r--r--manifest1
-rw-r--r--tests/cfg-add.testscript124
-rw-r--r--tests/cfg-create.testscript77
-rw-r--r--tests/common.testscript1
-rw-r--r--tests/pkg-build.testscript20
-rw-r--r--tests/pkg-configure.testscript4
-rw-r--r--tests/pkg-fetch.testscript16
-rw-r--r--tests/pkg-purge.testscript2
-rw-r--r--tests/pkg-system.testscript4
-rw-r--r--tests/pkg-unpack.testscript24
61 files changed, 2685 insertions, 882 deletions
diff --git a/bpkg/auth.cxx b/bpkg/auth.cxx
index 0693abc..ed35f63 100644
--- a/bpkg/auth.cxx
+++ b/bpkg/auth.cxx
@@ -624,6 +624,7 @@ namespace bpkg
shared_ptr<const certificate>
authenticate_certificate (const common_options& co,
const dir_path* conf,
+ database* db,
const optional<string>& pem,
const repository_location& rl,
const optional<string>& dependent_trust)
@@ -650,18 +651,20 @@ namespace bpkg
? auth_real (co, fp, *pem, rl, dependent_trust).cert
: auth_dummy (co, fp.abbreviated, rl);
}
- else if (transaction::has_current ())
+ else if (db != nullptr)
{
+ assert (transaction::has_current ());
+
r = auth_cert (co,
*conf,
- transaction::current ().database (),
+ *db,
pem,
rl,
dependent_trust);
}
else
{
- database db (open (*conf, trace));
+ database db (*conf, trace, false /* pre_attach */);
transaction t (db);
r = auth_cert (co, *conf, db, pem, rl, dependent_trust);
t.commit ();
diff --git a/bpkg/auth.hxx b/bpkg/auth.hxx
index b5ae1ff..f6d0365 100644
--- a/bpkg/auth.hxx
+++ b/bpkg/auth.hxx
@@ -38,6 +38,7 @@ namespace bpkg
shared_ptr<const certificate>
authenticate_certificate (const common_options&,
const dir_path* configuration,
+ database*,
const optional<string>& cert_pem,
const repository_location&,
const optional<string>& dependent_trust);
diff --git a/bpkg/bpkg.cli b/bpkg/bpkg.cli
index 48f655e..6b8250f 100644
--- a/bpkg/bpkg.cli
+++ b/bpkg/bpkg.cli
@@ -50,6 +50,11 @@ namespace bpkg
configuration is an amalgamation that contains packages as subprojects
(see \l{bpkg-cfg-create(1)} for details).
+ Build configurations can be associated with each other, so that while a
+ package is built in one configuration, some of its dependencies can be
+ built in associated potentially shared configurations (see
+ \l{bpkg-cfg-create(1)} for details).
+
A \i{bpkg package} is an archive or directory (potentially in a version
control system) that contains a \cb{build2} project plus the package
\cb{manifest} file. \cb{bpkg} can either use package archives/directories
@@ -168,6 +173,11 @@ namespace bpkg
"\l{bpkg-cfg-create(1)} \- create configuration"
}
+ bool cfg-add
+ {
+ "\l{bpkg-cfg-add(1)} \- associate configurations"
+ }
+
bool rep-info
{
"\l{bpkg-rep-info(1)} \- print repository information"
diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx
index f1ee302..7697168 100644
--- a/bpkg/bpkg.cxx
+++ b/bpkg/bpkg.cxx
@@ -21,6 +21,7 @@
//
#include <bpkg/help.hxx>
+#include <bpkg/cfg-add.hxx>
#include <bpkg/cfg-create.hxx>
#include <bpkg/pkg-build.hxx>
@@ -498,6 +499,7 @@ try
#define CFG_COMMAND(CMD, TMP) COMMAND_IMPL(cfg_, "cfg-", CMD, false, TMP)
CFG_COMMAND (create, false); // Temp dir initialized manually.
+ CFG_COMMAND (add, true);
// pkg-* commands
//
diff --git a/bpkg/buildfile b/bpkg/buildfile
index cb09ca9..2016d38 100644
--- a/bpkg/buildfile
+++ b/bpkg/buildfile
@@ -15,6 +15,7 @@ import libs += libodb-sqlite%lib{odb-sqlite}
options_topics = \
bpkg-options \
+cfg-add-options \
cfg-create-options \
common-options \
configuration-options \
@@ -149,6 +150,7 @@ if $cli.configured
# cfg-* command.
#
+ cli.cxx{cfg-add-options}: cli{cfg-add}
cli.cxx{cfg-create-options}: cli{cfg-create}
# rep-* command.
diff --git a/bpkg/cfg-add.cli b/bpkg/cfg-add.cli
new file mode 100644
index 0000000..357856c
--- /dev/null
+++ b/bpkg/cfg-add.cli
@@ -0,0 +1,79 @@
+// file : bpkg/cfg-add.cli
+// license : MIT; see accompanying LICENSE file
+
+include <bpkg/configuration.cli>;
+
+"\section=1"
+"\name=bpkg-cfg-add"
+"\summary=associate configuration"
+
+namespace bpkg
+{
+ {
+ "<options> <dir>",
+
+ "\h|SYNOPSIS|
+
+ \c{\b{bpkg cfg-add} [<options>] <dir>}
+
+ \h|DESCRIPTION|
+
+ The \cb{cfg-add} command symmetrically associates the current and the
+ specified \cb{bpkg} configurations (see \l{bpkg-cfg-create(1)} for
+ details on associated configurations).
+
+ The associated configuration is normally referred by name when specified
+ on the \cb{bpkg} command line. Unless overriden with the \cb{--name}
+ option, the original configuration name is used for the association. If
+ the associated configuration is unnamed, then it can only be referred by
+ the identifier, automatically assigned when the association is created.
+
+ If the specified directory path is relative, then its path is rebased
+ against the current configuration directory path prior to being
+ saved. This way the associated configurations can be moved around
+ together with the relative locations preserved. If the specified
+ directory path is absolute, then it is just normalized instead, unless
+ the \cb{--relative} option is specified in which case it is rebased as if
+ it were relative.
+ "
+ }
+
+ class cfg_add_options: configuration_options
+ {
+ "\h|CFG-ADD OPTIONS|"
+
+ string --name|-n
+ {
+ "<name>",
+ "Override the associated configuration name."
+ }
+
+ bool --relative
+ {
+ "Rebase the associated configuration directory absolute path against the
+ current configuration directory path, in the same way as for the
+ relative paths. Has no effect on relative paths."
+ }
+ };
+
+ "
+ \h|DEFAULT OPTIONS FILES|
+
+ See \l{bpkg-default-options-files(1)} for an overview of the default
+ options files. For the \cb{cfg-add} 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-add.options
+ \
+
+ The following \cb{cfg-add} command options cannot be specified in the
+ default options files:
+
+ \
+ --directory|-d
+ \
+ "
+}
diff --git a/bpkg/cfg-add.cxx b/bpkg/cfg-add.cxx
new file mode 100644
index 0000000..921fb89
--- /dev/null
+++ b/bpkg/cfg-add.cxx
@@ -0,0 +1,182 @@
+// file : bpkg/cfg-add.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <bpkg/cfg-add.hxx>
+
+#include <bpkg/package.hxx>
+#include <bpkg/package-odb.hxx>
+#include <bpkg/database.hxx>
+#include <bpkg/diagnostics.hxx>
+
+using namespace std;
+
+namespace bpkg
+{
+ int
+ cfg_add (const cfg_add_options& o, cli::scanner& args)
+ try
+ {
+ tracer trace ("cfg_add");
+
+ if (o.name_specified ())
+ validate_configuration_name (o.name (), "--name|-n option value");
+
+ if (!args.more ())
+ fail << "configuration directory argument expected" <<
+ info << "run 'bpkg help cfg-add' for more information";
+
+ dir_path c (o.directory ());
+ l4 ([&]{trace << "configuration: " << c;});
+
+ dir_path ad (args.next ());
+ if (ad.empty ())
+ throw invalid_path (ad.representation ());
+
+ l4 ([&]{trace << "add configuration: " << ad;});
+
+ bool rel (ad.relative () || o.relative ());
+ normalize (ad, "specified associated configuration");
+
+ database db (c, trace, false /* pre_attach */, false /* sys_rep */, &ad);
+ transaction t (db);
+
+ const dir_path& cd (db.config); // Note: absolute and normalized.
+
+ // Load the self configuration object from the database of the
+ // configuration being associated to obtain its name, type, and uuid.
+ //
+ database& adb (db.attach (ad, false /* sys_rep */));
+
+ optional<string> name;
+ string type;
+ uuid uid;
+ {
+ shared_ptr<configuration> cf (adb.load<configuration> (0));
+ name = o.name_specified () ? o.name () : move (cf->name);
+ type = move (cf->type);
+ uid = cf->uuid;
+ }
+
+ // Verify that the name and path of the configuration being associated do
+ // not clash with already associated configurations. Fail if
+ // configurations with this uuid is already associated unless the
+ // association is implicit, in which case make it explicit and update its
+ // name and path.
+ //
+ shared_ptr<configuration> acf;
+
+ using query = query<configuration>;
+
+ for (shared_ptr<configuration> ac:
+ pointer_result (db.query<configuration> (query::id != 0)))
+ {
+ if (uid == ac->uuid)
+ {
+ if (ac->expl)
+ fail << "configuration " << uid.string () << " is already "
+ << "associated";
+
+ // Just cache the implicit association and update it later, when the
+ // name/path check is complete. But first make sure its type still
+ // matches.
+ //
+ if (type != ac->type)
+ fail << "configuration type '" << type << "' doesn't match "
+ << "existing association type '" << ac->type << "'";
+
+ acf = move (ac);
+ continue;
+ }
+
+ if (ad == ac->resolve_path (cd))
+ fail << "configuration '" << ad << "' is already associated: "
+ << ac->uuid.string ();
+
+ if (name && name == ac->name)
+ {
+ diag_record dr (fail);
+ dr << "configuration named '" << *name << "' is already associated: "
+ << ac->uuid.string ();
+
+ if (!o.name_specified ())
+ dr << info << "consider specifying name explicitly with --name|-n";
+ }
+ }
+
+ // If the implicit association already exists, then make it explicit and
+ // update its name and path. Otherwise, create the new association.
+ //
+ // Note that in the former case we assume that the current configuration
+ // is already associated with the configuration being associated.
+ //
+ // Don't move the associated configuration absolute path (ad) since it
+ // still be needed.
+ //
+ if (acf != nullptr)
+ {
+ acf->expl = true;
+ acf->name = move (name);
+ acf->path = rel ? ad.relative (cd) : ad;
+
+ db.update (acf);
+ }
+ else
+ {
+ // If the directory path of the configuration being associated is
+ // relative or the --relative option is specified, then rebase it
+ // against the current configuration directory path.
+ //
+ acf = make_shared<configuration> (move (name),
+ move (type),
+ rel ? ad.relative (cd) : ad,
+ true /* explicit */,
+ uid);
+
+ db.persist (acf);
+
+ // Now implicitly associate ourselves with the just associated
+ // configuration. Note that we associate ourselves as unnamed if the
+ // configuration name clashes.
+ //
+ shared_ptr<configuration> cf (db.load<configuration> (0));
+
+ name = move (cf->name);
+
+ for (auto& ac: adb.query<configuration> (query::id != 0))
+ {
+ if (cf->uuid == ac.uuid)
+ fail << "current configuration " << cf->uuid.string ()
+ << " is already associated with '" << ad << "'";
+
+ if (cd == ac.resolve_path (ad))
+ fail << "current configuration '" << cd << "' is already "
+ << "associated with '" << ad << "'";
+
+ if (name && name == ac.name)
+ name = nullopt;
+ }
+
+ // It feels natural to persist associated configuration paths both either
+ // relative or not.
+ //
+ cf = make_shared<configuration> (move (name),
+ move (cf->type),
+ rel ? cd.relative (ad) : cd,
+ false /* explicit */,
+ cf->uuid);
+
+ adb.persist (cf);
+ }
+
+ t.commit ();
+
+ if (verb)
+ text << "associated " << acf->type << " configuration " << ad;
+
+ return 0;
+ }
+ catch (const invalid_path& e)
+ {
+ fail << "invalid path: '" << e.path << "'" << endf;
+ }
+}
diff --git a/bpkg/cfg-add.hxx b/bpkg/cfg-add.hxx
new file mode 100644
index 0000000..70d8c61
--- /dev/null
+++ b/bpkg/cfg-add.hxx
@@ -0,0 +1,18 @@
+// file : bpkg/cfg-add.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_CFG_ADD_HXX
+#define BPKG_CFG_ADD_HXX
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/cfg-add-options.hxx>
+
+namespace bpkg
+{
+ int
+ cfg_add (const cfg_add_options&, cli::scanner& args);
+}
+
+#endif // BPKG_CFG_ADD_HXX
diff --git a/bpkg/cfg-create.cli b/bpkg/cfg-create.cli
index 0b32604..0147c5c 100644
--- a/bpkg/cfg-create.cli
+++ b/bpkg/cfg-create.cli
@@ -51,6 +51,25 @@ namespace bpkg
\
bpkg create cxx. \"?cli\"
\
+
+ The configurations can be associated with each other, so that dependent
+ and dependency packages can be built across multiple potentially shared
+ configurations (see \l{bpkg-cfg-add(1)} for details). For a convenience
+ of referring the configuration on the \cb{bpkg} command line in the
+ future, it can be named using the \cb{--name|-n} option.
+
+ The dependent and its dependency packages are only assumed to be
+ compatible at runtime if types of the associated configurations they were
+ built in are identical. The configuration type can be specified using the
+ \cb{--type|-t} option. The predefined types are \cb{host} and \cb{target}
+ (default). Configurations of the \cb{host} type are normally used for
+ building compile-time dependencies while of the \cb{target} type for the
+ runtime dependencies.
+
+ At creation time a unique id (UUID) is assigned to the configuration. It,
+ in particular, is used for the configuration associations integrity
+ checks. Unless it is explicitly specified with the \cb{--config-uuid}
+ option, it is generated automatically.
"
}
@@ -58,6 +77,27 @@ namespace bpkg
{
"\h|CFG-CREATE OPTIONS|"
+ string --name|-n
+ {
+ "<name>",
+ "The name of the configuration being created. This name will be used by
+ default when this configuration is associated with other configurations
+ (\l{bpkg-cfg-add(1)}). By default, configuration is created unnamed."
+ }
+
+ string --type|-t = "target"
+ {
+ "<type>",
+ "The type of the configuration being created. By default, configuration
+ of the \cb{target} type is created."
+ }
+
+ uuid --config-uuid
+ {
+ "<uuid>",
+ "Use this unique id for the configuration rather than auto-generate it."
+ }
+
dir_path --directory|-d (".")
{
"<dir>",
diff --git a/bpkg/cfg-create.cxx b/bpkg/cfg-create.cxx
index f3ca80d..21dcf04 100644
--- a/bpkg/cfg-create.cxx
+++ b/bpkg/cfg-create.cxx
@@ -17,6 +17,12 @@ namespace bpkg
{
tracer trace ("cfg_create");
+ if (o.name_specified ())
+ validate_configuration_name (o.name (), "--name|-n option value");
+
+ if (o.type ().empty ())
+ fail << "empty --type|-t option value";
+
if (o.existing () && o.wipe ())
fail << "both --existing|-e and --wipe specified";
@@ -149,7 +155,20 @@ namespace bpkg
// Create the database.
//
- database db (open (c, trace, true));
+ // Auto-generate the configuration UUID, unless it is specified
+ // explicitly.
+ //
+ shared_ptr<configuration> sc (
+ make_shared<configuration> ((o.name_specified ()
+ ? o.name ()
+ : optional<string> ()),
+ o.type (),
+ (o.config_uuid_specified ()
+ ? o.config_uuid ()
+ : uuid ())));
+
+ database db (c, sc, trace);
+ transaction t (db);
// Add the special, root repository object with empty location and
// containing a single repository fragment having an empty location as
@@ -161,8 +180,6 @@ namespace bpkg
// locations and as a search starting point for held packages (see
// pkg-build for details).
//
- transaction t (db);
-
shared_ptr<repository_fragment> fr (
make_shared<repository_fragment> (repository_location ()));
diff --git a/bpkg/database.cxx b/bpkg/database.cxx
index a866274..73c5c95 100644
--- a/bpkg/database.cxx
+++ b/bpkg/database.cxx
@@ -3,45 +3,27 @@
#include <bpkg/database.hxx>
+#include <map>
+
#include <odb/schema-catalog.hxx>
#include <odb/sqlite/exceptions.hxx>
+#include <libbutl/sha256.mxx>
+
#include <bpkg/package.hxx>
#include <bpkg/package-odb.hxx>
#include <bpkg/diagnostics.hxx>
-#include <bpkg/system-repository.hxx>
using namespace std;
namespace bpkg
{
- using namespace odb::sqlite;
- using odb::schema_catalog;
-
- // Use a custom connection factory to automatically set and clear the
- // BPKG_OPEN_CONFIG environment variable. A bit heavy-weight but seems like
- // the best option.
- //
- static const string open_name ("BPKG_OPEN_CONFIG");
-
- class conn_factory: public single_connection_factory // No need for pool.
- {
- public:
- conn_factory (const dir_path& d)
- {
- setenv (open_name, normalize (d, "configuration").string ());
- }
-
- virtual
- ~conn_factory ()
- {
- unsetenv (open_name);
- }
- };
+ namespace sqlite = odb::sqlite;
// Register the data migration functions.
//
- // NOTE: remember to qualify table names if using native statements.
+ // NOTE: remember to qualify table names with \"main\". if using native
+ // statements.
//
template <odb::schema_version v>
using migration_entry = odb::data_migration_entry<v, DB_SCHEMA_VERSION_BASE>;
@@ -59,84 +41,431 @@ namespace bpkg
}
});
- database
- open (const dir_path& d, tracer& tr, bool create)
+ static const migration_entry<9>
+ migrate_v9 ([] (odb::database& db)
{
- tracer trace ("open");
+ // Add the unnamed self configuration of the target type.
+ //
+ shared_ptr<configuration> sc (
+ make_shared<configuration> (optional<string> (), "target"));
+
+ db.persist (sc);
+ });
+ static inline path
+ cfg_path (const dir_path& d, bool create)
+ {
path f (d / bpkg_dir / "bpkg.sqlite3");
if (!create && !exists (f))
fail << d << " does not look like a bpkg configuration directory";
+ return f;
+ }
+
+ // Automatically set and clear the BPKG_OPEN_CONFIG environment variable in
+ // the main database constructor and destructor.
+ //
+ static const string open_name ("BPKG_OPEN_CONFIG");
+
+ struct database::impl
+ {
+ sqlite::connection_ptr conn; // Main connection.
+
+ map<dir_path, database> attached_map;
+
+ impl (sqlite::connection_ptr&& c): conn (move (c)) {}
+ };
+
+ database::
+ database (const dir_path& d,
+ configuration* create,
+ odb::tracer& tr,
+ bool pre_attach,
+ bool sys_rep,
+ const dir_path* pre_assoc)
+ : sqlite::database (
+ cfg_path (d, create != nullptr).string (),
+ SQLITE_OPEN_READWRITE | (create != nullptr ? SQLITE_OPEN_CREATE : 0),
+ true, // Enable FKs.
+ "", // Default VFS.
+ unique_ptr<sqlite::connection_factory> (
+ new sqlite::serial_connection_factory)), // Single connection.
+ config (normalize (d, "configuration"))
+ {
+ bpkg::tracer trace ("database");
+
+ // Cache the (single) main connection we will be using.
+ //
+ unique_ptr<impl> ig ((impl_ = new impl (connection ())));
+
try
{
- database db (f.string (),
- SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0),
- true, // Enable FKs.
- "", // Default VFS.
- unique_ptr<connection_factory> (new conn_factory (d)));
-
- db.tracer (trace);
-
- // Lock the database for as long as the connection is active. First
- // we set locking_mode to EXCLUSIVE which instructs SQLite not to
- // release any locks until the connection is closed. Then we force
- // SQLite to acquire the write lock by starting exclusive transaction.
- // See the locking_mode pragma documentation for details. This will
- // also fail if the database is inaccessible (e.g., file does not
- // exist, already used by another process, etc).
- //
- using odb::sqlite::transaction; // Skip the wrapper.
+ tracer_guard tg (*this, trace);
+ // Lock the database for as long as the connection is active. First we
+ // set locking_mode to EXCLUSIVE which instructs SQLite not to release
+ // any locks until the connection is closed. Then we force SQLite to
+ // acquire the write lock by starting exclusive transaction. See the
+ // locking_mode pragma documentation for details. This will also fail if
+ // the database is inaccessible (e.g., file does not exist, already used
+ // by another process, etc).
+ //
+ // Note that here we assume that any database that is ATTACHED within an
+ // exclusive transaction gets the same treatment.
+ //
try
{
- db.connection ()->execute ("PRAGMA locking_mode = EXCLUSIVE");
- transaction t (db.begin_exclusive ());
+ using odb::schema_catalog;
+
+ impl_->conn->execute ("PRAGMA locking_mode = EXCLUSIVE");
- if (create)
{
- // Create the new schema.
- //
- if (db.schema_version () != 0)
- fail << f << ": already has database schema";
+ sqlite::transaction t (impl_->conn->begin_exclusive ());
+
+ if (create != nullptr)
+ {
+ // Create the new schema and persist the self configuration.
+ //
+ if (schema_version () != 0)
+ fail << name () << ": already has database schema";
+
+ schema_catalog::create_schema (*this);
+
+ persist (*create); // Also assigns id.
+
+ // Cache the configuration information.
+ //
+ cache_config (*create);
+ }
+ else
+ {
+ // Migrate the associated databases cluster.
+ //
+ migrate ();
+
+ if (pre_assoc != nullptr)
+ attach (*pre_assoc, false /* sys_rep */).migrate ();
- schema_catalog::create_schema (db);
+ // Cache the configuration information.
+ //
+ shared_ptr<configuration> c (load<configuration> (0));
+ cache_config (*c);
+
+ if (sys_rep)
+ load_system_repository ();
+ }
+
+ t.commit ();
}
- else
+
+ // Detach potentially attached during migration the (pre-)associated
+ // databases.
+ //
+ detach_all ();
+
+ if (pre_attach)
{
- // Migrate the database if necessary.
- //
- schema_catalog::migrate (db);
+ sqlite::transaction t (begin_exclusive ());
+ attach_explicit (sys_rep);
+ t.commit ();
}
-
- t.commit ();
}
catch (odb::timeout&)
{
fail << "configuration " << d << " is already used by another process";
}
+ }
+ catch (const sqlite::database_exception& e)
+ {
+ fail << name () << ": " << e.message ();
+ }
+
+ tracer (tr);
+
+ // @@ Don't we need to keep adding to BPKG_OPEN_CONFIG as we attach (and
+ // clear/reset in detach_all())?
+ //
+ setenv (open_name, normalize (d, "configuration").string ());
+
+ ig.release (); // Will be leaked if anything further throws.
+ }
+
+ database::
+ database (impl* i,
+ const dir_path& d,
+ string schema,
+ bool sys_rep)
+ : sqlite::database (i->conn,
+ cfg_path (d, false /* create */).string (),
+ move (schema)),
+ config (d),
+ impl_ (i)
+ {
+ bpkg::tracer trace ("database");
+
+ try
+ {
+ tracer_guard tg (*this, trace);
+
+ shared_ptr<configuration> cf (load<configuration> (0));
+ cache_config (*cf);
+
+ if (sys_rep)
+ load_system_repository ();
+ }
+ catch (const sqlite::database_exception& e)
+ {
+ fail << name () << ": " << e.message ();
+ }
+ }
+
+ database::
+ ~database ()
+ {
+ if (impl_ != nullptr && // Not a moved-from database?
+ schema ().empty ()) // Main database?
+ {
+ delete impl_;
+
+ unsetenv (open_name);
+ }
+ }
+
+ database::
+ database (database&& db)
+ : sqlite::database (move (db)),
+ uuid (db.uuid),
+ type (move (db.type)),
+ config (move (db.config)),
+ system_repository (move (db.system_repository)),
+ impl_ (db.impl_),
+ explicit_associations_ (move (db.explicit_associations_))
+ {
+ db.impl_ = nullptr; // See ~database().
+ }
+
+ void database::
+ tracer (tracer_type* t)
+ {
+ sqlite::database::main_database ().tracer (t);
+
+ for (auto& db: impl_->attached_map)
+ db.second.sqlite::database::tracer (t);
+ }
+
+ void database::
+ migrate ()
+ {
+ using odb::schema_catalog;
+
+ odb::schema_version sv (schema_version ());
+ odb::schema_version scv (schema_catalog::current_version (*this));
+
+ if (sv != scv)
+ {
+ if (sv < schema_catalog::base_version (*this))
+ fail << "configuration '" << config << "' is too old";
+
+ if (sv > scv)
+ fail << "configuration '" << config << "' is too new";
+
+ // Note that we need to migrate the current database before the
+ // associated ones to properly handle association cycles.
+ //
+ schema_catalog::migrate (*this);
+
+ for (auto& c: query<configuration> (odb::query<configuration>::id != 0))
+ attach (c.resolve_path (config), false /* sys_rep */).migrate ();
+ }
+ }
+
+ void database::
+ cache_config (const configuration& cfg)
+ {
+ uuid = cfg.uuid;
+ type = cfg.type;
+ }
+
+ void database::
+ load_system_repository ()
+ {
+ // Query for all the packages with the system substate and enter their
+ // versions into system_repository as non-authoritative. This way an
+ // available_package (e.g., a stub) will automatically "see" system
+ // version, if one is known.
+ //
+ assert (transaction::has_current ());
+
+ for (const auto& p: query<selected_package> (
+ odb::query<selected_package>::substate == "system"))
+ system_repository.insert (p.name, p.version, false /* authoritative */);
+ }
+
+ database& database::
+ attach (const dir_path& d, bool sys_rep)
+ {
+ assert (d.absolute () && d.normalized ());
+
+ // Check if we are trying to attach the main database.
+ //
+ database& md (main_database ());
+ if (d == md.config)
+ return md;
+
+ auto& am (impl_->attached_map);
+
+ auto i (am.find (d));
+
+ if (i == am.end ())
+ {
+ // We know from the implementation that 4-character schema names are
+ // optimal. So try to come up with a unique abbreviated hash that is 4
+ // or more characters long.
+ //
+ string schema;
+ {
+ butl::sha256 h (d.string ());
+
+ for (size_t n (4);; ++n)
+ {
+ schema = h.abbreviated_string (n);
+
+ if (find_if (am.begin (), am.end (),
+ [&schema] (const map<dir_path, database>::value_type& v)
+ {
+ return v.second.schema () == schema;
+ }) == am.end ())
+ break;
+ }
+ }
- // Query for all the packages with the system substate and enter their
- // versions into system_repository as non-authoritative. This way an
- // available_package (e.g., a stub) will automatically "see" system
- // version, if one is known.
+ // If attaching out of an exclusive transaction (all our transactions
+ // are exclusive), start one to force database locking (see the below
+ // locking_mode discussion for details).
//
- transaction t (db.begin ());
+ sqlite::transaction t;
+ if (!sqlite::transaction::has_current ())
+ t.reset (begin_exclusive ());
- for (const auto& p:
- db.query<selected_package> (
- query<selected_package>::substate == "system"))
- system_repository.insert (p.name, p.version, false);
+ try
+ {
+ i = am.insert (
+ make_pair (d, database (impl_, d, move (schema), sys_rep))).first;
+ }
+ catch (odb::timeout&)
+ {
+ fail << "configuration " << d << " is already used by another process";
+ }
- t.commit ();
+ if (!t.finalized ())
+ t.commit ();
+ }
+
+ return i->second;
+ }
+
+ void database::
+ detach_all ()
+ {
+ assert (schema ().empty ());
+
+ explicit_associations_ = nullopt;
- db.tracer (tr); // Switch to the caller's tracer.
- return db;
+ for (auto i (impl_->attached_map.begin ());
+ i != impl_->attached_map.end (); )
+ {
+ i->second.detach ();
+ i = impl_->attached_map.erase (i);
}
- catch (const database_exception& e)
+ }
+
+ void database::
+ attach_explicit (bool sys_rep)
+ {
+ assert (transaction::has_current ());
+
+ if (!explicit_associations_)
+ {
+ explicit_associations_ = associated_configs ();
+
+ for (auto& ac: query<configuration> (odb::query<configuration>::expl))
+ {
+ database& db (attach (ac.resolve_path (config), sys_rep));
+ explicit_associations_->push_back (associated_config {*ac.id, db});
+ db.attach_explicit (sys_rep);
+ }
+ }
+ }
+
+ associated_configs& database::
+ implicit_associations ()
+ {
+ assert (transaction::has_current ());
+
+ // Note that cached implicit associations must at least contain the self
+ // database.
+ //
+ if (implicit_associations_.empty ())
{
- fail << f << ": " << e.message () << endf;
+ for (auto& ac: query<configuration> (!odb::query<configuration>::expl))
+ {
+ database& db (attach (ac.resolve_path (config), false /* sys_rep */));
+ implicit_associations_.push_back (associated_config {*ac.id, db});
+ }
}
+
+ return implicit_associations_;
+ }
+
+ database& database::
+ find_attached (uint64_t i)
+ {
+ assert (explicit_associations_);
+
+ // Check if this is the current database id.
+ //
+ if (i == 0)
+ return *this;
+
+ // Note that there shouldn't be too many databases, so the linear search
+ // is OK.
+ //
+ auto r (find_if (explicit_associations_->begin (),
+ explicit_associations_->end (),
+ [&i] (const associated_config& ad)
+ {
+ return ad.id == i;
+ }));
+
+ if (r != explicit_associations_->end ())
+ return r->db;
+
+ fail << "no configuration with id " << i << " is associated with "
+ << config << endf;
+ }
+
+ database& database::
+ find_attached (const uuid_type& i)
+ {
+ assert (explicit_associations_);
+
+ // Check if this is the current database uuid.
+ //
+ if (i == uuid)
+ return *this;
+
+ auto r (find_if (explicit_associations_->begin (),
+ explicit_associations_->end (),
+ [&i] (const associated_config& ad)
+ {
+ return ad.db.get ().uuid == i;
+ }));
+
+ if (r != explicit_associations_->end ())
+ return r->db;
+
+ fail << "no configuration with uuid " << i.string () << " is associated "
+ << "with " << config << endf;
}
}
diff --git a/bpkg/database.hxx b/bpkg/database.hxx
index 42270d8..97d4739 100644
--- a/bpkg/database.hxx
+++ b/bpkg/database.hxx
@@ -16,6 +16,7 @@
#include <bpkg/utility.hxx>
#include <bpkg/diagnostics.hxx>
+#include <bpkg/system-repository.hxx>
namespace bpkg
{
@@ -23,21 +24,222 @@ namespace bpkg
using odb::result;
using odb::session;
- using odb::sqlite::database;
+ class configuration;
+ class database;
+
+ struct associated_config
+ {
+ uint64_t id;
+ reference_wrapper<database> db; // Needs to be move-assignable.
+ };
+
+ using associated_configs = small_vector<associated_config, 1>;
+
+ // Derive a custom database class that handles attaching/detaching
+ // additional configurations.
+ //
+ class database: public odb::sqlite::database
+ {
+ public:
+ using uuid_type = bpkg::uuid;
+
+ // Create main database.
+ //
+ // The specified self configuration object is persisted (auto-generating
+ // the configuration id if absent) and the information it contains is
+ // cached in the database object.
+ //
+ database (const dir_path& cfg,
+ const shared_ptr<configuration>& sc,
+ odb::tracer& tr)
+ : database (cfg, sc.get (), tr, false, false, nullptr)
+ {
+ assert (sc != nullptr);
+ }
+
+ // Open main database.
+ //
+ // If configured non-system packages can potentially be loaded from this
+ // database, then pass true as the pre_attach argument to pre-attach the
+ // explicitly associated configuration databases, recursively (see
+ // _selected_package_ref::to_ptr() implementation for details on such a
+ // requirement). Note that a selected package can be loaded internally by
+ // some functions (package_iteration(), etc).
+ //
+ // If pre-associate is not NULL (should be absolute and normalized), then
+ // this configuration is treated as another associated configuration for
+ // schema migration purposes.
+ //
+ database (const dir_path& cfg,
+ odb::tracer& tr,
+ bool pre_attach,
+ bool sys_rep = true,
+ const dir_path* pre_associate = nullptr)
+ : database (cfg, nullptr, tr, pre_attach, sys_rep, pre_associate) {}
+
+ ~database ();
+
+ // Move-constructible but not move-assignable.
+ //
+ database (database&&);
+ database& operator= (database&&) = delete;
+
+ database (const database&) = delete;
+ database& operator= (const database&) = delete;
+
+ // Attach another (existing) database. The configuration directory should
+ // be absolute and normalized.
+ //
+ // Note that if the database is already attached, then the existing
+ // instance reference is returned and the sys_rep argument is ignored.
+ //
+ database&
+ attach (const dir_path&, bool sys_rep = true);
+
+ // Return the self reference if the id is 0 or uuid is the self id.
+ // Otherwise, find by id or uuid the attached database among explicitly
+ // associated configurations and issue diagnostics and fail if not found.
+ //
+ database&
+ find_attached (uint64_t);
+
+ database&
+ find_attached (const uuid_type&);
+
+ // Note that while attach() can be called on the attached database,
+ // detach_all() should only be called on the main database.
+ //
+ void
+ detach_all ();
+
+ database&
+ main_database ()
+ {
+ return static_cast<database&> (odb::sqlite::database::main_database ());
+ }
+
+ // Return the configuration explicit associations. Assumes that the main
+ // database has been created with the pre_attach flag set to true.
+ //
+ associated_configs&
+ explicit_associations ()
+ {
+ assert (explicit_associations_);
+ return *explicit_associations_;
+ }
+
+ // Return the configuration implicit associations, including the self
+ // configuration. Load and cache the information on the first call.
+ //
+ associated_configs&
+ implicit_associations ();
+
+ // Set the specified tracer for the whole associated databases cluster.
+ //
+ using tracer_type = odb::tracer;
+
+ void
+ tracer (tracer_type*);
+
+ void
+ tracer (tracer_type& t) {tracer (&t);}
+
+ using odb::sqlite::database::tracer;
+
+ public:
+ uuid_type uuid;
+ string type;
+
+ dir_path config; // Absolute and normalized configuration directory.
+
+ // Per-configuration system repository (only loaded if sys_rep above is
+ // true).
+ //
+ bpkg::system_repository system_repository;
+
+ private:
+ struct impl;
+
+ // Create/open main database.
+ //
+ database (const dir_path& cfg,
+ configuration* create,
+ odb::tracer&,
+ bool pre_attach,
+ bool sys_rep,
+ const dir_path* pre_associate);
+
+ // Create attached database.
+ //
+ database (impl*,
+ const dir_path& cfg,
+ string schema,
+ bool sys_rep);
+
+ // If necessary, migrate this database and all the associated databases,
+ // recursively. Leave associated databases attached. Must be called inside
+ // the transaction.
+ //
+ // Note that since the whole associated databases cluster is migrated at
+ // once, it is assumed that if migration is unnecessary for a database
+ // then it is also unnecessary for its associated databases.
+ //
+ void
+ migrate ();
+
+ void
+ cache_config (const configuration&);
+
+ // Must be called inside the transaction.
+ //
+ void
+ load_system_repository ();
+
+ void
+ attach_explicit (bool sys_rep);
+
+ impl* impl_;
+
+ optional<associated_configs> explicit_associations_;
+ associated_configs implicit_associations_;
+ };
+
+ // @@ EC
+ //
+ inline bool
+ operator== (const database& x, const database& y)
+ {
+ return &x == &y;
+ }
+
+ inline bool
+ operator< (const database& x, const database& y)
+ {
+ // Note that if we ever need consistent configuration ordering, then we
+ // can compare the config paths or uuids.
+ //
+ return &x < &y;
+ }
// Transaction wrapper that allow the creation of dummy transactions (start
// is false) that in reality use an existing transaction.
//
- struct transaction
+ // Note that there can be multiple databases attached to the primary one and
+ // normally a transaction object is passed around together with a specific
+ // database. Thus, we don't provide the database accessor function, so that
+ // the database is always chosen deliberately.
+ //
+ class transaction
{
+ public:
using database_type = bpkg::database;
explicit
transaction (database_type& db, bool start = true)
- : db_ (db), start_ (start), t_ () // Finalized.
+ : start_ (start), t_ () // Finalized.
{
if (start)
- t_.reset (db.begin ());
+ t_.reset (db.begin_exclusive ()); // See locking_mode for details.
}
void
@@ -54,38 +256,22 @@ namespace bpkg
t_.rollback ();
}
- database_type&
- database ()
- {
- return db_;
- }
-
static bool
has_current ()
{
return odb::sqlite::transaction::has_current ();
}
- static odb::sqlite::transaction&
- current ()
- {
- return odb::sqlite::transaction::current ();
- }
-
private:
- database_type& db_;
bool start_;
odb::sqlite::transaction t_;
};
- database
- open (const dir_path& configuration, tracer&, bool create = false);
-
struct tracer_guard
{
tracer_guard (database& db, tracer& t)
: db_ (db), t_ (db.tracer ()) {db.tracer (t);}
- ~tracer_guard () {db_.tracer (*t_);}
+ ~tracer_guard () {db_.tracer (t_);}
private:
database& db_;
diff --git a/bpkg/fetch-git.cxx b/bpkg/fetch-git.cxx
index 0c21af6..6cbd9a8 100644
--- a/bpkg/fetch-git.cxx
+++ b/bpkg/fetch-git.cxx
@@ -6,7 +6,6 @@
#include <map>
#include <libbutl/git.mxx>
-#include <libbutl/utility.mxx> // digit(), xdigit()
#include <libbutl/filesystem.mxx> // path_entry
#include <libbutl/path-pattern.mxx>
#include <libbutl/semantic-version.mxx>
diff --git a/bpkg/forward.hxx b/bpkg/forward.hxx
index becf628..7e625e8 100644
--- a/bpkg/forward.hxx
+++ b/bpkg/forward.hxx
@@ -4,12 +4,10 @@
#ifndef BPKG_FORWARD_HXX
#define BPKG_FORWARD_HXX
-#include <odb/sqlite/forward.hxx>
-
namespace bpkg
{
- using odb::sqlite::database;
- struct transaction;
+ class database;
+ class transaction;
// <bpkg/package.hxx>
//
diff --git a/bpkg/package.cxx b/bpkg/package.cxx
index 3532f3d..3af537d 100644
--- a/bpkg/package.cxx
+++ b/bpkg/package.cxx
@@ -15,6 +15,64 @@ namespace bpkg
{
const version wildcard_version (0, "0", nullopt, nullopt, 0);
+ // configuration
+ //
+ configuration::
+ configuration (optional<string> n, string t, const uuid_type& uid)
+ : id (0),
+ uuid (uid),
+ name (move (n)),
+ type (move (t)),
+ expl (0)
+ {
+ if (uuid.nil ())
+ try
+ {
+ uuid = uuid_type::generate ();
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to generate configuration uuid: " << e;
+ }
+ }
+
+ dir_path configuration::
+ effective_path (const dir_path& d) const
+ {
+ if (path.relative ())
+ {
+ dir_path r (d / path);
+
+ string what ("associated with '" + d.representation () +
+ "' configuration " + diag_name ());
+
+ normalize (r, what.c_str ());
+ return r;
+ }
+ else
+ return path;
+ }
+
+ void
+ validate_configuration_name (const string& s, const char* what)
+ {
+ if (s.empty ())
+ fail << "empty " << what;
+
+ if (!(alpha (s[0]) || s[0] == '_'))
+ fail << "invalid " << what << " '" << s << "': illegal first character "
+ << "(must be alphabetic or underscore)";
+
+ for (auto i (s.cbegin () + 1), e (s.cend ()); i != e; ++i)
+ {
+ char c (*i);
+
+ if (!(alnum (c) || c == '_' || c == '-'))
+ fail << "invalid " << what << " '" << s << "': illegal character "
+ << "(must be alphabetic, digit, underscore, or dash)";
+ }
+ }
+
// available_package_id
//
bool
@@ -26,6 +84,47 @@ namespace bpkg
// available_package
//
+ const version* available_package::
+ system_version (database& db) const
+ {
+ if (!system_version_)
+ {
+ if (const system_package* sp = db.system_repository.find (id.name))
+ {
+ // Only cache if it is authoritative.
+ //
+ if (sp->authoritative)
+ system_version_ = sp->version;
+ else
+ return &sp->version;
+ }
+ }
+
+ return system_version_ ? &*system_version_ : nullptr;
+ }
+
+ pair<const version*, bool> available_package::
+ system_version_authoritative (database& db) const
+ {
+ const system_package* sp (db.system_repository.find (id.name));
+
+ if (!system_version_)
+ {
+ if (sp != nullptr)
+ {
+ // Only cache if it is authoritative.
+ //
+ if (sp->authoritative)
+ system_version_ = sp->version;
+ else
+ return make_pair (&sp->version, false);
+ }
+ }
+
+ return make_pair (system_version_ ? &*system_version_ : nullptr,
+ sp != nullptr ? sp->authoritative : false);
+ }
+
odb::result<available_package>
query_available (database& db,
const package_name& name,
@@ -34,6 +133,8 @@ namespace bpkg
{
using query = query<available_package>;
+ // @@ EC GOOD
+ //
query q (query::id.name == name);
const auto& vm (query::id.version);
@@ -309,24 +410,20 @@ namespace bpkg
}
void
- check_any_available (const dir_path& c,
- transaction& t,
- const diag_record* dr)
+ check_any_available (database& db, transaction&, const diag_record* dr)
{
- database& db (t.database ());
-
if (db.query_value<repository_count> () == 0)
{
diag_record d;
(dr != nullptr ? *dr << info : d << fail)
- << "configuration " << c << " has no repositories" <<
+ << "configuration " << db.config << " has no repositories" <<
info << "use 'bpkg rep-add' to add a repository";
}
else if (db.query_value<available_package_count> () == 0)
{
diag_record d;
(dr != nullptr ? *dr << info : d << fail)
- << "configuration " << c << " has no available packages" <<
+ << "configuration " << db.config << " has no available packages" <<
info << "use 'bpkg rep-fetch' to fetch available packages list";
}
}
@@ -381,6 +478,26 @@ namespace bpkg
// selected_package
//
+ _selected_package_ref::
+ _selected_package_ref (const lazy_shared_ptr<selected_package>& p)
+ : configuration (static_cast<database&> (p.database ()).uuid),
+ prerequisite (p.object_id ())
+ {
+ }
+
+ lazy_shared_ptr<selected_package> _selected_package_ref::
+ to_ptr (odb::database& db) &&
+ {
+ // Note that if this points to a different configuration, then it should
+ // already be pre-attached since it must be explicitly associated.
+ //
+ return lazy_shared_ptr<selected_package> (
+ configuration.nil ()
+ ? db
+ : static_cast<database&> (db).find_attached (configuration),
+ move (prerequisite));
+ }
+
string selected_package::
version_string () const
{
@@ -389,8 +506,8 @@ namespace bpkg
optional<version>
package_iteration (const common_options& o,
- const dir_path& c,
- transaction& t,
+ database& db,
+ transaction&,
const dir_path& d,
const package_name& n,
const version& v,
@@ -398,7 +515,6 @@ namespace bpkg
{
tracer trace ("package_iteration");
- database& db (t.database ());
tracer_guard tg (db, trace);
if (check_external)
@@ -447,7 +563,7 @@ namespace bpkg
//
if (!changed && p->external ())
{
- dir_path src_root (p->effective_src_root (c));
+ dir_path src_root (p->effective_src_root (db.config));
// We need to complete and normalize the source directory as it may
// generally be completed against the configuration directory (unlikely
@@ -502,8 +618,8 @@ namespace bpkg
{
switch (s)
{
- case package_substate::none: return "none";
- case package_substate::system: return "system";
+ case package_substate::none: return "none";
+ case package_substate::system: return "system";
}
return string (); // Should never reach.
@@ -532,4 +648,18 @@ namespace bpkg
return os;
}
+
+ // package_dependent
+ //
+ odb::result<package_dependent>
+ query_dependents (database& db,
+ const package_name& dep,
+ database& dep_db)
+ {
+ using query = query<package_dependent>;
+
+ return db.query<package_dependent> (
+ "prerequisite = " + query::_val (dep.string ()) + "AND" +
+ "configuration = " + query::_val (dep_db.uuid.string ()));
+ }
}
diff --git a/bpkg/package.hxx b/bpkg/package.hxx
index cee2dd6..797ab8a 100644
--- a/bpkg/package.hxx
+++ b/bpkg/package.hxx
@@ -18,7 +18,7 @@
#include <libbpkg/package-name.hxx>
#include <bpkg/types.hxx>
-#include <bpkg/forward.hxx> // transaction
+#include <bpkg/forward.hxx> // database, transaction
#include <bpkg/utility.hxx>
#include <bpkg/diagnostics.hxx>
@@ -27,7 +27,7 @@
//
#define DB_SCHEMA_VERSION_BASE 6
-#pragma db model version(DB_SCHEMA_VERSION_BASE, 8, closed)
+#pragma db model version(DB_SCHEMA_VERSION_BASE, 9, open)
namespace bpkg
{
@@ -44,6 +44,7 @@ namespace bpkg
};
using optional_string = optional<string>;
+ using optional_uint64_t = optional<uint64_t>;
// path
//
@@ -67,6 +68,14 @@ namespace bpkg
to((?) ? (?)->string () : bpkg::optional_string ()) \
from((?) ? bpkg::dir_path (*(?)) : bpkg::optional_dir_path ())
+ // uuid
+ //
+ // Map nil UUID to empty string.
+ //
+ #pragma db map type(uuid) as(string) \
+ to((?).nil () ? std::string () : (?).string ()) \
+ from((?).empty () ? bpkg::uuid () : bpkg::uuid (?))
+
// timestamp
//
using butl::timestamp;
@@ -136,6 +145,100 @@ void assert (int);
namespace bpkg
{
+ // Self or associated bpkg configuration.
+ //
+ // The self configuration object captures information about the current
+ // configuration. This information is cached in the associated configuration
+ // objects stored in other configurations, which associate the current one.
+ //
+ #pragma db object pointer(shared_ptr) session
+ class configuration
+ {
+ public:
+ using uuid_type = bpkg::uuid;
+
+ // Zero for the self configuration and is auto-assigned for associated
+ // configurations when the object is persisted.
+ //
+ optional<uint64_t> id; // Object id.
+
+ uuid_type uuid;
+ optional<string> name;
+ string type;
+ dir_path path; // Is empty for the self configuration.
+
+ // True if the association is created explicitly by the user, rather than
+ // created automatically as a reverse association.
+ //
+ bool expl;
+
+ // Database mapping.
+ //
+ #pragma db member(id) id auto
+ #pragma db member(path) unique
+ #pragma db member(expl) column("explicit")
+
+ public:
+ // Create the self configuration. Generate the UUID, unless specified.
+ //
+ configuration (optional<string> n,
+ string t,
+ const uuid_type& uid = uuid_type ());
+
+ // Create an associated configuration.
+ //
+ configuration (optional<string> n,
+ string t,
+ dir_path p,
+ bool e,
+ const uuid_type& uid)
+ : uuid (uid),
+ name (move (n)),
+ type (move (t)),
+ path (move (p)),
+ expl (e) {}
+
+ // If the configuration path is absolute, then return it as is. Otherwise,
+ // return it completed against the specified primary directory path and
+ // normalized afterwords. The primary configuration directory should be
+ // absolute and normalized. Issue diagnostics and fail on the path
+ // conversion error.
+ //
+ // Note that the self configuration object is naturally supported by this
+ // function.
+ //
+ dir_path
+ effective_path (const dir_path&) const;
+
+ const dir_path&
+ resolve_path (const dir_path& d)
+ {
+ if (path.relative ())
+ path = effective_path (d);
+
+ return path;
+ }
+
+ string
+ diag_name () const
+ {
+ assert (id.has_value());
+ return name ? *name : to_string (*id);
+ }
+
+ private:
+ friend class odb::access;
+ configuration () = default;
+ };
+
+ // Verify that a string is a valid configuration name, that is non-empty,
+ // containing only alpha-numeric characters, '_', '-' (except for the first
+ // character which can only be alphabetic or '_'). Issue diagnostics and
+ // fail if that's not the case.
+ //
+ void
+ validate_configuration_name (const string&, const char* what);
+
// version
//
// Sometimes we need to split the version into two parts: the part
@@ -612,48 +715,13 @@ namespace bpkg
// we do not implicitly assume a wildcard version.
//
const version_type*
- system_version () const
- {
- if (!system_version_)
- {
- if (const system_package* sp = system_repository.find (id.name))
- {
- // Only cache if it is authoritative.
- //
- if (sp->authoritative)
- system_version_ = sp->version;
- else
- return &sp->version;
- }
- }
-
- return system_version_ ? &*system_version_ : nullptr;
- }
+ system_version (database&) const;
// As above but also return an indication if the version information is
// authoritative.
//
pair<const version_type*, bool>
- system_version_authoritative () const
- {
- const system_package* sp (system_repository.find (id.name));
-
- if (!system_version_)
- {
- if (sp != nullptr)
- {
- // Only cache if it is authoritative.
- //
- if (sp->authoritative)
- system_version_ = sp->version;
- else
- return make_pair (&sp->version, false);
- }
- }
-
- return make_pair (system_version_ ? &*system_version_ : nullptr,
- sp != nullptr ? sp->authoritative : false);
- }
+ system_version_authoritative (database&) const;
// Database mapping.
//
@@ -785,9 +853,7 @@ namespace bpkg
// NULL, print the error message and fail.
//
void
- check_any_available (const dir_path& configuration,
- transaction&,
- const diag_record* = nullptr);
+ check_any_available (database&, transaction&, const diag_record* = nullptr);
// package_state
//
@@ -818,7 +884,7 @@ namespace bpkg
enum class package_substate
{
none,
- system // System package; valid states: configured.
+ system // System package; valid states: configured.
};
string
@@ -867,12 +933,49 @@ namespace bpkg
// single constraint, we don't support multiple dependencies on the same
// package (e.g., two ranges of versions). See pkg_configure().
//
+ // Note also that the pointer can point to a selected package in another
+ // database.
+ //
class selected_package;
+ // Note that the keys for this map needs to be created with the database
+ // passed to their constructor, which is required for persisting them (see
+ // _selected_package_ref() implementation for details).
+ //
using package_prerequisites = std::map<lazy_shared_ptr<selected_package>,
optional<version_constraint>,
compare_lazy_ptr>;
+ // Database mapping for lazy_shared_ptr<selected_package> to configuration
+ // UUID and package name.
+ //
+ #pragma db value
+ struct _selected_package_ref
+ {
+ using ptr = lazy_shared_ptr<selected_package>;
+
+ uuid configuration;
+ package_name prerequisite;
+
+ explicit
+ _selected_package_ref (const ptr&);
+
+ _selected_package_ref () = default;
+
+ // Note: nil configuration UUID is recognized as the same configuration to
+ // facilitate migration.
+ //
+ ptr
+ to_ptr (odb::database&) &&;
+
+ #pragma db member(configuration) default("") // Nil UUID.
+ };
+
+ #pragma db map type(_selected_package_ref::ptr) \
+ as(_selected_package_ref) \
+ to(bpkg::_selected_package_ref (?)) \
+ from(std::move (?).to_ptr (*db))
+
#pragma db object pointer(shared_ptr) session
class selected_package
{
@@ -1004,8 +1107,8 @@ namespace bpkg
//
#pragma db member(name) id
- #pragma db member(prerequisites) id_column("package") \
- key_column("prerequisite") key_not_null value_column("")
+ #pragma db member(prerequisites) id_column("package") \
+ key_column("") value_column("")
// Explicit aggregate initialization for C++20 (private default ctor).
//
@@ -1083,7 +1186,7 @@ namespace bpkg
optional<version>
package_iteration (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
const dir_path&,
const package_name&,
@@ -1179,25 +1282,22 @@ namespace bpkg
// Return a list of packages that depend on this package along with
// their constraints.
//
+ // @@ Using raw container table since ODB doesn't support containers in
+ // views yet.
+ //
/*
- #pragma db view object(selected_package) \
- container(selected_package::prerequisites = pp inner: pp.key)
+ #pragma db view container(selected_package::prerequisites = pp)
struct package_dependent
{
- #pragma db column(pp.id)
- string name;
+ #pragma db column("pp.package")
+ package_name name;
- #pragma db column(pp.value)
+ #pragma db column("pp.")
optional<version_constraint> constraint;
};
*/
- // @@ Using raw container table since ODB doesn't support containers
- // in views yet.
- //
- #pragma db view object(selected_package) \
- table("main.selected_package_prerequisites" = "pp" inner: \
- "pp.prerequisite = " + selected_package::name)
+ #pragma db view table("main.selected_package_prerequisites" = "pp")
struct package_dependent
{
#pragma db column("pp.package")
@@ -1207,6 +1307,14 @@ namespace bpkg
optional<version_constraint> constraint;
};
+ // Query dependent in the specified database of a dependency potentially
+ // residing in other database.
+ //
+ odb::result<package_dependent>
+ query_dependents (database& dependent_db,
+ const package_name& dependency,
+ database& dependency_db);
+
// Return a count of repositories that contain this repository fragment.
//
#pragma db view table("main.repository_fragments")
diff --git a/bpkg/package.xml b/bpkg/package.xml
index e54829c..fce78f0 100644
--- a/bpkg/package.xml
+++ b/bpkg/package.xml
@@ -1,4 +1,25 @@
<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="9">
+ <add-table name="main.configuration" kind="object">
+ <column name="id" type="INTEGER" null="true"/>
+ <column name="uuid" type="TEXT" null="true"/>
+ <column name="name" type="TEXT" null="true"/>
+ <column name="type" type="TEXT" null="true"/>
+ <column name="path" type="TEXT" null="true"/>
+ <column name="explicit" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="configuration_path_i" type="UNIQUE">
+ <column name="path"/>
+ </index>
+ </add-table>
+ <alter-table name="main.selected_package_prerequisites">
+ <add-column name="configuration" type="TEXT" null="true" default="''"/>
+ <drop-foreign-key name="prerequisite_fk"/>
+ </alter-table>
+ </changeset>
+
<changeset version="8">
<alter-table name="main.repository">
<add-column name="local" type="INTEGER" null="true"/>
diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli
index e5a6118..9f5827f 100644
--- a/bpkg/pkg-build.cli
+++ b/bpkg/pkg-build.cli
@@ -156,6 +156,14 @@ namespace bpkg
bpkg build libfoo/2.0.0 # upgrade libfoo 2.0.0 to hold,
# also hold version 2.0.0
\
+
+ Packages can be built in associated configurations by specifying the
+ \cb{--config-id} option (see \l{bpkg-cfg-create(1)} for details). A
+ dependency package built in an associated configuration can be referred
+ by dependents in the current configuration only if the configuration
+ types are the same. In contrast, a held package built in an associated
+ configuration can be referred in the current configuration
+ unconditionally.
"
}
@@ -243,6 +251,15 @@ namespace bpkg
are purged. Refer to the \cb{--output-purge} option in
\l{bpkg-pkg-checkout(1)} for details."
}
+
+ uint64_t --config-id
+ {
+ "<id>",
+ "Numeric identifier of the associated configuration the package should
+ be built in. By default, the configuration id is zero which denotes
+ the current configuration. In a sense, a configuration is always
+ associated with itself under the zero id."
+ }
};
class pkg_build_options: configuration_options,
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index 1f1bb0c..dcd250d 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -195,7 +195,6 @@ namespace bpkg
//
static pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>
make_available (const common_options& options,
- const dir_path& c,
database& db,
const shared_ptr<selected_package>& sp)
{
@@ -214,7 +213,7 @@ namespace bpkg
// moment).
//
shared_ptr<repository_fragment> af (
- db.find<repository_fragment> (
+ db.main_database ().find<repository_fragment> (
sp->repository_fragment.canonical_name ()));
// The package is in at least fetched state, which means we should
@@ -225,10 +224,10 @@ namespace bpkg
package_manifest m (
sp->state == package_state::fetched
? pkg_verify (options,
- a->absolute () ? *a : c / *a,
+ a->absolute () ? *a : db.config / *a,
true /* ignore_unknown */,
false /* expand_values */)
- : pkg_verify (sp->effective_src_root (c),
+ : pkg_verify (sp->effective_src_root (db.config),
true /* ignore_unknown */,
// Copy potentially fixed up version from selected package.
[&sp] (version& v) {v = sp->version;}));
@@ -249,6 +248,28 @@ namespace bpkg
return r;
}
+ // Used as a key for maps containing data for packages across multiple
+ // associated configurations. Assume that the respective databases are not
+ // detached during map lifetimes.
+ //
+ struct config_package
+ {
+ database& db;
+ package_name name;
+
+ bool
+ operator== (const config_package& v) const
+ {
+ return name == v.name && db == v.db;
+ }
+
+ bool
+ operator< (const config_package& v) const
+ {
+ return name != v.name ? (name < v.name) : (db < v.db);
+ }
+ };
+
// 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
@@ -311,6 +332,8 @@ namespace bpkg
//
optional<action_type> action;
+ reference_wrapper<database> db; // Needs to be move-assignable.
+
shared_ptr<selected_package> selected; // NULL if not selected.
shared_ptr<available_package> available; // Can be NULL, fake/transient.
@@ -373,6 +396,9 @@ namespace bpkg
// Set of package names that caused this package to be built or adjusted.
// Empty name signifies user selection.
//
+ // @@ EC Seems we also need to record which configuration the packages
+ // come from.
+ //
set<package_name> required_by;
bool
@@ -413,6 +439,16 @@ namespace bpkg
//
static const uint16_t adjust_reconfigure = 0x0002;
+ // @@
+ //
+ static const uint16_t adjust_link = 0x0004;
+
+ bool
+ link () const
+ {
+ return (adjustments & adjust_link) != 0;
+ }
+
bool
reconfigure () const
{
@@ -434,17 +470,19 @@ namespace bpkg
//
assert (available != nullptr &&
(system
- ? available->system_version () != nullptr
+ ? available->system_version (db) != nullptr
: !available->stub ()));
- return system ? *available->system_version () : available->version;
+ return system ? *available->system_version (db) : available->version;
}
string
available_name_version () const
{
assert (available != nullptr);
- return package_string (available->id.name, available_version (), system);
+ return package_string (available->id.name,
+ available_version (),
+ system);
}
// Merge constraints, required-by package names, hold_* flags,
@@ -453,9 +491,16 @@ namespace bpkg
void
merge (build_package&& p)
{
- // We don't merge into pre-entered objects, and from/into drops.
+ // We don't merge objects from different configurations.
+ //
+ assert (db == p.db);
+
+ // We don't merge into pre-entered objects, and from/into links and
+ // drops.
//
- assert (action && *action != drop && (!p.action || *p.action != drop));
+ assert (action &&
+ *action != drop &&
+ ((!p.action && !p.link ()) || *p.action != drop));
// Copy the user-specified options/variables.
//
@@ -545,18 +590,46 @@ namespace bpkg
{
assert (!pkg.action);
- auto p (map_.emplace (move (name), data_type {end (), move (pkg)}));
+ database& db (pkg.db); // Save before move() call.
+ auto p (map_.emplace (config_package {db, move (name)},
+ data_type {end (), move (pkg)}));
+
assert (p.second);
}
+ void
+ collect_link (package_name name, database& target, database& link)
+ {
+ build_package bp {
+ nullopt, // Action.
+ target,
+ nullptr, // Selected package.
+ nullptr, // Available package/repository frag.
+ nullptr,
+ false, // Hold package.
+ false, // Hold version.
+ {}, // Constraints.
+ false, // System.
+ false, // Keep output directory.
+ nullopt, // Checkout root.
+ false, // Checkout purge.
+ strings (), // Configuration variables.
+ {}, // Required by.
+ build_package::adjust_link};
+
+ // If there is already a build object in the link configuration, then
+ // leave it there.
+ //
+ map_.emplace (config_package {link, move (name)},
+ data_type {end (), move (bp)});
+ }
+
// Collect the package being built. Return its pointer if this package
// version was, in fact, added to the map and NULL if it was already there
// or the existing version was preferred. So can be used as bool.
//
build_package*
collect_build (const common_options& options,
- const dir_path& cd,
- database& db,
build_package pkg,
postponed_packages* recursively = nullptr)
{
@@ -569,7 +642,7 @@ namespace bpkg
assert (pkg.action && *pkg.action == build_package::build &&
pkg.available != nullptr);
- auto i (map_.find (pkg.available->id.name));
+ auto i (map_.find (pkg.db, pkg.available->id.name, false /* resolve */));
// If we already have an entry for this package name, then we
// have to pick one over the other.
@@ -589,7 +662,9 @@ namespace bpkg
if (!bp.action || *bp.action != build_package::build) // Non-build.
{
- pkg.merge (move (bp));
+ if (!bp.link ())
+ pkg.merge (move (bp));
+
bp = move (pkg);
}
else // Build.
@@ -636,7 +711,7 @@ namespace bpkg
//
if (auto c1 = test (p2, p1))
{
- const package_name& n (i->first);
+ const package_name& n (i->first.name);
const string& d1 (c1->dependent);
const string& d2 (c2->dependent);
@@ -694,8 +769,10 @@ namespace bpkg
// Note: copy; see emplace() below.
//
+ database& db (pkg.db); // Save before move() call.
package_name n (pkg.available->id.name);
- i = map_.emplace (move (n), data_type {end (), move (pkg)}).first;
+ i = map_.emplace (config_package {db, move (n)},
+ data_type {end (), move (pkg)}).first;
}
build_package& p (i->second.package);
@@ -721,7 +798,7 @@ namespace bpkg
// reasoning wrong.
//
if (recursively != nullptr)
- collect_build_prerequisites (options, cd, db, p, recursively);
+ collect_build_prerequisites (options, p, recursively);
return &p;
}
@@ -737,8 +814,6 @@ namespace bpkg
//
void
collect_build_prerequisites (const common_options& options,
- const dir_path& cd,
- database& db,
const build_package& pkg,
postponed_packages* postponed)
{
@@ -768,6 +843,9 @@ namespace bpkg
const shared_ptr<repository_fragment>& af (pkg.repository_fragment);
const package_name& name (ap->id.name);
+ database& pdb (pkg.db);
+ database& mdb (pdb.main_database ());
+
for (const dependency_alternatives_ex& da: ap->dependencies)
{
if (da.conditional) // @@ TODO
@@ -823,7 +901,7 @@ namespace bpkg
//
const version_constraint* dep_constr (nullptr);
- auto i (map_.find (dn));
+ auto i (map_.find (pdb, dn, true /* resolve */));
if (i != map_.end ())
{
const build_package& bp (i->second.package);
@@ -858,6 +936,10 @@ namespace bpkg
}
}
+ // @@ EC
+ //
+ database& ddb (i != map_.end () ? i->first.db : pdb);
+
const dependency& d (!dep_constr
? dp
: dependency {dn, *dep_constr});
@@ -867,7 +949,7 @@ namespace bpkg
// constraint, then we don't want to be forcing its upgrade (or,
// worse, downgrade).
//
- shared_ptr<selected_package> dsp (db.find<selected_package> (dn));
+ shared_ptr<selected_package> dsp (ddb.find<selected_package> (dn));
pair<shared_ptr<available_package>,
shared_ptr<repository_fragment>> rp;
@@ -897,11 +979,11 @@ namespace bpkg
// doesn't really matter).
//
shared_ptr<repository_fragment> root (
- db.load<repository_fragment> (""));
+ mdb.load<repository_fragment> (""));
rp = system
- ? find_available_one (db, dn, nullopt, root)
- : find_available_one (db,
+ ? find_available_one (mdb, dn, nullopt, root)
+ : find_available_one (mdb,
dn,
version_constraint (dsp->version),
root);
@@ -910,7 +992,7 @@ namespace bpkg
// (returning stub as an available package feels wrong).
//
if (dap == nullptr || dap->stub ())
- rp = make_available (options, cd, db, dsp);
+ rp = make_available (options, ddb, dsp);
}
else
// Remember that we may be forcing up/downgrade; we will deal with
@@ -976,7 +1058,7 @@ namespace bpkg
// the package is recognized. An unrecognized package means the
// broken/stale repository (see below).
//
- rp = find_available_one (db,
+ rp = find_available_one (mdb,
dn,
!system ? d.constraint : nullopt,
af);
@@ -1024,17 +1106,17 @@ namespace bpkg
// version constraint). If it were, then the system version
// wouldn't be NULL and would satisfy itself.
//
- if (dap->system_version () == nullptr)
+ if (dap->system_version (ddb) == nullptr)
fail << "dependency " << d << " of package " << name << " is "
<< "not available in source" <<
info << "specify ?sys:" << dn << " if it is available from "
<< "the system";
- if (!satisfies (*dap->system_version (), d.constraint))
+ if (!satisfies (*dap->system_version (ddb), d.constraint))
fail << "dependency " << d << " of package " << name << " is "
<< "not available in source" <<
info << package_string (dn,
- *dap->system_version (),
+ *dap->system_version (ddb),
true /* system */)
<< " does not satisfy the constrains";
@@ -1042,7 +1124,7 @@ namespace bpkg
}
else
{
- auto p (dap->system_version_authoritative ());
+ auto p (dap->system_version_authoritative (ddb));
if (p.first != nullptr &&
p.second && // Authoritative.
@@ -1053,6 +1135,7 @@ namespace bpkg
build_package bp {
build_package::build,
+ ddb,
dsp,
dap,
rp.second,
@@ -1096,7 +1179,7 @@ namespace bpkg
// build foo ?sys:bar/2
//
const build_package* p (
- collect_build (options, cd, db, move (bp), postponed));
+ collect_build (options, move (bp), postponed));
if (p != nullptr && force && !dep_optional)
{
@@ -1144,12 +1227,13 @@ namespace bpkg
// Collect the package being dropped.
//
void
- collect_drop (shared_ptr<selected_package> sp)
+ collect_drop (database& db, shared_ptr<selected_package> sp)
{
const package_name& nm (sp->name);
build_package p {
build_package::drop,
+ db,
move (sp),
nullptr,
nullptr,
@@ -1164,12 +1248,14 @@ namespace bpkg
{}, // Required by.
0}; // Adjustments.
- auto i (map_.find (nm));
+ auto i (map_.find (db, nm, false /* resolve */));
if (i != map_.end ())
{
build_package& bp (i->second.package);
+ assert (!bp.link ());
+
// Can't think of the scenario when this happens. We would start
// collecting from scratch (see below).
//
@@ -1180,15 +1266,16 @@ namespace bpkg
bp = move (p);
}
else
- map_.emplace (nm, data_type {end (), move (p)});
+ map_.emplace (config_package {db, nm},
+ data_type {end (), move (p)});
}
// Collect the package being unheld.
//
void
- collect_unhold (const shared_ptr<selected_package>& sp)
+ collect_unhold (database& db, const shared_ptr<selected_package>& sp)
{
- auto i (map_.find (sp->name));
+ auto i (map_.find (db, sp->name, false /* resolve */));
// Currently, it must always be pre-entered.
//
@@ -1196,10 +1283,13 @@ namespace bpkg
build_package& bp (i->second.package);
+ assert (!bp.link ());
+
if (!bp.action) // Pre-entered.
{
build_package p {
build_package::adjust,
+ db,
sp,
nullptr,
nullptr,
@@ -1223,20 +1313,17 @@ namespace bpkg
void
collect_build_prerequisites (const common_options& o,
- const dir_path& cd,
database& db,
const package_name& name,
postponed_packages& postponed)
{
- auto mi (map_.find (name));
+ auto mi (map_.find (db, name, true /* resolve */));
assert (mi != map_.end ());
- collect_build_prerequisites (o, cd, db, mi->second.package, &postponed);
+ collect_build_prerequisites (o, mi->second.package, &postponed);
}
void
collect_build_postponed (const common_options& o,
- const dir_path& cd,
- database& db,
postponed_packages& pkgs)
{
// Try collecting postponed packages for as long as we are making
@@ -1247,7 +1334,7 @@ namespace bpkg
postponed_packages npkgs;
for (const build_package* p: pkgs)
- collect_build_prerequisites (o, cd, db, *p, prog ? &npkgs : nullptr);
+ collect_build_prerequisites (o, *p, prog ? &npkgs : nullptr);
assert (prog); // collect_build_prerequisites() should have failed.
prog = (npkgs != pkgs);
@@ -1262,10 +1349,10 @@ namespace bpkg
// possible.
//
iterator
- order (const package_name& name, bool reorder = true)
+ order (database& db, const package_name& name, bool reorder = true)
{
package_names chain;
- return order (name, chain, reorder);
+ return order (db, name, chain, reorder);
}
// If a configured package is being up/down-graded then that means
@@ -1284,7 +1371,7 @@ namespace bpkg
// to also notice this.
//
void
- collect_order_dependents (database& db)
+ collect_order_dependents ()
{
// For each package on the list we want to insert all its dependents
// before it so that they get configured after the package on which
@@ -1305,18 +1392,20 @@ namespace bpkg
// Dropped package may have no dependents.
//
if (*p.action != build_package::drop && p.reconfigure ())
- collect_order_dependents (db, i);
+ collect_order_dependents (i);
}
}
void
- collect_order_dependents (database& db, iterator pos)
+ collect_order_dependents (iterator pos)
{
tracer trace ("collect_order_dependents");
assert (pos != end ());
build_package& p (*pos);
+
+ database& pdb (p.db);
const shared_ptr<selected_package>& sp (p.selected);
const package_name& n (sp->name);
@@ -1328,166 +1417,179 @@ namespace bpkg
? sp->version.compare (p.available_version ())
: 0);
- using query = query<package_dependent>;
-
- for (auto& pd: db.query<package_dependent> (query::name == n))
+ // Query dependents in all implicitly associated databases, including
+ // the current database.
+ //
+ for (associated_config& ac: pdb.implicit_associations ())
{
- package_name& dn (pd.name);
- auto i (map_.find (dn));
-
- // First make sure the up/downgraded package still satisfies this
- // dependent.
- //
- bool check (ud != 0 && pd.constraint);
+ database& ddb (ac.db);
- // There is one tricky aspect: the dependent could be in the process
- // of being up/downgraded as well. In this case all we need to do is
- // detect this situation and skip the test since all the (new)
- // contraints of this package have been satisfied in collect_build().
- //
- if (check && i != map_.end () && i->second.position != end ())
+ for (auto& pd: query_dependents (ddb, n, pdb))
{
- build_package& dp (i->second.package);
+ package_name& dn (pd.name);
+ auto i (map_.find (ddb, dn, false /* resolve */));
- check = dp.available == nullptr ||
- (dp.selected->system () == dp.system &&
- dp.selected->version == dp.available_version ());
- }
+ if (i != map_.end ())
+ assert (!i->second.package.link ());
- if (check)
- {
- const version& av (p.available_version ());
- const version_constraint& c (*pd.constraint);
+ // First make sure the up/downgraded package still satisfies this
+ // dependent.
+ //
+ bool check (ud != 0 && pd.constraint);
- if (!satisfies (av, c))
+ // There is one tricky aspect: the dependent could be in the process
+ // of being up/downgraded as well. In this case all we need to do is
+ // detect this situation and skip the test since all the (new)
+ // contraints of this package have been satisfied in
+ // collect_build().
+ //
+ if (check && i != map_.end () && i->second.position != end ())
{
- diag_record dr (fail);
-
- dr << "unable to " << (ud < 0 ? "up" : "down") << "grade "
- << "package " << *sp << " to ";
+ build_package& dp (i->second.package);
- // Print both (old and new) package names in full if the system
- // attribution changes.
- //
- if (p.system != sp->system ())
- dr << p.available_name_version ();
- else
- dr << av; // Can't be the wildcard otherwise would satisfy.
+ check = dp.available == nullptr ||
+ (dp.selected->system () == dp.system &&
+ dp.selected->version == dp.available_version ());
+ }
- dr << info << "because package " << dn << " depends on (" << n
- << " " << c << ")";
+ if (check)
+ {
+ const version& av (p.available_version ());
+ const version_constraint& c (*pd.constraint);
- string rb;
- if (!p.user_selection ())
+ if (!satisfies (av, c))
{
- for (const package_name& n: p.required_by)
- rb += ' ' + n.string ();
- }
+ diag_record dr (fail);
- if (!rb.empty ())
- dr << info << "package " << p.available_name_version ()
- << " required by" << rb;
+ dr << "unable to " << (ud < 0 ? "up" : "down") << "grade "
+ << "package " << *sp << " to ";
- dr << info << "explicitly request up/downgrade of package " << dn;
+ // Print both (old and new) package names in full if the system
+ // attribution changes.
+ //
+ if (p.system != sp->system ())
+ dr << p.available_name_version ();
+ else
+ dr << av; // Can't be the wildcard otherwise would satisfy.
- dr << info << "or explicitly specify package " << n << " version "
- << "to manually satisfy these constraints";
- }
+ dr << info << "because package " << dn << " depends on (" << n
+ << " " << c << ")";
- // Add this contraint to the list for completeness.
- //
- p.constraints.emplace_back (dn.string (), c);
- }
+ string rb;
+ if (!p.user_selection ())
+ {
+ for (const package_name& n: p.required_by)
+ rb += ' ' + n.string ();
+ }
- auto adjustment = [&dn, &n, &db] () -> build_package
- {
- shared_ptr<selected_package> dsp (db.load<selected_package> (dn));
- bool system (dsp->system ()); // Save flag before the move(dsp) call.
-
- return build_package {
- build_package::adjust,
- move (dsp),
- nullptr, // No available package/repository fragment.
- nullptr,
- nullopt, // Hold package.
- nullopt, // Hold version.
- {}, // Constraints.
- system,
- false, // Keep output directory.
- nullopt, // Checkout root.
- false, // Checkout purge.
- strings (), // Configuration variables.
- {n}, // Required by (dependency).
- build_package::adjust_reconfigure};
- };
+ if (!rb.empty ())
+ dr << info << "package " << p.available_name_version ()
+ << " required by" << rb;
- // We can have three cases here: the package is already on the
- // list, the package is in the map (but not on the list) and it
- // is in neither.
- //
- // If the existing entry is a drop, then we skip it. If it is
- // pre-entered, is an adjustment, or is a build that is not supposed
- // to be built (not in the list), then we merge it into the new
- // adjustment entry. Otherwise (is a build in the list), we just add
- // the reconfigure adjustment flag to it.
- //
- if (i != map_.end ())
- {
- build_package& dp (i->second.package);
- iterator& dpos (i->second.position);
+ dr << info << "explicitly request up/downgrade of package "
+ << dn;
- if (!dp.action || // Pre-entered.
- *dp.action != build_package::build || // Non-build.
- dpos == end ()) // Build not in the list.
- {
- // Skip the droped package.
- //
- if (dp.action && *dp.action == build_package::drop)
- continue;
+ dr << info << "or explicitly specify package " << n
+ << " version to manually satisfy these constraints";
+ }
- build_package bp (adjustment ());
- bp.merge (move (dp));
- dp = move (bp);
+ // Add this contraint to the list for completeness.
+ //
+ p.constraints.emplace_back (dn.string (), c);
}
- else // Build in the list.
- dp.adjustments |= build_package::adjust_reconfigure;
-
- // It may happen that the dependent is already in the list but is
- // not properly ordered against its dependencies that get into the
- // list via another dependency path. Thus, we check if the dependent
- // is to the right of its dependency and, if that's the case,
- // reinsert it in front of the dependency.
+
+ auto adjustment = [&dn, &n, &ddb] () -> build_package
+ {
+ shared_ptr<selected_package> dsp (ddb.load<selected_package> (dn));
+
+ bool system (dsp->system ()); // Save before the move(dsp) call.
+
+ return build_package {
+ build_package::adjust,
+ ddb,
+ move (dsp),
+ nullptr, // No available package/repository fragment.
+ nullptr,
+ nullopt, // Hold package.
+ nullopt, // Hold version.
+ {}, // Constraints.
+ system,
+ false, // Keep output directory.
+ nullopt, // Checkout root.
+ false, // Checkout purge.
+ strings (), // Configuration variables.
+ {n}, // Required by (dependency).
+ build_package::adjust_reconfigure};
+ };
+
+ // We can have three cases here: the package is already on the
+ // list, the package is in the map (but not on the list) and it
+ // is in neither.
+ //
+ // If the existing entry is a drop, then we skip it. If it is
+ // pre-entered, is an adjustment, or is a build that is not supposed
+ // to be built (not in the list), then we merge it into the new
+ // adjustment entry. Otherwise (is a build in the list), we just add
+ // the reconfigure adjustment flag to it.
//
- if (dpos != end ())
+ if (i != map_.end ())
{
- for (auto i (pos); i != end (); ++i)
+ build_package& dp (i->second.package);
+ iterator& dpos (i->second.position);
+
+ if (!dp.action || // Pre-entered.
+ *dp.action != build_package::build || // Non-build.
+ dpos == end ()) // Build not in the list.
{
- if (i == dpos)
+ // Skip the droped package.
+ //
+ if (dp.action && *dp.action == build_package::drop)
+ continue;
+
+ build_package bp (adjustment ());
+ bp.merge (move (dp));
+ dp = move (bp);
+ }
+ else // Build in the list.
+ dp.adjustments |= build_package::adjust_reconfigure;
+
+ // It may happen that the dependent is already in the list but is
+ // not properly ordered against its dependencies that get into the
+ // list via another dependency path. Thus, we check if the
+ // dependent is to the right of its dependency and, if that's the
+ // case, reinsert it in front of the dependency.
+ //
+ if (dpos != end ())
+ {
+ for (auto i (pos); i != end (); ++i)
{
- erase (dpos);
- dpos = insert (pos, dp);
- break;
+ if (i == dpos)
+ {
+ erase (dpos);
+ dpos = insert (pos, dp);
+ break;
+ }
}
}
+ else
+ dpos = insert (pos, dp);
}
else
- dpos = insert (pos, dp);
- }
- else
- {
- i = map_.emplace (
- move (dn), data_type {end (), adjustment ()}).first;
+ {
+ i = map_.emplace (config_package {ddb, move (dn)},
+ data_type {end (), adjustment ()}).first;
- i->second.position = insert (pos, i->second.package);
- }
+ i->second.position = insert (pos, i->second.package);
+ }
- // Recursively collect our own dependents inserting them before us.
- //
- // Note that we cannot end up with an infinite recursion for
- // configured packages due to a dependency cycle (see order() for
- // details).
- //
- collect_order_dependents (db, i->second.position);
+ // Recursively collect our own dependents inserting them before us.
+ //
+ // Note that we cannot end up with an infinite recursion for
+ // configured packages due to a dependency cycle (see order() for
+ // details).
+ //
+ collect_order_dependents (i->second.position);
+ }
}
}
@@ -1508,34 +1610,50 @@ namespace bpkg
}
private:
- using package_names = small_vector<reference_wrapper<const package_name>,
- 16>;
+ struct config_package_name
+ {
+ database& db;
+ reference_wrapper<const package_name> name;
+
+ bool
+ operator== (const config_package_name& v)
+ {
+ return name.get () == v.name.get () && db == v.db;
+ }
+ };
+ using package_names = small_vector<config_package_name, 16>;
iterator
- order (const package_name& name, package_names& chain, bool reorder)
+ order (database& db,
+ const package_name& name,
+ package_names& chain,
+ bool reorder)
{
// Every package that we order should have already been collected.
//
- auto mi (map_.find (name));
+ auto mi (map_.find (db, name, true /* resolve */));
assert (mi != map_.end ());
build_package& p (mi->second.package);
assert (p.action); // Can't order just a pre-entered package.
+ database& pdb (p.db);
+
// Make sure there is no dependency cycle.
//
+ config_package_name cp {pdb, name};
{
- auto i (find (chain.begin (), chain.end (), name));
+ auto i (find (chain.begin (), chain.end (), cp));
if (i != chain.end ())
{
diag_record dr (fail);
dr << "dependency cycle detected involving package " << name;
- auto nv = [this] (const package_name& name)
+ auto nv = [this] (const config_package_name& cp)
{
- auto mi (map_.find (name));
+ auto mi (map_.find (cp.db, cp.name, false /* resolve */));
assert (mi != map_.end ());
build_package& p (mi->second.package);
@@ -1551,7 +1669,7 @@ namespace bpkg
return p.available_name_version ();
};
- for (chain.push_back (name); i != chain.end () - 1; ++i)
+ for (chain.push_back (cp); i != chain.end () - 1; ++i)
dr << info << nv (*i) << " depends on " << nv (*(i + 1));
}
}
@@ -1573,7 +1691,7 @@ namespace bpkg
// position of its "earliest" prerequisite -- this is where it
// will be inserted.
//
- const shared_ptr<selected_package>& sp (p.selected);
+ const shared_ptr<selected_package>& sp (p.selected);
const shared_ptr<available_package>& ap (p.available);
bool build (*p.action == build_package::build);
@@ -1620,7 +1738,7 @@ namespace bpkg
bool order_disfigured (src_conf && disfigure (p));
- chain.push_back (name);
+ chain.push_back (cp);
// Order the build dependencies.
//
@@ -1635,13 +1753,14 @@ namespace bpkg
{
for (const auto& p: sp->prerequisites)
{
+ database& db (static_cast<database&> (p.first.database ()));
const package_name& name (p.first.object_id ());
// The prerequisites may not necessarily be in the map.
//
- auto i (map_.find (name));
+ auto i (map_.find (db, name, false /* resolve */));
if (i != map_.end () && i->second.package.action)
- update (order (name, chain, false /* reorder */));
+ update (order (db, name, chain, false /* reorder */));
}
// We just ordered them among other prerequisites.
@@ -1667,7 +1786,7 @@ namespace bpkg
if (da.buildtime && (dn == "build2" || dn == "bpkg"))
continue;
- update (order (d.name, chain, false /* reorder */));
+ update (order (db, d.name, chain, false /* reorder */));
}
}
}
@@ -1678,14 +1797,15 @@ namespace bpkg
{
for (const auto& p: sp->prerequisites)
{
+ database& db (static_cast<database&> (p.first.database ()));
const package_name& name (p.first.object_id ());
// The prerequisites may not necessarily be in the map.
//
- auto i (map_.find (name));
+ auto i (map_.find (db, name, false /* reorder */));
if (i != map_.end () && disfigure (i->second.package))
- update (order (name, chain, false /* reorder */));
+ update (order (db, name, chain, false /* reorder */));
}
}
@@ -1701,7 +1821,30 @@ namespace bpkg
build_package package;
};
- map<package_name, data_type> map_;
+ class config_package_map: public map<config_package, data_type>
+ {
+ public:
+ using base_type = map<config_package, data_type>;
+
+ iterator
+ find (database& db, const package_name& pn, bool resolve)
+ {
+ iterator r (base_type::find (config_package {db, pn}));
+
+ if (r != end () && r->second.package.link () && resolve)
+ {
+ r = base_type::find (config_package {r->second.package.db, pn});
+
+ // Should always resolve but never to a link (for now at least).
+ //
+ assert (r != end () && !r->second.package.link ());
+ }
+
+ return r;
+ }
+ };
+
+ config_package_map map_;
};
// Return a patch version constraint for the selected package if it has a
@@ -1755,15 +1898,16 @@ namespace bpkg
//
struct dependency_package
{
- package_name name;
- optional<version_constraint> constraint; // nullopt if unspecified.
- shared_ptr<selected_package> selected; // NULL if not present.
- bool system;
- bool patch; // Only for an empty version.
- bool keep_out;
- optional<dir_path> checkout_root;
- bool checkout_purge;
- strings config_vars; // Only if not system.
+ database& db;
+ package_name name;
+ optional<version_constraint> constraint; // nullopt if unspecified.
+ shared_ptr<selected_package> selected; // NULL if not present.
+ bool system;
+ bool patch; // Only for an empty version.
+ bool keep_out;
+ optional<dir_path> checkout_root;
+ bool checkout_purge;
+ strings config_vars; // Only if not system.
};
using dependency_packages = vector<dependency_package>;
@@ -1784,10 +1928,10 @@ namespace bpkg
//
struct evaluate_result
{
- shared_ptr<available_package> available;
+ shared_ptr<available_package> available;
shared_ptr<bpkg::repository_fragment> repository_fragment;
- bool unused;
- bool system; // Is meaningless if unused.
+ bool unused;
+ bool system; // Is meaningless if unused.
};
using package_dependents = vector<pair<shared_ptr<selected_package>,
@@ -1806,8 +1950,8 @@ namespace bpkg
static optional<evaluate_result>
evaluate_dependency (database& db,
- const dependency_packages& deps,
const shared_ptr<selected_package>& sp,
+ const dependency_packages& deps,
bool ignore_unsatisfiable)
{
tracer trace ("evaluate_dependency");
@@ -1816,10 +1960,24 @@ namespace bpkg
const package_name& nm (sp->name);
- // Query the dependents and bail out if the dependency is unused.
+ // Query the dependents in all implicitly associated databases, including
+ // the current database, and bail out if the dependency is unused.
+ //
+ // @@ EC NOTE: this code is executed even for the bdep sync "noop case" if
+ // the configuration contains any dependencies. Such a sync operation
+ // may drop some unused dependencies.
//
- auto pds (db.query<package_dependent> (
- query<package_dependent>::name == nm));
+ // We can potentially fix that returning nullopt early if deps is empty
+ // and --keep-unused is specified, and change bdep to also pass
+ // --keep-unused.
+ //
+ vector<pair<database&, package_dependent>> pds;
+
+ for (associated_config& ac: db.implicit_associations ())
+ {
+ for (auto& pd: query_dependents (ac.db, nm, db))
+ pds.emplace_back (ac.db, move (pd));
+ }
if (pds.empty ())
{
@@ -1836,7 +1994,10 @@ namespace bpkg
//
auto i (find_if (
deps.begin (), deps.end (),
- [&nm] (const dependency_package& i) {return i.name == nm;}));
+ [&nm, &db] (const dependency_package& i)
+ {
+ return i.name == nm && i.db == db;
+ }));
if (i == deps.end ())
return nullopt;
@@ -1871,12 +2032,18 @@ namespace bpkg
set<shared_ptr<repository_fragment>> repo_frags;
package_dependents dependents;
+ database& mdb (db.main_database ());
+
for (auto& pd: pds)
{
- shared_ptr<selected_package> dsp (db.load<selected_package> (pd.name));
+ database& ddb (pd.first);
+ package_dependent& dep (pd.second);
+
+ shared_ptr<selected_package> dsp (
+ ddb.load<selected_package> (dep.name));
shared_ptr<available_package> dap (
- db.find<available_package> (
+ mdb.find<available_package> (
available_package_id (dsp->name, dsp->version)));
if (dap != nullptr)
@@ -1887,7 +2054,7 @@ namespace bpkg
repo_frags.insert (pl.repository_fragment.load ());
}
- dependents.emplace_back (move (dsp), move (pd.constraint));
+ dependents.emplace_back (move (dsp), move (dep.constraint));
}
return evaluate_dependency (db,
@@ -1953,7 +2120,7 @@ namespace bpkg
vector<pair<shared_ptr<available_package>,
shared_ptr<repository_fragment>>> afs (
- find_available (db,
+ find_available (db.main_database (),
nm,
c,
vector<shared_ptr<repository_fragment>> (rfs.begin (),
@@ -1981,12 +2148,12 @@ namespace bpkg
bool stub (false);
bool ssys (sp->system ());
- assert (!dsys || system_repository.find (nm) != nullptr);
+ assert (!dsys || db.system_repository.find (nm) != nullptr);
for (auto& af: afs)
{
shared_ptr<available_package>& ap (af.first);
- const version& av (!dsys ? ap->version : *ap->system_version ());
+ const version& av (!dsys ? ap->version : *ap->system_version (db));
// If we aim to upgrade to the latest version and it tends to be less
// then the selected one, then what we currently have is the best that
@@ -2174,6 +2341,7 @@ namespace bpkg
//
struct recursive_package
{
+ database& db;
package_name name;
bool upgrade; // true -- upgrade, false -- patch.
bool recursive; // true -- recursive, false -- immediate.
@@ -2187,18 +2355,18 @@ namespace bpkg
static optional<bool>
upgrade_dependencies (database& db,
const package_name& nm,
- const recursive_packages& recs,
+ const recursive_packages& rs,
bool recursion = false)
{
- auto i (find_if (recs.begin (), recs.end (),
- [&nm] (const recursive_package& i) -> bool
+ auto i (find_if (rs.begin (), rs.end (),
+ [&nm, &db] (const recursive_package& i) -> bool
{
- return i.name == nm;
+ return i.name == nm && i.db == db;
}));
optional<bool> r;
- if (i != recs.end () && i->recursive >= recursion)
+ if (i != rs.end () && i->recursive >= recursion)
{
r = i->upgrade;
@@ -2206,20 +2374,28 @@ namespace bpkg
return r;
}
- for (const auto& pd: db.query<package_dependent> (
- query<package_dependent>::name == nm))
+ // Query dependents in all implicitly associated databases, including
+ // the current database.
+ //
+ for (associated_config& ac: db.implicit_associations ())
{
- // Note that we cannot end up with an infinite recursion for configured
- // packages due to a dependency cycle (see order() for details).
- //
- if (optional<bool> u = upgrade_dependencies (db, pd.name, recs, true))
+ database& ddb (ac.db);
+
+ for (auto& pd: query_dependents (ddb, nm, db))
{
- if (!r || *r < *u) // Upgrade wins patch.
+ // Note that we cannot end up with an infinite recursion for
+ // configured packages due to a dependency cycle (see order() for
+ // details).
+ //
+ if (optional<bool> u = upgrade_dependencies (ddb, pd.name, rs, true))
{
- r = u;
+ if (!r || *r < *u) // Upgrade wins patch.
+ {
+ r = u;
- if (*r) // Upgrade (vs patch)?
- return r;
+ if (*r) // Upgrade (vs patch)?
+ return r;
+ }
}
}
}
@@ -2240,8 +2416,8 @@ namespace bpkg
//
static optional<evaluate_result>
evaluate_recursive (database& db,
- const recursive_packages& recs,
const shared_ptr<selected_package>& sp,
+ const recursive_packages& recs,
bool ignore_unsatisfiable)
{
tracer trace ("evaluate_recursive");
@@ -2255,42 +2431,49 @@ namespace bpkg
set<shared_ptr<repository_fragment>> repo_frags;
package_dependents dependents;
- auto pds (db.query<package_dependent> (
- query<package_dependent>::name == sp->name));
-
// Only collect repository fragments (for best version selection) of
// (immediate) dependents that have a hit (direct or indirect) in recs.
// Note, however, that we collect constraints from all the dependents.
//
optional<bool> upgrade;
- for (const auto& pd: pds)
+ // Query dependents in all implicitly associated databases, including the
+ // current database.
+ //
+ for (associated_config& ac: db.implicit_associations ())
{
- shared_ptr<selected_package> dsp (db.load<selected_package> (pd.name));
- dependents.emplace_back (dsp, move (pd.constraint));
+ database& ddb (ac.db);
- if (optional<bool> u = upgrade_dependencies (db, pd.name, recs))
+ for (auto& pd: query_dependents (ddb, sp->name, db))
{
- if (!upgrade || *upgrade < *u) // Upgrade wins patch.
- upgrade = u;
- }
- else
- continue;
+ shared_ptr<selected_package> dsp (
+ ddb.load<selected_package> (pd.name));
- // While we already know that the dependency upgrade is required, we
- // continue to iterate over dependents, collecting the repository
- // fragments and the constraints.
- //
- shared_ptr<available_package> dap (
- db.find<available_package> (
- available_package_id (dsp->name, dsp->version)));
+ dependents.emplace_back (dsp, move (pd.constraint));
- if (dap != nullptr)
- {
- assert (!dap->locations.empty ());
+ if (optional<bool> u = upgrade_dependencies (ddb, pd.name, recs))
+ {
+ if (!upgrade || *upgrade < *u) // Upgrade wins patch.
+ upgrade = u;
+ }
+ else
+ continue;
- for (const auto& pl: dap->locations)
- repo_frags.insert (pl.repository_fragment.load ());
+ // While we already know that the dependency upgrade is required, we
+ // continue to iterate over dependents, collecting the repository
+ // fragments and the constraints.
+ //
+ shared_ptr<available_package> dap (
+ db.find<available_package> (
+ available_package_id (dsp->name, dsp->version)));
+
+ if (dap != nullptr)
+ {
+ assert (!dap->locations.empty ());
+
+ for (const auto& pl: dap->locations)
+ repo_frags.insert (pl.repository_fragment.load ());
+ }
}
}
@@ -2320,11 +2503,7 @@ namespace bpkg
}
static void
- execute_plan (const pkg_build_options&,
- const dir_path&,
- database&,
- build_package_list&,
- bool simulate);
+ execute_plan (const pkg_build_options&, build_package_list&, bool simulate);
using pkg_options = pkg_build_pkg_options;
@@ -2396,6 +2575,12 @@ namespace bpkg
}
dst.checkout_purge (src.checkout_purge () || dst.checkout_purge ());
+
+ if (!dst.config_id_specified () && src.config_id_specified ())
+ {
+ dst.config_id (src.config_id ());
+ dst.config_id_specified (true);
+ }
}
static bool
@@ -2412,7 +2597,8 @@ namespace bpkg
x.patch_immediate () == y.patch_immediate () &&
x.patch_recursive () == y.patch_recursive () &&
x.checkout_root () == y.checkout_root () &&
- x.checkout_purge () == y.checkout_purge ();
+ x.checkout_purge () == y.checkout_purge () &&
+ x.config_id () == y.config_id ();
}
int
@@ -2434,7 +2620,9 @@ namespace bpkg
fail << "package name argument expected" <<
info << "run 'bpkg help pkg-build' for more information";
- database db (open (c, trace)); // Also populates the system repository.
+ // Also populates the system repository.
+ //
+ database mdb (c, trace, true /* pre_attach */);
// Note that the session spans all our transactions. The idea here is that
// selected_package objects in build_packages below will be cached in this
@@ -2460,6 +2648,7 @@ namespace bpkg
repository_location location;
pkg_options options;
strings config_vars;
+ database* db; // Is a pointer since we build these objects incrementally.
};
vector<pkg_spec> specs;
@@ -2509,7 +2698,7 @@ namespace bpkg
vector<repository_location> locations;
- transaction t (db);
+ transaction t (mdb);
while (args.more ())
{
@@ -2559,6 +2748,8 @@ namespace bpkg
fail << e << " grouped for argument '" << a << "'";
}
+ ps.db = &mdb.find_attached (ps.options.config_id ());
+
if (!a.empty () && a[0] == '?')
{
ps.options.dependency (true);
@@ -2643,7 +2834,7 @@ namespace bpkg
( query::local && u + " COLLATE nocase = " + query::_val (l)));
#endif
- auto rs (db.query<repository> (q));
+ auto rs (mdb.query<repository> (q));
auto i (rs.begin ());
if (i != rs.end ())
@@ -2686,10 +2877,11 @@ namespace bpkg
t.commit ();
+ // Fetch the repositories in the package-specific configurations.
+ //
if (!locations.empty ())
rep_fetch (o,
- c,
- db,
+ mdb,
locations,
o.fetch_shallow (),
string () /* reason for "fetching ..." */);
@@ -2712,6 +2904,7 @@ namespace bpkg
string value;
pkg_options options;
strings config_vars;
+ reference_wrapper<database> db; // Needs to be move-assignable.
};
// Create the parsed package argument.
@@ -2720,11 +2913,13 @@ namespace bpkg
package_name nm,
optional<version_constraint> vc,
pkg_options os,
- strings vs) -> pkg_arg
+ strings vs,
+ database& db) -> pkg_arg
{
assert (!vc || !vc->empty ()); // May not be empty if present.
- pkg_arg r {sc, move (nm), move (vc), string (), move (os), move (vs)};
+ pkg_arg r {
+ sc, move (nm), move (vc), string (), move (os), move (vs), db};
switch (sc)
{
@@ -2738,14 +2933,14 @@ namespace bpkg
//
assert (r.constraint->min_version == r.constraint->max_version);
- const system_package* sp (system_repository.find (r.name));
+ const system_package* sp (db.system_repository.find (r.name));
// Will deal with all the duplicates later.
//
if (sp == nullptr || !sp->authoritative)
- system_repository.insert (r.name,
- *r.constraint->min_version,
- true /* authoritative */);
+ db.system_repository.insert (r.name,
+ *r.constraint->min_version,
+ true /* authoritative */);
break;
}
@@ -2757,14 +2952,18 @@ namespace bpkg
// Create the unparsed package argument.
//
- auto arg_raw = [] (string v, pkg_options os, strings vs) -> pkg_arg
+ auto arg_raw = [] (string v,
+ pkg_options os,
+ strings vs,
+ database& db) -> pkg_arg
{
return pkg_arg {package_scheme::none,
package_name (),
nullopt /* constraint */,
move (v),
move (os),
- move (vs)};
+ move (vs),
+ db};
};
auto arg_parsed = [] (const pkg_arg& a) {return !a.name.empty ();};
@@ -2826,6 +3025,11 @@ namespace bpkg
append (v, s);
};
+ auto add_num = [&add_string] (const char* o, auto v)
+ {
+ add_string (o, to_string (v));
+ };
+
const pkg_options& o (a.options);
add_bool ("--keep-out", o.keep_out ());
@@ -2843,6 +3047,9 @@ namespace bpkg
add_bool ("--checkout-purge", o.checkout_purge ());
+ if (o.config_id_specified ())
+ add_num ("--config-id", o.config_id ());
+
// Compose the option/variable group.
//
if (!s.empty () || !a.config_vars.empty ())
@@ -2875,7 +3082,7 @@ namespace bpkg
//
vector<shared_ptr<available_package>> stubs;
- transaction t (db);
+ transaction t (mdb);
// Don't fold the zero revision if building the package from source so
// that we build the exact X+0 package revision if it is specified.
@@ -2936,12 +3143,14 @@ namespace bpkg
move (n),
move (vc),
move (ps.options),
- move (ps.config_vars)));
+ move (ps.config_vars),
+ *ps.db));
}
else // Add unparsed.
pkg_args.push_back (arg_raw (move (ps.packages),
move (ps.options),
- move (ps.config_vars)));
+ move (ps.config_vars),
+ *ps.db));
continue;
}
@@ -2951,7 +3160,7 @@ namespace bpkg
// presence of --no-fetch option.
//
shared_ptr<repository> r (
- db.find<repository> (ps.location.canonical_name ()));
+ mdb.find<repository> (ps.location.canonical_name ()));
if (r == nullptr)
fail << "repository '" << ps.location
@@ -2974,7 +3183,7 @@ namespace bpkg
{
using query = query<repository_fragment_package>;
- for (const auto& rp: db.query<repository_fragment_package> (
+ for (const auto& rp: mdb.query<repository_fragment_package> (
(query::repository_fragment::name ==
rf.fragment.load ()->name) +
order_by_version_desc (query::package::id.version)))
@@ -2989,7 +3198,7 @@ namespace bpkg
if (ps.options.patch ())
{
shared_ptr<selected_package> sp (
- db.find<selected_package> (nm));
+ ps.db->find<selected_package> (nm));
// It seems natural in the presence of --patch option to only
// patch the selected packages and not to build new packages if
@@ -3044,7 +3253,8 @@ namespace bpkg
pv.first,
version_constraint (pv.second),
ps.options,
- ps.config_vars));
+ ps.config_vars,
+ *ps.db));
}
}
else // Packages with optional versions in the coma-separated list.
@@ -3101,7 +3311,7 @@ namespace bpkg
if (!vc)
{
if (ps.options.patch () &&
- (sp = db.find<selected_package> (n)) != nullptr)
+ (sp = ps.db->find<selected_package> (n)) != nullptr)
{
c = patch_constraint (sp);
@@ -3117,7 +3327,7 @@ namespace bpkg
}
shared_ptr<available_package> ap (
- find_available_one (db, n, c, rfs, false /* prereq */).first);
+ find_available_one (mdb, n, c, rfs, false /* prereq */).first);
// Fail if no available package is found or only a stub is
// available and we are building a source package.
@@ -3158,7 +3368,8 @@ namespace bpkg
move (n),
move (vc),
ps.options,
- ps.config_vars));
+ ps.config_vars,
+ *ps.db));
}
}
}
@@ -3180,14 +3391,14 @@ namespace bpkg
// Check if the package is a duplicate. Return true if it is but
// harmless.
//
- map<package_name, pkg_arg> package_map;
+ map<config_package, pkg_arg> package_map;
auto check_dup = [&package_map, &arg_string, &arg_parsed]
(const pkg_arg& pa) -> bool
{
assert (arg_parsed (pa));
- auto r (package_map.emplace (pa.name, pa));
+ auto r (package_map.emplace (config_package {pa.db, pa.name}, pa));
const pkg_arg& a (r.first->second);
assert (arg_parsed (a));
@@ -3212,9 +3423,10 @@ namespace bpkg
return !r.second;
};
- transaction t (db);
+ transaction t (mdb);
- shared_ptr<repository_fragment> root (db.load<repository_fragment> (""));
+ shared_ptr<repository_fragment> root (
+ mdb.load<repository_fragment> (""));
// Here is what happens here: for unparsed package args we are going to
// try and guess whether we are dealing with a package archive, package
@@ -3228,7 +3440,8 @@ namespace bpkg
bool diag (false);
for (auto i (pkg_args.begin ()); i != pkg_args.end (); )
{
- pkg_arg& pa (*i);
+ pkg_arg& pa (*i);
+ database& pdb (pa.db);
// Reduce all the potential variations (archive, directory, package
// name, package name/version) to a single available_package object.
@@ -3280,7 +3493,8 @@ namespace bpkg
m.name,
version_constraint (m.version),
move (pa.options),
- move (pa.config_vars));
+ move (pa.config_vars),
+ pdb);
af = root;
ap = make_shared<available_package> (move (m));
@@ -3354,7 +3568,7 @@ namespace bpkg
//
if (optional<version> v =
package_iteration (o,
- c,
+ pdb,
t,
d,
m.name,
@@ -3366,7 +3580,8 @@ namespace bpkg
m.name,
version_constraint (m.version),
move (pa.options),
- move (pa.config_vars));
+ move (pa.config_vars),
+ pdb);
ap = make_shared<available_package> (move (m));
af = root;
@@ -3424,7 +3639,8 @@ namespace bpkg
move (n),
move (vc),
move (pa.options),
- move (pa.config_vars));
+ move (pa.config_vars),
+ pdb);
}
l4 ([&]{trace << "package: " << arg_string (pa);});
@@ -3442,7 +3658,7 @@ namespace bpkg
assert (!arg_sys (pa));
if (pa.options.patch () &&
- (sp = db.find<selected_package> (pa.name)) != nullptr)
+ (sp = pdb.find<selected_package> (pa.name)) != nullptr)
{
c = patch_constraint (sp);
@@ -3461,7 +3677,7 @@ namespace bpkg
else if (!arg_sys (pa))
c = pa.constraint;
- auto rp (find_available_one (db, pa.name, c, root));
+ auto rp (find_available_one (mdb, pa.name, c, root));
ap = move (rp.first);
af = move (rp.second);
}
@@ -3499,7 +3715,7 @@ namespace bpkg
l4 ([&]{trace << "stashing recursive package "
<< arg_string (pa);});
- rec_pkgs.push_back (recursive_package {pa.name, *u, *r});
+ rec_pkgs.push_back (recursive_package {pdb, pa.name, *u, *r});
}
}
@@ -3515,23 +3731,24 @@ namespace bpkg
// Make sure that the package is known.
//
auto apr (!pa.constraint || sys
- ? find_available (db, pa.name, nullopt)
- : find_available (db, pa.name, *pa.constraint));
+ ? find_available (mdb, pa.name, nullopt)
+ : find_available (mdb, pa.name, *pa.constraint));
if (apr.empty ())
{
diag_record dr (fail);
dr << "unknown package " << arg_string (pa, false /* options */);
- check_any_available (c, t, &dr);
+ check_any_available (mdb, t, &dr);
}
// Save before the name move.
//
- sp = db.find<selected_package> (pa.name);
+ sp = pdb.find<selected_package> (pa.name);
dep_pkgs.push_back (
- dependency_package {move (pa.name),
+ dependency_package {pdb,
+ move (pa.name),
move (pa.constraint),
move (sp),
sys,
@@ -3554,7 +3771,7 @@ namespace bpkg
// the same as the selected package).
//
if (sp == nullptr)
- sp = db.find<selected_package> (pa.name);
+ sp = pdb.find<selected_package> (pa.name);
if (sp != nullptr && sp->state == package_state::broken)
fail << "unable to build broken package " << pa.name <<
@@ -3581,7 +3798,7 @@ namespace bpkg
if (ap == nullptr)
{
if (pa.constraint &&
- find_available_one (db,
+ find_available_one (mdb,
pa.name,
nullopt,
root).first != nullptr)
@@ -3663,7 +3880,7 @@ namespace bpkg
// Let's help the new user out here a bit.
//
- check_any_available (c, t, &dr);
+ check_any_available (mdb, t, &dr);
}
else
{
@@ -3686,7 +3903,7 @@ namespace bpkg
{
assert (sp != nullptr && sp->system () == arg_sys (pa));
- auto rp (make_available (o, c, db, sp));
+ auto rp (make_available (o, pdb, sp));
ap = rp.first;
af = rp.second; // Could be NULL (orphan).
}
@@ -3704,6 +3921,7 @@ namespace bpkg
//
build_package p {
build_package::build,
+ pdb,
move (sp),
move (ap),
move (af),
@@ -3737,6 +3955,10 @@ namespace bpkg
// If this is just pkg-build -u|-p, then we are upgrading all held
// packages.
//
+ // Note that currently we only upgrade the current configuration held
+ // packages. Later, we may consider also upgrading the explicitly
+ // associated configurations, recursively.
+ //
if (hold_pkgs.empty () && dep_pkgs.empty () &&
(o.upgrade () || o.patch ()))
{
@@ -3744,15 +3966,14 @@ namespace bpkg
for (shared_ptr<selected_package> sp:
pointer_result (
- db.query<selected_package> (query::state == "configured" &&
- query::hold_package)))
+ mdb.query<selected_package> (query::state == "configured" &&
+ query::hold_package)))
{
// Let's skip upgrading system packages as they are, probably,
// configured as such for a reason.
//
if (sp->system ())
continue;
-
const package_name& name (sp->name);
optional<version_constraint> pc;
@@ -3768,7 +3989,7 @@ namespace bpkg
continue;
}
- auto apr (find_available_one (db, name, pc, root));
+ auto apr (find_available_one (mdb, name, pc, root));
shared_ptr<available_package> ap (move (apr.first));
if (ap == nullptr || ap->stub ())
@@ -3784,7 +4005,7 @@ namespace bpkg
// Let's help the new user out here a bit.
//
- check_any_available (c, t, &dr);
+ check_any_available (mdb, t, &dr);
}
// We will keep the output directory only if the external package is
@@ -3793,7 +4014,8 @@ namespace bpkg
bool keep_out (o.keep_out () && sp->external ());
build_package p {
- build_package::build,
+ build_package::build,
+ mdb,
move (sp),
move (ap),
move (apr.second),
@@ -3818,7 +4040,7 @@ namespace bpkg
//
if (o.immediate () || o.recursive ())
rec_pkgs.push_back (
- recursive_package {name, o.upgrade (), o.recursive ()});
+ recursive_package {mdb, name, o.upgrade (), o.recursive ()});
}
}
@@ -3881,14 +4103,15 @@ namespace bpkg
{
struct dep
{
- package_name name; // Empty if up/down-grade.
+ reference_wrapper<database> db;
+ package_name name; // Empty if up/down-grade.
// Both are NULL if drop.
//
- shared_ptr<available_package> available;
+ shared_ptr<available_package> available;
shared_ptr<bpkg::repository_fragment> repository_fragment;
- bool system;
+ bool system;
};
vector<dep> deps;
@@ -3899,7 +4122,7 @@ namespace bpkg
l4 ([&]{trace << "refining execution plan"
<< (scratch ? " from scratch" : "");});
- transaction t (db);
+ transaction t (mdb);
build_packages::postponed_packages postponed;
@@ -3921,6 +4144,7 @@ namespace bpkg
{
build_package bp {
nullopt, // Action.
+ p.db,
nullptr, // Selected package.
nullptr, // Available package/repository frag.
nullptr,
@@ -3939,6 +4163,9 @@ namespace bpkg
bp.constraints.emplace_back ("command line", *p.constraint);
pkgs.enter (p.name, move (bp));
+
+ if (p.db != mdb)
+ pkgs.collect_link (p.name, p.db, mdb);
}
// Pre-collect user selection to make sure dependency-forced
@@ -3946,12 +4173,17 @@ namespace bpkg
// specify packages on the command line does not matter).
//
for (const build_package& p: hold_pkgs)
- pkgs.collect_build (o, c, db, p);
+ {
+ pkgs.collect_build (o, p);
+
+ if (p.db != mdb)
+ pkgs.collect_link (p.name (), p.db, mdb);
+ }
// Collect all the prerequisites of the user selection.
//
for (const build_package& p: hold_pkgs)
- pkgs.collect_build_prerequisites (o, c, db, p.name (), postponed);
+ pkgs.collect_build_prerequisites (o, p.db, p.name (), postponed);
// Note that we need to collect unheld after prerequisites, not to
// overwrite the pre-entered entries before they are used to provide
@@ -3960,7 +4192,7 @@ namespace bpkg
for (const dependency_package& p: dep_pkgs)
{
if (p.selected != nullptr && p.selected->hold_package)
- pkgs.collect_unhold (p.selected);
+ pkgs.collect_unhold (p.db, p.selected);
}
scratch = false;
@@ -3973,12 +4205,16 @@ namespace bpkg
//
for (const dep& d: deps)
{
+ database& ddb (d.db);
+
if (d.available == nullptr)
- pkgs.collect_drop (db.load<selected_package> (d.name));
+ {
+ pkgs.collect_drop (ddb, ddb.load<selected_package> (d.name));
+ }
else
{
shared_ptr<selected_package> sp (
- db.find<selected_package> (d.name));
+ ddb.find<selected_package> (d.name));
// We will keep the output directory only if the external package
// is replaced with an external one (see above for details).
@@ -3993,6 +4229,7 @@ namespace bpkg
//
build_package p {
build_package::build,
+ ddb,
move (sp),
d.available,
d.repository_fragment,
@@ -4007,14 +4244,14 @@ namespace bpkg
{package_name ()}, // Required by (command line).
0}; // Adjustments.
- pkgs.collect_build (o, c, db, p, &postponed /* recursively */);
+ pkgs.collect_build (o, move (p), &postponed /* recursively */);
}
}
// Handle the (combined) postponed collection.
//
if (!postponed.empty ())
- pkgs.collect_build_postponed (o, c, db, postponed);
+ pkgs.collect_build_postponed (o, postponed);
// Now that we have collected all the package versions that we need to
// build, arrange them in the "dependency order", that is, with every
@@ -4030,16 +4267,16 @@ namespace bpkg
// appear (e.g., on the plan) last.
//
for (const dep& d: deps)
- pkgs.order (d.name, false /* reorder */);
+ pkgs.order (d.db, d.name, false /* reorder */);
for (const build_package& p: reverse_iterate (hold_pkgs))
- pkgs.order (p.name ());
+ pkgs.order (p.db, p.name ());
// Collect and order all the dependents that we will need to
// reconfigure because of the up/down-grades of packages that are now
// on the list.
//
- pkgs.collect_order_dependents (db);
+ pkgs.collect_order_dependents ();
// And, finally, make sure all the packages that we need to unhold
// are on the list.
@@ -4047,7 +4284,7 @@ namespace bpkg
for (const dependency_package& p: dep_pkgs)
{
if (p.selected != nullptr && p.selected->hold_package)
- pkgs.order (p.name, false /* reorder */);
+ pkgs.order (p.db, p.name, false /* reorder */);
}
// We are about to execute the plan on the database (but not on the
@@ -4056,18 +4293,21 @@ namespace bpkg
// below for details).
//
using selected_packages = session::object_map<selected_package>;
- auto selected_packages_session = [&db, &ses] () -> selected_packages*
+ auto sp_session = [] (const auto& tm) -> selected_packages*
{
- auto& m (ses.map ()[&db]);
- auto i (m.find (&typeid (selected_package)));
- return (i != m.end ()
+ auto i (tm.find (&typeid (selected_package)));
+ return (i != tm.end ()
? &static_cast<selected_packages&> (*i->second)
: nullptr);
};
- selected_packages old_sp;
- if (const selected_packages* sp = selected_packages_session ())
- old_sp = *sp;
+ map<const odb::database*, selected_packages> old_sp;
+
+ for (const auto& dps: ses.map ())
+ {
+ if (const selected_packages* sps = sp_session (dps.second))
+ old_sp.emplace (dps.first, *sps);
+ }
// Note that we need to perform the execution on the copies of the
// build/drop_package objects to preserve the original ones. The
@@ -4078,14 +4318,15 @@ namespace bpkg
vector<build_package> tmp (pkgs.begin (), pkgs.end ());
build_package_list bl (tmp.begin (), tmp.end ());
- execute_plan (o, c, db, bl, true /* simulate */);
+ execute_plan (o, bl, true /* simulate */);
}
// Return nullopt if no changes to the dependency are necessary. This
// value covers both the "no change is required" and the "no
// recommendation available" cases.
//
- auto eval_dep = [&db, &dep_pkgs, &rec_pkgs] (
+ auto eval_dep = [&dep_pkgs, &rec_pkgs] (
+ database& db,
const shared_ptr<selected_package>& sp,
bool ignore_unsatisfiable = true) -> optional<evaluate_result>
{
@@ -4094,7 +4335,7 @@ namespace bpkg
// See if there is an optional dependency upgrade recommendation.
//
if (!sp->hold_package)
- r = evaluate_dependency (db, dep_pkgs, sp, ignore_unsatisfiable);
+ r = evaluate_dependency (db, sp, dep_pkgs, ignore_unsatisfiable);
// If none, then see for the recursive dependency upgrade
// recommendation.
@@ -4103,7 +4344,7 @@ namespace bpkg
// configured as such for a reason.
//
if (!r && !sp->system () && !rec_pkgs.empty ())
- r = evaluate_recursive (db, rec_pkgs, sp, ignore_unsatisfiable);
+ r = evaluate_recursive (db, sp, rec_pkgs, ignore_unsatisfiable);
// Translate the "no change" result to nullopt.
//
@@ -4113,16 +4354,18 @@ namespace bpkg
// The empty version means that the package must be dropped.
//
const version ev;
- auto target_version = [&ev] (const shared_ptr<available_package>& ap,
- bool sys) -> const version&
+ auto target_version = [&ev]
+ (database& db,
+ const shared_ptr<available_package>& ap,
+ bool sys) -> const version&
{
if (ap == nullptr)
return ev;
if (sys)
{
- assert (ap->system_version () != nullptr);
- return *ap->system_version ();
+ assert (ap->system_version (db) != nullptr);
+ return *ap->system_version (db);
}
return ap->version;
@@ -4135,15 +4378,17 @@ namespace bpkg
{
bool s (false);
+ database& db (i->db);
+
// Here we scratch if evaluate changed its mind or if the resulting
// version doesn't match what we expect it to be.
//
if (auto sp = db.find<selected_package> (i->name))
{
- const version& dv (target_version (i->available, i->system));
+ const version& dv (target_version (db, i->available, i->system));
- if (optional<evaluate_result> r = eval_dep (sp))
- s = dv != target_version (r->available, r->system) ||
+ if (optional<evaluate_result> r = eval_dep (db, sp))
+ s = dv != target_version (db, r->available, r->system) ||
i->system != r->system;
else
s = dv != sp->version || i->system != sp->system ();
@@ -4170,7 +4415,7 @@ namespace bpkg
// make sure that the unsatisfiable dependency, if left, is
// reported.
//
- auto need_refinement = [&eval_dep, &deps, &rec_pkgs, &db, &o] (
+ auto need_refinement = [&eval_dep, &deps, &rec_pkgs, &mdb, &o] (
bool diag = false) -> bool
{
// Examine the new dependency set for any up/down-grade/drops.
@@ -4184,24 +4429,44 @@ namespace bpkg
if (rec_pkgs.empty ())
q = q && !query::hold_package;
- for (shared_ptr<selected_package> sp:
- pointer_result (db.query<selected_package> (q)))
+ // It seems right to only evaluate dependencies in the explicitly
+ // associated configurations, recursively. Indeed, we shouldn't be
+ // up/down-grading or dropping packages in configurations that
+ // only contain dependents, some of which we may only reconfigure.
+ //
+ set<reference_wrapper<database>> dbs;
+ auto eval_deps = [&deps, &r, diag, &q, &o, &dbs, &eval_dep]
+ (database& db, const auto& eval_deps)
{
- if (optional<evaluate_result> er = eval_dep (sp, !diag))
+ if (!dbs.insert (db).second)
+ return;
+
+ for (shared_ptr<selected_package> sp:
+ pointer_result (db.query<selected_package> (q)))
{
- // Skip unused if we were instructed to keep them.
- //
- if (o.keep_unused () && er->available == nullptr)
- continue;
+ if (optional<evaluate_result> er = eval_dep (db, sp, !diag))
+ {
+ // Skip unused if we were instructed to keep them.
+ //
+ if (o.keep_unused () && er->available == nullptr)
+ continue;
- if (!diag)
- deps.push_back (dep {sp->name,
- move (er->available),
- move (er->repository_fragment),
- er->system});
- r = true;
+ if (!diag)
+ deps.push_back (dep {db,
+ sp->name,
+ move (er->available),
+ move (er->repository_fragment),
+ er->system});
+
+ r = true;
+ }
}
- }
+
+ for (associated_config& c: db.explicit_associations ())
+ eval_deps (c.db, eval_deps);
+ };
+
+ eval_deps (mdb, eval_deps);
return r;
};
@@ -4217,7 +4482,7 @@ namespace bpkg
//
t.rollback ();
{
- transaction t (db);
+ transaction t (mdb);
// First reload all the selected_package object that could have been
// modified (conceptually, we should only modify what's on the
@@ -4231,58 +4496,106 @@ namespace bpkg
{
assert (p.action);
+ database& pdb (p.db);
+
if (*p.action == build_package::drop)
{
assert (p.selected != nullptr);
ses.cache_insert<selected_package> (
- db, p.selected->name, p.selected);
+ pdb, p.selected->name, p.selected);
}
if (p.selected != nullptr)
- db.reload (*p.selected);
+ pdb.reload (*p.selected);
}
// Now remove all the newly created selected_package objects from
// the session. The tricky part is to distinguish newly created ones
// from newly loaded (and potentially cached).
//
- if (selected_packages* sp = selected_packages_session ())
+ for (bool rescan (true); rescan; )
{
- for (bool rescan (true); rescan; )
- {
- rescan = false;
+ rescan = false;
- for (auto i (sp->begin ()); i != sp->end (); )
+ for (const auto& dps: ses.map ())
+ {
+ if (selected_packages* sps = sp_session (dps.second))
{
- bool erased (false);
- auto j (old_sp.find (i->first));
-
+ auto j (old_sp.find (dps.first)); // Find the database.
+
+ // Note that if a database has been introduced only during
+ // simulation, then we could just clear all its selected
+ // packages in one shot. Let's however, be cautious and remove
+ // them iteratively to make sure that none of them is left at
+ // the end (no more rescan is necessary), being referenced
+ // from somewhere besides the session object (which would be a
+ // bug).
+ //
if (j == old_sp.end ())
{
- if (i->second.use_count () == 1)
+ if (!sps->empty ())
{
- // This might cause another object's use count to drop.
- //
- i = sp->erase (i);
- erased = true;
- rescan = true;
+ for (auto i (sps->begin ()); i != sps->end (); )
+ {
+ if (i->second.use_count () == 1)
+ {
+ // This might cause another object's use count to drop.
+ //
+ i = sps->erase (i);
+ rescan = true;
+ }
+ else
+ ++i;
+ }
}
+
+ continue;
}
- // It may also happen that the object was erased from the
- // database and then recreated. In this case we restore the
- // pointer that is stored in the session.
- //
- else if (i->second != j->second)
+
+ const selected_packages& osp (j->second);
+
+ for (auto i (sps->begin ()); i != sps->end (); )
{
- // This might cause another object's use count to drop.
+ bool erased (false);
+ auto j (osp.find (i->first));
+
+ if (j == osp.end ())
+ {
+ if (i->second.use_count () == 1)
+ {
+ // This might cause another object's use count to drop.
+ //
+ i = sps->erase (i);
+ erased = true;
+ rescan = true;
+ }
+ }
+ // It may also happen that the object was erased from the
+ // database and then recreated. In this case we restore the
+ // pointer that is stored in the session.
//
- i->second = j->second;
- rescan = true;
+ else if (i->second != j->second)
+ {
+ // This might cause another object's use count to drop.
+ //
+ i->second = j->second;
+ rescan = true;
+ }
+
+ if (!erased)
+ ++i;
}
+ }
+ }
- if (!erased)
- ++i;
+ if (!rescan)
+ {
+ for (const auto& dps: ses.map ())
+ {
+ if (selected_packages* sps = sp_session (dps.second))
+ assert (old_sp.find (dps.first) != old_sp.end () ||
+ sps->empty ());
}
}
}
@@ -4487,7 +4800,7 @@ namespace bpkg
// prerequsites got upgraded/downgraded and that the user may want to in
// addition update (that update_dependents flag above).
//
- execute_plan (o, c, db, pkgs, false /* simulate */);
+ execute_plan (o, pkgs, false /* simulate */);
if (o.configure_only ())
return 0;
@@ -4512,7 +4825,8 @@ namespace bpkg
if (!sp->system () && // System package doesn't need update.
p.user_selection ())
- upkgs.push_back (pkg_command_vars {sp,
+ upkgs.push_back (pkg_command_vars {p.db,
+ sp,
strings () /* vars */,
false /* cwd */});
}
@@ -4527,13 +4841,14 @@ namespace bpkg
assert (p.action);
if (*p.action == build_package::adjust && p.reconfigure ())
- upkgs.push_back (pkg_command_vars {p.selected,
+ upkgs.push_back (pkg_command_vars {p.db,
+ p.selected,
strings () /* vars */,
false /* cwd */});
}
}
- pkg_update (c, o, o.for_ (), strings (), upkgs);
+ pkg_update (o, o.for_ (), strings (), upkgs);
if (verb && !o.no_result ())
{
@@ -4546,8 +4861,6 @@ namespace bpkg
static void
execute_plan (const pkg_build_options& o,
- const dir_path& c,
- database& db,
build_package_list& build_pkgs,
bool simulate)
{
@@ -4569,12 +4882,13 @@ namespace bpkg
if (*p.action != build_package::drop && !p.reconfigure ())
continue;
+ database& pdb (p.db);
shared_ptr<selected_package>& sp (p.selected);
// Each package is disfigured in its own transaction, so that we
// always leave the configuration in a valid state.
//
- transaction t (db, !simulate /* start */);
+ transaction t (pdb, !simulate /* start */);
// Reset the flag if the package being unpacked is not an external one.
//
@@ -4608,7 +4922,7 @@ namespace bpkg
// Commits the transaction.
//
- pkg_disfigure (c, o, t, sp, !p.keep_out, simulate);
+ pkg_disfigure (o, pdb, t, sp, !p.keep_out, simulate);
assert (sp->state == package_state::unpacked ||
sp->state == package_state::transient);
@@ -4640,6 +4954,8 @@ namespace bpkg
{
assert (p.action);
+ database& pdb (p.db);
+
shared_ptr<selected_package>& sp (p.selected);
const shared_ptr<available_package>& ap (p.available);
@@ -4657,8 +4973,8 @@ namespace bpkg
{
assert (!sp->system ());
- transaction t (db, !simulate /* start */);
- pkg_purge (c, t, sp, simulate); // Commits the transaction.
+ transaction t (pdb, !simulate /* start */);
+ pkg_purge (pdb, t, sp, simulate); // Commits the transaction.
if (verbose && !o.no_result ())
text << "purged " << *sp;
@@ -4686,8 +5002,8 @@ namespace bpkg
{
if (sp != nullptr && !sp->system ())
{
- transaction t (db, !simulate /* start */);
- pkg_purge (c, t, sp, simulate); // Commits the transaction.
+ transaction t (pdb, !simulate /* start */);
+ pkg_purge (pdb, t, sp, simulate); // Commits the transaction.
if (verbose && !o.no_result ())
text << "purged " << *sp;
@@ -4717,7 +5033,7 @@ namespace bpkg
if (pl.repository_fragment.object_id () != "") // Special root?
{
- transaction t (db, !simulate /* start */);
+ transaction t (pdb, !simulate /* start */);
// Go through package repository fragments to decide if we should
// fetch, checkout or unpack depending on the available repository
@@ -4750,7 +5066,7 @@ namespace bpkg
case repository_basis::archive:
{
sp = pkg_fetch (o,
- c,
+ pdb,
t,
ap->id.name,
p.available_version (),
@@ -4762,7 +5078,7 @@ namespace bpkg
{
sp = p.checkout_root
? pkg_checkout (o,
- c,
+ pdb,
t,
ap->id.name,
p.available_version (),
@@ -4771,7 +5087,7 @@ namespace bpkg
p.checkout_purge,
simulate)
: pkg_checkout (o,
- c,
+ pdb,
t,
ap->id.name,
p.available_version (),
@@ -4782,7 +5098,7 @@ namespace bpkg
case repository_basis::directory:
{
sp = pkg_unpack (o,
- c,
+ pdb,
t,
ap->id.name,
p.available_version (),
@@ -4796,11 +5112,11 @@ namespace bpkg
//
else if (exists (pl.location))
{
- transaction t (db, !simulate /* start */);
+ transaction t (pdb, !simulate /* start */);
sp = pkg_fetch (
o,
- c,
+ pdb,
t,
pl.location, // Archive path.
true, // Replace
@@ -4856,11 +5172,11 @@ namespace bpkg
{
if (sp != nullptr)
{
- transaction t (db, !simulate /* start */);
+ transaction t (pdb, !simulate /* start */);
// Commits the transaction.
//
- sp = pkg_unpack (o, c, t, ap->id.name, simulate);
+ sp = pkg_unpack (o, pdb, t, ap->id.name, simulate);
if (verbose && !o.no_result ())
text << "unpacked " << *sp;
@@ -4870,9 +5186,9 @@ namespace bpkg
const package_location& pl (ap->locations[0]);
assert (pl.repository_fragment.object_id () == ""); // Special root.
- transaction t (db, !simulate /* start */);
+ transaction t (pdb, !simulate /* start */);
sp = pkg_unpack (o,
- c,
+ pdb,
t,
path_cast<dir_path> (pl.location),
true, // Replace.
@@ -4915,14 +5231,25 @@ namespace bpkg
if (sp != nullptr && sp->state == package_state::configured)
continue;
- transaction t (db, !simulate /* start */);
+ database& pdb (p.db);
+
+ transaction t (pdb, !simulate /* start */);
// Note that pkg_configure() commits the transaction.
//
if (p.system)
- sp = pkg_configure_system (ap->id.name, p.available_version (), t);
+ sp = pkg_configure_system (ap->id.name,
+ p.available_version (),
+ pdb,
+ t);
else if (ap != nullptr)
- pkg_configure (c, o, t, sp, ap->dependencies, p.config_vars, simulate);
+ pkg_configure (o,
+ pdb,
+ t,
+ sp,
+ ap->dependencies,
+ p.config_vars,
+ simulate);
else // Dependent.
{
// Must be in the unpacked state since it was disfigured on the first
@@ -4931,12 +5258,12 @@ namespace bpkg
assert (sp->state == package_state::unpacked);
package_manifest m (
- pkg_verify (sp->effective_src_root (c),
+ pkg_verify (sp->effective_src_root (pdb.config),
true /* ignore_unknown */,
[&sp] (version& v) {v = sp->version;}));
- pkg_configure (c,
- o,
+ pkg_configure (o,
+ p.db,
t,
sp,
convert (move (m.dependencies)),
@@ -4962,6 +5289,8 @@ namespace bpkg
if (*p.action == build_package::drop)
continue;
+ database& pdb (p.db);
+
const shared_ptr<selected_package>& sp (p.selected);
assert (sp != nullptr);
@@ -4982,8 +5311,8 @@ namespace bpkg
sp->hold_package = hp;
sp->hold_version = hv;
- transaction t (db, !simulate /* start */);
- db.update (sp);
+ transaction t (pdb, !simulate /* start */);
+ pdb.update (sp);
t.commit ();
if (verbose > 1)
diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx
index 3b99496..6fc933a 100644
--- a/bpkg/pkg-checkout.cxx
+++ b/bpkg/pkg-checkout.cxx
@@ -84,7 +84,7 @@ namespace bpkg
//
static shared_ptr<selected_package>
pkg_checkout (const common_options& o,
- dir_path c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -95,7 +95,6 @@ namespace bpkg
{
tracer trace ("pkg_checkout");
- database& db (t.database ());
tracer_guard tg (db, trace);
// See if this package already exists in this configuration.
@@ -111,7 +110,8 @@ namespace bpkg
{
diag_record dr (fail);
- dr << "package " << n << " already exists in configuration " << c <<
+ dr << "package " << n << " already exists in configuration "
+ << db.config <<
info << "version: " << p->version_string ()
<< ", state: " << p->state
<< ", substate: " << p->substate;
@@ -121,7 +121,7 @@ namespace bpkg
}
}
- check_any_available (c, t);
+ check_any_available (db, t);
// Note that here we compare including the revision (see pkg-fetch()
// implementation for more details).
@@ -163,7 +163,7 @@ namespace bpkg
auto_rmdir rmd;
optional<string> mc;
- const dir_path& ord (output_root ? *output_root : c);
+ const dir_path& ord (output_root ? *output_root : db.config);
dir_path d (ord / dir_path (n.string () + '-' + v.string ()));
// An incomplete checkout may result in an unusable repository state
@@ -185,11 +185,11 @@ namespace bpkg
// if the previous checkout have failed or been interrupted.
//
dir_path sd (repository_state (rl));
- dir_path rd (c / repos_dir / sd);
+ dir_path rd (db.config / repos_dir / sd);
if (!exists (rd))
fail << "missing repository directory for package " << n << " " << v
- << " in configuration " << c <<
+ << " in configuration " << db.config <<
info << "run 'bpkg rep-fetch' to repair";
// The repository temporary directory.
@@ -301,7 +301,7 @@ namespace bpkg
// replacing. Once this is done, there is no going back. If things go
// badly, we can't simply abort the transaction.
//
- pkg_purge_fs (c, t, p, simulate);
+ pkg_purge_fs (db, t, p, simulate);
// Note that if the package name spelling changed then we need to update
// it, to make sure that the subsequent commands don't fail and the
@@ -315,15 +315,14 @@ namespace bpkg
}
}
- // Make the package and configuration paths absolute and normalized.
- // If the package is inside the configuration, use the relative path.
- // This way we can move the configuration around.
+ // Make the package path absolute and normalized. If the package is inside
+ // the configuration, use the relative path. This way we can move the
+ // configuration around.
//
- normalize (c, "configuration");
normalize (d, "package");
- if (d.sub (c))
- d = d.leaf (c);
+ if (d.sub (db.config))
+ d = d.leaf (db.config);
if (p != nullptr)
{
@@ -367,7 +366,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_checkout (const common_options& o,
- const dir_path& c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -377,7 +376,7 @@ namespace bpkg
bool simulate)
{
return pkg_checkout (o,
- c,
+ db,
t,
move (n),
move (v),
@@ -389,7 +388,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_checkout (const common_options& o,
- const dir_path& c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -397,7 +396,7 @@ namespace bpkg
bool simulate)
{
return pkg_checkout (o,
- c,
+ db,
t,
move (n),
move (v),
@@ -415,7 +414,7 @@ namespace bpkg
dir_path c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
@@ -437,7 +436,7 @@ namespace bpkg
//
if (o.output_root_specified ())
p = pkg_checkout (o,
- c,
+ db,
t,
move (n),
move (v),
@@ -447,7 +446,7 @@ namespace bpkg
false /* simulate */);
else
p = pkg_checkout (o,
- c,
+ db,
t,
move (n),
move (v),
diff --git a/bpkg/pkg-checkout.hxx b/bpkg/pkg-checkout.hxx
index 47b1ad0..3a058b5 100644
--- a/bpkg/pkg-checkout.hxx
+++ b/bpkg/pkg-checkout.hxx
@@ -25,7 +25,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_checkout (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
package_name,
version,
@@ -40,7 +40,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_checkout (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
package_name,
version,
diff --git a/bpkg/pkg-command.cxx b/bpkg/pkg-command.cxx
index 11f10f0..c1042eb 100644
--- a/bpkg/pkg-command.cxx
+++ b/bpkg/pkg-command.cxx
@@ -18,7 +18,6 @@ namespace bpkg
{
void
pkg_command (const string& cmd,
- const dir_path& c,
const common_options& o,
const string& cmd_v,
const strings& cvars,
@@ -78,7 +77,7 @@ namespace bpkg
assert (p->state == package_state::configured);
assert (p->out_root); // Should be present since configured.
- dir_path out_root (p->effective_out_root (c));
+ dir_path out_root (p->effective_out_root (pv.db.config));
l4 ([&]{trace << p->name << " out_root: " << out_root;});
if (bspec.back () != '(')
@@ -135,9 +134,11 @@ namespace bpkg
{
// Note: no package-specific variables (global ones still apply).
//
- ps.push_back (pkg_command_vars {d,
- strings () /* vars */,
- package_cwd});
+ ps.push_back (
+ pkg_command_vars {static_cast<database&> (pr.first.database ()),
+ d,
+ strings () /* vars */,
+ package_cwd});
if (recursive)
collect_dependencies (d, recursive, package_cwd, ps);
@@ -246,9 +247,9 @@ namespace bpkg
dr << info << "run 'bpkg help pkg-" << cmd << "' for more information";
}
+ database db (c, trace, true /* pre_attach */);
vector<pkg_command_vars> ps;
{
- database db (open (c, trace));
transaction t (db);
// We need to suppress duplicate dependencies for the recursive command
@@ -256,11 +257,11 @@ namespace bpkg
//
session ses;
- auto add = [&ps, recursive, immediate, package_cwd] (
+ auto add = [&db, &ps, recursive, immediate, package_cwd] (
const shared_ptr<selected_package>& p,
strings vars)
{
- ps.push_back (pkg_command_vars {p, move (vars), package_cwd});
+ ps.push_back (pkg_command_vars {db, p, move (vars), package_cwd});
// Note that it can only be recursive or immediate but not both.
//
@@ -325,7 +326,7 @@ namespace bpkg
t.commit ();
}
- pkg_command (cmd, c, o, cmd_v, cvars, ps);
+ pkg_command (cmd, o, cmd_v, cvars, ps);
if (verb && !o.no_result ())
{
diff --git a/bpkg/pkg-command.hxx b/bpkg/pkg-command.hxx
index 40a55f2..7a4269f 100644
--- a/bpkg/pkg-command.hxx
+++ b/bpkg/pkg-command.hxx
@@ -33,15 +33,15 @@ namespace bpkg
struct pkg_command_vars
{
+ database& db;
shared_ptr<selected_package> pkg;
- strings vars; // Package-specific command line vars.
+ strings vars; // Package-specific command line vars.
bool cwd; // Change the working directory to the package directory.
};
void
pkg_command (const string& cmd,
- const dir_path& configuration,
const common_options&,
const string& cmd_variant,
const strings& common_vars,
diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx
index cd55575..98b8d25 100644
--- a/bpkg/pkg-configure.cxx
+++ b/bpkg/pkg-configure.cxx
@@ -20,14 +20,13 @@ namespace bpkg
{
package_prerequisites
pkg_configure_prerequisites (const common_options& o,
- transaction& t,
+ database& db,
+ transaction&,
const dependencies& deps,
const package_name& package)
{
package_prerequisites r;
- database& db (t.database ());
-
for (const dependency_alternatives_ex& da: deps)
{
assert (!da.conditional); //@@ TODO
@@ -63,15 +62,20 @@ namespace bpkg
// build and target machines are the same. See also pkg-build.
}
- if (shared_ptr<selected_package> dp = db.find<selected_package> (n))
+ auto add_prerequisite = [&n, &d, &r] (database& db)
{
- if (dp->state != package_state::configured)
- continue;
+ shared_ptr<selected_package> dp (db.find<selected_package> (n));
- if (!satisfies (dp->version, d.constraint))
- continue;
+ if (dp == nullptr ||
+ dp->state != package_state::configured ||
+ !satisfies (dp->version, d.constraint))
+ return false;
- auto p (r.emplace (dp, d.constraint));
+ // See the package_prerequisites definition for details on creating
+ // the map keys with the database passed.
+ //
+ auto p (r.emplace (lazy_shared_ptr<selected_package> (db, dp),
+ d.constraint));
// Currently we can only capture a single constraint, so if we
// already have a dependency on this package and one constraint is
@@ -93,9 +97,35 @@ namespace bpkg
c = d.constraint;
}
- satisfied = true;
- break;
+ return true;
+ };
+
+ satisfied = add_prerequisite (db);
+
+ if (!satisfied)
+ {
+ for (associated_config& c: db.explicit_associations ())
+ {
+ database& cdb (c.db);
+
+ // Consider run-time dependencies only in configurations of the
+ // same type.
+ //
+ if (db.type == cdb.type)
+ {
+ satisfied = add_prerequisite (cdb);
+
+ // @@ EC Should we continue to go through the associated configs
+ // and fail if multiple dependency can be chosen?
+ //
+ if (satisfied)
+ break;
+ }
+ }
}
+
+ if (satisfied)
+ break;
}
if (!satisfied)
@@ -106,8 +136,8 @@ namespace bpkg
}
void
- pkg_configure (const dir_path& c,
- const common_options& o,
+ pkg_configure (const common_options& o,
+ database& db,
transaction& t,
const shared_ptr<selected_package>& p,
const dependencies& deps,
@@ -119,17 +149,16 @@ namespace bpkg
assert (p->state == package_state::unpacked);
assert (p->src_root); // Must be set since unpacked.
- database& db (t.database ());
tracer_guard tg (db, trace);
- dir_path src_root (p->effective_src_root (c));
+ dir_path src_root (p->effective_src_root (db.config));
// Calculate package's out_root.
//
dir_path out_root (
p->external ()
- ? c / dir_path (p->name.string ())
- : c / dir_path (p->name.string () + "-" + p->version.string ()));
+ ? db.config / dir_path (p->name.string ())
+ : db.config / dir_path (p->name.string () + "-" + p->version.string ()));
l4 ([&]{trace << "src_root: " << src_root << ", "
<< "out_root: " << out_root;});
@@ -139,10 +168,38 @@ namespace bpkg
//
assert (p->prerequisites.empty ());
- p->prerequisites = pkg_configure_prerequisites (o, t, deps, p->name);
+ p->prerequisites = pkg_configure_prerequisites (o, db, t, deps, p->name);
if (!simulate)
{
+ // Add the config.import.* variables for prerequisites from the
+ // associated configurations.
+ //
+ strings imports;
+
+ for (const auto& pp: p->prerequisites)
+ {
+ database& pdb (static_cast<database&> (pp.first.database ()));
+
+ if (pdb != db)
+ {
+ shared_ptr<selected_package> sp (pp.first.load ());
+
+ assert (sp->out_root);
+
+ dir_path od (pdb.config / *sp->out_root);
+
+ // Note that here we assume that the prerequisite package exports a
+ // single target named as a package itself, which may not be the
+ // case.
+ //
+ // @@ EC Is there a way to improve this?
+ //
+ imports.push_back (
+ "config.import." + sp->name.string () + "='" + od.string () + "'");
+ }
+ }
+
// Form the buildspec.
//
string bspec;
@@ -162,7 +219,7 @@ namespace bpkg
//
try
{
- run_b (o, verb_b::quiet, vars, bspec);
+ run_b (o, verb_b::quiet, imports, vars, bspec);
}
catch (const failed&)
{
@@ -180,7 +237,7 @@ namespace bpkg
// Commits the transaction.
//
- pkg_disfigure (c, o, t, p, true /* clean */, false /* simulate */);
+ pkg_disfigure (o, db, t, p, true /* clean */, false /* simulate */);
throw;
}
}
@@ -195,11 +252,11 @@ namespace bpkg
shared_ptr<selected_package>
pkg_configure_system (const package_name& n,
const version& v,
+ database& db,
transaction& t)
{
tracer trace ("pkg_configure_system");
- database& db (t.database ());
tracer_guard tg (db, trace);
shared_ptr<selected_package> p (
@@ -269,7 +326,7 @@ namespace bpkg
if (ps == package_scheme::sys && !vars.empty ())
fail << "configuration variables specified for a system package";
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
@@ -297,7 +354,7 @@ namespace bpkg
if (filter_one (root, db.query<available_package> (q)).first == nullptr)
fail << "unknown package " << n;
- p = pkg_configure_system (n, v.empty () ? wildcard_version : v, t);
+ p = pkg_configure_system (n, v.empty () ? wildcard_version : v, db, t);
}
else
{
@@ -319,8 +376,8 @@ namespace bpkg
true /* ignore_unknown */,
[&p] (version& v) {v = p->version;}));
- pkg_configure (c,
- o,
+ pkg_configure (o,
+ db,
t,
p,
convert (move (m.dependencies)),
diff --git a/bpkg/pkg-configure.hxx b/bpkg/pkg-configure.hxx
index b708df5..6585fc7 100644
--- a/bpkg/pkg-configure.hxx
+++ b/bpkg/pkg-configure.hxx
@@ -26,8 +26,8 @@ namespace bpkg
// Configure the package, update its state, and commit the transaction.
//
void
- pkg_configure (const dir_path& configuration,
- const common_options&,
+ pkg_configure (const common_options&,
+ database&,
transaction&,
const shared_ptr<selected_package>&,
const dependencies&,
@@ -37,7 +37,10 @@ namespace bpkg
// Configure a system package and commit the transaction.
//
shared_ptr<selected_package>
- pkg_configure_system (const package_name&, const version&, transaction&);
+ pkg_configure_system (const package_name&,
+ const version&,
+ database&,
+ transaction&);
// Return package prerequisites given its dependencies. Fail if some of the
// prerequisites are not configured or don't satisfy the package's
@@ -46,6 +49,7 @@ namespace bpkg
//
package_prerequisites
pkg_configure_prerequisites (const common_options&,
+ database&,
transaction&,
const dependencies&,
const package_name&);
diff --git a/bpkg/pkg-disfigure.cxx b/bpkg/pkg-disfigure.cxx
index 9347bbc..f347d17 100644
--- a/bpkg/pkg-disfigure.cxx
+++ b/bpkg/pkg-disfigure.cxx
@@ -15,8 +15,8 @@ using namespace butl;
namespace bpkg
{
void
- pkg_disfigure (const dir_path& c,
- const common_options& o,
+ pkg_disfigure (const common_options& o,
+ database& db,
transaction& t,
const shared_ptr<selected_package>& p,
bool clean,
@@ -29,28 +29,32 @@ namespace bpkg
l4 ([&]{trace << *p;});
- database& db (t.database ());
tracer_guard tg (db, trace);
// Check that we have no dependents.
//
if (p->state == package_state::configured)
{
- using query = query<package_dependent>;
-
- auto r (db.query<package_dependent> (query::name == p->name));
-
- if (!r.empty ())
+ // Query dependents in all implicitly associated databases, including
+ // the current database.
+ //
+ diag_record dr;
+ for (associated_config& ac: db.implicit_associations ())
{
- diag_record dr;
- dr << fail << "package " << p->name << " still has dependents:";
+ auto r (query_dependents (ac.db, p->name, db));
- for (const package_dependent& pd: r)
+ if (!r.empty ())
{
- dr << info << "package " << pd.name;
+ if (dr.empty ())
+ dr << fail << "package " << p->name << " still has dependents:";
+
+ for (const package_dependent& pd: r)
+ {
+ dr << info << "package " << pd.name;
- if (pd.constraint)
- dr << " on " << p->name << " " << *pd.constraint;
+ if (pd.constraint)
+ dr << " on " << p->name << " " << *pd.constraint;
+ }
}
}
}
@@ -66,6 +70,12 @@ namespace bpkg
return;
}
+ // @@ EC If substate == package_substate::reference, then exclude this
+ // configuration from dependent_configs of the selected package in the
+ // referenced configuration. Then do as for the system substate above
+ // and return.
+ //
+
// Since we are no longer configured, clear the prerequisites list.
//
p->prerequisites.clear ();
@@ -75,8 +85,8 @@ namespace bpkg
if (!simulate)
{
- dir_path src_root (p->effective_src_root (c));
- dir_path out_root (p->effective_out_root (c));
+ dir_path src_root (p->effective_src_root (db.config));
+ dir_path out_root (p->effective_out_root (db.config));
l4 ([&]{trace << "src_root: " << src_root << ", "
<< "out_root: " << out_root;});
@@ -207,7 +217,7 @@ namespace bpkg
package_name n (parse_package_name (args.next (),
false /* allow_version */));
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
shared_ptr<selected_package> p (db.find<selected_package> (n));
@@ -221,7 +231,7 @@ namespace bpkg
// Commits the transaction.
//
- pkg_disfigure (c, o, t, p, !o.keep_out (), false /* simulate */);
+ pkg_disfigure (o, db, t, p, !o.keep_out (), false /* simulate */);
assert (p->state == package_state::unpacked ||
p->state == package_state::transient);
diff --git a/bpkg/pkg-disfigure.hxx b/bpkg/pkg-disfigure.hxx
index 5121050..d15b007 100644
--- a/bpkg/pkg-disfigure.hxx
+++ b/bpkg/pkg-disfigure.hxx
@@ -22,8 +22,8 @@ namespace bpkg
// for that matter).
//
void
- pkg_disfigure (const dir_path& configuration,
- const common_options&,
+ pkg_disfigure (const common_options&,
+ database&,
transaction&,
const shared_ptr<selected_package>&,
bool clean,
diff --git a/bpkg/pkg-drop.cxx b/bpkg/pkg-drop.cxx
index 6ea6769..b7df672 100644
--- a/bpkg/pkg-drop.cxx
+++ b/bpkg/pkg-drop.cxx
@@ -102,18 +102,24 @@ namespace bpkg
dependent_names& dns,
const shared_ptr<selected_package>& p)
{
- using query = query<package_dependent>;
-
- for (auto& pd: db.query<package_dependent> (query::name == p->name))
+ // Query dependents in all implicitly associated databases, including
+ // the current database.
+ //
+ for (associated_config& ac: db.implicit_associations ())
{
- const package_name& dn (pd.name);
+ database& ddb (ac.db);
- if (map_.find (dn) == map_.end ())
+ for (auto& pd: query_dependents (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 (dn) == map_.end ())
+ {
+ shared_ptr<selected_package> dp (ddb.load<selected_package> (dn));
+ dns.push_back (dependent_name {dn, p->name});
+ collect (dp, drop_reason::dependent);
+ collect_dependents (ddb, dns, dp);
+ }
}
}
}
@@ -250,15 +256,24 @@ namespace bpkg
// 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))
+ // Query dependents in all implicitly associated databases, including
+ // the current database.
+ //
+ for (associated_config& ac: db.implicit_associations ())
{
- if (map_.find (pd.name) == map_.end ())
+ database& ddb (ac.db);
+
+ for (auto& pd: query_dependents (ddb, p->name, db))
{
- keep = false;
- break;
+ if (map_.find (pd.name) == map_.end ())
+ {
+ keep = false;
+ break;
+ }
}
+
+ if (!keep)
+ break;
}
if (!keep)
@@ -284,14 +299,16 @@ namespace bpkg
drop_package package;
};
+ // @@ EC Add db to the key, with all the consequences (see pkg-build for
+ // details).
+ //
map<package_name, data_type> map_;
};
// Drop ordered list of packages.
//
static int
- pkg_drop (const dir_path& c,
- const pkg_drop_options& o,
+ pkg_drop (const pkg_drop_options& o,
database& db,
const drop_packages& pkgs,
bool drop_prq,
@@ -372,7 +389,7 @@ namespace bpkg
// Commits the transaction.
//
- pkg_disfigure (c, o, t, p, true /* clean */, false /* simulate */);
+ pkg_disfigure (o, db, t, p, true /* clean */, false /* simulate */);
assert (p->state == package_state::unpacked ||
p->state == package_state::transient);
@@ -407,7 +424,7 @@ namespace bpkg
// 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;
@@ -436,7 +453,7 @@ namespace bpkg
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
@@ -576,6 +593,6 @@ namespace bpkg
t.commit ();
}
- return pkg_drop (c, o, db, pkgs, drop_prq, need_prompt);
+ return pkg_drop (o, db, pkgs, drop_prq, need_prompt);
}
}
diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx
index 24883c5..d496c49 100644
--- a/bpkg/pkg-fetch.cxx
+++ b/bpkg/pkg-fetch.cxx
@@ -24,7 +24,7 @@ namespace bpkg
// Return the selected package object which may replace the existing one.
//
static shared_ptr<selected_package>
- pkg_fetch (dir_path c,
+ pkg_fetch (database& db,
transaction& t,
package_name n,
version v,
@@ -35,18 +35,16 @@ namespace bpkg
{
tracer trace ("pkg_fetch");
- database& db (t.database ());
tracer_guard tg (db, trace);
- // Make the archive and configuration paths absolute and normalized.
- // If the archive is inside the configuration, use the relative path.
- // This way we can move the configuration around.
+ // Make the archive path absolute and normalized. If the archive is
+ // inside the configuration, use the relative path. This way we can move
+ // the configuration around.
//
- normalize (c, "configuration");
normalize (a, "archive");
- if (a.sub (c))
- a = a.leaf (c);
+ if (a.sub (db.config))
+ a = a.leaf (db.config);
shared_ptr<selected_package> p (db.find<selected_package> (n));
if (p != nullptr)
@@ -55,7 +53,7 @@ namespace bpkg
// replacing. Once this is done, there is no going back. If things
// go badly, we can't simply abort the transaction.
//
- pkg_purge_fs (c, t, p, simulate);
+ pkg_purge_fs (db, t, p, simulate);
// Note that if the package name spelling changed then we need to update
// it, to make sure that the subsequent commands don't fail and the
@@ -113,14 +111,13 @@ namespace bpkg
// or fetching one.
//
static void
- pkg_fetch_check (const dir_path& c,
- transaction& t,
+ pkg_fetch_check (database& db,
+ transaction&,
const package_name& n,
bool replace)
{
tracer trace ("pkg_fetch_check");
- database& db (t.database ());
tracer_guard tg (db, trace);
if (shared_ptr<selected_package> p = db.find<selected_package> (n))
@@ -132,7 +129,8 @@ namespace bpkg
{
diag_record dr (fail);
- dr << "package " << n << " already exists in configuration " << c <<
+ dr << "package " << n << " already exists in configuration "
+ << db.config <<
info << "version: " << p->version_string ()
<< ", state: " << p->state
<< ", substate: " << p->substate;
@@ -145,7 +143,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_fetch (const common_options& co,
- const dir_path& c,
+ database& db,
transaction& t,
path a,
bool replace,
@@ -170,12 +168,12 @@ namespace bpkg
// Check/diagnose an already existing package.
//
- pkg_fetch_check (c, t, m.name, replace);
+ pkg_fetch_check (db, t, m.name, replace);
// Use the special root repository fragment as the repository fragment of
// this package.
//
- return pkg_fetch (c,
+ return pkg_fetch (db,
t,
move (m.name),
move (m.version),
@@ -187,7 +185,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_fetch (const common_options& co,
- const dir_path& c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -196,14 +194,15 @@ namespace bpkg
{
tracer trace ("pkg_fetch");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Check/diagnose an already existing package.
//
- pkg_fetch_check (c, t, n, replace);
+ pkg_fetch_check (db, t, n, replace);
- check_any_available (c, t);
+ database& mdb (db.main_database ());
+
+ check_any_available (mdb, t);
// Note that here we compare including the revision (unlike, say in
// pkg-status). Which means one cannot just specify 1.0.0 and get 1.0.0+1
@@ -211,7 +210,7 @@ namespace bpkg
// a low-level command where some extra precision doesn't hurt.
//
shared_ptr<available_package> ap (
- db.find<available_package> (available_package_id (n, v)));
+ mdb.find<available_package> (available_package_id (n, v)));
if (ap == nullptr)
fail << "package " << n << " " << v << " is not available";
@@ -243,7 +242,7 @@ namespace bpkg
<< "from " << pl->repository_fragment->name;
auto_rmfile arm;
- path a (c / pl->location.leaf ());
+ path a (db.config / pl->location.leaf ());
if (!simulate)
{
@@ -269,7 +268,7 @@ namespace bpkg
}
shared_ptr<selected_package> p (
- pkg_fetch (c,
+ pkg_fetch (db,
t,
move (n),
move (v),
@@ -290,7 +289,7 @@ namespace bpkg
dir_path c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
@@ -305,7 +304,7 @@ namespace bpkg
info << "run 'bpkg help pkg-fetch' for more information";
p = pkg_fetch (o,
- c,
+ db,
t,
path (args.next ()),
o.replace (),
@@ -327,7 +326,7 @@ namespace bpkg
info << "run 'bpkg help pkg-fetch' for more information";
p = pkg_fetch (o,
- c,
+ db,
t,
move (n),
move (v),
diff --git a/bpkg/pkg-fetch.hxx b/bpkg/pkg-fetch.hxx
index e9d753b..9dd53f6 100644
--- a/bpkg/pkg-fetch.hxx
+++ b/bpkg/pkg-fetch.hxx
@@ -23,7 +23,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_fetch (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
path archive,
bool replace,
@@ -36,7 +36,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_fetch (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
package_name,
version,
diff --git a/bpkg/pkg-purge.cxx b/bpkg/pkg-purge.cxx
index f6589bb..3e4e3d4 100644
--- a/bpkg/pkg-purge.cxx
+++ b/bpkg/pkg-purge.cxx
@@ -15,7 +15,7 @@ using namespace butl;
namespace bpkg
{
void
- pkg_purge_fs (const dir_path& c,
+ pkg_purge_fs (database& db,
transaction& t,
const shared_ptr<selected_package>& p,
bool simulate,
@@ -26,7 +26,6 @@ namespace bpkg
assert (p->state == package_state::fetched ||
p->state == package_state::unpacked);
- database& db (t.database ());
tracer_guard tg (db, trace);
try
@@ -35,7 +34,7 @@ namespace bpkg
{
if (!simulate)
{
- dir_path d (p->effective_src_root (c));
+ dir_path d (p->effective_src_root (db.config));
if (exists (d)) // Don't complain if someone did our job for us.
rm_r (d);
@@ -56,7 +55,9 @@ namespace bpkg
{
if (!simulate)
{
- path a (p->archive->absolute () ? *p->archive : c / *p->archive);
+ path a (p->archive->absolute ()
+ ? *p->archive
+ : db.config / *p->archive);
if (exists (a))
rm (a);
@@ -83,7 +84,7 @@ namespace bpkg
}
void
- pkg_purge (const dir_path& c,
+ pkg_purge (database& db,
transaction& t,
const shared_ptr<selected_package>& p,
bool simulate)
@@ -93,11 +94,10 @@ namespace bpkg
tracer trace ("pkg_purge");
- database& db (t.database ());
tracer_guard tg (db, trace);
assert (!p->out_root);
- pkg_purge_fs (c, t, p, simulate, true);
+ pkg_purge_fs (db, t, p, simulate, true);
db.erase (p);
t.commit ();
@@ -120,7 +120,7 @@ namespace bpkg
package_name n (parse_package_name (args.next (),
false /* allow_version */));
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
shared_ptr<selected_package> p (db.find<selected_package> (n));
@@ -201,7 +201,7 @@ namespace bpkg
else
{
assert (!p->out_root);
- pkg_purge_fs (c, t, p, false /* simulate */, !o.keep ());
+ pkg_purge_fs (db, t, p, false /* simulate */, !o.keep ());
}
// Finally, update the database state.
diff --git a/bpkg/pkg-purge.hxx b/bpkg/pkg-purge.hxx
index 215e468..ac82bf4 100644
--- a/bpkg/pkg-purge.hxx
+++ b/bpkg/pkg-purge.hxx
@@ -19,7 +19,7 @@ namespace bpkg
// transaction. If this fails, set the package state to broken.
//
void
- pkg_purge (const dir_path& configuration,
+ pkg_purge (database&,
transaction&,
const shared_ptr<selected_package>&,
bool simulate);
@@ -29,7 +29,7 @@ namespace bpkg
// set the package state to broken, commit the transaction, and fail.
//
void
- pkg_purge_fs (const dir_path& configuration,
+ pkg_purge_fs (database&,
transaction&,
const shared_ptr<selected_package>&,
bool simulate,
diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx
index 655ee8b..9bbea90 100644
--- a/bpkg/pkg-status.cxx
+++ b/bpkg/pkg-status.cxx
@@ -266,7 +266,7 @@ namespace bpkg
const dir_path& c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx
index 9685f3e..c06de40 100644
--- a/bpkg/pkg-unpack.cxx
+++ b/bpkg/pkg-unpack.cxx
@@ -24,14 +24,13 @@ namespace bpkg
// diagnose all the illegal cases.
//
static void
- pkg_unpack_check (const dir_path& c,
- transaction& t,
+ pkg_unpack_check (database& db,
+ transaction&,
const package_name& n,
bool replace)
{
tracer trace ("pkg_update_check");
- database& db (t.database ());
tracer_guard tg (db, trace);
if (shared_ptr<selected_package> p = db.find<selected_package> (n))
@@ -43,7 +42,8 @@ namespace bpkg
{
diag_record dr (fail);
- dr << "package " << n << " already exists in configuration " << c <<
+ dr << "package " << n << " already exists in configuration "
+ << db.config <<
info << "version: " << p->version_string ()
<< ", state: " << p->state
<< ", substate: " << p->substate;
@@ -59,7 +59,7 @@ namespace bpkg
//
static shared_ptr<selected_package>
pkg_unpack (const common_options& o,
- dir_path c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -70,7 +70,6 @@ namespace bpkg
{
tracer trace ("pkg_unpack");
- database& db (t.database ());
tracer_guard tg (db, trace);
optional<string> mc;
@@ -78,15 +77,14 @@ namespace bpkg
if (!simulate)
mc = sha256 (o, d / manifest_file);
- // Make the package and configuration paths absolute and normalized.
- // If the package is inside the configuration, use the relative path.
- // This way we can move the configuration around.
+ // Make the package path absolute and normalized. If the package is inside
+ // the configuration, use the relative path. This way we can move the
+ // configuration around.
//
- normalize (c, "configuration");
normalize (d, "package");
- if (d.sub (c))
- d = d.leaf (c);
+ if (d.sub (db.config))
+ d = d.leaf (db.config);
shared_ptr<selected_package> p (db.find<selected_package> (n));
@@ -96,7 +94,7 @@ namespace bpkg
// replacing. Once this is done, there is no going back. If things
// go badly, we can't simply abort the transaction.
//
- pkg_purge_fs (c, t, p, simulate);
+ pkg_purge_fs (db, t, p, simulate);
// Note that if the package name spelling changed then we need to update
// it, to make sure that the subsequent commands don't fail and the
@@ -150,7 +148,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_unpack (const common_options& o,
- const dir_path& c,
+ database& db,
transaction& t,
const dir_path& d,
bool replace,
@@ -177,19 +175,19 @@ namespace bpkg
// Check/diagnose an already existing package.
//
- pkg_unpack_check (c, t, m.name, replace);
+ pkg_unpack_check (db, t, m.name, replace);
// Fix-up the package version.
//
if (optional<version> v = package_iteration (
- o, c, t, d, m.name, m.version, true /* check_external */))
+ o, db, t, d, m.name, m.version, true /* check_external */))
m.version = move (*v);
// Use the special root repository fragment as the repository fragment of
// this package.
//
return pkg_unpack (o,
- c,
+ db,
t,
move (m.name),
move (m.version),
@@ -201,7 +199,7 @@ namespace bpkg
shared_ptr<selected_package>
pkg_unpack (const common_options& o,
- const dir_path& c,
+ database& db,
transaction& t,
package_name n,
version v,
@@ -210,14 +208,13 @@ namespace bpkg
{
tracer trace ("pkg_unpack");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Check/diagnose an already existing package.
//
- pkg_unpack_check (c, t, n, replace);
+ pkg_unpack_check (db, t, n, replace);
- check_any_available (c, t);
+ check_any_available (db, t);
// Note that here we compare including the revision (see pkg-fetch()
// implementation for more details).
@@ -253,7 +250,7 @@ namespace bpkg
const repository_location& rl (pl->repository_fragment->location);
return pkg_unpack (o,
- c,
+ db,
t,
move (n),
move (v),
@@ -265,20 +262,20 @@ namespace bpkg
shared_ptr<selected_package>
pkg_unpack (const common_options& co,
- const dir_path& c,
+ database& db,
transaction& t,
const package_name& name,
bool simulate)
{
tracer trace ("pkg_unpack");
- database& db (t.database ());
tracer_guard tg (db, trace);
shared_ptr<selected_package> p (db.find<selected_package> (name));
if (p == nullptr)
- fail << "package " << name << " does not exist in configuration " << c;
+ fail << "package " << name << " does not exist in configuration "
+ << db.config;
if (p->state != package_state::fetched)
fail << "package " << name << " is " << p->state <<
@@ -293,7 +290,8 @@ namespace bpkg
// Also, since we must have verified the archive during fetch,
// here we can just assume what the resulting directory will be.
//
- dir_path d (c / dir_path (p->name.string () + '-' + p->version.string ()));
+ dir_path d (db.config /
+ dir_path (p->name.string () + '-' + p->version.string ()));
if (exists (d))
fail << "package directory " << d << " already exists";
@@ -306,7 +304,7 @@ namespace bpkg
// If the archive path is not absolute, then it must be relative
// to the configuration.
//
- path a (p->archive->absolute () ? *p->archive : c / *p->archive);
+ path a (p->archive->absolute () ? *p->archive : db.config / *p->archive);
l4 ([&]{trace << "archive: " << a;});
@@ -317,17 +315,17 @@ namespace bpkg
try
{
- pair<process, process> pr (start_extract (co, a, c));
+ pair<process, process> pr (start_extract (co, a, db.config));
// While it is reasonable to assuming the child process issued
// diagnostics, tar, specifically, doesn't mention the archive name.
//
if (!pr.second.wait () || !pr.first.wait ())
- fail << "unable to extract " << a << " to " << c;
+ fail << "unable to extract " << a << " to " << db.config;
}
catch (const process_error& e)
{
- fail << "unable to extract " << a << " to " << c << ": " << e;
+ fail << "unable to extract " << a << " to " << db.config << ": " << e;
}
mc = sha256 (co, d / manifest_file);
@@ -356,7 +354,7 @@ namespace bpkg
const dir_path& c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
shared_ptr<selected_package> p;
@@ -371,7 +369,7 @@ namespace bpkg
info << "run 'bpkg help pkg-unpack' for more information";
p = pkg_unpack (o,
- c,
+ db,
t,
dir_path (args.next ()),
o.replace (),
@@ -400,9 +398,9 @@ namespace bpkg
// "unpack" it from the directory-based repository.
//
p = v.empty ()
- ? pkg_unpack (o, c, t, n, false /* simulate */)
+ ? pkg_unpack (o, db, t, n, false /* simulate */)
: pkg_unpack (o,
- c,
+ db,
t,
move (n),
move (v),
diff --git a/bpkg/pkg-unpack.hxx b/bpkg/pkg-unpack.hxx
index 107322b..c6496d6 100644
--- a/bpkg/pkg-unpack.hxx
+++ b/bpkg/pkg-unpack.hxx
@@ -23,7 +23,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_unpack (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
const dir_path&,
bool replace,
@@ -34,7 +34,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_unpack (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
const package_name&,
bool simulate);
@@ -45,7 +45,7 @@ namespace bpkg
//
shared_ptr<selected_package>
pkg_unpack (const common_options&,
- const dir_path& configuration,
+ database&,
transaction&,
package_name,
version,
diff --git a/bpkg/pkg-update.hxx b/bpkg/pkg-update.hxx
index d7b9536..41fead0 100644
--- a/bpkg/pkg-update.hxx
+++ b/bpkg/pkg-update.hxx
@@ -28,13 +28,12 @@ namespace bpkg
}
inline void
- pkg_update (const dir_path& configuration,
- const common_options& o,
+ pkg_update (const common_options& o,
const string& cmd_variant,
const strings& common_vars,
const vector<pkg_command_vars>& pkgs)
{
- pkg_command ("update", configuration, o, cmd_variant, common_vars, pkgs);
+ pkg_command ("update", o, cmd_variant, common_vars, pkgs);
}
}
diff --git a/bpkg/rep-add.cxx b/bpkg/rep-add.cxx
index 6856437..81b1286 100644
--- a/bpkg/rep-add.cxx
+++ b/bpkg/rep-add.cxx
@@ -16,12 +16,12 @@ namespace bpkg
{
shared_ptr<repository>
rep_add (const common_options& o,
- transaction& t,
+ database& db,
+ transaction&,
const repository_location& rl)
{
const string& rn (rl.canonical_name ());
- database& db (t.database ());
shared_ptr<repository> r (db.find<repository> (rn));
bool updated (false);
@@ -65,7 +65,7 @@ namespace bpkg
fail << "repository location argument expected" <<
info << "run 'bpkg help rep-add' for more information";
- database db (open (c, trace));
+ database db (c, trace, false /* pre_attach */);
transaction t (db);
session s; // Repository dependencies can have cycles.
@@ -77,7 +77,7 @@ namespace bpkg
? optional<repository_type> (o.type ())
: nullopt));
- rep_add (o, t, rl);
+ rep_add (o, db, t, rl);
}
t.commit ();
diff --git a/bpkg/rep-add.hxx b/bpkg/rep-add.hxx
index 0062cdc..d5cec5d 100644
--- a/bpkg/rep-add.hxx
+++ b/bpkg/rep-add.hxx
@@ -22,7 +22,10 @@ namespace bpkg
// repository if it is not already.
//
shared_ptr<repository>
- rep_add (const common_options&, transaction&, const repository_location&);
+ rep_add (const common_options&,
+ database&,
+ transaction&,
+ const repository_location&);
}
#endif // BPKG_REP_ADD_HXX
diff --git a/bpkg/rep-fetch.cxx b/bpkg/rep-fetch.cxx
index ef4c110..85e64d7 100644
--- a/bpkg/rep-fetch.cxx
+++ b/bpkg/rep-fetch.cxx
@@ -49,6 +49,7 @@ namespace bpkg
static rep_fetch_data
rep_fetch_pkg (const common_options& co,
const dir_path* conf,
+ database* db,
const repository_location& rl,
const optional<string>& dependent_trust,
bool ignore_unknown)
@@ -71,7 +72,7 @@ namespace bpkg
if (a)
{
cert = authenticate_certificate (
- co, conf, cert_pem, rl, dependent_trust);
+ co, conf, db, cert_pem, rl, dependent_trust);
a = !cert->dummy ();
}
@@ -556,6 +557,7 @@ namespace bpkg
static rep_fetch_data
rep_fetch (const common_options& co,
const dir_path* conf,
+ database* db,
const repository_location& rl,
const optional<string>& dt,
bool iu,
@@ -563,7 +565,7 @@ namespace bpkg
{
switch (rl.type ())
{
- case repository_type::pkg: return rep_fetch_pkg (co, conf, rl, dt, iu);
+ case repository_type::pkg: return rep_fetch_pkg (co, conf, db, rl, dt, iu);
case repository_type::dir: return rep_fetch_dir (co, rl, iu, ev);
case repository_type::git: return rep_fetch_git (co, conf, rl, iu, ev);
}
@@ -579,7 +581,13 @@ namespace bpkg
bool iu,
bool ev)
{
- return rep_fetch (co, conf, rl, nullopt /* dependent_trust */, iu, ev);
+ return rep_fetch (co,
+ conf,
+ nullptr /* database */,
+ rl,
+ nullopt /* dependent_trust */,
+ iu,
+ ev);
}
// Return an existing repository fragment or create a new one. Update the
@@ -591,7 +599,7 @@ namespace bpkg
static shared_ptr<repository_fragment>
rep_fragment (const common_options& co,
- const dir_path& conf,
+ database& db,
transaction& t,
const repository_location& rl,
rep_fetch_data::fragment&& fr,
@@ -601,7 +609,6 @@ namespace bpkg
{
tracer trace ("rep_fragment");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Calculate the fragment location.
@@ -852,7 +859,7 @@ namespace bpkg
// details).
//
if (exists && !full_fetch)
- rep_remove_package_locations (t, rf->name);
+ rep_remove_package_locations (db, t, rf->name);
for (package_manifest& pm: fr.packages)
{
@@ -868,7 +875,7 @@ namespace bpkg
optional<version> v (
package_iteration (
co,
- conf,
+ db,
t,
path_cast<dir_path> (rl.path () / *pm.location),
pm.name,
@@ -956,7 +963,7 @@ namespace bpkg
//
static void
rep_fetch (const common_options& co,
- const dir_path& conf,
+ database& db,
transaction& t,
const shared_ptr<repository>& r,
const optional<string>& dependent_trust,
@@ -970,7 +977,6 @@ namespace bpkg
{
tracer trace ("rep_fetch(rep)");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Check that the repository is not fetched yet and register it as fetched
@@ -990,7 +996,8 @@ namespace bpkg
//
if (need_auth (co, r->location))
authenticate_certificate (co,
- &conf,
+ &db.config,
+ &db,
r->certificate,
r->location,
dependent_trust);
@@ -1059,7 +1066,8 @@ namespace bpkg
//
rep_fetch_data rfd (
rep_fetch (co,
- &conf,
+ &db.config,
+ &db,
rl,
dependent_trust,
true /* ignore_unknow */,
@@ -1079,7 +1087,7 @@ namespace bpkg
string nm (fr.friendly_name); // Don't move, still may be used.
shared_ptr<repository_fragment> rf (rep_fragment (co,
- conf,
+ db,
t,
rl,
move (fr),
@@ -1156,7 +1164,7 @@ namespace bpkg
rm (pr);
auto fetch = [&co,
- &conf,
+ &db,
&t,
&fetched_repositories,
&removed_repositories,
@@ -1171,7 +1179,7 @@ namespace bpkg
assert (i != repo_trust.end ());
rep_fetch (co,
- conf,
+ db,
t,
r,
i->second,
@@ -1206,7 +1214,7 @@ namespace bpkg
static void
rep_fetch (const common_options& o,
- const dir_path& conf,
+ database& db,
transaction& t,
const vector<lazy_shared_ptr<repository>>& repos,
bool shallow,
@@ -1215,7 +1223,6 @@ namespace bpkg
{
tracer trace ("rep_fetch(repos)");
- database& db (t.database ());
tracer_guard tg (db, trace);
// As a fist step we fetch repositories recursively building the list of
@@ -1243,7 +1250,7 @@ namespace bpkg
//
for (const lazy_shared_ptr<repository>& r: repos)
rep_fetch (o,
- conf,
+ db,
t,
r.load (),
nullopt /* dependent_trust */,
@@ -1258,7 +1265,7 @@ namespace bpkg
// Remove dangling repositories.
//
for (const shared_ptr<repository>& r: removed_repositories)
- rep_remove (conf, t, r);
+ rep_remove (db, t, r);
// Remove dangling repository fragments.
//
@@ -1277,7 +1284,7 @@ namespace bpkg
//
assert (f == rf);
- rep_remove_fragment (conf, t, rf);
+ rep_remove_fragment (db, t, rf);
}
}
@@ -1409,7 +1416,7 @@ namespace bpkg
warn << "repository state is now broken and will be cleaned up" <<
info << "run 'bpkg rep-fetch' to update";
- rep_remove_clean (o, conf, t.database ());
+ rep_remove_clean (o, db);
}
throw;
@@ -1418,7 +1425,6 @@ namespace bpkg
void
rep_fetch (const common_options& o,
- const dir_path& conf,
database& db,
const vector<repository_location>& rls,
bool shallow,
@@ -1449,12 +1455,12 @@ namespace bpkg
// case, which is ok.
//
if (ua.find (r) == ua.end () || r.load ()->location.url () != rl.url ())
- rep_add (o, t, rl);
+ rep_add (o, db, t, rl);
repos.emplace_back (r);
}
- rep_fetch (o, conf, t, repos, shallow, false /* full_fetch */, reason);
+ rep_fetch (o, db, t, repos, shallow, false /* full_fetch */, reason);
t.commit ();
}
@@ -1471,7 +1477,11 @@ namespace bpkg
//
vector<lazy_shared_ptr<repository>> repos;
- database db (open (c, trace));
+ // Pre-attach the explicitly associated databases since we call
+ // package_iteration() (see the database contructor for details).
+ //
+ database db (c, trace, true /* pre_attach */);
+
transaction t (db);
session s; // Repository dependencies can have cycles.
@@ -1535,7 +1545,7 @@ namespace bpkg
//
auto i (ua.find (r));
if (i == ua.end () || i->load ()->location.url () != rl.url ())
- r = lazy_shared_ptr<repository> (db, rep_add (o, t, rl));
+ r = lazy_shared_ptr<repository> (db, rep_add (o, db, t, rl));
}
repos.emplace_back (move (r));
@@ -1562,7 +1572,7 @@ namespace bpkg
}
}
- rep_fetch (o, c, t, repos, o.shallow (), full_fetch, reason);
+ rep_fetch (o, db, t, repos, o.shallow (), full_fetch, reason);
size_t rcount (0), pcount (0);
if (verb)
diff --git a/bpkg/rep-fetch.hxx b/bpkg/rep-fetch.hxx
index 4ddce5b..12f7672 100644
--- a/bpkg/rep-fetch.hxx
+++ b/bpkg/rep-fetch.hxx
@@ -69,7 +69,6 @@ namespace bpkg
//
void
rep_fetch (const common_options&,
- const dir_path& conf,
database&,
const vector<repository_location>&,
bool shallow,
diff --git a/bpkg/rep-list.cxx b/bpkg/rep-list.cxx
index 5b961c0..67b25bf 100644
--- a/bpkg/rep-list.cxx
+++ b/bpkg/rep-list.cxx
@@ -107,7 +107,7 @@ namespace bpkg
fail << "unexpected argument '" << args.next () << "'" <<
info << "run 'bpkg help rep-list' for more information";
- database db (open (c, trace));
+ database db (c, trace, false /* pre_attach */);
transaction t (db);
session s; // Repository dependencies can have cycles.
diff --git a/bpkg/rep-remove.cxx b/bpkg/rep-remove.cxx
index c377fc5..6e9fcea 100644
--- a/bpkg/rep-remove.cxx
+++ b/bpkg/rep-remove.cxx
@@ -94,11 +94,12 @@ namespace bpkg
}
void
- rep_remove_package_locations (transaction& t, const string& fragment_name)
+ rep_remove_package_locations (database& db,
+ transaction&,
+ const string& fragment_name)
{
tracer trace ("rep_remove_package_locations");
- database& db (t.database ());
tracer_guard tg (db, trace);
using query = query<repository_fragment_package>;
@@ -141,15 +142,12 @@ namespace bpkg
}
void
- rep_remove (const dir_path& c,
- transaction& t,
- const shared_ptr<repository>& r)
+ rep_remove (database& db, transaction& t, const shared_ptr<repository>& r)
{
assert (!r->name.empty ()); // Can't be the root repository.
tracer trace ("rep_remove");
- database& db (t.database ());
tracer_guard tg (db, trace);
if (reachable (db, r))
@@ -164,7 +162,7 @@ namespace bpkg
// Remove dangling repository fragments.
//
for (const repository::fragment_type& fr: r->fragments)
- rep_remove_fragment (c, t, fr.fragment.load ());
+ rep_remove_fragment (db, t, fr.fragment.load ());
// If there are no repositories stayed in the database then no repository
// fragments should stay either.
@@ -188,7 +186,7 @@ namespace bpkg
if (!d.empty ())
{
- dir_path sd (c / repos_dir / d);
+ dir_path sd (db.config / repos_dir / d);
if (exists (sd))
{
@@ -219,13 +217,12 @@ namespace bpkg
}
void
- rep_remove_fragment (const dir_path& c,
+ rep_remove_fragment (database& db,
transaction& t,
const shared_ptr<repository_fragment>& rf)
{
tracer trace ("rep_remove_fragment");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Bail out if the repository fragment is still used.
@@ -240,7 +237,7 @@ namespace bpkg
// it contains. Note that this must be done before the repository fragment
// removal.
//
- rep_remove_package_locations (t, rf->name);
+ rep_remove_package_locations (db, t, rf->name);
// Remove the repository fragment.
//
@@ -265,10 +262,10 @@ namespace bpkg
// Prior to removing a prerequisite/complement we need to make sure it
// still exists, which may not be the case due to the dependency cycle.
//
- auto remove = [&c, &db, &t] (const lazy_weak_ptr<repository>& rp)
+ auto remove = [&db, &t] (const lazy_weak_ptr<repository>& rp)
{
if (shared_ptr<repository> r = db.find<repository> (rp.object_id ()))
- rep_remove (c, t, r);
+ rep_remove (db, t, r);
};
for (const lazy_weak_ptr<repository>& cr: rf->complements)
@@ -285,10 +282,7 @@ namespace bpkg
}
void
- rep_remove_clean (const common_options& o,
- const dir_path& c,
- database& db,
- bool quiet)
+ rep_remove_clean (const common_options& o, database& db, bool quiet)
{
tracer trace ("rep_remove_clean");
tracer_guard tg (db, trace);
@@ -336,7 +330,7 @@ namespace bpkg
// Remove repository state subdirectories.
//
- dir_path rd (c / repos_dir);
+ dir_path rd (db.config / repos_dir);
try
{
@@ -384,13 +378,13 @@ namespace bpkg
dr << info << "run 'bpkg help rep-remove' for more information";
}
- database db (open (c, trace));
+ database db (c, trace, false /* pre_attach */);
// Clean the configuration if requested.
//
if (o.clean ())
{
- rep_remove_clean (o, c, db, false /* quiet */);
+ rep_remove_clean (o, db, false /* quiet */);
return 0;
}
@@ -484,7 +478,7 @@ namespace bpkg
//
for (const lazy_shared_ptr<repository>& r: repos)
{
- rep_remove (c, t, r.load ());
+ rep_remove (db, t, r.load ());
if (verb && !o.no_result ())
text << "removed " << r.object_id ();
diff --git a/bpkg/rep-remove.hxx b/bpkg/rep-remove.hxx
index f85aec5..ca6e640 100644
--- a/bpkg/rep-remove.hxx
+++ b/bpkg/rep-remove.hxx
@@ -20,15 +20,13 @@ namespace bpkg
// repository fragments.
//
void
- rep_remove (const dir_path& conf,
- transaction&,
- const shared_ptr<repository>&);
+ rep_remove (database&, transaction&, const shared_ptr<repository>&);
// Remove a repository fragment if it is not referenced by any repository,
// also removing its unreachable complements and prerequisites.
//
void
- rep_remove_fragment (const dir_path& conf,
+ rep_remove_fragment (database&,
transaction&,
const shared_ptr<repository_fragment>&);
@@ -50,16 +48,15 @@ namespace bpkg
// - Remove all available packages.
//
void
- rep_remove_clean (const common_options&,
- const dir_path& conf,
- database&,
- bool quiet = true);
+ rep_remove_clean (const common_options&, database&, bool quiet = true);
// Remove a repository fragment from locations of the available packages it
// contains. Remove packages that come from only this repository fragment.
//
void
- rep_remove_package_locations (transaction&, const string& fragment_name);
+ rep_remove_package_locations (database&,
+ transaction&,
+ const string& fragment_name);
}
#endif // BPKG_REP_REMOVE_HXX
diff --git a/bpkg/system-repository.cxx b/bpkg/system-repository.cxx
index de4e61e..d7a47b7 100644
--- a/bpkg/system-repository.cxx
+++ b/bpkg/system-repository.cxx
@@ -5,9 +5,7 @@
namespace bpkg
{
- system_repository_type system_repository;
-
- const version& system_repository_type::
+ const version& system_repository::
insert (const package_name& name, const version& v, bool authoritative)
{
auto p (map_.emplace (name, system_package {v, authoritative}));
diff --git a/bpkg/system-repository.hxx b/bpkg/system-repository.hxx
index 1168ec0..f33d622 100644
--- a/bpkg/system-repository.hxx
+++ b/bpkg/system-repository.hxx
@@ -32,7 +32,7 @@ namespace bpkg
bool authoritative;
};
- class system_repository_type
+ class system_repository
{
public:
const version&
@@ -48,8 +48,6 @@ namespace bpkg
private:
std::map<package_name, system_package> map_;
};
-
- extern system_repository_type system_repository;
}
#endif // BPKG_SYSTEM_REPOSITORY_HXX
diff --git a/bpkg/types-parsers.cxx b/bpkg/types-parsers.cxx
index be95219..d5ddb28 100644
--- a/bpkg/types-parsers.cxx
+++ b/bpkg/types-parsers.cxx
@@ -67,6 +67,31 @@ namespace bpkg
parse_path (x, s);
}
+ void parser<uuid>::
+ parse (uuid& x, bool& xs, scanner& s)
+ {
+ xs = true;
+
+ const char* o (s.next ());
+
+ if (!s.more ())
+ throw missing_value (o);
+
+ const char* v (s.next ());
+
+ try
+ {
+ x = uuid (v);
+
+ if (x.nil ())
+ throw invalid_value (o, v);
+ }
+ catch (const invalid_argument&)
+ {
+ throw invalid_value (o, v);
+ }
+ }
+
void parser<auth>::
parse (auth& x, bool& xs, scanner& s)
{
diff --git a/bpkg/types-parsers.hxx b/bpkg/types-parsers.hxx
index 38b7cee..d687156 100644
--- a/bpkg/types-parsers.hxx
+++ b/bpkg/types-parsers.hxx
@@ -49,6 +49,16 @@ namespace bpkg
};
template <>
+ struct parser<uuid>
+ {
+ static void
+ parse (uuid&, bool&, scanner&);
+
+ static void
+ merge (uuid& b, const uuid& a) {b = a;}
+ };
+
+ template <>
struct parser<auth>
{
static void
diff --git a/bpkg/types.hxx b/bpkg/types.hxx
index 65dba60..26e3b5d 100644
--- a/bpkg/types.hxx
+++ b/bpkg/types.hxx
@@ -23,6 +23,7 @@
#include <libbutl/url.mxx>
#include <libbutl/path.mxx>
+#include <libbutl/uuid.hxx>
#include <libbutl/process.mxx>
#include <libbutl/utility.mxx> // icase_compare_string,
// compare_reference_target
@@ -92,6 +93,10 @@ namespace bpkg
using butl::basic_path;
using butl::invalid_path;
+ // <libbutl/uuid.mxx>
+ //
+ using butl::uuid;
+
using butl::path_cast;
using paths = std::vector<path>;
diff --git a/bpkg/utility.hxx b/bpkg/utility.hxx
index 4360118..cf28644 100644
--- a/bpkg/utility.hxx
+++ b/bpkg/utility.hxx
@@ -42,6 +42,11 @@ namespace bpkg
using butl::icasecmp;
using butl::reverse_iterate;
+ using butl::alpha;
+ using butl::alnum;
+ using butl::digit;
+ using butl::xdigit;
+
using butl::make_guard;
using butl::make_exception_guard;
diff --git a/doc/buildfile b/doc/buildfile
index 201d41c..42ec7c1 100644
--- a/doc/buildfile
+++ b/doc/buildfile
@@ -2,6 +2,7 @@
# license : MIT; see accompanying LICENSE file
cmds = \
+bpkg-cfg-add \
bpkg-cfg-create \
bpkg-help \
bpkg-pkg-build \
diff --git a/doc/cli.sh b/doc/cli.sh
index 995efcc..119d112 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 help pkg-clean pkg-configure pkg-disfigure \
+pages="cfg-add cfg-create 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 repository-types \
diff --git a/manifest b/manifest
index e42542b..39c187a 100644
--- a/manifest
+++ b/manifest
@@ -20,5 +20,6 @@ depends: * bpkg >= 0.13.0
requires: ? cli ; Only required if changing .cli files.
depends: libodb [2.5.0-b.20.1 2.5.0-b.21)
depends: libodb-sqlite [2.5.0-b.20.1 2.5.0-b.21)
+depends: libsqlite3 ^3.21.0 ; ATTACH in transaction
depends: libbutl [0.14.0-a.0.1 0.14.0-a.1)
depends: libbpkg [0.14.0-a.0.1 0.14.0-a.1)
diff --git a/tests/cfg-add.testscript b/tests/cfg-add.testscript
new file mode 100644
index 0000000..d5ef55b
--- /dev/null
+++ b/tests/cfg-add.testscript
@@ -0,0 +1,124 @@
+# file : tests/cfg-add.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include common.testscript
+
+cfg_create += 2>!
+
+# @@ To verify the association result use cfg-list command rather than
+# pkg-status, when implemented.
+#
+
+test.arguments += -d cfg
+
+uuid = '18f48b4b-b5d9-4712-b98c-1930df1c4228'
+
+: success
+:
+{
+ $cfg_create -d cfg --name test --config-uuid "$uuid";
+ $cfg_create -d acfg --name shared &acfg/***;
+
+ # Associate configuration.
+ #
+ $* acfg 2>>/~%EOE%;
+ %associated target configuration .+/acfg/%
+ EOE
+
+ $pkg_status -d cfg libfoo >'libfoo unknown';
+ $pkg_status -d acfg libfoo >'libfoo unknown';
+
+ # Test that the repeated association is reported.
+ #
+ $* acfg 2>>/~%EOE% != 0;
+ %error: configuration [-0-9a-fA-F]{36} is already associated%
+ EOE
+
+ rm -r acfg;
+
+ $cfg_create -d acfg --name shared &acfg/***;
+
+ # Test that the path clash is reported.
+ #
+ $* acfg 2>>/~%EOE% != 0;
+ %error: configuration '.+/acfg/' is already associated: [-0-9a-fA-F]{36}%
+ EOE
+
+ # Test that the name clash is reported.
+ #
+ $cfg_create -d acfg2 --name shared &acfg2/***;
+
+ $* acfg2 2>>/~%EOE% != 0;
+ %error: configuration named 'shared' is already associated: [-0-9a-fA-F]{36}%
+ info: consider specifying name explicitly with --name|-n
+ EOE
+
+ $* acfg2 -n shared 2>>/~%EOE% != 0;
+ %error: configuration named 'shared' is already associated: [-0-9a-fA-F]{36}%
+ EOE
+
+ # Associate the second configuration.
+ #
+ $* acfg2 -n shared2 2>>/~%EOE%;
+ %associated target configuration .+%
+ EOE
+
+ $pkg_status -d cfg libfoo >'libfoo unknown';
+ $pkg_status -d acfg2 libfoo >'libfoo unknown';
+
+ # Test that the configuration type mismatch is reported.
+ #
+ mv cfg cfg.tmp;
+ $cfg_create -d cfg --type host --config-uuid "$uuid";
+
+ $* -d acfg2 cfg 2>>EOE != 0;
+ error: configuration type 'host' doesn't match existing association type 'target'
+ EOE
+ rm -r cfg;
+ mv cfg.tmp cfg;
+
+ # Make the implicit association explicit.
+ #
+ $* -d acfg2 cfg 2>>/~%EOE%;
+ %associated target configuration .+%
+ EOE
+
+ $pkg_status -d cfg libfoo >'libfoo unknown';
+ $pkg_status -d acfg2 libfoo >'libfoo unknown';
+
+ $* -d acfg2 cfg 2>>/~%EOE% != 0;
+ %error: configuration [-0-9a-fA-F]{36} is already associated%
+ EOE
+
+ # Test that the repeated reverse association is reported.
+ #
+ rm -r cfg;
+ $cfg_create -d cfg --config-uuid "$uuid";
+
+ $* acfg2 2>>/~"%EOE%" != 0;
+ %error: current configuration $uuid is already associated with '.+/acfg2/'%
+ EOE
+
+ # Test that the reverse association path clash is reported.
+ #
+ rm -r cfg;
+ $cfg_create -d cfg --name test &cfg/***;
+
+ $* acfg2 2>>/~%EOE% != 0;
+ %error: current configuration '.+/cfg/' is already associated with '.+/acfg2/'%
+ EOE
+
+ # Make sure that current configuration is reverse associated as unnamed.
+ #
+ # @@ Make sure that's really the case when the cfg-list command is
+ # implemented.
+ #
+ $cfg_create -d cfg2 --name test &cfg2/***;
+
+ $* -d cfg2 acfg2 2>>/~%EOE%;
+ %associated target configuration .+/acfg2/%
+ EOE
+
+ $pkg_status -d cfg2 libfoo >'libfoo unknown';
+ $pkg_status -d acfg2 libfoo >'libfoo unknown'
+}
diff --git a/tests/cfg-create.testscript b/tests/cfg-create.testscript
index 9461dad..ce8a96d 100644
--- a/tests/cfg-create.testscript
+++ b/tests/cfg-create.testscript
@@ -5,6 +5,9 @@
config_cxx = config.cxx=$quote($recall($cxx.path) $cxx.config.mode, true)
+# @@ To verify the creation result use cfg-list command rather than
+# pkg-status, when implemented.
+#
pkg_status += -d cfg
: non-empty
@@ -86,3 +89,77 @@ EOE
$pkg_status libfoo >'libfoo unknown'
}
}
+
+: name
+:
+{
+ test.arguments += -d cfg
+
+ : valid
+ :
+ {
+ $* --name foo 2>>/~%EOE% &cfg/***;
+ %created new configuration in .+/cfg/%
+ EOE
+
+ # @@ To verify the result use cfg-list, when implemented.
+ #
+ $pkg_status libfoo >'libfoo unknown'
+ }
+
+ : invalid
+ :
+ : Also use the short option.
+ :
+ $* -n 123 2>>EOE != 0
+ error: invalid --name|-n option value '123': illegal first character (must be alphabetic or underscore)
+ EOE
+}
+
+: type
+:
+{
+ test.arguments += -d cfg
+
+ : valid
+ :
+ {
+ $* --type host 2>>/~%EOE% &cfg/***;
+ %created new configuration in .+/cfg/%
+ EOE
+
+ $pkg_status libfoo >'libfoo unknown'
+ }
+
+ : invalid
+ :
+ : Also use the short option.
+ :
+ $* --type '' 2>>EOE != 0
+ error: empty --type|-t option value
+ EOE
+}
+
+: uuid
+:
+{
+ test.arguments += -d cfg
+
+ : valid
+ :
+ {
+ $* --config-uuid '18f48b4b-b5d9-4712-b98c-1930df1c4228' 2>>/~%EOE% &cfg/***;
+ %created new configuration in .+/cfg/%
+ EOE
+
+ $pkg_status libfoo >'libfoo unknown'
+ }
+
+ : invalid
+ :
+ : Also use the short option.
+ :
+ $* --config-uuid '123' 2>>EOE != 0
+ error: invalid value '123' for option '--config-uuid'
+ EOE
+}
diff --git a/tests/common.testscript b/tests/common.testscript
index 5db8c6a..4593d11 100644
--- a/tests/common.testscript
+++ b/tests/common.testscript
@@ -32,6 +32,7 @@ test.options += --default-options $options_guard \
# (for example, to make sure that configuration post-test state is valid and is
# as expected).
#
+cfg_add = $* cfg-add
cfg_create = $* cfg-create
pkg_build = $* pkg-build
pkg_checkout = $* pkg-checkout
diff --git a/tests/pkg-build.testscript b/tests/pkg-build.testscript
index 50db679..c973726 100644
--- a/tests/pkg-build.testscript
+++ b/tests/pkg-build.testscript
@@ -223,27 +223,27 @@ test.options += --no-progress
: unknown-package
:
$clone_root_cfg;
- $* libfoo 2>>/EOE != 0
+ $* libfoo 2>>/~%EOE% != 0
error: unknown package libfoo
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
: unknown-package-ver
:
$clone_root_cfg;
- $* libfoo/1.0.0 2>>/EOE != 0
+ $* libfoo/1.0.0 2>>/~%EOE% != 0
error: unknown package libfoo
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
: unknown-package-constraint
:
$clone_root_cfg;
- $* 'libfoo>1.0.0' 2>>/EOE != 0
+ $* 'libfoo>1.0.0' 2>>/~%EOE% != 0
error: unknown package libfoo
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
@@ -281,9 +281,9 @@ test.options += --no-progress
$* libfoo/1.1.0 libfoo/1.1.0 >'update libfoo/1.1.0';
- $* libfoo/1.0.0 2>>/EOE != 0;
+ $* libfoo/1.0.0 2>>/~%EOE% != 0;
error: unknown package libfoo
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
@@ -1319,9 +1319,9 @@ test.options += --no-progress
$rep_remove $rep/t2 $rep/t5;
- $* --upgrade 2>>/EOE != 0;
+ $* --upgrade 2>>/~%EOE% != 0;
error: libbar is not available
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
diff --git a/tests/pkg-configure.testscript b/tests/pkg-configure.testscript
index 5a7d8aa..4e4a6fe 100644
--- a/tests/pkg-configure.testscript
+++ b/tests/pkg-configure.testscript
@@ -214,7 +214,7 @@ if ($posix && "$uid" != '0')
chmod 555 cfg/libhello;
$pkg_disfigure libhello 2>>/~%EOE% != 0;
- %error: unable to remove directory cfg/libhello/.+%
+ %error: unable to remove directory .+/cfg/libhello/.+%
info: package libhello is now broken; use 'pkg-purge' to remove
EOE
@@ -255,7 +255,7 @@ if ($posix && "$uid" != '0')
$* libhello 2>>/~%EOE% != 0;
%error: unable to create directory cfg/libhello/build/.+%
- %error: unable to remove directory cfg/libhello/.+%
+ %error: unable to remove directory .+/cfg/libhello/.+%
info: package libhello is now broken; use 'pkg-purge' to remove
EOE
diff --git a/tests/pkg-fetch.testscript b/tests/pkg-fetch.testscript
index 7d32523..b0d61a6 100644
--- a/tests/pkg-fetch.testscript
+++ b/tests/pkg-fetch.testscript
@@ -84,8 +84,8 @@ $* libfoo/1/2/3 2>>EOE != 0
: no-repositories
:
$clone_cfg;
-$* libfoo/1.0.0 2>>/EOE != 0
- error: configuration cfg/ has no repositories
+$* libfoo/1.0.0 2>>/~%EOE% != 0
+ %error: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
@@ -94,8 +94,8 @@ $* libfoo/1.0.0 2>>/EOE != 0
{
$clone_cfg && $rep_add $rep/t1;
- $* libfoo/1.0.0 2>>/EOE != 0
- error: configuration cfg/ has no available packages
+ $* libfoo/1.0.0 2>>/~%EOE% != 0
+ %error: configuration .+/cfg/ has no available packages%
info: use 'bpkg rep-fetch' to fetch available packages list
EOE
}
@@ -121,14 +121,14 @@ $* libfoo/1.0.0 2>>/EOE != 0
$pkg_status libfoo/1.0.0 1>'libfoo fetched 1.0.0';
- $* libfoo/1.0.0 2>>/EOE != 0;
- error: package libfoo already exists in configuration cfg/
+ $* libfoo/1.0.0 2>>/~%EOE% != 0;
+ %error: package libfoo already exists in configuration .+/cfg/%
info: version: 1.0.0, state: fetched, substate: none
info: use 'pkg-fetch --replace|-r' to replace
EOE
- $* -e $src/t1/libfoo-1.0.0.tar.gz 2>>/EOE != 0;
- error: package libfoo already exists in configuration cfg/
+ $* -e $src/t1/libfoo-1.0.0.tar.gz 2>>/~%EOE% != 0;
+ %error: package libfoo already exists in configuration .+/cfg/%
info: version: 1.0.0, state: fetched, substate: none
info: use 'pkg-fetch --replace|-r' to replace
EOE
diff --git a/tests/pkg-purge.testscript b/tests/pkg-purge.testscript
index c4f8f1c..16ee34c 100644
--- a/tests/pkg-purge.testscript
+++ b/tests/pkg-purge.testscript
@@ -152,7 +152,7 @@ if ($posix && "$uid" != '0')
chmod 000 cfg/libfoo-1.0.0;
$* libfoo 2>>/~%EOE% != 0;
- %error: unable to remove directory cfg/libfoo-1.0.0/.+%
+ %error: unable to remove directory .+/cfg/libfoo-1.0.0/.+%
info: package libfoo is now broken; use 'pkg-purge --force' to remove
EOE
diff --git a/tests/pkg-system.testscript b/tests/pkg-system.testscript
index 26d6893..390cbc7 100644
--- a/tests/pkg-system.testscript
+++ b/tests/pkg-system.testscript
@@ -52,9 +52,9 @@ rep_remove += -d cfg 2>!
{
$clone_cfg;
- $pkg_build 'sys:libbar' 2>>/EOE != 0;
+ $pkg_build 'sys:libbar' 2>>/~%EOE% != 0;
error: unknown package libbar
- info: configuration cfg/ has no repositories
+ % info: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
diff --git a/tests/pkg-unpack.testscript b/tests/pkg-unpack.testscript
index 6190552..c15ed92 100644
--- a/tests/pkg-unpack.testscript
+++ b/tests/pkg-unpack.testscript
@@ -103,8 +103,8 @@ $* 2>>EOE != 0
{
$clone_cfg && $pkg_fetch libfoo/1.0.0;
- $* -e $src/libfoo-1.1.0 2>>/EOE != 0;
- error: package libfoo already exists in configuration cfg/
+ $* -e $src/libfoo-1.1.0 2>>/~%EOE% != 0;
+ %error: package libfoo already exists in configuration .+/cfg/%
info: version: 1.0.0, state: fetched, substate: none
info: use 'pkg-unpack --replace|-r' to replace
EOE
@@ -125,8 +125,8 @@ $* 2>>EOE != 0
{
$clone_cfg && $pkg_fetch libfoo/1.0.0;
- $* -e $src/libfoo-1.1.0 2>>/EOE != 0;
- error: package libfoo already exists in configuration cfg/
+ $* -e $src/libfoo-1.1.0 2>>/~%EOE% != 0;
+ %error: package libfoo already exists in configuration .+/cfg/%
info: version: 1.0.0, state: fetched, substate: none
info: use 'pkg-unpack --replace|-r' to replace
EOE
@@ -144,8 +144,8 @@ $* 2>>EOE != 0
$* libfoo 2>'unpacked libfoo/1.0.0';
- $* -e $src/libfoo-1.1.0 2>>/EOE != 0;
- error: package libfoo already exists in configuration cfg/
+ $* -e $src/libfoo-1.1.0 2>>/~%EOE% != 0;
+ %error: package libfoo already exists in configuration .+/cfg/%
info: version: 1.0.0, state: unpacked, substate: none
info: use 'pkg-unpack --replace|-r' to replace
EOE
@@ -161,8 +161,8 @@ $* 2>>EOE != 0
{
$clone_cfg;
- $* libfoo 2>>/EOE != 0;
- error: package libfoo does not exist in configuration cfg/
+ $* libfoo 2>>/~%EOE% != 0;
+ %error: package libfoo does not exist in configuration .+/cfg/%
EOE
$* -e $src/libfoo-1.1.0 2>'using libfoo/1.1.0 (external)';
@@ -213,8 +213,8 @@ $* 2>>EOE != 0
{
$clone_root_cfg;
- $* libfoo/1.1.0 2>>/EOE != 0
- error: configuration cfg/ has no repositories
+ $* libfoo/1.1.0 2>>/~%EOE% != 0
+ %error: configuration .+/cfg/ has no repositories%
info: use 'bpkg rep-add' to add a repository
EOE
}
@@ -224,8 +224,8 @@ $* 2>>EOE != 0
{
$clone_root_cfg && $rep_add $src/libfoo-1.1.0;
- $* libfoo/1.1.0 2>>/EOE != 0
- error: configuration cfg/ has no available packages
+ $* libfoo/1.1.0 2>>/~%EOE% != 0
+ %error: configuration .+/cfg/ has no available packages%
info: use 'bpkg rep-fetch' to fetch available packages list
EOE
}