From bc24eec7208187e171fd61ced7130fd8e2828257 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 14 Mar 2018 00:54:05 +0300 Subject: Add support for version iteration --- bpkg/fetch-git.cxx | 4 +- bpkg/manifest-utility.cxx | 1 + bpkg/manifest-utility.hxx | 1 + bpkg/package.cxx | 77 +++++++- bpkg/package.hxx | 309 +++++++++++++++++++++++++------- bpkg/package.ixx | 6 +- bpkg/package.xml | 20 +++ bpkg/pkg-build.cxx | 50 ++++-- bpkg/pkg-checkout.cxx | 5 + bpkg/pkg-configure.cxx | 1 + bpkg/pkg-fetch.cxx | 1 + bpkg/pkg-purge.cxx | 23 ++- bpkg/pkg-status.cxx | 6 +- bpkg/pkg-unpack.cxx | 31 +++- bpkg/pkg-unpack.hxx | 3 +- bpkg/pkg-verify.cxx | 5 +- bpkg/rep-fetch.cxx | 95 ++++++++-- tests/common/git/state0/libbar.tar | Bin 71680 -> 71680 bytes tests/common/git/state0/libfoo.tar | Bin 296960 -> 296960 bytes tests/common/git/state0/style-basic.tar | Bin 71680 -> 71680 bytes tests/common/git/state0/style.tar | Bin 133120 -> 133120 bytes tests/common/git/state1/libbaz.tar | Bin 61440 -> 61440 bytes tests/common/git/state1/libfoo.tar | Bin 378880 -> 378880 bytes tests/common/git/state1/style-basic.tar | Bin 71680 -> 71680 bytes tests/common/git/state1/style.tar | Bin 133120 -> 133120 bytes tests/pkg-build.test | 101 ++++++++--- tests/rep-fetch.test | 147 ++++++++++++++- tests/rep-fetch/libhello-1.0.0 | 1 + 28 files changed, 732 insertions(+), 155 deletions(-) create mode 120000 tests/rep-fetch/libhello-1.0.0 diff --git a/bpkg/fetch-git.cxx b/bpkg/fetch-git.cxx index 833c3a7..c22bafb 100644 --- a/bpkg/fetch-git.cxx +++ b/bpkg/fetch-git.cxx @@ -1056,8 +1056,8 @@ namespace bpkg if (verb) info << "re-cloning " << rl.canonical_name () << " due to location change" << - info << "new location " << rl.url () << - info << "old location " << u; + info << "new location " << rl << + info << "old location " << l; rm_r (d); return git_clone (co, rl, destdir); diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx index b35af4a..4a7d0db 100644 --- a/bpkg/manifest-utility.cxx +++ b/bpkg/manifest-utility.cxx @@ -20,6 +20,7 @@ namespace bpkg const path repositories_file ("repositories.manifest"); const path packages_file ("packages.manifest"); const path signature_file ("signature.manifest"); + const path manifest_file ("manifest"); package_scheme parse_package_scheme (const char*& s) diff --git a/bpkg/manifest-utility.hxx b/bpkg/manifest-utility.hxx index e7bc149..e7be005 100644 --- a/bpkg/manifest-utility.hxx +++ b/bpkg/manifest-utility.hxx @@ -15,6 +15,7 @@ namespace bpkg extern const path repositories_file; // repositories.manifest extern const path packages_file; // packages.manifest extern const path signature_file; // signature.manifest + extern const path manifest_file; // manifest // Package naming schemes. // diff --git a/bpkg/package.cxx b/bpkg/package.cxx index 2e5bffe..966be72 100644 --- a/bpkg/package.cxx +++ b/bpkg/package.cxx @@ -8,12 +8,14 @@ #include // find_if() #include +#include +#include using namespace std; namespace bpkg { - const version wildcard_version (0, "0", nullopt, 0); + const version wildcard_version (0, "0", nullopt, 0, 0); // available_package_id // @@ -158,6 +160,79 @@ namespace bpkg return os; } + optional + package_iteration (const common_options& o, + const dir_path& c, + transaction& t, + const dir_path& d, + const string& n, + const version& v, + bool check_external) + { + tracer trace ("package_iteration"); + + database& db (t.database ()); + tracer_guard tg (db, trace); + + if (check_external) + { + using query = query; + + query q ( + query::package::id.name == n && + compare_version_eq (query::package::id.version, v, true, false)); + + for (const auto& pr: db.query (q)) + { + const shared_ptr& r (pr.repository); + + if (r->location.directory_based ()) + fail << "external package " << n << '/' << v + << " is already available from " + << r->location.canonical_name (); + } + } + + shared_ptr p (db.find (n)); + + if (p == nullptr || !p->src_root || + compare_version_ne (v, p->version, true, false)) + return nullopt; + + string mc (sha256 (o, d / manifest_file)); + + assert (p->manifest_checksum); + + bool changed (mc != *p->manifest_checksum); + + // If the manifest didn't changed but the selected package points to an + // external source directory, then we also check if the directory have + // moved. + // + if (!changed && p->external ()) + { + dir_path src_root (p->src_root->absolute () + ? *p->src_root + : c / *p->src_root); + + // We need to complete and normalize the source directory as it may + // generally be completed against the configuration directory (unlikely + // but possible), that can be relative and/or not normalized. + // + src_root.complete ().normalize (); + + changed = src_root != dir_path (d).complete ().normalize (); + } + + return !changed + ? p->version + : version (v.epoch, + v.upstream, + v.release, + v.revision, + p->version.iteration + 1); + } + // state // string diff --git a/bpkg/package.hxx b/bpkg/package.hxx index 5905dd2..8c714c4 100644 --- a/bpkg/package.hxx +++ b/bpkg/package.hxx @@ -17,6 +17,7 @@ #include #include +#include // transaction #include #pragma db model version(4, 4, open) @@ -93,6 +94,7 @@ namespace bpkg string canonical_upstream; string canonical_release; uint16_t revision; + uint32_t iteration; string upstream; optional release; }; @@ -134,6 +136,7 @@ namespace bpkg string canonical_upstream; string canonical_release; uint16_t revision; + uint32_t iteration; // By default SQLite3 uses BINARY collation for TEXT columns. So while this // means we don't need to do anything special to make "absent" (~) and @@ -147,14 +150,14 @@ namespace bpkg #pragma db value transient struct upstream_version: version { - #pragma db member(upstream_) virtual(string) \ - get(this.upstream) \ - set(this = bpkg::version (0, std::move (?), std::string (), 0)) + #pragma db member(upstream_) virtual(string) \ + get(this.upstream) \ + set(this = bpkg::version (0, std::move (?), std::string (), 0, 0)) #pragma db member(release_) virtual(optional_string) \ get(this.release) \ set(this = bpkg::version ( \ - 0, std::move (this.upstream), std::move (?), 0)) + 0, std::move (this.upstream), std::move (?), 0, 0)) upstream_version () = default; upstream_version (version v): version (move (v)) {} @@ -164,7 +167,12 @@ namespace bpkg void init (const canonical_version& cv, const upstream_version& uv) { - *this = version (cv.epoch, uv.upstream, uv.release, cv.revision); + *this = version (cv.epoch, + uv.upstream, + uv.release, + cv.revision, + cv.iteration); + assert (cv.canonical_upstream == canonical_upstream && cv.canonical_release == canonical_release); } @@ -175,12 +183,14 @@ namespace bpkg (?).canonical_upstream, \ (?).canonical_release, \ (?).revision, \ + (?).iteration, \ (?).upstream, \ (?).release}) \ from(bpkg::version ((?).epoch, \ std::move ((?).upstream), \ std::move ((?).release), \ - (?).revision)) + (?).revision, \ + (?).iteration)) using optional_version = optional; using _optional_version = optional<_version>; @@ -191,6 +201,7 @@ namespace bpkg (?)->canonical_upstream, \ (?)->canonical_release, \ (?)->revision, \ + (?)->iteration, \ (?)->upstream, \ (?)->release} \ : bpkg::_optional_version ()) \ @@ -198,7 +209,8 @@ namespace bpkg ? bpkg::version ((?)->epoch, \ std::move ((?)->upstream), \ std::move ((?)->release), \ - (?)->revision) \ + (?)->revision, \ + (?)->iteration) \ : bpkg::optional_version ()) // repository_location @@ -279,7 +291,6 @@ namespace bpkg operator size_t () const {return result;} }; - // package_location // #pragma db value @@ -596,6 +607,11 @@ namespace bpkg optional src_root; bool purge_src; + // The checksum of the manifest file located in the source directory. + // Must be present if the source directory is present. + // + optional manifest_checksum; + // Path to the output directory of this package, if any. It is // always relative to the configuration directory, and is // for external packages and - for others. It is @@ -656,6 +672,47 @@ namespace bpkg ostream& operator<< (ostream&, const selected_package&); + // Check if the directory containing the specified package version should be + // considered its iteration. Return the version of this iteration if that's + // the case and nullopt otherwise. + // + // Notes: + // + // - The package directory is considered an iteration of the package if this + // upstream version and revision is already present (selected) in the + // configuration and has a source directory. If that's the case, then the + // specified directory path and the checksum of the manifest file it + // contains are compared to the ones of the package present in the + // configuration. If both match, then the present package version + // (including its iteration, if any) is returned. Otherwise (the package + // has moved and/or the packaging information has changed), the present + // package version with the incremented iteration number is returned. Note + // that the directory path is matched only for the external selected + // packages. + // + // - Only a single package iteration is valid per version in the + // configuration. This, in particular, means that a package of the + // specific upstream version and revision shouldn't come from multiple + // external (source) directories. + // + // If requested, the function checks if an external package of this + // upstream version and revision is already available in the configuration + // and fails if that's the case. + // + // - The manifest file located in the specified directory is not parsed, and + // so is not checked to match the specified package name and version. + // + class common_options; + + optional + package_iteration (const common_options&, + const dir_path& configuration, + transaction&, + const dir_path&, + const string& name, + const version&, + bool check_external); + // certificate // // Information extracted from a repository X.509 certificate. The actual @@ -809,17 +866,18 @@ namespace bpkg // Return a list of packages available from this repository. // - #pragma db view object(repository) \ - table("available_package_locations" = "pl" inner: \ - "pl.repository = " + repository::name) \ - object(available_package = package inner: \ - "pl.name = " + package::id.name + "AND" + \ - "pl.version_epoch = " + package::id.version.epoch + "AND" + \ - "pl.version_canonical_upstream = " + \ - package::id.version.canonical_upstream + "AND" + \ - "pl.version_canonical_release = " + \ - package::id.version.canonical_release + "AND" + \ - "pl.version_revision = " + package::id.version.revision) + #pragma db view object(repository) \ + table("available_package_locations" = "pl" inner: \ + "pl.repository = " + repository::name) \ + object(available_package = package inner: \ + "pl.name = " + package::id.name + "AND" + \ + "pl.version_epoch = " + package::id.version.epoch + "AND" + \ + "pl.version_canonical_upstream = " + \ + package::id.version.canonical_upstream + "AND" + \ + "pl.version_canonical_release = " + \ + package::id.version.canonical_release + "AND" + \ + "pl.version_revision = " + package::id.version.revision + "AND" + \ + "pl.version_iteration = " + package::id.version.iteration) struct repository_package { shared_ptr package; // Must match the alias (see above). @@ -827,18 +885,47 @@ namespace bpkg operator const shared_ptr () const {return package;} }; + // Return a list of repositories the packages come from. + // + #pragma db view object(repository) \ + table("available_package_locations" = "pl" inner: \ + "pl.repository = " + repository::name) \ + object(available_package = package inner: \ + "pl.name = " + package::id.name + "AND" + \ + "pl.version_epoch = " + package::id.version.epoch + "AND" + \ + "pl.version_canonical_upstream = " + \ + package::id.version.canonical_upstream + "AND" + \ + "pl.version_canonical_release = " + \ + package::id.version.canonical_release + "AND" + \ + "pl.version_revision = " + package::id.version.revision + "AND" + \ + "pl.version_iteration = " + package::id.version.iteration) + struct package_repository + { + #pragma db column(package::id) + available_package_id package_id; + + using repository_type = bpkg::repository; + shared_ptr repository; + }; + // Version comparison operators. // // They allow comparing objects that have epoch, canonical_upstream, - // canonical_release, and revision data members. The idea is that this - // works for both query members of types version and canonical_version + // canonical_release, revision, and iteration data members. The idea is that + // this works for both query members of types version and canonical_version // as well as for comparing canonical_version to version. // + // Note that if the comparison operation ignores the revision, then it also + // unconditionally ignores the iteration (that semantically extends the + // revision). + // template inline auto - compare_version_eq (const T1& x, const T2& y, bool revision) + compare_version_eq (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + // Since we don't quite know what T1 and T2 are (and where the resulting // expression will run), let's not push our luck with something like // (!revision || x.revision == y.revision). @@ -847,9 +934,11 @@ namespace bpkg x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release); - return revision - ? r && x.revision == y.revision - : r; + return !revision + ? r + : !iteration + ? r && x.revision == y.revision + : r && x.revision == y.revision && x.iteration == y.iteration; } /* @@ -865,68 +954,111 @@ namespace bpkg template inline auto - compare_version_ne (const T1& x, const T2& y, bool revision) + compare_version_ne (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + auto r (x.epoch != y.epoch || x.canonical_upstream != y.canonical_upstream || x.canonical_release != y.canonical_release); - return revision - ? r || x.revision != y.revision - : r; + return !revision + ? r + : !iteration + ? r || x.revision != y.revision + : r || x.revision != y.revision || x.iteration != y.iteration; } template inline auto operator!= (const T1& x, const T2& y) -> decltype (x.epoch != y.epoch) { - return compare_version_ne (x, y, true); + return compare_version_ne (x, y, true, true); } template inline auto - compare_version_lt (const T1& x, const T2& y, bool revision) + compare_version_lt (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + auto r ( x.epoch < y.epoch || (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release < y.canonical_release)); - return revision - ? r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release == y.canonical_release && x.revision < y.revision) - : r; + if (revision) + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision < y.revision); + + if (iteration) + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision == y.revision && + x.iteration < y.iteration); + } + + return r; } template inline auto operator< (const T1& x, const T2& y) -> decltype (x.epoch < y.epoch) { - return compare_version_lt (x, y, true); + return compare_version_lt (x, y, true, true); } template inline auto - compare_version_le (const T1& x, const T2& y, bool revision) + compare_version_le (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + auto r ( x.epoch < y.epoch || (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream)); - return revision - ? r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release < y.canonical_release) || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release == y.canonical_release && x.revision <= y.revision) - : r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release <= y.canonical_release); + if (!revision) + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release <= y.canonical_release); + } + else + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release < y.canonical_release); + + if (!iteration) + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision <= y.revision); + else + r = r || + + (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision < y.revision) || + + (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision == y.revision && + x.iteration <= y.iteration); + } + + return r; } /* @@ -942,54 +1074,93 @@ namespace bpkg template inline auto - compare_version_gt (const T1& x, const T2& y, bool revision) + compare_version_gt (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + auto r ( x.epoch > y.epoch || (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release > y.canonical_release)); - return revision - ? r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release == y.canonical_release && x.revision > y.revision) - : r; + if (revision) + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision > y.revision); + + if (iteration) + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision == y.revision && + x.iteration > y.iteration); + } + + return r; } template inline auto operator> (const T1& x, const T2& y) -> decltype (x.epoch > y.epoch) { - return compare_version_gt (x, y, true); + return compare_version_gt (x, y, true, true); } template inline auto - compare_version_ge (const T1& x, const T2& y, bool revision) + compare_version_ge (const T1& x, const T2& y, bool revision, bool iteration) -> decltype (x.epoch == y.epoch) { + assert (revision || !iteration); // !revision && iteration is meaningless. + auto r ( x.epoch > y.epoch || (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream)); - return revision - ? r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release > y.canonical_release) || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release == y.canonical_release && x.revision >= y.revision) - : r || - (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && - x.canonical_release >= y.canonical_release); + if (!revision) + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release >= y.canonical_release); + } + else + { + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release > y.canonical_release); + + if (!iteration) + r = r || (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision >= y.revision); + else + r = r || + + (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision > y.revision) || + + (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && + x.revision == y.revision && + x.iteration >= y.iteration); + } + + return r; } template inline auto operator>= (const T1& x, const T2& y) -> decltype (x.epoch >= y.epoch) { - return compare_version_ge (x, y, true); + return compare_version_ge (x, y, true, true); } template @@ -1001,16 +1172,20 @@ namespace bpkg + x.epoch + "DESC," + x.canonical_upstream + "DESC," + x.canonical_release + "DESC," - + x.revision + "DESC"; + + x.revision + "DESC," + + x.iteration + "DESC"; } +/* + Currently unused (and probably should stay that way). template inline auto order_by_revision_desc (const T& x) -> //decltype ("ORDER BY" + x.epoch) decltype (x.revision == 0) { - return "ORDER BY" + x.revision + "DESC"; + return "ORDER BY" + x.revision + "DESC," + x.iteration + "DESC"; } +*/ } #include diff --git a/bpkg/package.ixx b/bpkg/package.ixx index 26f63ff..5834e2e 100644 --- a/bpkg/package.ixx +++ b/bpkg/package.ixx @@ -9,7 +9,11 @@ namespace bpkg inline available_package_id:: available_package_id (string n, const bpkg::version& v) : name (move (n)), - version {v.epoch, v.canonical_upstream, v.canonical_release, v.revision} + version {v.epoch, + v.canonical_upstream, + v.canonical_release, + v.revision, + v.iteration} { } } diff --git a/bpkg/package.xml b/bpkg/package.xml index bc995be..10014e7 100644 --- a/bpkg/package.xml +++ b/bpkg/package.xml @@ -52,6 +52,7 @@ + @@ -61,6 +62,7 @@ + @@ -69,6 +71,7 @@ + @@ -78,12 +81,14 @@ + + @@ -92,6 +97,7 @@ + @@ -106,6 +112,7 @@ + @@ -116,12 +123,14 @@ + + @@ -130,6 +139,7 @@ + @@ -141,6 +151,7 @@ + @@ -148,12 +159,14 @@ + + @@ -164,12 +177,14 @@ + + @@ -178,6 +193,7 @@ +
@@ -186,6 +202,7 @@ + @@ -198,6 +215,7 @@ + @@ -210,12 +228,14 @@ + + diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index 4621fb9..9d58cc2 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -85,7 +85,11 @@ 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)); + // Also note that we always compare ignoring the iteration, as it can + // not be specified in the manifest/command line. This way the latest + // iteration will always be picked up. + // + query qs (compare_version_eq (vm, wildcard_version, false, false)); if (c->min_version && c->max_version && @@ -93,7 +97,7 @@ namespace bpkg { const version& v (*c->min_version); - q = q && (compare_version_eq (vm, v, v.revision != 0) || qs); + q = q && (compare_version_eq (vm, v, v.revision != 0, false) || qs); } else { @@ -104,9 +108,9 @@ namespace bpkg const version& v (*c->min_version); if (c->min_open) - qr = compare_version_gt (vm, v, v.revision != 0); + qr = compare_version_gt (vm, v, v.revision != 0, false); else - qr = compare_version_ge (vm, v, v.revision != 0); + qr = compare_version_ge (vm, v, v.revision != 0, false); } if (c->max_version) @@ -114,9 +118,9 @@ namespace bpkg const version& v (*c->max_version); if (c->max_open) - qr = qr && compare_version_lt (vm, v, v.revision != 0); + qr = qr && compare_version_lt (vm, v, v.revision != 0, false); else - qr = qr && compare_version_le (vm, v, v.revision != 0); + qr = qr && compare_version_le (vm, v, v.revision != 0, false); } q = q && (qr || qs); @@ -177,8 +181,7 @@ namespace bpkg // Copy the possibly fixed up version from the selected package. // - if (sp->external ()) - m.version = sp->version; + m.version = sp->version; return make_pair (make_shared (move (m)), move (ar)); } @@ -1463,19 +1466,22 @@ namespace bpkg package_manifest m (pkg_verify (d, true, diag)); + // This is a package directory. + // + l4 ([&]{trace << "directory " << d;}); + n = m.name; + // Fix-up the package version to properly decide if we need to - // upgrade/downgrade the package. + // upgrade/downgrade the package. Note that throwing failed + // from here on will be fatal. // - optional pv (package_version (o, d)); + if (optional v = package_version (o, d)) + m.version = move (*v); - if (pv) - m.version = move (*pv); + if (optional v = package_iteration ( + o, c, t, d, n, m.version, true /* check_external */)) + m.version = move (*v); - // 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; @@ -1491,7 +1497,12 @@ namespace bpkg } catch (const failed&) { - // Not a valid package directory. + // If the package name is detected then this is a valid package + // directory, but something went wrong afterwards, and we are + // done in this case. + // + if (!n.empty ()) + throw; } } } @@ -2094,7 +2105,8 @@ namespace bpkg } case repository_basis::directory: { - sp = pkg_unpack (c, + sp = pkg_unpack (o, + c, t, ap->id.name, p.available_version (), diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx index 14f84cb..6e4a7a5 100644 --- a/bpkg/pkg-checkout.cxx +++ b/bpkg/pkg-checkout.cxx @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -162,6 +163,8 @@ namespace bpkg false /* quiet */, strings ({"config.dist.root=" + c.representation ()})); + string mc (sha256 (o, d / manifest_file)); + if (p != nullptr) { // Clean up the source directory and archive of the package we are @@ -175,6 +178,7 @@ namespace bpkg p->repository = rl; p->src_root = d.leaf (); p->purge_src = true; + p->manifest_checksum = move (mc); db.update (p); } @@ -194,6 +198,7 @@ namespace bpkg false, d.leaf (), // Source root. true, // Purge directory. + move (mc), nullopt, // No output directory yet. {}}); // No prerequisites captured yet. diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx index 2bb1b20..e23fca1 100644 --- a/bpkg/pkg-configure.cxx +++ b/bpkg/pkg-configure.cxx @@ -209,6 +209,7 @@ namespace bpkg false, // No auto-purge (does not get there). nullopt, // No source directory. false, + nullopt, // No manifest checksum. nullopt, // No output directory. {}}); // No prerequisites. diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index 664c92f..71bd968 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -79,6 +79,7 @@ namespace bpkg purge, nullopt, // No source directory yet. false, + nullopt, // No manifest checksum. nullopt, // No output directory yet. {}}); // No prerequisites captured yet. diff --git a/bpkg/pkg-purge.cxx b/bpkg/pkg-purge.cxx index fa5ce01..e1cd113 100644 --- a/bpkg/pkg-purge.cxx +++ b/bpkg/pkg-purge.cxx @@ -37,19 +37,30 @@ namespace bpkg if (exists (d)) // Don't complain if someone did our job for us. rm_r (d); - p->src_root = nullopt; p->purge_src = false; } - if (p->purge_archive && archive) + // Let's forget about the possibly non-purged source directory, as the + // selected package may now be reused for unrelated package version. + // + p->src_root = nullopt; + p->manifest_checksum = nullopt; + + if (archive) { - path a (p->archive->absolute () ? *p->archive : c / *p->archive); + if (p->purge_archive) + { + path a (p->archive->absolute () ? *p->archive : c / *p->archive); - if (exists (a)) - rm (a); + if (exists (a)) + rm (a); + p->purge_archive = false; + } + + // Let's forget about the possibly non-purged archive (see above). + // p->archive = nullopt; - p->purge_archive = false; } } catch (const failed&) diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx index b440ad9..e5144ca 100644 --- a/bpkg/pkg-status.cxx +++ b/bpkg/pkg-status.cxx @@ -65,7 +65,8 @@ namespace bpkg if (!p.version.empty ()) q = q && compare_version_eq (query::id.version, p.version, - p.version.revision != 0); + p.version.revision != 0, + false); // And if we found an existing package, then only look for versions // greater than what already exists. Note that for a system wildcard @@ -233,7 +234,8 @@ namespace bpkg if (!p.version.empty ()) q = q && compare_version_eq (query::version, p.version, - p.version.revision != 0); + p.version.revision != 0, + false); p.selected = db.query_one (q); } diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx index d9d54ea..41c72f4 100644 --- a/bpkg/pkg-unpack.cxx +++ b/bpkg/pkg-unpack.cxx @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -60,8 +61,11 @@ namespace bpkg } } + // Select the external package in this configuration. + // static shared_ptr - pkg_unpack (dir_path c, + pkg_unpack (const common_options& o, + dir_path c, transaction& t, string n, version v, @@ -74,6 +78,8 @@ namespace bpkg database& db (t.database ()); tracer_guard tg (db, trace); + string mc (sha256 (o, d / manifest_file)); + // Make the package and configuration paths absolute and normalized. // If the package is inside the configuration, use the relative path. // This way we can move the configuration around. @@ -99,6 +105,7 @@ namespace bpkg p->repository = move (rl); p->src_root = move (d); p->purge_src = purge; + p->manifest_checksum = move (mc); db.update (p); } @@ -116,12 +123,15 @@ namespace bpkg false, // Don't purge archive. move (d), purge, + move (mc), nullopt, // No output directory yet. {}}); // No prerequisites captured yet. db.persist (p); } + assert (p->external ()); + t.commit (); return p; } @@ -150,15 +160,18 @@ namespace bpkg // Fix-up the package version. // - optional v (package_version (o, d)); + if (optional v = package_version (o, d)) + m.version = move (*v); - if (v) + if (optional v = package_iteration ( + o, c, t, d, m.name, m.version, true /* check_external */)) m.version = move (*v); // Use the special root repository as the repository of this // package. // - return pkg_unpack (c, + return pkg_unpack (o, + c, t, move (m.name), move (m.version), @@ -168,7 +181,8 @@ namespace bpkg } shared_ptr - pkg_unpack (const dir_path& c, + pkg_unpack (const common_options& o, + const dir_path& c, transaction& t, string n, version v, @@ -224,7 +238,8 @@ namespace bpkg const repository_location& rl (pl->repository->location); - return pkg_unpack (c, + return pkg_unpack (o, + c, t, move (n), move (v), @@ -386,6 +401,8 @@ namespace bpkg p->src_root = d.leaf (); // For now assuming to be in configuration. p->purge_src = true; + p->manifest_checksum = sha256 (co, d / manifest_file); + p->state = package_state::unpacked; db.update (p); @@ -444,7 +461,7 @@ namespace bpkg // p = v.empty () ? pkg_unpack (o, c, t, n) - : pkg_unpack (c, t, move (n), move (v), o.replace ()); + : pkg_unpack (o, c, t, move (n), move (v), o.replace ()); } if (verb && !o.no_result ()) diff --git a/bpkg/pkg-unpack.hxx b/bpkg/pkg-unpack.hxx index a04d197..83acb97 100644 --- a/bpkg/pkg-unpack.hxx +++ b/bpkg/pkg-unpack.hxx @@ -40,7 +40,8 @@ namespace bpkg // repository and commit the transaction. // shared_ptr - pkg_unpack (const dir_path& configuration, + pkg_unpack (const common_options&, + const dir_path& configuration, transaction&, string name, version, diff --git a/bpkg/pkg-verify.cxx b/bpkg/pkg-verify.cxx index 3d96136..a220480 100644 --- a/bpkg/pkg-verify.cxx +++ b/bpkg/pkg-verify.cxx @@ -9,6 +9,7 @@ #include #include +#include using namespace std; using namespace butl; @@ -20,7 +21,7 @@ namespace bpkg try { dir_path pd (package_dir (af)); - path mf (pd / path ("manifest")); + path mf (pd / manifest_file); // If diag is false, we need to make tar not print any diagnostics. There // doesn't seem to be an option to suppress this and the only way is to @@ -115,7 +116,7 @@ namespace bpkg { // Parse the manifest. // - path mf (d / path ("manifest")); + path mf (d / manifest_file); if (!exists (mf)) { diff --git a/bpkg/rep-fetch.cxx b/bpkg/rep-fetch.cxx index 343f97b..9558301 100644 --- a/bpkg/rep-fetch.cxx +++ b/bpkg/rep-fetch.cxx @@ -201,7 +201,7 @@ namespace bpkg }; dir_path d (repo_dir / path_cast (*sm.location)); - path f (d / path ("manifest")); + path f (d / manifest_file); if (!exists (f)) failure ("no manifest file"); @@ -272,20 +272,6 @@ namespace bpkg ignore_unknown, rl)); - // @@ Here we will need to go through packages and check if there is the - // selected package of the same version (without regards to the - // iteration component), and having the different package manifest - // hash. If that's the case then set the manifest version iteration - // component as the selected package version iteation + 1. - // - // @@ It also seems that the manifest hash should be stored in the (new) - // member of the package_location struct. Later this value will be - // transmitted into the selected package objects by pkg_unpack(). - // - // @@ Should we later check and fail if any available package contains - // several package locations with different non-zero (for those that - // come from the directory repo) manifest hashes? - // return rep_fetch_data {move (rms), move (fps), nullptr}; } @@ -689,6 +675,29 @@ namespace bpkg { package_manifest& pm (fp.manifest); + // Fix-up the external package version iteration number. + // + if (rl.directory_based ()) + { + // Note that we can't check if the external package of this upstream + // version and revision is already available in the configuration + // until we fetch all the repositories, as some of the available + // packages are still due to be removed. + // + optional v ( + package_iteration ( + co, + conf, + t, + path_cast (rl.path () / *fp.manifest.location), + pm.name, + pm.version, + false /* check_external */)); + + if (v) + pm.version = move (*v); + } + // We might already have this package in the database. // bool persist (false); @@ -757,6 +766,11 @@ namespace bpkg bool shallow, const optional& reason) { + tracer trace ("rep_fetch(repos)"); + + database& db (t.database ()); + tracer_guard tg (db, trace); + // As a fist step we fetch repositories recursively building the list of // the former prerequisites and complements to be considered for removal. // @@ -774,13 +788,62 @@ namespace bpkg repositories fetched; repositories removed; + // Fetch the requested repositories, recursively. + // for (const lazy_shared_ptr& r: repos) rep_fetch (o, conf, t, r.load (), fetched, removed, shallow, reason); - // Finally, remove dangling repositories. + // Remove dangling repositories. // for (const shared_ptr& r: removed) rep_remove (conf, t, r); + + // Finally, make sure that the external packages are available from a + // single repository. + // + // Sort the packages by name and version. This way the external packages + // with the same upstream version and revision will be adjacent. + // + using query = query; + const auto& qv (query::package::id.version); + + query q ("ORDER BY" + query::package::id.name + "," + + qv.epoch + "," + + qv.canonical_upstream + "," + + qv.canonical_release + "," + + qv.revision + "," + + qv.iteration); + + available_package_id ap; + shared_ptr rp; + + for (const auto& pr: db.query (q)) + { + const shared_ptr& r (pr.repository); + if (!r->location.directory_based ()) + continue; + + // Fail if the external package is of the same upstream version and + // revision as the previous one. + // + const available_package_id& id (pr.package_id); + + if (id.name == ap.name && + compare_version_eq (id.version, ap.version, true, false)) + { + shared_ptr p (db.load (id)); + const version& v (p->version); + + fail << "external package " << id.name << '/' + << version (v.epoch, v.upstream, v.release, v.revision, 0) + << " is available from two repositories" << + info << "repository " << rp->location << + info << "repository " << r->location; + } + + ap = id; + rp = r; + } } catch (const failed&) { diff --git a/tests/common/git/state0/libbar.tar b/tests/common/git/state0/libbar.tar index 4861d35..bbb6f81 100644 Binary files a/tests/common/git/state0/libbar.tar and b/tests/common/git/state0/libbar.tar differ diff --git a/tests/common/git/state0/libfoo.tar b/tests/common/git/state0/libfoo.tar index 2707476..6f9af32 100644 Binary files a/tests/common/git/state0/libfoo.tar and b/tests/common/git/state0/libfoo.tar differ diff --git a/tests/common/git/state0/style-basic.tar b/tests/common/git/state0/style-basic.tar index 9444ab8..6a7a1f2 100644 Binary files a/tests/common/git/state0/style-basic.tar and b/tests/common/git/state0/style-basic.tar differ diff --git a/tests/common/git/state0/style.tar b/tests/common/git/state0/style.tar index 7989e77..6295f34 100644 Binary files a/tests/common/git/state0/style.tar and b/tests/common/git/state0/style.tar differ diff --git a/tests/common/git/state1/libbaz.tar b/tests/common/git/state1/libbaz.tar index b2bf286..ee8ad3e 100644 Binary files a/tests/common/git/state1/libbaz.tar and b/tests/common/git/state1/libbaz.tar differ diff --git a/tests/common/git/state1/libfoo.tar b/tests/common/git/state1/libfoo.tar index 859637a..4221cdd 100644 Binary files a/tests/common/git/state1/libfoo.tar and b/tests/common/git/state1/libfoo.tar differ diff --git a/tests/common/git/state1/style-basic.tar b/tests/common/git/state1/style-basic.tar index a8a3a88..8bc8b85 100644 Binary files a/tests/common/git/state1/style-basic.tar and b/tests/common/git/state1/style-basic.tar differ diff --git a/tests/common/git/state1/style.tar b/tests/common/git/state1/style.tar index 8ef9104..feedcaa 100644 Binary files a/tests/common/git/state1/style.tar and b/tests/common/git/state1/style.tar differ diff --git a/tests/pkg-build.test b/tests/pkg-build.test index fefaf00..e70bb1a 100644 --- a/tests/pkg-build.test +++ b/tests/pkg-build.test @@ -57,34 +57,17 @@ # | |-- libbar-1.2.0.tar.gz # | `-- repositories.manifest # | -# `-- git -# | |-- libbar.git -> style-basic.git (prerequisite) -# | |-- libbaz.git -# | `-- style-basic.git +# |-- libhello-1.0.0 +# | |-- build +# | | |-- bootstrap.build +# | | |-- export.build +# | | `-- root.build +# | `-- * # | -# `-- libhello-1.0.0 -# |-- build -# | |-- bootstrap.build -# | |-- export.build -# | `-- root.build -# |-- buildfile -# |-- hello -# | |-- buildfile -# | |-- export -# | |-- hello -# | `-- hello.cxx -# |-- INSTALL -# |-- manifest -# |-- tests -# | |-- build -# | | |-- bootstrap.build -# | | `-- root.build -# | |-- buildfile -# | `-- test -# | |-- buildfile -# | |-- driver.cxx -# | `-- test.out -# `-- version +# `-- git +# |-- libbar.git -> style-basic.git (prerequisite) +# |-- libbaz.git +# `-- style-basic.git # Prepare repositories used by tests if running in the local mode. # @@ -114,6 +97,7 @@ pkg_purge += -d cfg pkg_status += -d cfg pkg_unpack += -d cfg 2>! rep_add += -d cfg 2>! +rep_remove += -d cfg 2>! rep_fetch += -d cfg --auth all --trust-yes 2>! : libfoo @@ -805,7 +789,7 @@ rep_fetch += -d cfg --auth all --trust-yes 2>! disfigured libfoo/1.0.0 using libfoo/1.1.0 (external) configured libfoo/1.1.0 - %info: .+dir\{libfoo-1.1.0.\} is up to date% + %info: .+dir\{libfoo.\} is up to date% updated libfoo/1.1.0 EOE @@ -1565,6 +1549,9 @@ rep_fetch += -d cfg --auth all --trust-yes 2>! +$clone_cfg + # @@ Uncomment the following tests when -p option is supported for the cp + # builtin. + #\ : dir-repo : : Test that libhello is built incrementally. May re-link due to the @@ -1593,6 +1580,11 @@ rep_fetch += -d cfg --auth all --trust-yes 2>! { +$clone_cfg + # To avoid 'external package is already available' failure for the + # nested tests. + # + +$rep_remove --all + : arg : { @@ -1627,6 +1619,7 @@ rep_fetch += -d cfg --auth all --trust-yes 2>! test -d cfg/libhello-1.0.1/ == 1 } } + #\ : archive : @@ -1700,6 +1693,58 @@ rep_fetch += -d cfg --auth all --trust-yes 2>! -$pkg_purge libhello 2>'purged libhello/1.0.0' } +: iter +: +{ + test.arguments += --yes # Is a command-specific option. + + : already-available + : + { + $clone_root_cfg; + $rep_add $src/libfoo-1.1.0 --type dir && $rep_fetch; + + $* $src/libfoo-1.1.0/ 2>>~%EOE% != 0 + %error: external package libfoo/1.1.0 is already available from dir:.+libfoo-1.1.0% + EOE + } + + : upgrade + : + { + $clone_root_cfg; + + $* $src/libfoo-1.1.0/ 2>>~%EOE%; + using libfoo/1.1.0 (external) + configured libfoo/1.1.0 + %info: .+dir\{libfoo.\} is up to date% + updated libfoo/1.1.0 + EOE + + cp -r $src/libfoo-1.1.0 libfoo; + + $* libfoo/ 2>>~%EOE%; + disfigured libfoo/1.1.0 + using libfoo/1.1.0#1 (external) + configured libfoo/1.1.0#1 + %info: .+dir\{libfoo.\} is up to date% + updated libfoo/1.1.0#1 + EOE + + $rep_add $src/libfoo-1.1.0 --type dir && $rep_fetch; + + $* libfoo 2>>~%EOE%; + disfigured libfoo/1.1.0#1 + using libfoo/1.1.0#2 (external) + configured libfoo/1.1.0#2 + %info: .+dir\{libfoo.\} is up to date% + updated libfoo/1.1.0#2 + EOE + + $pkg_disfigure libfoo 2>'disfigured libfoo/1.1.0#2' + } +} + : git-rep : if ($git_supported != true) diff --git a/tests/rep-fetch.test b/tests/rep-fetch.test index 1ee0d4b..535627d 100644 --- a/tests/rep-fetch.test +++ b/tests/rep-fetch.test @@ -32,6 +32,13 @@ # | |-- libhello-1.0.0.tar.gz # | `-- repositories.manifest # | +# |-- libhello-1.0.0 +# | |-- build +# | | |-- bootstrap.build +# | | |-- export.build +# | | `-- root.build +# | `-- * +# | # |-- circle # | |-- extra -> stable (prerequisite) # | | |-- libbar-1.1.0+1.tar.gz @@ -102,9 +109,14 @@ $git_extract $src/git/state1/style-basic.tar &$out_git/state1/*** end -rep_add += -d cfg 2>! -rep_list += -d cfg --prerequisites --complements -pkg_status += -d cfg +rep_add += -d cfg 2>! +rep_list += -d cfg --prerequisites --complements +rep_remove += -d cfg 2>! +pkg_status += -d cfg +pkg_fetch += -d cfg 2>! +pkg_unpack += -d cfg 2>! +pkg_checkout += -d cfg 2>! +pkg_purge += -d cfg : no-repos : @@ -403,6 +415,135 @@ if ($remote != true) } } +: iter +: +{ + rep_add += --type dir + + : multiple-repos + : + { + cp -r $src/libhello-1.0.0 libhello1; + cp -r $src/libhello-1.0.0 libhello2; + + $clone_root_cfg && $rep_add libhello1 libhello2; + + $* 2>>~%EOE% != 0 + %fetching dir:.+libhello1% + %fetching dir:.+libhello2% + error: external package libhello/1.0.0 is available from two repositories + % info: repository .+libhello1% + % info: repository .+libhello2% + EOE + } + + : inc + : + { + : path-changed + : + { + $clone_root_cfg && $rep_add $src/libhello-1.0.0; + + $* 2>!; + $pkg_unpack libhello/1.0.0; + + $rep_remove --all; + + cp -r $src/libhello-1.0.0 libhello; + $rep_add libhello; + + $* 2>!; + + $pkg_status libhello >'unpacked 1.0.0; available 1.0.0#1 sys:?' + } + + : manifest-changed + : + { + cp -r $src/libhello-1.0.0 libhello; + + $clone_root_cfg && $rep_add libhello; + + $* 2>!; + $pkg_unpack libhello/1.0.0; + + echo "" >+ libhello/manifest; + $* 2>!; + + $pkg_status libhello >'unpacked 1.0.0; available 1.0.0#1 sys:?' + } + + : pkg-rep + { + +$clone_root_cfg + + +$* --auth all --trust-yes $rep/hello &cfg/.bpkg/certs/** 2>! + +$pkg_fetch libhello/1.0.0 + +$pkg_unpack libhello + + : unchanged-external + : + { + $clone_cfg && $rep_add $src/libhello-1.0.0; + $* 2>!; + + $pkg_status libhello >'unpacked 1.0.0; available sys:?' + } + + : changed-external + : + { + cp -r $src/libhello-1.0.0 libhello; + echo "" >+ libhello/manifest; + + $clone_cfg && $rep_add libhello; + $* 2>!; + + $pkg_status libhello >'unpacked 1.0.0; available 1.0.0#1 sys:?' + } + + -$pkg_purge libhello 2>'purged libhello/1.0.0' + } + + : git-rep + : + if ($remote != true) + { + rep = $canonicalize([dir_path] $out_git/state0); + + $clone_root_cfg; + + $* "$rep/style.git#master" 2>! &cfg/.bpkg/repos/*/***; + $pkg_checkout "style/1.0.0" 2>!; + + $rep_add $rep/style.git; + $* 2>!; + + $pkg_status style >"unpacked 1.0.0; available sys:?"; + + $pkg_purge style 2>"purged style/1.0.0" + } + } + + : no-inc + : + { + $clone_root_cfg; + + $pkg_unpack -e $src/libhello-1.0.0; + + $rep_add $src/libhello-1.0.0; + + $* 2>>~%EOE%; + %fetching dir:.+libhello-1.0.0% + 1 package(s) in 1 repository(s) + EOE + + $pkg_status libhello >'unpacked 1.0.0; available sys:?' + } +} + : git-rep : if ($git_supported != true) diff --git a/tests/rep-fetch/libhello-1.0.0 b/tests/rep-fetch/libhello-1.0.0 new file mode 120000 index 0000000..614ff24 --- /dev/null +++ b/tests/rep-fetch/libhello-1.0.0 @@ -0,0 +1 @@ +../common/libhello-1.0.0/ \ No newline at end of file -- cgit v1.1