From 53c2aa8e382dd50d09b385285bc3fa0b645ace0a Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 19 Aug 2016 17:37:29 +0300 Subject: Support system packages --- bpkg/buildfile | 89 +-- bpkg/database.cxx | 17 + bpkg/manifest-utility | 14 + bpkg/manifest-utility.cxx | 15 + bpkg/package | 95 ++- bpkg/package.cxx | 44 +- bpkg/package.xml | 30 +- bpkg/pkg-build.cli | 32 +- bpkg/pkg-build.cxx | 869 ++++++++++++++------- bpkg/pkg-command.cxx | 8 +- bpkg/pkg-configure | 6 + bpkg/pkg-configure.cli | 20 +- bpkg/pkg-configure.cxx | 86 +- bpkg/pkg-disfigure.cli | 5 +- bpkg/pkg-disfigure.cxx | 22 +- bpkg/pkg-drop.cxx | 14 +- bpkg/pkg-fetch.cxx | 7 +- bpkg/pkg-purge.cxx | 12 +- bpkg/pkg-status.cli | 35 +- bpkg/pkg-status.cxx | 86 +- bpkg/pkg-unpack.cxx | 9 +- bpkg/rep-info.cxx | 2 +- bpkg/satisfaction.cxx | 3 + bpkg/system-repository | 55 ++ bpkg/system-repository.cxx | 33 + bpkg/utility | 3 +- tests/pkg/1/build2.org/satisfy/libfoo-0.0.0.tar.gz | Bin 348 -> 0 bytes tests/pkg/1/build2.org/satisfy/libfoo-0.0.1.tar.gz | Bin 0 -> 352 bytes tests/pkg/1/build2.org/system/foo-2.tar.gz | Bin 0 -> 356 bytes tests/pkg/1/build2.org/system/libbar-0+1.tar.gz | Bin 0 -> 239 bytes tests/pkg/1/build2.org/system/libbar-1.tar.gz | Bin 0 -> 352 bytes tests/pkg/1/build2.org/system/libbar-2.tar.gz | Bin 0 -> 353 bytes tests/pkg/1/build2.org/system/libbaz-2.tar.gz | Bin 0 -> 338 bytes tests/pkg/1/build2.org/system/t1/foo-2.tar.gz | 1 + tests/pkg/1/build2.org/system/t1/libbar-1.tar.gz | 1 + tests/pkg/1/build2.org/system/t1/libbar-2.tar.gz | 1 + tests/pkg/1/build2.org/system/t1/libbaz-2.tar.gz | 1 + tests/pkg/1/build2.org/system/t1/repositories | 1 + tests/pkg/1/build2.org/system/t2/foo-2.tar.gz | 1 + tests/pkg/1/build2.org/system/t2/libbar-0+1.tar.gz | 1 + tests/pkg/1/build2.org/system/t2/repositories | 1 + tests/test.sh | 687 ++++++++++++---- 42 files changed, 1732 insertions(+), 574 deletions(-) create mode 100644 bpkg/system-repository create mode 100644 bpkg/system-repository.cxx delete mode 100644 tests/pkg/1/build2.org/satisfy/libfoo-0.0.0.tar.gz create mode 100644 tests/pkg/1/build2.org/satisfy/libfoo-0.0.1.tar.gz create mode 100644 tests/pkg/1/build2.org/system/foo-2.tar.gz create mode 100644 tests/pkg/1/build2.org/system/libbar-0+1.tar.gz create mode 100644 tests/pkg/1/build2.org/system/libbar-1.tar.gz create mode 100644 tests/pkg/1/build2.org/system/libbar-2.tar.gz create mode 100644 tests/pkg/1/build2.org/system/libbaz-2.tar.gz create mode 120000 tests/pkg/1/build2.org/system/t1/foo-2.tar.gz create mode 120000 tests/pkg/1/build2.org/system/t1/libbar-1.tar.gz create mode 120000 tests/pkg/1/build2.org/system/t1/libbar-2.tar.gz create mode 120000 tests/pkg/1/build2.org/system/t1/libbaz-2.tar.gz create mode 100644 tests/pkg/1/build2.org/system/t1/repositories create mode 120000 tests/pkg/1/build2.org/system/t2/foo-2.tar.gz create mode 120000 tests/pkg/1/build2.org/system/t2/libbar-0+1.tar.gz create mode 100644 tests/pkg/1/build2.org/system/t2/repositories 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 #include +#include +#include #include +#include 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 ( + query::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 [:]. 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 [/]. // 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 #include -#pragma db model version(3, 3, open) +#pragma db model version(2, 3, open) namespace bpkg { @@ -96,6 +96,7 @@ namespace bpkg } #include +#include // 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; + // 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 sha256sum; + private: + #pragma db transient + mutable optional 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 @@ - + + + + + + + + + + + + + + + + + + @@ -231,16 +248,5 @@
- - - - - - - - - - -
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 ; namespace bpkg { { - " ", + " ", "\h|SYNOPSIS| - \c{\b{bpkg pkg-build}|\b{build} [] ([/] | | /)...} + \c{\b{bpkg pkg-build}|\b{build} [] ([:][/] | + | /)...} \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 () with optional - package version () 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 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 () 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 is not specified, then the + latest available version will be fetched. To downgrade, the desired + version must be specified explicitly. + + A package name () can also be prefixed with a package 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 + () 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 () or to the package directory (/; 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 #include +#include #include #include #include #include #include #include +#include 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> + static pair, shared_ptr> find_available (database& db, const string& name, const shared_ptr& 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> + static pair, shared_ptr> 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 (sp->name, sp->version), + nullptr); + // First see if we can find its repository. // shared_ptr ar ( @@ -215,6 +223,24 @@ namespace bpkg vector 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> { - // 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& sp (p.selected); - const shared_ptr& 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& 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& ar (p.repository); + const shared_ptr& ap (pkg.available); + const shared_ptr& 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& 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& sp (p.selected); - const shared_ptr& 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; @@ -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 dsp (db.load (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 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 ar; shared_ptr 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 (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 (move (m)); ar = root; - ap->locations.push_back (package_location {root, move (d)}); + ap = make_shared (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 (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 () == 0) - dr << info << "configuration " << c << " has no repositories" - << info << "use 'bpkg rep-add' to add a repository"; - else if (db.query_value () == 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 () == 0) + dr << info << "configuration " << c << " has no repositories" + << info << "use 'bpkg rep-add' to add a repository"; + else if (db.query_value () == 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& sp (p.selected); - const shared_ptr& 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& sp (p.selected); + shared_ptr& 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& sp (p.selected); + shared_ptr& sp (p.selected); + const shared_ptr& 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& 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 // transaction, selected_package #include +#include #include namespace bpkg @@ -24,6 +25,11 @@ namespace bpkg transaction&, const shared_ptr&, const strings& config_vars); + + // Configure a system package and commit the transaction. + // + shared_ptr + 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 ; namespace bpkg { { - " ", + " ", "\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:}[/]} + syntax. If the package version () 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 #include #include +#include #include #include @@ -146,6 +147,36 @@ namespace bpkg t.commit (); } + shared_ptr + 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 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 p (db.find (n)); + shared_ptr 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 (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 rep (db.load ("")); // Root. - if (p->state != package_state::unpacked) - fail << "package " << n << " is " << p->state << - info << "expected it to be unpacked"; + using query = query; + query q (query::id.name == n); - l4 ([&]{trace << p->name << " " << p->version;}); + if (filter_one (rep, db.query (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 (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& 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> aps; { + shared_ptr rep (db.load ("")); // Root. + using query = query; 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 (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 (""), - db.query (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 (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 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 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 + +#include + +#include +#include + +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 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 + +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 // assert() #include // make_move_iterator() -#include // reverse_iterate() +#include // casecmp(), reverse_iterate() #include // uncaught_exception() @@ -32,6 +32,7 @@ namespace bpkg // // + using butl::casecmp; using butl::reverse_iterate; // Widely-used paths. diff --git a/tests/pkg/1/build2.org/satisfy/libfoo-0.0.0.tar.gz b/tests/pkg/1/build2.org/satisfy/libfoo-0.0.0.tar.gz deleted file mode 100644 index befd2a4..0000000 Binary files a/tests/pkg/1/build2.org/satisfy/libfoo-0.0.0.tar.gz and /dev/null differ diff --git a/tests/pkg/1/build2.org/satisfy/libfoo-0.0.1.tar.gz b/tests/pkg/1/build2.org/satisfy/libfoo-0.0.1.tar.gz new file mode 100644 index 0000000..2ab5094 Binary files /dev/null and b/tests/pkg/1/build2.org/satisfy/libfoo-0.0.1.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/foo-2.tar.gz b/tests/pkg/1/build2.org/system/foo-2.tar.gz new file mode 100644 index 0000000..7fef159 Binary files /dev/null and b/tests/pkg/1/build2.org/system/foo-2.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/libbar-0+1.tar.gz b/tests/pkg/1/build2.org/system/libbar-0+1.tar.gz new file mode 100644 index 0000000..9f90936 Binary files /dev/null and b/tests/pkg/1/build2.org/system/libbar-0+1.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/libbar-1.tar.gz b/tests/pkg/1/build2.org/system/libbar-1.tar.gz new file mode 100644 index 0000000..4b52278 Binary files /dev/null and b/tests/pkg/1/build2.org/system/libbar-1.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/libbar-2.tar.gz b/tests/pkg/1/build2.org/system/libbar-2.tar.gz new file mode 100644 index 0000000..28ad509 Binary files /dev/null and b/tests/pkg/1/build2.org/system/libbar-2.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/libbaz-2.tar.gz b/tests/pkg/1/build2.org/system/libbaz-2.tar.gz new file mode 100644 index 0000000..3e9c3e6 Binary files /dev/null and b/tests/pkg/1/build2.org/system/libbaz-2.tar.gz differ diff --git a/tests/pkg/1/build2.org/system/t1/foo-2.tar.gz b/tests/pkg/1/build2.org/system/t1/foo-2.tar.gz new file mode 120000 index 0000000..39d2e10 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t1/foo-2.tar.gz @@ -0,0 +1 @@ +../foo-2.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t1/libbar-1.tar.gz b/tests/pkg/1/build2.org/system/t1/libbar-1.tar.gz new file mode 120000 index 0000000..971bed1 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t1/libbar-1.tar.gz @@ -0,0 +1 @@ +../libbar-1.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t1/libbar-2.tar.gz b/tests/pkg/1/build2.org/system/t1/libbar-2.tar.gz new file mode 120000 index 0000000..2c8027b --- /dev/null +++ b/tests/pkg/1/build2.org/system/t1/libbar-2.tar.gz @@ -0,0 +1 @@ +../libbar-2.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t1/libbaz-2.tar.gz b/tests/pkg/1/build2.org/system/t1/libbaz-2.tar.gz new file mode 120000 index 0000000..111a2e7 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t1/libbaz-2.tar.gz @@ -0,0 +1 @@ +../libbaz-2.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t1/repositories b/tests/pkg/1/build2.org/system/t1/repositories new file mode 100644 index 0000000..5b70556 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t1/repositories @@ -0,0 +1 @@ +: 1 diff --git a/tests/pkg/1/build2.org/system/t2/foo-2.tar.gz b/tests/pkg/1/build2.org/system/t2/foo-2.tar.gz new file mode 120000 index 0000000..39d2e10 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t2/foo-2.tar.gz @@ -0,0 +1 @@ +../foo-2.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t2/libbar-0+1.tar.gz b/tests/pkg/1/build2.org/system/t2/libbar-0+1.tar.gz new file mode 120000 index 0000000..82bdcba --- /dev/null +++ b/tests/pkg/1/build2.org/system/t2/libbar-0+1.tar.gz @@ -0,0 +1 @@ +../libbar-0+1.tar.gz \ No newline at end of file diff --git a/tests/pkg/1/build2.org/system/t2/repositories b/tests/pkg/1/build2.org/system/t2/repositories new file mode 100644 index 0000000..5b70556 --- /dev/null +++ b/tests/pkg/1/build2.org/system/t2/repositories @@ -0,0 +1 @@ +: 1 diff --git a/tests/test.sh b/tests/test.sh index e6d3033..154177f 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -176,7 +176,7 @@ function stat () local s=`$bpkg pkg-status -d $cfg $1` if [ "$s" != "$2" ]; then - error "status $1: '"$s"', expected: '"$2"'" + error "status $1: '$s', expected: '$2'" fi } @@ -189,8 +189,6 @@ function gone () fi } -#if false; then - # Repository certificate fingerprint. # function rep_cert_fp () @@ -229,7 +227,6 @@ function location () ## Low-level commands. ## - ## ## pkg-verify ## @@ -264,7 +261,7 @@ fail rep-info # repository location expected test rep-info --trust-yes $rep/common/foo/testing <= 1.1.0) @@ -1136,15 +1126,15 @@ test rep-add $rep/satisfy/t4c test rep-fetch --trust-yes test pkg-build -p libbaz <libbar>=2), libbar/2 +test rep-create pkg/1/build2.org/system/t2 # foo/2 (->libbar>=2), libbar/0+1 +test rep-create pkg/1/build2.org/system/t3 # ->t2; foo/2 (->libbar>=2) + +function build () +{ + test build -p $* + test build -y $* +} + +# Fetch system/t1 repository: foo/2 (->libbar/2), libbar/2 +# +test cfg-create --wipe +test rep-add $rep/system/t1 +test rep-fetch --trust-yes + +# Fail to build different combinations of package duplicates on the command +# line. +# +fail build sys:libbar ?sys:libbar +fail build ?sys:libbar sys:libbar +fail build ?sys:libbar libbar +fail build libbar ?sys:libbar +fail build sys:libbar libbar +fail build libbar sys:libbar + +# Build sys:libbar/*. +# +build sys:libbar <<< 'configure sys:libbar/*' +stat libbar \ + 'configured,system * hold_package hold_version; available 2 1 sys:?' +stat libbaz 'available 2 sys:?' + +# Build foo with preconfigured sys:libbar/*. +# +build foo <<< "build foo/2" +stat foo 'configured 2 hold_package; available sys:?' +stat libbar \ + 'configured,system * hold_package hold_version; available 2 1 sys:?' +stat libbaz 'available 2 sys:?' + +# Reconfigure sys:libbar/* to 2. +# +build sys:libbar/2 <libbar>=2), libbar/0+1 +# +test cfg-create --wipe +test rep-add $rep/system/t2 +test rep-fetch --trust-yes + +# Fail to build foo having no system package configured. +# +fail build foo +fail build foo libbar +stat foo 'available 2 sys:?' +stat libbar 'available sys:?' + +# Build foo configuring sys:libbar. +# +build foo sys:libbar <libbar>=2), libbar/0+1 +# +test cfg-create --wipe +test rep-add $rep/system/t3 # ->t2; foo/2 (->libbar>=2) +test rep-fetch --trust-yes + +# After test number of faulty builds, then build foo ?sys:libbar/2. Afterwards +# fail attempts to reconfigure libbar. +# +fail build foo +fail build sys:libbar/1 +fail build foo sys:libbar/1 +fail build foo ?sys:libbar/1 +stat foo 'available 2 sys:?' +stat libbar 'unknown' + +build foo ?sys:libbar/2 <