aboutsummaryrefslogtreecommitdiff
path: root/bpkg
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-08-19 17:37:29 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-08-29 18:20:03 +0300
commit53c2aa8e382dd50d09b385285bc3fa0b645ace0a (patch)
tree6d23d091bc57c0aa8d8a529e63ec2f2f22322a3a /bpkg
parenta4b29effed15b0a3e9309a4633a3ada37f3081e6 (diff)
Support system packages
Diffstat (limited to 'bpkg')
-rw-r--r--bpkg/buildfile89
-rw-r--r--bpkg/database.cxx17
-rw-r--r--bpkg/manifest-utility14
-rw-r--r--bpkg/manifest-utility.cxx15
-rw-r--r--bpkg/package95
-rw-r--r--bpkg/package.cxx44
-rw-r--r--bpkg/package.xml30
-rw-r--r--bpkg/pkg-build.cli32
-rw-r--r--bpkg/pkg-build.cxx869
-rw-r--r--bpkg/pkg-command.cxx8
-rw-r--r--bpkg/pkg-configure6
-rw-r--r--bpkg/pkg-configure.cli20
-rw-r--r--bpkg/pkg-configure.cxx86
-rw-r--r--bpkg/pkg-disfigure.cli5
-rw-r--r--bpkg/pkg-disfigure.cxx22
-rw-r--r--bpkg/pkg-drop.cxx14
-rw-r--r--bpkg/pkg-fetch.cxx7
-rw-r--r--bpkg/pkg-purge.cxx12
-rw-r--r--bpkg/pkg-status.cli35
-rw-r--r--bpkg/pkg-status.cxx86
-rw-r--r--bpkg/pkg-unpack.cxx9
-rw-r--r--bpkg/rep-info.cxx2
-rw-r--r--bpkg/satisfaction.cxx3
-rw-r--r--bpkg/system-repository55
-rw-r--r--bpkg/system-repository.cxx33
-rw-r--r--bpkg/utility3
26 files changed, 1210 insertions, 401 deletions
diff --git a/bpkg/buildfile b/bpkg/buildfile
index 8f834f6..551e9c0 100644
--- a/bpkg/buildfile
+++ b/bpkg/buildfile
@@ -7,50 +7,51 @@ import libs += libbutl%lib{butl}
import libs += libodb%lib{odb}
import libs += libodb-sqlite%lib{odb-sqlite}
-exe{bpkg}: \
-{hxx cxx}{ archive } \
-{hxx cxx}{ auth } \
-{hxx }{ bpkg-version } \
-{ cxx}{ bpkg } {hxx ixx cxx}{ bpkg-options } \
-{hxx cxx}{ cfg-create } {hxx ixx cxx}{ cfg-create-options } \
-{hxx cxx}{ checksum } \
- {hxx ixx cxx}{ common-options } \
- {hxx ixx cxx}{ configuration-options } \
-{hxx cxx}{ database } \
-{hxx cxx}{ diagnostics } \
-{hxx cxx}{ fetch } \
-{hxx }{ forward } \
-{hxx cxx}{ help } {hxx ixx cxx}{ help-options } \
-{hxx cxx}{ manifest-utility } \
-{hxx cxx}{ openssl } \
-{hxx }{ options-types } \
-{hxx ixx cxx}{ package } \
-{hxx ixx cxx}{ package-odb } file{ package.xml } \
-{hxx cxx}{ pkg-build } {hxx ixx cxx}{ pkg-build-options } \
-{hxx }{ pkg-clean } {hxx ixx cxx}{ pkg-clean-options } \
-{hxx cxx}{ pkg-drop } {hxx ixx cxx}{ pkg-drop-options } \
-{hxx cxx}{ pkg-command } \
-{hxx cxx}{ pkg-configure } {hxx ixx cxx}{ pkg-configure-options } \
-{hxx cxx}{ pkg-disfigure } {hxx ixx cxx}{ pkg-disfigure-options } \
-{hxx cxx}{ pkg-fetch } {hxx ixx cxx}{ pkg-fetch-options } \
-{hxx }{ pkg-install } {hxx ixx cxx}{ pkg-install-options } \
-{hxx cxx}{ pkg-purge } {hxx ixx cxx}{ pkg-purge-options } \
-{hxx cxx}{ pkg-status } {hxx ixx cxx}{ pkg-status-options } \
-{hxx }{ pkg-test } {hxx ixx cxx}{ pkg-test-options } \
-{hxx }{ pkg-uninstall } {hxx ixx cxx}{ pkg-uninstall-options } \
-{hxx cxx}{ pkg-unpack } {hxx ixx cxx}{ pkg-unpack-options } \
-{hxx }{ pkg-update } {hxx ixx cxx}{ pkg-update-options } \
-{hxx cxx}{ pkg-verify } {hxx ixx cxx}{ pkg-verify-options } \
-{hxx cxx}{ rep-add } {hxx ixx cxx}{ rep-add-options } \
-{hxx cxx}{ rep-create } {hxx ixx cxx}{ rep-create-options } \
-{hxx cxx}{ rep-fetch } {hxx ixx cxx}{ rep-fetch-options } \
-{hxx cxx}{ rep-info } {hxx ixx cxx}{ rep-info-options } \
- {hxx cxx}{ repository-signing } \
-{hxx cxx}{ satisfaction } \
-{hxx }{ types } \
-{hxx cxx}{ types-parsers } \
-{hxx cxx}{ utility } \
-{hxx }{ wrapper-traits } \
+exe{bpkg}: \
+{hxx cxx}{ archive } \
+{hxx cxx}{ auth } \
+{hxx }{ bpkg-version } \
+{ cxx}{ bpkg } {hxx ixx cxx}{ bpkg-options } \
+{hxx cxx}{ cfg-create } {hxx ixx cxx}{ cfg-create-options } \
+{hxx cxx}{ checksum } \
+ {hxx ixx cxx}{ common-options } \
+ {hxx ixx cxx}{ configuration-options } \
+{hxx cxx}{ database } \
+{hxx cxx}{ diagnostics } \
+{hxx cxx}{ fetch } \
+{hxx }{ forward } \
+{hxx cxx}{ help } {hxx ixx cxx}{ help-options } \
+{hxx cxx}{ manifest-utility } \
+{hxx cxx}{ openssl } \
+{hxx }{ options-types } \
+{hxx ixx cxx}{ package } \
+{hxx ixx cxx}{ package-odb } file{ package.xml } \
+{hxx cxx}{ pkg-build } {hxx ixx cxx}{ pkg-build-options } \
+{hxx }{ pkg-clean } {hxx ixx cxx}{ pkg-clean-options } \
+{hxx cxx}{ pkg-drop } {hxx ixx cxx}{ pkg-drop-options } \
+{hxx cxx}{ pkg-command } \
+{hxx cxx}{ pkg-configure } {hxx ixx cxx}{ pkg-configure-options } \
+{hxx cxx}{ pkg-disfigure } {hxx ixx cxx}{ pkg-disfigure-options } \
+{hxx cxx}{ pkg-fetch } {hxx ixx cxx}{ pkg-fetch-options } \
+{hxx }{ pkg-install } {hxx ixx cxx}{ pkg-install-options } \
+{hxx cxx}{ pkg-purge } {hxx ixx cxx}{ pkg-purge-options } \
+{hxx cxx}{ pkg-status } {hxx ixx cxx}{ pkg-status-options } \
+{hxx }{ pkg-test } {hxx ixx cxx}{ pkg-test-options } \
+{hxx }{ pkg-uninstall } {hxx ixx cxx}{ pkg-uninstall-options } \
+{hxx cxx}{ pkg-unpack } {hxx ixx cxx}{ pkg-unpack-options } \
+{hxx }{ pkg-update } {hxx ixx cxx}{ pkg-update-options } \
+{hxx cxx}{ pkg-verify } {hxx ixx cxx}{ pkg-verify-options } \
+{hxx cxx}{ rep-add } {hxx ixx cxx}{ rep-add-options } \
+{hxx cxx}{ rep-create } {hxx ixx cxx}{ rep-create-options } \
+{hxx cxx}{ rep-fetch } {hxx ixx cxx}{ rep-fetch-options } \
+{hxx cxx}{ rep-info } {hxx ixx cxx}{ rep-info-options } \
+ {hxx cxx}{ repository-signing } \
+{hxx cxx}{ satisfaction } \
+{hxx cxx}{ system-repository } \
+{hxx }{ types } \
+{hxx cxx}{ types-parsers } \
+{hxx cxx}{ utility } \
+{hxx }{ wrapper-traits } \
$libs
# Disable VC "unknown pragma" warning.
diff --git a/bpkg/database.cxx b/bpkg/database.cxx
index 6eec6f8..418265c 100644
--- a/bpkg/database.cxx
+++ b/bpkg/database.cxx
@@ -7,7 +7,10 @@
#include <odb/schema-catalog.hxx>
#include <odb/sqlite/exceptions.hxx>
+#include <bpkg/package>
+#include <bpkg/package-odb>
#include <bpkg/diagnostics>
+#include <bpkg/system-repository>
using namespace std;
@@ -76,6 +79,20 @@ namespace bpkg
fail << "configuration " << d << " is already used by another process";
}
+ // 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.
+ //
+ transaction t (db.begin ());
+
+ for (const auto& p:
+ db.query<selected_package> (
+ query<selected_package>::substate == "system"))
+ system_repository.insert (p.name, p.version, false);
+
+ t.commit ();
+
db.tracer (tr); // Switch to the caller's tracer.
return db;
}
diff --git a/bpkg/manifest-utility b/bpkg/manifest-utility
index f956ea9..9b1b3ee 100644
--- a/bpkg/manifest-utility
+++ b/bpkg/manifest-utility
@@ -12,6 +12,20 @@
namespace bpkg
{
+ // Package naming schemes.
+ //
+ enum class package_scheme
+ {
+ none,
+ sys
+ };
+
+ // Extract scheme from [<scheme>:]<package>. Position the pointer right after
+ // the scheme end if present, otherwise leave unchanged.
+ //
+ package_scheme
+ parse_package_scheme (const char*&);
+
// Extract name and version components from <name>[/<version>].
//
string
diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx
index 5aa90b9..41215cf 100644
--- a/bpkg/manifest-utility.cxx
+++ b/bpkg/manifest-utility.cxx
@@ -10,6 +10,21 @@ using namespace std;
namespace bpkg
{
+ package_scheme
+ parse_package_scheme (const char*& s)
+ {
+ // Ignore the character case for consistency with a case insensitivity of
+ // URI schemes some of which we may support in the future.
+ //
+ if (casecmp (s, "sys:", 4) == 0)
+ {
+ s += 4;
+ return package_scheme::sys;
+ }
+
+ return package_scheme::none;
+ }
+
string
parse_package_name (const char* s)
{
diff --git a/bpkg/package b/bpkg/package
index 42cf2af..533c919 100644
--- a/bpkg/package
+++ b/bpkg/package
@@ -19,7 +19,7 @@
#include <bpkg/types>
#include <bpkg/utility>
-#pragma db model version(3, 3, open)
+#pragma db model version(2, 3, open)
namespace bpkg
{
@@ -96,6 +96,7 @@ namespace bpkg
}
#include <bpkg/manifest>
+#include <bpkg/system-repository>
// Prevent assert() macro expansion in get/set expressions. This should
// appear after all #include directives since the assert() macro is
@@ -283,6 +284,12 @@ namespace bpkg
using dependencies = vector<dependency_alternatives>;
+ // Wildcard version. Satisfies any dependency constraint and is represented
+ // as 0+0 (which is also the "stub version"; since a real version is always
+ // greater than the stub version, we reuse it to signify a special case).
+ //
+ extern const version wildcard_version;
+
// available_package
//
#pragma db value
@@ -302,6 +309,8 @@ namespace bpkg
class available_package
{
public:
+ using version_type = bpkg::version;
+
available_package_id id;
upstream_version version;
@@ -330,6 +339,10 @@ namespace bpkg
//
optional<string> sha256sum;
+ private:
+ #pragma db transient
+ mutable optional<version_type> system_version_;
+
public:
available_package (package_manifest&& m)
: id (move (m.name), m.version),
@@ -337,6 +350,40 @@ namespace bpkg
dependencies (move (m.dependencies)),
sha256sum (move (m.sha256sum)) {}
+ // Create a stub available package with a fixed system version. This
+ // constructor is only used to create transient/fake available packages
+ // based on the system selected packages.
+ //
+ available_package (string n, version_type sysv)
+ : id (move (n), wildcard_version),
+ version (wildcard_version),
+ system_version_ (sysv) {}
+
+ bool
+ stub () const {return version.compare (wildcard_version, true) == 0;}
+
+ // Return package system version if one has been discovered. Note that
+ // 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;
+ }
+
// Database mapping.
//
#pragma db member(id) id column("")
@@ -391,6 +438,7 @@ namespace bpkg
//
enum class package_state
{
+ transient, // No longer or not yet in the database.
broken,
fetched,
unpacked,
@@ -410,6 +458,26 @@ namespace bpkg
to(to_string (?)) \
from(bpkg::to_package_state (?))
+ // package_substate
+ //
+ enum class package_substate
+ {
+ none,
+ system // System package; valid states: configured.
+ };
+
+ string
+ to_string (package_substate);
+
+ package_substate
+ to_package_substate (const string&); // May throw invalid_argument.
+
+ inline ostream&
+ operator<< (ostream& os, package_substate s) {return os << to_string (s);}
+
+ #pragma db map type(package_substate) as(string) \
+ to(to_string (?)) \
+ from(bpkg::to_package_substate (?))
// package
//
@@ -422,6 +490,7 @@ namespace bpkg
string name; // Object id.
version_type version;
package_state state;
+ package_substate substate;
// The hold flags indicate whether this package and/or version
// should be retained in the configuration. A held package will
@@ -481,6 +550,23 @@ namespace bpkg
compare_lazy_ptr>;
prerequisites_type prerequisites;
+ bool
+ system () const
+ {
+ // The system substate is only valid for the configured state.
+ //
+ assert (substate != package_substate::system ||
+ state == package_state::configured);
+
+ return substate == package_substate::system;
+ }
+
+ // Represent the wildcard version with the "*" string. Represent naturally
+ // all other versions.
+ //
+ string
+ version_string () const;
+
// Database mapping.
//
#pragma db member(name) id
@@ -488,11 +574,18 @@ namespace bpkg
#pragma db member(prerequisites) id_column("package") \
key_column("prerequisite") value_column("") key_not_null
+ #pragma db member(substate) default("none")
+
private:
friend class odb::access;
selected_package () = default;
};
+ // Print the package name, version and the 'sys:' prefix for the system
+ // substate. The wildcard version is represented with the "*" string.
+ //
+ ostream&
+ operator<< (ostream&, const selected_package&);
// certificate
//
diff --git a/bpkg/package.cxx b/bpkg/package.cxx
index 2cf96ff..6089c23 100644
--- a/bpkg/package.cxx
+++ b/bpkg/package.cxx
@@ -11,6 +11,8 @@ using namespace std;
namespace bpkg
{
+ const version wildcard_version (0, "0", nullopt, 0);
+
// available_package_id
//
bool
@@ -22,7 +24,6 @@ namespace bpkg
// available_package
//
-
// Check if the package is available from the specified repository,
// its prerequisite repositories, or one of their complements,
// recursively. Return the first repository that contains the
@@ -104,6 +105,21 @@ namespace bpkg
return result ();
}
+ // selected_package
+ //
+ string selected_package::
+ version_string () const
+ {
+ return version != wildcard_version ? version.string () : "*";
+ }
+
+ ostream&
+ operator<< (ostream& os, const selected_package& p)
+ {
+ os << (p.system () ? "sys:" : "") << p.name << "/" << p.version_string ();
+ return os;
+ }
+
// state
//
string
@@ -111,6 +127,7 @@ namespace bpkg
{
switch (s)
{
+ case package_state::transient: return "transient";
case package_state::broken: return "broken";
case package_state::fetched: return "fetched";
case package_state::unpacked: return "unpacked";
@@ -123,13 +140,36 @@ namespace bpkg
package_state
to_package_state (const string& s)
{
- if (s == "broken") return package_state::broken;
+ if (s == "transient") return package_state::transient;
+ else if (s == "broken") return package_state::broken;
else if (s == "fetched") return package_state::fetched;
else if (s == "unpacked") return package_state::unpacked;
else if (s == "configured") return package_state::configured;
else throw invalid_argument ("invalid package state '" + s + "'");
}
+ // substate
+ //
+ string
+ to_string (package_substate s)
+ {
+ switch (s)
+ {
+ case package_substate::none: return "none";
+ case package_substate::system: return "system";
+ }
+
+ return string (); // Should never reach.
+ }
+
+ package_substate
+ to_package_substate (const string& s)
+ {
+ if (s == "none") return package_substate::none;
+ else if (s == "system") return package_substate::system;
+ else throw invalid_argument ("invalid package substate '" + s + "'");
+ }
+
// certificate
//
ostream&
diff --git a/bpkg/package.xml b/bpkg/package.xml
index 900794c..268cdc4 100644
--- a/bpkg/package.xml
+++ b/bpkg/package.xml
@@ -1,5 +1,22 @@
<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
- <model version="3">
+ <changeset version="3">
+ <alter-table name="selected_package">
+ <add-column name="substate" type="TEXT" null="true" default="'none'"/>
+ </alter-table>
+ <add-table name="certificate" kind="object">
+ <column name="fingerprint" type="TEXT" null="true"/>
+ <column name="name" type="TEXT" null="true"/>
+ <column name="organization" type="TEXT" null="true"/>
+ <column name="email" type="TEXT" null="true"/>
+ <column name="start_date" type="INTEGER" null="true"/>
+ <column name="end_date" type="INTEGER" null="true"/>
+ <primary-key>
+ <column name="fingerprint"/>
+ </primary-key>
+ </add-table>
+ </changeset>
+
+ <model version="2">
<table name="repository" kind="object">
<column name="name" type="TEXT" null="true"/>
<column name="location" type="TEXT" null="true"/>
@@ -231,16 +248,5 @@
</references>
</foreign-key>
</table>
- <table name="certificate" kind="object">
- <column name="fingerprint" type="TEXT" null="true"/>
- <column name="name" type="TEXT" null="true"/>
- <column name="organization" type="TEXT" null="true"/>
- <column name="email" type="TEXT" null="true"/>
- <column name="start_date" type="INTEGER" null="true"/>
- <column name="end_date" type="INTEGER" null="true"/>
- <primary-key>
- <column name="fingerprint"/>
- </primary-key>
- </table>
</model>
</changelog>
diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli
index 222519b..f741e7a 100644
--- a/bpkg/pkg-build.cli
+++ b/bpkg/pkg-build.cli
@@ -11,11 +11,12 @@ include <bpkg/configuration.cli>;
namespace bpkg
{
{
- "<options> <pkg> <ver> <file> <dir>",
+ "<options> <scheme> <pkg> <ver> <file> <dir>",
"\h|SYNOPSIS|
- \c{\b{bpkg pkg-build}|\b{build} [<options>] (<pkg>[/<ver>] | <file> | <dir>/)...}
+ \c{\b{bpkg pkg-build}|\b{build} [<options>] ([<scheme>:]<pkg>[/<ver>] |
+ <file> | <dir>/)...}
\h|DESCRIPTION|
@@ -24,14 +25,25 @@ namespace bpkg
upgrade or downgrade packages that already exists in the configuration.
Each package can be specified as just the name (<pkg>) with optional
- package version (<ver>) in which case the package will be automatically
- fetched from one of the repositories. See the \l{bpkg-rep-add(1)} and
- \l{bpkg-rep-fetch(1)} commands for more information on package
- repositories. If <ver> is not specified, then the latest available
- version will be built. To downgrade, the desired version must be
- specified explicitly.
-
- Alternatively, the package can be specified as either the path to the
+ package version (<ver>) in which case the source code for the package
+ will be automatically fetched from one of the repositories. See the
+ \l{bpkg-rep-add(1)} and \l{bpkg-rep-fetch(1)} commands for more
+ information on package repositories. If <ver> is not specified, then the
+ latest available version will be fetched. To downgrade, the desired
+ version must be specified explicitly.
+
+ A package name (<pkg>) can also be prefixed with a package scheme
+ (<scheme>:). Currently the only recognized scheme is \cb{sys:} which
+ instructs \cb{pkg-build} to configure the package as available from the
+ system rather than building it from source. If the system package version
+ (<ver>) is not specified, then it is considered to be unknown but
+ satisfying any dependency constraint. Such a version is displayed as
+ \cb{*}. In certain cases you may want to indicate that a certain package
+ is available from the system but only add it to the configuration if it
+ is required by other packages being built. In this case you can use the
+ \cb{?sys:} system scheme variant.
+
+ Alternatively, a package can be specified as either the path to the
package archive (<file>) or to the package directory (<dir>/; note that
it must end with a directory separator). See the \l{bpkg-pkg-fetch(1)}
and \l{bpkg-pkg-unpack(1)} commands for more information on the semantics
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index 84e8f19..d88e312 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -21,12 +21,14 @@
#include <bpkg/common-options>
#include <bpkg/pkg-drop>
+#include <bpkg/pkg-purge>
#include <bpkg/pkg-fetch>
#include <bpkg/pkg-unpack>
#include <bpkg/pkg-update>
#include <bpkg/pkg-verify>
#include <bpkg/pkg-configure>
#include <bpkg/pkg-disfigure>
+#include <bpkg/system-repository>
using namespace std;
using namespace butl;
@@ -44,9 +46,10 @@ namespace bpkg
// prerequisite repositories, and their complements, recursively
// (note: recursivity applies to complements, not prerequisites).
// Return the package and the repository in which it was found or
- // NULL for both if not found.
+ // NULL for both if not found. Note that a stub satisfies any
+ // constraint.
//
- pair<shared_ptr<available_package>, shared_ptr<repository>>
+ static pair<shared_ptr<available_package>, shared_ptr<repository>>
find_available (database& db,
const string& name,
const shared_ptr<repository>& r,
@@ -58,7 +61,7 @@ namespace bpkg
const auto& vm (query::id.version);
// If there is a constraint, then translate it to the query. Otherwise,
- // get the latest version.
+ // get the latest version or stub versions if present.
//
if (c)
{
@@ -77,27 +80,28 @@ namespace bpkg
// subsequent revision of a package version are just as (un)satisfactory
// as the first one.
//
+ query qs (compare_version_eq (vm, wildcard_version, false));
+
if (c->min_version &&
c->max_version &&
*c->min_version == *c->max_version)
{
const version& v (*c->min_version);
- q = q && compare_version_eq (vm, v, v.revision != 0);
-
- if (v.revision == 0)
- q += order_by_revision_desc (vm);
+ q = q && (compare_version_eq (vm, v, v.revision != 0) || qs);
}
else
{
+ query qr (true);
+
if (c->min_version)
{
const version& v (*c->min_version);
if (c->min_open)
- q = q && compare_version_gt (vm, v, v.revision != 0);
+ qr = compare_version_gt (vm, v, v.revision != 0);
else
- q = q && compare_version_ge (vm, v, v.revision != 0);
+ qr = compare_version_ge (vm, v, v.revision != 0);
}
if (c->max_version)
@@ -105,16 +109,16 @@ namespace bpkg
const version& v (*c->max_version);
if (c->max_open)
- q = q && compare_version_lt (vm, v, v.revision != 0);
+ qr = qr && compare_version_lt (vm, v, v.revision != 0);
else
- q = q && compare_version_le (vm, v, v.revision != 0);
+ qr = qr && compare_version_le (vm, v, v.revision != 0);
}
- q += order_by_version_desc (vm);
+ q = q && (qr || qs);
}
}
- else
- q += order_by_version_desc (vm);
+
+ q += order_by_version_desc (vm);
// Filter the result based on the repository to which each version
// belongs.
@@ -127,7 +131,7 @@ namespace bpkg
// that the package locations list is left empty and that the
// returned repository could be NULL if the package is an orphan.
//
- pair<shared_ptr<available_package>, shared_ptr<repository>>
+ static pair<shared_ptr<available_package>, shared_ptr<repository>>
make_available (const common_options& options,
const dir_path& cd,
database& db,
@@ -135,6 +139,10 @@ namespace bpkg
{
assert (sp != nullptr && sp->state != package_state::broken);
+ if (sp->system ())
+ return make_pair (make_shared<available_package> (sp->name, sp->version),
+ nullptr);
+
// First see if we can find its repository.
//
shared_ptr<repository> ar (
@@ -215,6 +223,24 @@ namespace bpkg
vector<constraint_type> constraints;
+ // System package indicator. See also a note in collect()'s constraint
+ // merging code.
+ //
+ bool system;
+
+ const version&
+ available_version () const
+ {
+ // This should have been diagnosed before creating build_package object.
+ //
+ assert (available != nullptr &&
+ (system
+ ? available->system_version () != nullptr
+ : !available->stub ()));
+
+ return system ? *available->system_version () : available->version;
+ }
+
// Set of package names that caused this package to be built. Empty
// name signifies user selection.
//
@@ -247,21 +273,42 @@ namespace bpkg
return selected != nullptr &&
selected->state == package_state::configured &&
(reconfigure_ || // Must be checked first, available could be NULL.
- selected->version != available->version);
+ selected->system () != system ||
+ selected->version != available_version ());
+ }
+
+ bool
+ user_selection () const
+ {
+ return required_by.find ("") != required_by.end ();
+ }
+
+ string
+ available_name () const
+ {
+ assert (available != nullptr);
+
+ const version& v (available_version ());
+ string vs (v == wildcard_version ? "/*" : "/" + v.string ());
+
+ return system
+ ? "sys:" + available->id.name + vs
+ : available->id.name + vs;
}
};
struct build_packages: list<reference_wrapper<build_package>>
{
- // Collect the package. Return true if this package version was,
- // in fact, added to the map and false if it was already there
- // or the existing version was preferred.
+ // Collect the package. 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.
//
- bool
+ build_package*
collect (const common_options& options,
const dir_path& cd,
database& db,
- build_package&& pkg)
+ build_package&& pkg,
+ bool recursively)
{
using std::swap; // ...and not list::swap().
@@ -283,10 +330,7 @@ namespace bpkg
build_package* p1 (&i->second.package);
build_package* p2 (&pkg);
- // If versions are the same, then all we have to do is copy the
- // constraint (p1/p2 already point to where we would want them to).
- //
- if (p1->available->version != p2->available->version)
+ if (p1->available_version () != p2->available_version ())
{
using constraint_type = build_package::constraint_type;
@@ -295,7 +339,7 @@ namespace bpkg
// should prefer. So get the first to try into p1 and the second
// to try -- into p2.
//
- if (p2->available->version > p1->available->version)
+ if (p2->available_version () > p1->available_version ())
swap (p1, p2);
// See if pv's version satisfies pc's constraints. Return the
@@ -306,7 +350,7 @@ namespace bpkg
-> const constraint_type*
{
for (const constraint_type& c: pc->constraints)
- if (!satisfies (pv->available->version, c.value))
+ if (!satisfies (pv->available_version (), c.value))
return &c;
return nullptr;
@@ -326,8 +370,8 @@ namespace bpkg
fail << "unable to satisfy constraints on package " << n <<
info << d1 << " depends on (" << n << " " << c1->value << ")" <<
info << d2 << " depends on (" << n << " " << c2->value << ")" <<
- info << "available " << n << " " << p1->available->version <<
- info << "available " << n << " " << p2->available->version <<
+ info << "available " << p1->available_name () <<
+ info << "available " << p2->available_name () <<
info << "explicitly specify " << n << " version to manually "
<< "satisfy both constraints";
}
@@ -335,9 +379,19 @@ namespace bpkg
swap (p1, p2);
}
- l4 ([&]{trace << "pick " << n << " " << p1->available->version
- << " over " << p2->available->version;});
+ l4 ([&]{trace << "pick " << p1->available_name ()
+ << " over " << p2->available_name ();});
}
+ // If versions are the same, then we still need to pick the entry as
+ // one of them can build a package from source while another configure
+ // a system package. We prefer a user-selected entry (if there is
+ // one). If none of them is user-selected we prefer a source package
+ // over a system one. Copy the constraints from the thrown aways entry
+ // to the selected one.
+ //
+ else if (p2->user_selection () ||
+ (!p1->user_selection () && !p2->system))
+ swap (p1, p2);
// See if we are replacing the object. If not, then we don't
// need to collect its prerequisites since that should have
@@ -369,47 +423,71 @@ namespace bpkg
(p2->hold_version && *p2->hold_version > *p1->hold_version))
p1->hold_version = p2->hold_version;
+ // Note that we don't copy the build_package::system flag. If it was
+ // set from the command line ("strong system") then we will also have
+ // the '== 0' constraint which means that this build_package object
+ // will never be replaced.
+ //
+ // For other cases ("weak system") we don't want to copy system over
+ // in order not prevent, for example, system to non-system upgrade.
+
if (!replace)
- return false;
+ return nullptr;
}
else
{
// This is the first time we are adding this package name to the map.
//
- string n (pkg.available->id.name); // Note: copy; see emplace() below.
-
- l4 ([&]{trace << "add " << n << " " << pkg.available->version;});
+ l4 ([&]{trace << "add " << pkg.available_name ();});
+ string n (pkg.available->id.name); // Note: copy; see emplace() below.
i = map_.emplace (move (n), data_type {end (), move (pkg)}).first;
}
- // Now collect all the prerequisites recursively. But first "prune"
- // this process if the package is already configured since that would
- // mean all its prerequisites are configured as well. Note that this
- // is not merely an optimization: the package could be an orphan in
- // which case the below logic will fail (no repository in which to
- // search for prerequisites). By skipping the prerequisite check we
- // are able to gracefully handle configured orphans.
- //
- const build_package& p (i->second.package);
- const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
+ build_package& p (i->second.package);
+
+ if (recursively)
+ collect_prerequisites (options, cd, db, p);
+
+ return &p;
+ }
- if (sp != nullptr &&
- sp->version == ap->version &&
- sp->state == package_state::configured)
- return true;
+ // Collect the package prerequisites recursively. But first "prune" this
+ // process if the package we build is a system one or is already configured
+ // since that would mean all its prerequisites are configured as well. Note
+ // that this is not merely an optimization: the package could be an orphan
+ // in which case the below logic will fail (no repository in which to
+ // search for prerequisites). By skipping the prerequisite check we are
+ // able to gracefully handle configured orphans.
+ //
+ void
+ collect_prerequisites (const common_options& options,
+ const dir_path& cd,
+ database& db,
+ const build_package& pkg)
+ {
+ tracer trace ("collect_prerequisites");
+
+ const shared_ptr<selected_package>& sp (pkg.selected);
+
+ if (pkg.system ||
+ (sp != nullptr &&
+ sp->state == package_state::configured &&
+ sp->substate != package_substate::system &&
+ sp->version == pkg.available_version ()))
+ return;
// Show how we got here if things go wrong.
//
auto g (
make_exception_guard (
- [&ap] ()
+ [&pkg] ()
{
- info << "while satisfying " << ap->id.name << " " << ap->version;
+ info << "while satisfying " << pkg.available_name ();
}));
- const shared_ptr<repository>& ar (p.repository);
+ const shared_ptr<available_package>& ap (pkg.available);
+ const shared_ptr<repository>& ar (pkg.repository);
const string& name (ap->id.name);
for (const dependency_alternatives& da: ap->dependencies)
@@ -430,7 +508,7 @@ namespace bpkg
// need the repository to allow orphans without prerequisites).
//
if (ar == nullptr)
- fail << "package " << name << " " << ap->version << " is orphaned" <<
+ fail << "package " << pkg.available_name () << " is orphaned" <<
info << "explicitly upgrade it to a new version";
// We look for prerequisites only in the repositories of this package
@@ -444,8 +522,9 @@ namespace bpkg
// the user can always override it by explicitly building libhello.
//
auto rp (find_available (db, d.name, ar, d.constraint));
+ shared_ptr<available_package>& dap (rp.first);
- if (rp.first == nullptr)
+ if (dap == nullptr)
{
diag_record dr;
dr << fail << "unknown prerequisite " << d << " of package " << name;
@@ -455,6 +534,27 @@ namespace bpkg
<< "be broken";
}
+ // If all that's available is a stub then we need to make sure the
+ // package is present in the system repository and it's version
+ // satisfies the constraint.
+ //
+ if (dap->stub ())
+ {
+ if (dap->system_version () == nullptr)
+ fail << "prerequisite " << d << " of package " << name << " is "
+ << "not available in source" <<
+ info << "specify ?sys:" << d.name << " if it is available "
+ << "from the system";
+
+ if (!satisfies (*dap->system_version (), d.constraint))
+ {
+ fail << "prerequisite " << d << " of package " << name << " is "
+ << "not available in source" <<
+ info << "sys:" << d.name << "/" << *dap->system_version ()
+ << " does not satisfy the constrains";
+ }
+ }
+
// Next see if this package is already selected. If we already
// have it in the configuraion and it satisfies our dependency
// constraint, then we don't want to be forcing its upgrade (or,
@@ -479,13 +579,14 @@ namespace bpkg
build_package dp {
dsp,
- rp.first,
+ dap,
rp.second,
- nullopt, // Hold package.
- nullopt, // Hold version.
- {}, // Constraints.
- {name}, // Required by.
- false}; // Reconfigure.
+ nullopt, // Hold package.
+ nullopt, // Hold version.
+ {}, // Constraints.
+ dap->stub (), // System.
+ {name}, // Required by.
+ false}; // Reconfigure.
// Add our constraint, if we have one.
//
@@ -497,15 +598,32 @@ namespace bpkg
// and the version is not held, then warn, unless we are running
// quiet. Downgrade or upgrade of a held version -- refuse.
//
- if (collect (options, cd, db, move (dp)) && force)
+ // Note though that while the prerequisite was collected it could have
+ // happen because it is an optional system package and so not being
+ // pre-collected earlier. Meanwhile the package version was specified
+ // explicitly and we shouldn't consider that as a dependency-driven
+ // up/down-grade enforcement. To recognize such a case we just need to
+ // check for the system flag, so if it is true then the prerequisite
+ // is an optional system package. If it were non-optional it wouldn't
+ // be being collected now since it must have been pre-collected
+ // earlier. And if it were created from the selected package then
+ // the force flag wouldn't haven been true.
+ //
+ // Here is an example of the situation we need to handle properly:
+ //
+ // repo: foo/2(->bar/2), bar/0+1
+ // build sys:bar/1
+ // build foo ?sys:bar/2
+ //
+ const build_package* p (collect (options, cd, db, move (dp), true));
+ if (p != nullptr && force && !p->system)
{
- const version& sv (dsp->version);
- const version& av (rp.first->version);
+ const version& av (p->available_version ());
- // Fail if downgrade or held.
+ // Fail if downgrade non-system package or held.
//
- bool u (av > sv);
- bool f (dsp->hold_version || !u);
+ bool u (av > dsp->version);
+ bool f (dsp->hold_version || (!u && !dsp->system ()));
if (verb || f)
{
@@ -515,12 +633,18 @@ namespace bpkg
(f ? dr << fail : dr << warn)
<< "package " << name << " dependency on "
<< (c ? "(" : "") << d << (c ? ")" : "") << " is forcing "
- << (u ? "up" : "down") << "grade of " << d.name << " " << sv
- << " to " << av;
+ << (u ? "up" : "down") << "grade of " << *dsp << " to ";
+
+ // Print both (old and new) package names in full if the system
+ // attribution changes.
+ //
+ if (dsp->system ())
+ dr << p->available_name ();
+ else
+ dr << av; // Can't be a system version so is never wildcard.
if (dsp->hold_version)
- dr << info << "package version " << d.name << " " << sv
- << " is held";
+ dr << info << "package version " << *dsp << " is held";
if (f)
dr << info << "explicitly request version "
@@ -528,8 +652,17 @@ namespace bpkg
}
}
}
+ }
- return true;
+ void
+ collect_prerequisites (const common_options& options,
+ const dir_path& cd,
+ database& db,
+ const string& name)
+ {
+ auto mi (map_.find (name));
+ assert (mi != map_.end ());
+ collect_prerequisites (options, cd, db, mi->second.package);
}
// Order the previously-collected package with the specified name
@@ -583,45 +716,49 @@ namespace bpkg
};
// Similar to collect(), we can prune if the package is already
- // configured, right? Not so fast. While in collect() we didn't
- // need to add prerequisites of such a package, it doesn't mean
- // that they actually never ended up in the map via another way.
- // For example, some can be a part of the initial selection. And
- // in that case we must order things properly.
- //
- // So here we are going to do things differently depending on
- // whether the package is already configured or not. If it is,
- // then that means we can use its prerequisites list. Otherwise,
- // we use the manifest data.
+ // configured, right? Right for a system ones but not for others.
+ // While in collect() we didn't need to add prerequisites of such a
+ // package, it doesn't mean that they actually never ended up in the
+ // map via another way. For example, some can be a part of the initial
+ // selection. And in that case we must order things properly.
//
- if (sp != nullptr &&
- sp->version == ap->version &&
- sp->state == package_state::configured)
+ if (!p.system)
{
- for (const auto& p: sp->prerequisites)
+ // So here we are going to do things differently depending on
+ // whether the package is already configured or not. If it is and
+ // not as a system package, then that means we can use its
+ // prerequisites list. Otherwise, we use the manifest data.
+ //
+ if (sp != nullptr &&
+ sp->state == package_state::configured &&
+ sp->substate != package_substate::system &&
+ sp->version == p.available_version ())
{
- const string& name (p.first.object_id ());
+ for (const auto& p: sp->prerequisites)
+ {
+ const string& name (p.first.object_id ());
- // The prerequisites may not necessarily be in the map.
- //
- if (map_.find (name) != map_.end ())
- update (order (name, false));
+ // The prerequisites may not necessarily be in the map.
+ //
+ if (map_.find (name) != map_.end ())
+ update (order (name, false));
+ }
}
- }
- else
- {
- // We are iterating in reverse so that when we iterate over
- // the dependency list (also in reverse), prerequisites will
- // be built in the order that is as close to the manifest as
- // possible.
- //
- for (const dependency_alternatives& da:
- reverse_iterate (p.available->dependencies))
+ else
{
- assert (!da.conditional && da.size () == 1); // @@ TODO
- const dependency& d (da.front ());
+ // We are iterating in reverse so that when we iterate over
+ // the dependency list (also in reverse), prerequisites will
+ // be built in the order that is as close to the manifest as
+ // possible.
+ //
+ for (const dependency_alternatives& da:
+ reverse_iterate (ap->dependencies))
+ {
+ assert (!da.conditional && da.size () == 1); // @@ TODO
+ const dependency& d (da.front ());
- update (order (d.name, false));
+ update (order (d.name, false));
+ }
}
}
@@ -672,14 +809,15 @@ namespace bpkg
build_package& p (*pos);
const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
const string& n (sp->name);
// See if we are up/downgrading this package. In particular, the
// available package could be NULL meaning we are just reconfiguring.
//
- int ud (ap != nullptr ? sp->version.compare (ap->version) : 0);
+ int ud (p.available != nullptr
+ ? sp->version.compare (p.available_version ())
+ : 0);
using query = query<package_dependent>;
@@ -703,34 +841,43 @@ namespace bpkg
build_package& dp (i->second.package);
check = dp.available == nullptr ||
- dp.selected->version == dp.available->version;
+ (dp.selected->system () == dp.system &&
+ dp.selected->version == dp.available_version ());
}
if (check)
{
- const version& v (ap->version);
+ const version& av (p.available_version ());
const dependency_constraint& c (*pd.constraint);
- if (!satisfies (v, c))
+ if (!satisfies (av, c))
{
diag_record dr;
dr << fail << "unable to " << (ud < 0 ? "up" : "down") << "grade "
- << "package " << n << " " << sp->version << " to " << v;
+ << "package " << *sp << " to ";
+
+ // Print both (old and new) package names in full if the system
+ // attribution changes.
+ //
+ if (p.system != sp->system ())
+ dr << p.available_name ();
+ else
+ dr << av; // Can't be the wildcard otherwise would satisfy.
dr << info << "because package " << dn << " depends on (" << n
<< " " << c << ")";
string rb;
- if (p.required_by.find ("") == p.required_by.end ())
+ if (!p.user_selection ())
{
for (const string& n: p.required_by)
rb += ' ' + n;
}
if (!rb.empty ())
- dr << info << "package " << n << " " << v << " required by"
- << rb;
+ dr << info << "package " << p.available_name ()
+ << " required by" << rb;
dr << info << "explicitly request up/downgrade of package " << dn;
@@ -773,6 +920,7 @@ namespace bpkg
else
{
shared_ptr<selected_package> dsp (db.load<selected_package> (dn));
+ bool system (dsp->system ()); // Save flag before the move(dsp) call.
i = map_.emplace (
move (dn),
@@ -783,11 +931,12 @@ namespace bpkg
move (dsp),
nullptr,
nullptr,
- nullopt, // Hold package.
- nullopt, // Hold version.
- {}, // Constraints.
- {n}, // Required by.
- true} // Reconfigure.
+ nullopt, // Hold package.
+ nullopt, // Hold version.
+ {}, // Constraints.
+ system,
+ {n}, // Required by.
+ true} // Reconfigure.
}).first;
i->second.position = insert (pos, i->second.package);
@@ -810,7 +959,7 @@ namespace bpkg
};
int
- pkg_build (const pkg_build_options& o, cli::scanner& args)
+ pkg_build (const pkg_build_options& o, cli::scanner& a)
{
tracer trace ("pkg_build");
@@ -827,10 +976,66 @@ namespace bpkg
<< "specified" <<
info << "run 'bpkg help pkg-build' for more information";
- if (!args.more ())
+ if (!a.more ())
fail << "package name argument expected" <<
info << "run 'bpkg help pkg-build' for more information";
+ map<string, string> package_arg;
+
+ // Check if the package is a duplicate. Return true if it is but harmless.
+ //
+ auto check_dup = [&package_arg] (const string& n, const char* arg) -> bool
+ {
+ auto r (package_arg.emplace (n, arg));
+
+ if (!r.second && r.first->second != arg)
+ fail << "duplicate package " << n <<
+ info << "first mentioned as " << r.first->second <<
+ info << "second mentioned as " << arg;
+
+ return !r.second;
+ };
+
+ // Pre-scan the arguments and sort them out into optional and mandatory.
+ //
+ strings args;
+ while (a.more ())
+ {
+ const char* arg (a.next ());
+ const char* s (arg);
+
+ bool opt (s[0] == '?');
+ if (opt)
+ ++s;
+ else
+ args.push_back (s);
+
+ if (parse_package_scheme (s) == package_scheme::sys)
+ {
+ string n (parse_package_name (s));
+ version v (parse_package_version (s));
+
+ if (opt && check_dup (n, arg))
+ continue;
+
+ if (v.empty ())
+ v = wildcard_version;
+
+ const system_package* sp (system_repository.find (n));
+ if (sp == nullptr) // Will deal with all the duplicates later.
+ system_repository.insert (n, v, true);
+ }
+ else if (opt)
+ warn << "no information can be extracted from ?" << s <<
+ info << "package is ignored";
+ }
+
+ if (args.empty ())
+ {
+ warn << "nothing to build";
+ return 0;
+ }
+
database db (open (c, trace));
// Note that the session spans all our transactions. The idea here is
@@ -859,10 +1064,10 @@ namespace bpkg
// "guesses". So what we are going to do here is re-run them with full
// diagnostics if the name/version guess doesn't pan out.
//
- for (bool diag (false); args.more (); )
+ bool diag (false);
+ for (auto i (args.cbegin ()); i != args.cend (); )
{
- const char* s (args.peek ());
- size_t sn (strlen (s));
+ const char* package (i->c_str ());
// Reduce all the potential variations (archive, directory, package
// name, package name/version) to a single available_package object.
@@ -873,78 +1078,84 @@ namespace bpkg
shared_ptr<repository> ar;
shared_ptr<available_package> ap;
- // Is this a package archive?
- //
- try
- {
- path a (s);
- if (exists (a))
- {
- if (diag)
- info << "'" << s << "' does not appear to be a valid package "
- << "archive: ";
-
- package_manifest m (pkg_verify (o, a, true, diag));
-
- // This is a package archive (note that we shouldn't throw
- // failed from here on).
- //
- l4 ([&]{trace << "archive " << a;});
- n = m.name;
- v = m.version;
- ar = root;
- ap = make_shared<available_package> (move (m));
- ap->locations.push_back (package_location {root, move (a)});
- }
- }
- catch (const invalid_path&)
- {
- // Not a valid path so cannot be an archive.
- }
- catch (const failed&)
- {
- // Not a valid package archive.
- }
+ bool sys (parse_package_scheme (package) == package_scheme::sys);
- // Is this a package directory?
- //
- // We used to just check any name which led to some really bizarre
- // behavior where a sub-directory of the working directory happened
- // to contain a manifest file and was therefore treated as a package
- // directory. So now we will only do this test if the name ends with
- // the directory separator.
- //
- if (sn != 0 && path::traits::is_separator (s[sn - 1]))
+ if (!sys)
{
+ // Is this a package archive?
+ //
try
{
- dir_path d (s);
- if (exists (d))
+ path a (package);
+ if (exists (a))
{
if (diag)
- info << "'" << s << "' does not appear to be a valid package "
- << "directory: ";
+ info << "'" << package << "' does not appear to be a valid "
+ << "package archive: ";
- package_manifest m (pkg_verify (d, true, diag));
+ package_manifest m (pkg_verify (o, a, true, diag));
- // This is a package directory (note that we shouldn't throw
+ // This is a package archive (note that we shouldn't throw
// failed from here on).
//
- l4 ([&]{trace << "directory " << d;});
+ l4 ([&]{trace << "archive " << a;});
n = m.name;
v = m.version;
- ap = make_shared<available_package> (move (m));
ar = root;
- ap->locations.push_back (package_location {root, move (d)});
+ ap = make_shared<available_package> (move (m));
+ ap->locations.push_back (package_location {root, move (a)});
}
}
catch (const invalid_path&)
{
- // Not a valid path so cannot be a package directory.
+ // Not a valid path so cannot be an archive.
}
catch (const failed&)
{
- // Not a valid package directory.
+ // Not a valid package archive.
+ }
+
+ // Is this a package directory?
+ //
+ // We used to just check any name which led to some really bizarre
+ // behavior where a sub-directory of the working directory happened
+ // to contain a manifest file and was therefore treated as a package
+ // directory. So now we will only do this test if the name ends with
+ // the directory separator.
+ //
+ size_t pn (strlen (package));
+ if (pn != 0 && path::traits::is_separator (package[pn - 1]))
+ {
+ try
+ {
+ dir_path d (package);
+ if (exists (d))
+ {
+ if (diag)
+ info << "'" << package << "' does not appear to be a valid "
+ << "package directory: ";
+
+ package_manifest m (pkg_verify (d, true, diag));
+
+ // This is a package directory (note that we shouldn't throw
+ // failed from here on).
+ //
+ l4 ([&]{trace << "directory " << d;});
+ n = m.name;
+ v = m.version;
+ ap = make_shared<available_package> (move (m));
+ ar = root;
+ ap->locations.push_back (package_location {root, move (d)});
+ }
+ }
+ catch (const invalid_path&)
+ {
+ // Not a valid path so cannot be a package directory.
+ }
+ catch (const failed&)
+ {
+ // Not a valid package directory.
+ }
}
}
@@ -959,14 +1170,18 @@ namespace bpkg
{
try
{
- n = parse_package_name (s);
- v = parse_package_version (s);
- l4 ([&]{trace << "package " << n << "; version " << v;});
+ n = parse_package_name (package);
+ v = parse_package_version (package);
+
+ l4 ([&]{trace << (sys ? "system " : "") << "package " << n
+ << "; version " << v;});
- // Either get the user-specified version or the latest.
+ // Either get the user-specified version or the latest for a
+ // source code package. For a system package we peek the latest
+ // one just to ensure the package is recognized.
//
auto rp (
- v.empty ()
+ v.empty () || sys
? find_available (db, n, root, nullopt)
: find_available (db, n, root, dependency_constraint (v)));
@@ -980,7 +1195,10 @@ namespace bpkg
}
}
- args.next (); // We are handling this argument.
+ // We are handling this argument.
+ //
+ if (check_dup (n, i++->c_str ()))
+ continue;
// Load the package that may have already been selected and
// figure out what exactly we need to do here. The end goal
@@ -995,68 +1213,114 @@ namespace bpkg
info << "use 'pkg-purge --force' to remove";
bool found (true);
-
- // If the user asked for a specific version, then that's what
- // we ought to be building.
+ bool sys_advise (false);
+
+ // If the package is not available from the repository we can try to
+ // create it from the orphaned selected package. Meanwhile that doesn't
+ // make sense for a system package. The only purpose to configure a
+ // system package is to build it's dependent. But if the package is
+ // not in the repository then there is no dependent for it, otherwise
+ // the repository is broken.
//
- if (!v.empty ())
+ if (!sys)
{
- for (;;)
+ // If we failed to find the requested package we can still check if
+ // the package name is present in the repositories and if that's the
+ // case to inform a user about the possibility to configure the
+ // package as a system one on failure. Note we still can end up
+ // creating an orphan from the selected package and so succeed.
+ //
+ if (ap == nullptr)
{
- if (ap != nullptr) // Must be that version, see above.
- break;
-
- // Otherwise, our only chance is that the already selected
- // object is that exact version.
- //
- if (sp != nullptr && sp->version == v)
- break; // Derive ap from sp below.
-
- found = false;
- break;
+ if (!v.empty () &&
+ find_available (db, n, root, nullopt).first != nullptr)
+ sys_advise = true;
}
- }
- //
- // No explicit version was specified by the user.
- //
- else
- {
- if (ap != nullptr)
+ else if (ap->stub ())
{
- // Even if this package is already in the configuration, should
- // we have a newer version, we treat it as an upgrade request;
- // otherwise, why specify the package in the first place? We just
- // need to check if what we already have is "better" (i.e., newer).
- //
- if (sp != nullptr && ap->id.version < sp->version)
- ap = nullptr; // Derive ap from sp below.
+ sys_advise = true;
+ ap = nullptr;
}
- else
+
+ // If the user asked for a specific version, then that's what we
+ // ought to be building.
+ //
+ if (!v.empty ())
{
- if (sp == nullptr)
+ for (;;)
+ {
+ if (ap != nullptr) // Must be that version, see above.
+ break;
+
+ // Otherwise, our only chance is that the already selected object
+ // is that exact version.
+ //
+ if (sp != nullptr && !sp->system () && sp->version == v)
+ break; // Derive ap from sp below.
+
found = false;
+ break;
+ }
+ }
+ //
+ // No explicit version was specified by the user (not relevant for a
+ // system package, see above).
+ //
+ else
+ {
+ assert (!sys);
+
+ if (ap != nullptr)
+ {
+ assert (!ap->stub ());
- // Otherwise, derive ap from sp below.
+ // Even if this package is already in the configuration, should
+ // we have a newer version, we treat it as an upgrade request;
+ // otherwise, why specify the package in the first place? We just
+ // need to check if what we already have is "better" (i.e.,
+ // newer).
+ //
+ if (sp != nullptr && !sp->system () && ap->version < sp->version)
+ ap = nullptr; // Derive ap from sp below.
+ }
+ else
+ {
+ if (sp == nullptr || sp->system ())
+ found = false;
+
+ // Otherwise, derive ap from sp below.
+ }
}
}
+ else if (ap == nullptr)
+ found = false;
if (!found)
{
diag_record dr;
+ dr << fail;
- dr << fail << "unknown package " << n;
- if (!v.empty ())
- dr << " " << v;
+ if (!sys_advise)
+ {
+ dr << "unknown package " << n;
- // Let's help the new user out here a bit.
- //
- if (db.query_value<repository_count> () == 0)
- dr << info << "configuration " << c << " has no repositories"
- << info << "use 'bpkg rep-add' to add a repository";
- else if (db.query_value<available_package_count> () == 0)
- dr << info << "configuration " << c << " has no available packages"
- << info << "use 'bpkg rep-fetch' to fetch available packages "
- << "list";
+ // Let's help the new user out here a bit.
+ //
+ if (db.query_value<repository_count> () == 0)
+ dr << info << "configuration " << c << " has no repositories"
+ << info << "use 'bpkg rep-add' to add a repository";
+ else if (db.query_value<available_package_count> () == 0)
+ dr << info << "configuration " << c << " has no available "
+ << "packages"
+ << info << "use 'bpkg rep-fetch' to fetch available packages "
+ << "list";
+ }
+ else
+ {
+ dr << package << " is not available in source" <<
+ info << "specify sys:" << package << " if it is available from "
+ << "the system";
+ }
}
// If the available_package object is still NULL, then it means
@@ -1064,38 +1328,54 @@ namespace bpkg
//
if (ap == nullptr)
{
- assert (sp != nullptr);
+ assert (sp != nullptr && sp->system () == sys);
auto rp (make_available (o, c, db, sp));
ap = rp.first;
ar = rp.second; // Could be NULL (orphan).
}
+ if (v.empty () && sys)
+ v = wildcard_version;
+
// Finally add this package to the list.
//
- l4 ([&]{trace << "collect " << ap->id.name << " " << ap->version;});
-
build_package p {
move (sp),
move (ap),
move (ar),
- true, // Hold package.
- !v.empty (), // Hold version.
- {}, // Constraints.
- {""}, // Required by (command line).
- false}; // Reconfigure.
+ true, // Hold package.
+ !v.empty (), // Hold version.
+ {}, // Constraints.
+ sys,
+ {""}, // Required by (command line).
+ false}; // Reconfigure.
+
+ l4 ([&]{trace << "collect " << p.available_name ();});
// "Fix" the version the user asked for by adding the '==' constraint.
//
+ // Note: for a system package this must always be present (so that
+ // this build_package instance is never replaced).
+ //
if (!v.empty ())
p.constraints.emplace_back (
"command line",
dependency_constraint (v));
- pkgs.collect (o, c, db, move (p));
+ // Pre-collect user selection to make sure dependency-forced
+ // up/down-grades are handled properly (i.e., the order in which we
+ // specify packages on the command line does not matter).
+ //
+ pkgs.collect (o, c, db, move (p), false);
names.push_back (n);
}
+ // Collect all the packages prerequisites.
+ //
+ for (const string& n: names)
+ pkgs.collect_prerequisites (o, c, db, n);
+
// Now that we have collected all the package versions that we need
// to build, arrange them in the "dependency order", that is, with
// every package on the list only possibly depending on the ones
@@ -1128,11 +1408,10 @@ namespace bpkg
for (const build_package& p: reverse_iterate (pkgs))
{
const shared_ptr<selected_package>& sp (p.selected);
- const shared_ptr<available_package>& ap (p.available);
string act;
string cause;
- if (ap == nullptr)
+ if (p.available == nullptr)
{
// This is a dependent needing reconfiguration.
//
@@ -1147,8 +1426,8 @@ namespace bpkg
// make sure it is configured and updated.
//
if (sp == nullptr)
- act = "build ";
- else if (sp->version == ap->version)
+ act = p.system ? "configure " : "build ";
+ else if (sp->version == p.available_version ())
{
// If this package is already configured and is not part of the
// user selection, then there is nothing we will be explicitly
@@ -1160,17 +1439,25 @@ namespace bpkg
find (names.begin (), names.end (), sp->name) == names.end ())
continue;
- act = p.reconfigure () ? "reconfigure/build " : "build ";
+ act = p.system
+ ? "reconfigure "
+ : p.reconfigure ()
+ ? "reconfigure/build "
+ : "build ";
}
else
- act = sp->version < ap->version ? "upgrade " : "downgrade ";
+ act = p.system
+ ? "reconfigure "
+ : sp->version < p.available_version ()
+ ? "upgrade "
+ : "downgrade ";
- act += ap->id.name + ' ' + ap->version.string ();
+ act += p.available_name ();
cause = "required by";
}
string rb;
- if (p.required_by.find ("") == p.required_by.end ()) // User selection?
+ if (!p.user_selection ())
{
for (const string& n: p.required_by)
rb += ' ' + n;
@@ -1243,7 +1530,7 @@ namespace bpkg
// disfigure
//
- for (const build_package& p: pkgs)
+ for (build_package& p: pkgs)
{
// We are only interested in configured packages that are either
// up/down-graded or need reconfiguration (e.g., dependents).
@@ -1251,7 +1538,7 @@ namespace bpkg
if (!p.reconfigure ())
continue;
- const shared_ptr<selected_package>& sp (p.selected);
+ 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.
@@ -1272,13 +1559,31 @@ namespace bpkg
}
pkg_disfigure (c, o, t, sp); // Commits the transaction.
- assert (sp->state == package_state::unpacked);
+ assert (sp->state == package_state::unpacked ||
+ sp->state == package_state::transient);
if (verb)
- text << "disfigured " << sp->name << " " << sp->version;
+ text << (sp->state == package_state::transient
+ ? "purged "
+ : "disfigured ") << *sp;
+
+ // Selected system package is now gone from the database. Before we drop
+ // the object we need to make sure the hold state is preserved in the
+ // package being reconfigured.
+ //
+ if (sp->state == package_state::transient)
+ {
+ if (!p.hold_package)
+ p.hold_package = sp->hold_package;
+
+ if (!p.hold_version)
+ p.hold_version = sp->hold_version;
+
+ sp.reset ();
+ }
}
- // fetch/unpack
+ // purge/fetch/unpack
//
for (build_package& p: reverse_iterate (pkgs))
{
@@ -1288,9 +1593,36 @@ namespace bpkg
if (ap == nullptr) // Skip dependents.
continue;
+ // System package should not be fetched, it should only be configured on
+ // the next stage. Here we need to purge selected non-system package if
+ // present. Before we drop the object we need to make sure the hold
+ // state is preserved for the package being reconfigured.
+ //
+ if (p.system)
+ {
+ if (sp != nullptr && !sp->system ())
+ {
+ transaction t (db.begin ());
+ pkg_purge (c, t, sp); // Commits the transaction.
+
+ if (verb)
+ text << "purged " << *sp;
+
+ if (!p.hold_package)
+ p.hold_package = sp->hold_package;
+
+ if (!p.hold_version)
+ p.hold_version = sp->hold_version;
+
+ sp.reset ();
+ }
+
+ continue;
+ }
+
// Fetch if this is a new package or if we are up/down-grading.
//
- if (sp == nullptr || sp->version != ap->version)
+ if (sp == nullptr || sp->version != p.available_version ())
{
sp.reset (); // For the directory case below.
@@ -1305,7 +1637,7 @@ namespace bpkg
c,
t,
ap->id.name,
- ap->version,
+ p.available_version (),
true); // Replace; commits the transaction.
}
else if (exists (pl.location)) // Directory case is handled by unpack.
@@ -1324,7 +1656,7 @@ namespace bpkg
assert (sp->state == package_state::fetched);
if (verb)
- text << "fetched " << sp->name << " " << sp->version;
+ text << "fetched " << *sp;
}
}
@@ -1354,29 +1686,41 @@ namespace bpkg
assert (sp->state == package_state::unpacked);
if (verb)
- text << "unpacked " << sp->name << " " << sp->version;
+ text << "unpacked " << *sp;
}
}
// configure
//
- for (const build_package& p: reverse_iterate (pkgs))
+ for (build_package& p: reverse_iterate (pkgs))
{
- const shared_ptr<selected_package>& sp (p.selected);
+ shared_ptr<selected_package>& sp (p.selected);
+ const shared_ptr<available_package>& ap (p.available);
- assert (sp != nullptr);
+ // At this stage the package is either selected, in which case it's a
+ // source code one, or just available, in which case it is a system
+ // one. Note that a system package gets selected as being configured.
+ //
+ assert (sp != nullptr || p.system);
// We configure everything that isn't already configured.
//
- if (sp->state == package_state::configured)
+ if (sp != nullptr && sp->state == package_state::configured)
continue;
transaction t (db.begin ());
- pkg_configure (c, o, t, sp, strings ()); // Commits the transaction.
+
+ // Note that pkg_configure() commits the transaction.
+ //
+ if (p.system)
+ sp = pkg_configure_system (ap->id.name, p.available_version (), t);
+ else
+ pkg_configure (c, o, t, sp, strings ());
+
assert (sp->state == package_state::configured);
if (verb)
- text << "configured " << sp->name << " " << sp->version;
+ text << "configured " << *sp;
}
// Small detour: update the hold state. While we could have tried
@@ -1389,7 +1733,7 @@ namespace bpkg
assert (sp != nullptr);
// Note that we should only "increase" the hold_package state. For
- // version, if the user requested upgrade to the (unsepcified) latest,
+ // version, if the user requested upgrade to the (unspecified) latest,
// then we want to reset it.
//
bool hp (p.hold_package ? *p.hold_package : sp->hold_package);
@@ -1419,7 +1763,7 @@ namespace bpkg
text << "hold package " << sp->name;
if (hv)
- text << "hold version " << sp->name << " " << sp->version;
+ text << "hold version " << *sp;
}
}
}
@@ -1447,7 +1791,8 @@ namespace bpkg
{
const shared_ptr<selected_package>& sp (p.selected);
- if (find (names.begin (), names.end (), sp->name) != names.end ())
+ if (!sp->system () && // System package doesn't need update.
+ find (names.begin (), names.end (), sp->name) != names.end ())
upkgs.push_back (pkg_command_vars {sp, strings ()});
}
@@ -1476,7 +1821,7 @@ namespace bpkg
if (verb)
{
for (const pkg_command_vars& pv: upkgs)
- text << "updated " << pv.pkg->name << " " << pv.pkg->version;
+ text << "updated " << *pv.pkg;
}
return 0;
diff --git a/bpkg/pkg-command.cxx b/bpkg/pkg-command.cxx
index ba1b548..469e3a0 100644
--- a/bpkg/pkg-command.cxx
+++ b/bpkg/pkg-command.cxx
@@ -124,7 +124,10 @@ namespace bpkg
fail << "package " << n << " is " << p->state <<
info << "expected it to be configured";
- l4 ([&]{trace << p->name << " " << p->version;});
+ if (p->substate == package_substate::system)
+ fail << "cannot " << cmd << " system package " << n;
+
+ l4 ([&]{trace << *p;});
// Read package-specific variables.
//
@@ -142,8 +145,7 @@ namespace bpkg
if (verb)
{
for (const pkg_command_vars& pv: ps)
- text << cmd << (cmd.back () != 'e' ? "ed " : "d ")
- << pv.pkg->name << " " << pv.pkg->version;
+ text << cmd << (cmd.back () != 'e' ? "ed " : "d ") << *pv.pkg;
}
return 0;
diff --git a/bpkg/pkg-configure b/bpkg/pkg-configure
index 9b044e2..fc09ac5 100644
--- a/bpkg/pkg-configure
+++ b/bpkg/pkg-configure
@@ -9,6 +9,7 @@
#include <bpkg/forward> // transaction, selected_package
#include <bpkg/utility>
+#include <bpkg/package>
#include <bpkg/pkg-configure-options>
namespace bpkg
@@ -24,6 +25,11 @@ namespace bpkg
transaction&,
const shared_ptr<selected_package>&,
const strings& config_vars);
+
+ // Configure a system package and commit the transaction.
+ //
+ shared_ptr<selected_package>
+ pkg_configure_system (const string& name, const version&, transaction&);
}
#endif // BPKG_PKG_CONFIGURE
diff --git a/bpkg/pkg-configure.cli b/bpkg/pkg-configure.cli
index 274a3c3..c5fa586 100644
--- a/bpkg/pkg-configure.cli
+++ b/bpkg/pkg-configure.cli
@@ -11,7 +11,7 @@ include <bpkg/configuration.cli>;
namespace bpkg
{
{
- "<options> <pkg> <conf-var>",
+ "<options> <pkg> <ver> <conf-var>",
"\h|SYNOPSIS|
@@ -19,11 +19,19 @@ namespace bpkg
\h|DESCRIPTION|
- The \cb{pkg-configure} command configures the previously unpacked
- (\l{bpkg-pkg-unpack(1)}) package. The package inherits the common
- \cb{build2} configuration values that were specified when creating the
- configuration (\l{bpkg-cfg-create(1)}). Additional, package-specific
- configuration variables can be specified after the package name."
+ The \cb{pkg-configure} command configures either the previously unpacked
+ (\l{bpkg-pkg-unpack(1)}) source code package or a package that is present
+ in the system.
+
+ A source code package inherits the common \cb{build2} configuration
+ values that were specified when creating the configuration
+ (\l{bpkg-cfg-create(1)}). Additional, package-specific configuration
+ variables can be specified after the package name.
+
+ A system package is specified using the \c{\b{sys:}<pkg>[/<ver>]}
+ syntax. If the package version (<ver>) is not specified, then it is
+ considered to be unknown but satisfying any dependency constraint. Such a
+ version is displayed as \cb{*}."
}
class pkg_configure_options: configuration_options
diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx
index 16eefd2..81b1fe3 100644
--- a/bpkg/pkg-configure.cxx
+++ b/bpkg/pkg-configure.cxx
@@ -9,6 +9,7 @@
#include <bpkg/database>
#include <bpkg/diagnostics>
#include <bpkg/satisfaction>
+#include <bpkg/manifest-utility>
#include <bpkg/pkg-verify>
#include <bpkg/pkg-disfigure>
@@ -146,6 +147,36 @@ namespace bpkg
t.commit ();
}
+ shared_ptr<selected_package>
+ pkg_configure_system (const string& n, const version& v, transaction& t)
+ {
+ tracer trace ("pkg_configure_system");
+
+ database& db (t.database ());
+ tracer_guard tg (db, trace);
+
+ shared_ptr<selected_package> p (
+ new selected_package {
+ n,
+ v,
+ package_state::configured,
+ package_substate::system,
+ false, // Don't hold package.
+ false, // Don't hold version.
+ repository_location (), // Root repository.
+ nullopt, // No source archive.
+ false, // No auto-purge (does not get there).
+ nullopt, // No source directory.
+ false,
+ nullopt, // No output directory.
+ {}}); // No prerequisites.
+
+ db.persist (p);
+ t.commit ();
+
+ return p;
+ }
+
int
pkg_configure (const pkg_configure_options& o, cli::scanner& args)
{
@@ -175,25 +206,62 @@ namespace bpkg
fail << "package name argument expected" <<
info << "run 'bpkg help pkg-configure' for more information";
+ const char* package (n.c_str ());
+ package_scheme ps (parse_package_scheme (package));
+
+ if (ps == package_scheme::sys && !vars.empty ())
+ fail << "configuration variables specified for a system package";
+
database db (open (c, trace));
transaction t (db.begin ());
session s;
- shared_ptr<selected_package> p (db.find<selected_package> (n));
+ shared_ptr<selected_package> p;
+
+ // pkg_configure() commits the transaction.
+ //
+ if (ps == package_scheme::sys)
+ {
+ // Configure system package.
+ //
+ version v (parse_package_version (package));
+ n = parse_package_name (package);
+
+ p = db.find<selected_package> (n);
+
+ if (p != nullptr)
+ fail << "package " << n << " already exists in configuration " << c;
- if (p == nullptr)
- fail << "package " << n << " does not exist in configuration " << c;
+ shared_ptr<repository> rep (db.load<repository> ("")); // Root.
- if (p->state != package_state::unpacked)
- fail << "package " << n << " is " << p->state <<
- info << "expected it to be unpacked";
+ using query = query<available_package>;
+ query q (query::id.name == n);
- l4 ([&]{trace << p->name << " " << p->version;});
+ if (filter_one (rep, db.query<available_package> (q)).first == nullptr)
+ fail << "unknown package " << n;
- pkg_configure (c, o, t, p, vars); // Commits the transaction.
+ p = pkg_configure_system (n, v.empty () ? wildcard_version : v, t);
+ }
+ else
+ {
+ // Configure unpacked package.
+ //
+ p = db.find<selected_package> (n);
+
+ if (p == nullptr)
+ fail << "package " << n << " does not exist in configuration " << c;
+
+ if (p->state != package_state::unpacked)
+ fail << "package " << n << " is " << p->state <<
+ info << "expected it to be unpacked";
+
+ l4 ([&]{trace << *p;});
+
+ pkg_configure (c, o, t, p, vars);
+ }
if (verb)
- text << "configured " << p->name << " " << p->version;
+ text << "configured " << *p;
return 0;
}
diff --git a/bpkg/pkg-disfigure.cli b/bpkg/pkg-disfigure.cli
index c56aa4c..41fbbcd 100644
--- a/bpkg/pkg-disfigure.cli
+++ b/bpkg/pkg-disfigure.cli
@@ -20,8 +20,9 @@ namespace bpkg
\h|DESCRIPTION|
The \cb{pkg-disfigure} command disfigures the previously configured
- (via \l{bpkg-pkg-build(1)} or \l{bpkg-pkg-configure(1)}) package and
- returns it to the \cb{unpacked} state."
+ (via \l{bpkg-pkg-build(1)} or \l{bpkg-pkg-configure(1)}) package. A
+ source code package is returned to the \cb{unpacked} state. A system
+ package is removed from the configuration."
}
class pkg_disfigure_options: configuration_options
diff --git a/bpkg/pkg-disfigure.cxx b/bpkg/pkg-disfigure.cxx
index de9b681..37a2989 100644
--- a/bpkg/pkg-disfigure.cxx
+++ b/bpkg/pkg-disfigure.cxx
@@ -25,6 +25,8 @@ namespace bpkg
tracer trace ("pkg_disfigure");
+ l4 ([&]{trace << *p;});
+
database& db (t.database ());
tracer_guard tg (db, trace);
@@ -51,6 +53,17 @@ namespace bpkg
}
}
+ if (p->substate == package_substate::system)
+ {
+ db.erase (p);
+ t.commit ();
+
+ p->state = package_state::transient;
+ p->substate = package_substate::none;
+
+ return;
+ }
+
// Since we are no longer configured, clear the prerequisites list.
//
p->prerequisites.clear ();
@@ -154,12 +167,15 @@ namespace bpkg
fail << "package " << n << " is " << p->state <<
info << "expected it to be configured";
- l4 ([&]{trace << p->name << " " << p->version;});
-
pkg_disfigure (c, o, t, p); // Commits the transaction.
+ assert (p->state == package_state::unpacked ||
+ p->state == package_state::transient);
+
if (verb)
- text << "disfigured " << p->name << " " << p->version;
+ text << (p->state == package_state::transient
+ ? "purged "
+ : "disfigured ") << *p;
return 0;
}
diff --git a/bpkg/pkg-drop.cxx b/bpkg/pkg-drop.cxx
index 79a4870..fa9f0bd 100644
--- a/bpkg/pkg-drop.cxx
+++ b/bpkg/pkg-drop.cxx
@@ -352,10 +352,14 @@ namespace bpkg
//
transaction t (db.begin ());
pkg_disfigure (c, o, t, p); // Commits the transaction.
- assert (p->state == package_state::unpacked);
+
+ assert (p->state == package_state::unpacked ||
+ p->state == package_state::transient);
if (verb)
- text << "disfigured " << p->name;
+ text << (p->state == package_state::transient
+ ? "purged "
+ : "disfigured ") << p->name;
}
if (disfigure_only)
@@ -372,6 +376,9 @@ namespace bpkg
const shared_ptr<selected_package>& p (dp.package);
+ if (p->state == package_state::transient) // Fully purged by disfigure.
+ continue;
+
assert (p->state == package_state::fetched ||
p->state == package_state::unpacked);
@@ -526,7 +533,8 @@ namespace bpkg
for (const drop_package& dp: pkgs)
{
if (dp.reason == drop_reason::prerequisite)
- dr << text << dp.package->name;
+ dr << text << (dp.package->system () ? "sys:" : "")
+ << dp.package->name;
}
}
diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx
index 0534e65..0ff1199 100644
--- a/bpkg/pkg-fetch.cxx
+++ b/bpkg/pkg-fetch.cxx
@@ -71,6 +71,7 @@ namespace bpkg
move (n),
move (v),
package_state::fetched,
+ package_substate::none,
false, // hold package
false, // hold version
move (rl),
@@ -115,7 +116,9 @@ namespace bpkg
diag_record dr (fail);
dr << "package " << n << " already exists in configuration " << c <<
- info << "version: " << p->version << ", state: " << p->state;
+ info << "version: " << p->version_string ()
+ << ", state: " << p->state
+ << ", substate: " << p->substate;
if (s) // Suitable state for replace?
dr << info << "use 'pkg-fetch --replace|-r' to replace";
@@ -285,7 +288,7 @@ namespace bpkg
}
if (verb)
- text << "fetched " << p->name << " " << p->version;
+ text << "fetched " << *p;
return 0;
}
diff --git a/bpkg/pkg-purge.cxx b/bpkg/pkg-purge.cxx
index 34ca7f8..c61658e 100644
--- a/bpkg/pkg-purge.cxx
+++ b/bpkg/pkg-purge.cxx
@@ -82,6 +82,8 @@ namespace bpkg
db.erase (p);
t.commit ();
+
+ p->state = package_state::transient;
}
int
@@ -190,16 +192,18 @@ namespace bpkg
{
p->state = package_state::fetched;
db.update (p);
+ t.commit ();
}
}
else
+ {
db.erase (p);
-
- t.commit ();
+ t.commit ();
+ p->state = package_state::transient;
+ }
if (verb)
- text << (o.keep () ? "keeping archive " : "purged ")
- << p->name << " " << p->version;
+ text << (o.keep () ? "keeping archive " : "purged ") << *p;
return 0;
}
diff --git a/bpkg/pkg-status.cli b/bpkg/pkg-status.cli
index 25122cc..5bec10c 100644
--- a/bpkg/pkg-status.cli
+++ b/bpkg/pkg-status.cli
@@ -25,7 +25,9 @@ namespace bpkg
The status output format is regular. If several packages were specified,
then each line starts with the package name (and version, if specified)
- followed by '\cb{:}'. Then comes one of the following status words:
+ followed by '\cb{:}'. Then comes one of the status words listed below.
+ Some of them can be optionally followed by '\cb{,}' (no spaces) and a
+ sub-status word.
\dl|
@@ -49,7 +51,11 @@ namespace bpkg
\li|\cb{configured}
- Package is part of the configuration and is configured.|
+ Package is part of the configuration and is configured. May be
+ followed by the \cb{system} sub-status indicating a package coming
+ from the system. The version of such a system package (described
+ below) may be the special '\cb{*}' value indicating a wildcard
+ version.|
\li|\cb{broken}
@@ -58,7 +64,10 @@ namespace bpkg
If only the package name was specified without the package version, then
the \cb{available} status word is followed by the list of available
- versions.
+ versions. The last version on this list may have the \cb{sys:} prefix
+ indicating an available system version. Such a system version may be
+ the special '\cb{?}' value indicating that a package may or may not
+ be available from the system and its version is unknown.
Similarly, if only the package name was specified, then the \cb{fetched},
\cb{unpacked}, \cb{configured}, and \cb{broken} status words are followed
@@ -73,7 +82,7 @@ namespace bpkg
Below are some examples, assuming the configuration has \cb{libfoo}
\cb{1.0.0} configured and held as well as \cb{libfoo} \cb{1.1.0} and
- \cb{1.1.1} available from a repository.
+ \cb{1.1.1} available from source and \cb{1.1.0} from the system.
\
bpkg status libbar
@@ -86,10 +95,13 @@ namespace bpkg
configured hold_package
bpkg status libfoo/1.1.0
+ available 1.1.0 sys:1.1.0
+
+ bpkg status libfoo/1.1.1
available
bpkg status libfoo
- configured 1.0.0 hold_package; available 1.1.0 1.1.1
+ configured 1.0.0 hold_package; available 1.1.0 1.1.1 sys:1.1.0
bpkg status libfoo/1.0.0 libbar
libfoo/1.0.0: configured hold_package
@@ -102,12 +114,19 @@ namespace bpkg
bpkg status libfoo/1.0.0
unknown
- bpkg status libfoo/1.1.0
- available
+ bpkg status libfoo
+ available 1.1.0 1.1.1 sys:1.1.0
+ \
+
+ And assuming now that we built \cb{libfoo} as a system package with
+ the wildcard version:
+ \
bpkg status libfoo
- available 1.1.0 1.1.1
+ configured,system * hold_package; available 1.1.0 1.1.1 sys:1.1.0
+ unknown
\
+
"
}
diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx
index 1396b1a..0cb4168 100644
--- a/bpkg/pkg-status.cxx
+++ b/bpkg/pkg-status.cxx
@@ -58,31 +58,44 @@ namespace bpkg
// Now look for available packages.
//
+ bool available; // At least one vailable package (stub or not).
vector<shared_ptr<available_package>> aps;
{
+ shared_ptr<repository> rep (db.load<repository> ("")); // Root.
+
using query = query<available_package>;
query q (query::id.name == n);
- // If the user specified the version, then only look for that specific
- // version (we still do it since there might be other revisions).
- //
- if (!v.empty ())
- q = q && compare_version_eq (query::id.version, v, v.revision != 0);
-
- // And if we found an existing package, then only look for versions
- // greater than what already exists.
- //
- if (p != nullptr)
- q = q && query::id.version > p->version;
-
- q += order_by_version_desc (query::id.version);
+ available =
+ filter_one (rep, db.query<available_package> (q)).first != nullptr;
- // Only consider packages that are in repositories that were explicitly
- // added to the configuration and their complements, recursively.
- //
- aps = filter (db.load<repository> (""),
- db.query<available_package> (q));
+ if (available)
+ {
+ // If the user specified the version, then only look for that
+ // specific version (we still do it since there might be other
+ // revisions).
+ //
+ if (!v.empty ())
+ q = q &&
+ compare_version_eq (query::id.version, v, v.revision != 0);
+
+ // And if we found an existing package, then only look for versions
+ // greater than what already exists. Note that for a system wildcard
+ // version we will always show all available versions (since it's
+ // 0).
+ //
+ if (p != nullptr)
+ q = q && query::id.version > p->version;
+
+ q += order_by_version_desc (query::id.version);
+
+ // Only consider packages that are in repositories that were
+ // explicitly added to the configuration and their complements,
+ // recursively.
+ //
+ aps = filter (rep, db.query<available_package> (q));
+ }
}
if (multi)
@@ -101,10 +114,13 @@ namespace bpkg
{
cout << p->state;
+ if (p->substate != package_substate::none)
+ cout << ',' << p->substate;
+
// Also print the version of the package unless the user specified it.
//
if (v != p->version)
- cout << " " << p->version;
+ cout << ' ' << p->version_string ();
if (p->hold_package)
cout << " hold_package";
@@ -115,19 +131,39 @@ namespace bpkg
found = true;
}
- if (!aps.empty ())
+ if (available)
{
cout << (found ? "; " : "") << "available";
- // If the user specified the version, then there might only be one
- // entry in which case it is useless to repeat it.
+ // The idea is that in the future we will try to auto-discover a
+ // system version and then print that. For now we just say "maybe
+ // available from the system" but only if no version was specified by
+ // the user. We will later compare it if the user did specify the
+ // version.
//
- if (v.empty () || aps.size () > 1 || aps[0]->version != v)
+ bool sys (v.empty ());
+
+ if (!aps.empty ())
{
- for (shared_ptr<available_package> ap: aps)
- cout << ' ' << ap->version;
+ // If the user specified the version, then there might only be one
+ // entry in which case it is useless to repeat it. But we do want
+ // to print it if there is also a system one.
+ //
+ if (sys || v.empty () || aps.size () > 1 || aps[0]->version != v)
+ {
+ for (shared_ptr<available_package> ap: aps)
+ {
+ if (ap->stub ())
+ break; // All the rest are stubs so bail out.
+
+ cout << ' ' << ap->version;
+ }
+ }
}
+ if (sys)
+ cout << " sys:?";
+
found = true;
}
diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx
index 4424017..84a4618 100644
--- a/bpkg/pkg-unpack.cxx
+++ b/bpkg/pkg-unpack.cxx
@@ -67,7 +67,9 @@ namespace bpkg
diag_record dr (fail);
dr << "package " << n << " already exists in configuration " << c <<
- info << "version: " << p->version << ", state: " << p->state;
+ info << "version: " << p->version_string ()
+ << ", state: " << p->state
+ << ", substate: " << p->substate;
if (s) // Suitable state for replace?
dr << info << "use 'pkg-unpack --replace|-r' to replace";
@@ -95,6 +97,7 @@ namespace bpkg
move (m.name),
move (m.version),
package_state::unpacked,
+ package_substate::none,
false, // hold package
false, // hold version
repository_location (), // Root repository.
@@ -132,7 +135,7 @@ namespace bpkg
fail << "package " << name << " is " << p->state <<
info << "expected it to be fetched";
- l4 ([&]{trace << p->name << " " << p->version;});
+ l4 ([&]{trace << *p;});
assert (p->archive); // Should have archive in the fetched state.
@@ -258,7 +261,7 @@ namespace bpkg
}
if (verb)
- text << "unpacked " << p->name << " " << p->version;
+ text << "unpacked " << *p;
return 0;
}
diff --git a/bpkg/rep-info.cxx b/bpkg/rep-info.cxx
index 765bc70..2debf0f 100644
--- a/bpkg/rep-info.cxx
+++ b/bpkg/rep-info.cxx
@@ -230,7 +230,7 @@ namespace bpkg
else
{
for (const package_manifest& pm: pms)
- cout << pm.name << " " << pm.version << endl;
+ cout << pm.name << "/" << pm.version << endl;
}
}
}
diff --git a/bpkg/satisfaction.cxx b/bpkg/satisfaction.cxx
index a0ebbfb..bddf7e7 100644
--- a/bpkg/satisfaction.cxx
+++ b/bpkg/satisfaction.cxx
@@ -17,6 +17,9 @@ namespace bpkg
{
assert (!c.empty ());
+ if (v == wildcard_version)
+ return true;
+
bool s (true);
// See notes in pkg-build:find_available() on ignoring revision in
diff --git a/bpkg/system-repository b/bpkg/system-repository
new file mode 100644
index 0000000..9e2f3e4
--- /dev/null
+++ b/bpkg/system-repository
@@ -0,0 +1,55 @@
+// file : bpkg/system-repository -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_SYSTEM_REPOSITORY
+#define BPKG_SYSTEM_REPOSITORY
+
+#include <map>
+
+#include <bpkg/manifest>
+
+#include <bpkg/types>
+#include <bpkg/utility>
+
+namespace bpkg
+{
+ // A map of discovered system package versions. The information can be
+ // authoritative (i.e., it was provided by the user or auto-discovered
+ // on this run) or non-authoritative (i.e., comes from selected_packages
+ // that are present in the database; in a sence it was authoritative but
+ // on some previous run.
+ //
+ // Note that in our model we assume that once an authoritative version has
+ // been discovered, it does not change (on this run; see caching logic in
+ // available package).
+ //
+ struct system_package
+ {
+ using version_type = bpkg::version;
+
+ version_type version;
+ bool authoritative;
+ };
+
+ class system_repository_type
+ {
+ public:
+ const version&
+ insert (const string& name, const version&, bool authoritative);
+
+ const system_package*
+ find (const string& name)
+ {
+ auto i (map_.find (name));
+ return i != map_.end () ? &i->second : nullptr;
+ }
+
+ private:
+ std::map<string, system_package> map_;
+ };
+
+ extern system_repository_type system_repository;
+}
+
+#endif // BPKG_SYSTEM_REPOSITORY
diff --git a/bpkg/system-repository.cxx b/bpkg/system-repository.cxx
new file mode 100644
index 0000000..b2a892d
--- /dev/null
+++ b/bpkg/system-repository.cxx
@@ -0,0 +1,33 @@
+// file : bpkg/system-repository.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <bpkg/system-repository>
+
+namespace bpkg
+{
+ system_repository_type system_repository;
+
+ const version& system_repository_type::
+ insert (const string& name, const version& v, bool authoritative)
+ {
+ auto p (map_.emplace (name, system_package {v, authoritative}));
+
+ if (!p.second)
+ {
+ system_package& sp (p.first->second);
+
+ // We should not override authoritative information.
+ //
+ assert (!(authoritative && sp.authoritative));
+
+ if (authoritative >= sp.authoritative)
+ {
+ sp.authoritative = authoritative;
+ sp.version = v;
+ }
+ }
+
+ return p.first->second.version;
+ }
+}
diff --git a/bpkg/utility b/bpkg/utility
index a09cac5..a4bc6d3 100644
--- a/bpkg/utility
+++ b/bpkg/utility
@@ -11,7 +11,7 @@
#include <cassert> // assert()
#include <iterator> // make_move_iterator()
-#include <butl/utility> // reverse_iterate()
+#include <butl/utility> // casecmp(), reverse_iterate()
#include <exception> // uncaught_exception()
@@ -32,6 +32,7 @@ namespace bpkg
// <butl/utility>
//
+ using butl::casecmp;
using butl::reverse_iterate;
// Widely-used paths.