diff options
35 files changed, 646 insertions, 132 deletions
diff --git a/bpkg/bpkg.cli b/bpkg/bpkg.cli index 6b705d1..ffeb84a 100644 --- a/bpkg/bpkg.cli +++ b/bpkg/bpkg.cli @@ -236,6 +236,11 @@ namespace bpkg "\l{bpkg-pkg-unpack(1)} \- unpack package archive" } + bool pkg-checkout + { + "\l{bpkg-pkg-checkout(1)} \- check out package version" + } + bool pkg-configure { "\l{bpkg-pkg-configure(1)} \- configure package" diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index 79e3341..9606b66 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -24,6 +24,7 @@ #include <bpkg/cfg-create.hxx> #include <bpkg/pkg-build.hxx> +#include <bpkg/pkg-checkout.hxx> #include <bpkg/pkg-clean.hxx> #include <bpkg/pkg-configure.hxx> #include <bpkg/pkg-disfigure.hxx> @@ -277,6 +278,7 @@ try #define PKG_COMMAND(CMD) COMMAND_IMPL(pkg_, "pkg-", CMD, true) PKG_COMMAND (build); + PKG_COMMAND (checkout); PKG_COMMAND (clean); PKG_COMMAND (configure); PKG_COMMAND (disfigure); diff --git a/bpkg/buildfile b/bpkg/buildfile index 0db45c6..9bdf29b 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -21,6 +21,7 @@ common-options \ configuration-options \ help-options \ pkg-build-options \ +pkg-checkout-options \ pkg-clean-options \ pkg-configure-options \ pkg-disfigure-options \ @@ -73,6 +74,7 @@ if $cli.configured # pkg-* command. # cli.cxx{pkg-build-options}: cli{pkg-build} + cli.cxx{pkg-checkout-options}: cli{pkg-checkout} cli.cxx{pkg-clean-options}: cli{pkg-clean} cli.cxx{pkg-configure-options}: cli{pkg-configure} cli.cxx{pkg-disfigure-options}: cli{pkg-disfigure} diff --git a/bpkg/package.hxx b/bpkg/package.hxx index 67b1753..41e8bd6 100644 --- a/bpkg/package.hxx +++ b/bpkg/package.hxx @@ -41,8 +41,11 @@ namespace bpkg using optional_path = optional<path>; using optional_dir_path = optional<dir_path>; + // In some contexts it may denote directory, so lets preserve the trailing + // slash, if present. + // #pragma db map type(path) as(string) \ - to((?).string ()) from(bpkg::path (?)) + to((?).representation ()) from(bpkg::path (?)) #pragma db map type(optional_path) as(bpkg::optional_string) \ to((?) ? (?)->string () : bpkg::optional_string ()) \ @@ -290,8 +293,15 @@ namespace bpkg { using repository_type = bpkg::repository; + // State is the repository type-specific information that can be used + // to identify the repository state this package came from. For example, + // for a version control-based repository this could be a commit id. + // + // The localtion is the package location within this repository state. + // lazy_shared_ptr<repository_type> repository; - path location; // Relative to the repository. + string state; + path location; }; // dependencies @@ -526,6 +536,17 @@ namespace bpkg // package // + // A map of "effective" prerequisites (i.e., pointers to other selected + // packages) to optional dependency constraint. Note that because it is a + // single constraint, we don't support multiple dependencies on the same + // package (e.g., two ranges of versions). See pkg_configure(). + // + class selected_package; + + using package_prerequisites = std::map<lazy_shared_ptr<selected_package>, + optional<dependency_constraint>, + compare_lazy_ptr>; + #pragma db object pointer(shared_ptr) session class selected_package { @@ -585,15 +606,7 @@ namespace bpkg // optional<dir_path> out_root; - // A map of "effective" prerequisites (i.e., pointers to other selected - // packages) to optional dependency constraint. Note that because it is a - // single constraint, we don't support multiple dependencies on the same - // package (e.g., two ranges of versions). See pkg_configure(). - // - using prerequisites_type = std::map<lazy_shared_ptr<selected_package>, - optional<dependency_constraint>, - compare_lazy_ptr>; - prerequisites_type prerequisites; + package_prerequisites prerequisites; bool system () const diff --git a/bpkg/package.xml b/bpkg/package.xml index b012d6d..d411a7e 100644 --- a/bpkg/package.xml +++ b/bpkg/package.xml @@ -70,6 +70,7 @@ <column name="version_canonical_release" type="TEXT" null="true" options="COLLATE BINARY"/> <column name="version_revision" type="INTEGER" null="true"/> <column name="repository" type="TEXT" null="true"/> + <column name="state" type="TEXT" null="true"/> <column name="location" type="TEXT" null="true"/> <foreign-key name="object_id_fk" on-delete="CASCADE"> <column name="name"/> diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index 2dcacb7..67dbd51 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -1191,7 +1191,9 @@ namespace bpkg v = m.version; ar = root; ap = make_shared<available_package> (move (m)); - ap->locations.push_back (package_location {root, move (a)}); + ap->locations.push_back (package_location {root, + string () /* state */, + move (a)}); } } catch (const invalid_path&) @@ -1233,7 +1235,10 @@ namespace bpkg v = m.version; ap = make_shared<available_package> (move (m)); ar = root; - ap->locations.push_back (package_location {root, move (d)}); + ap->locations.push_back ( + package_location {root, + string () /* state */, + move (d)}); } } catch (const invalid_path&) diff --git a/bpkg/pkg-checkout.cli b/bpkg/pkg-checkout.cli new file mode 100644 index 0000000..3283a3f --- /dev/null +++ b/bpkg/pkg-checkout.cli @@ -0,0 +1,41 @@ +// file : bpkg/pkg-checkout.cli +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include <bpkg/configuration.cli>; + +"\section=1" +"\name=bpkg-pkg-checkout" +"\summary=check out package version" + +namespace bpkg +{ + { + "<options> <pkg> <ver>", + + "\h|SYNOPSIS| + + \c{\b{bpkg pkg-checkout} [<options>] <pkg>/<ver>} + + \h|DESCRIPTION| + + The \cb{pkg-checkout} command checks out the specified package version + from one of the version control-based repositories (\l{bpkg-rep-add(1)}). + The resulting package state is \cb{unpacked} (\l{bpkg-pkg-status(1)}). + + If the \cb{--replace|-r} option is specified, then \cb{pkg-checkout} will + replace the archive and/or source directory of a package that is already + in the \cb{unpacked} or \cb{fetched} state." + } + + class pkg_checkout_options: configuration_options + { + "\h|PKG-CHECKOUT OPTIONS|" + + bool --replace|-r + { + "Replace the source directory if the package is already fetched or + unpacked." + } + }; +} diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx new file mode 100644 index 0000000..83ab935 --- /dev/null +++ b/bpkg/pkg-checkout.cxx @@ -0,0 +1,246 @@ +// file : bpkg/pkg-checkout.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <bpkg/pkg-checkout.hxx> + +#include <libbutl/sha256.mxx> + +#include <libbpkg/manifest.hxx> + +#include <bpkg/package.hxx> +#include <bpkg/package-odb.hxx> +#include <bpkg/database.hxx> +#include <bpkg/diagnostics.hxx> +#include <bpkg/manifest-utility.hxx> + +#include <bpkg/pkg-purge.hxx> +#include <bpkg/pkg-configure.hxx> + +using namespace std; +using namespace butl; + +namespace bpkg +{ + shared_ptr<selected_package> + pkg_checkout (const common_options& o, + const dir_path& c, + transaction& t, + string n, + version v, + bool replace) + { + tracer trace ("pkg_checkout"); + + dir_path d (c / dir_path (n + '-' + v.string ())); + + if (exists (d)) + fail << "package directory " << d << " already exists"; + + database& db (t.database ()); + tracer_guard tg (db, trace); + + // See if this package already exists in this configuration. + // + shared_ptr<selected_package> p (db.find<selected_package> (n)); + + if (p != nullptr) + { + bool s (p->state == package_state::fetched || + p->state == package_state::unpacked); + + if (!replace || !s) + { + diag_record dr (fail); + + dr << "package " << n << " already exists in configuration " << c << + info << "version: " << p->version_string () + << ", state: " << p->state + << ", substate: " << p->substate; + + if (s) // Suitable state for replace? + dr << info << "use 'pkg-checkout --replace|-r' to replace"; + } + } + + if (db.query_value<repository_count> () == 0) + fail << "configuration " << c << " has no repositories" << + info << "use 'bpkg rep-add' to add a repository"; + + if (db.query_value<available_package_count> () == 0) + fail << "configuration " << c << " has no available packages" << + info << "use 'bpkg rep-fetch' to fetch available packages list"; + + // Note that here we compare including the revision (see pkg-fetch() + // implementation for more details). + // + shared_ptr<available_package> ap ( + db.find<available_package> (available_package_id (n, v))); + + if (ap == nullptr) + fail << "package " << n << " " << v << " is not available"; + + // Pick a version control-based repository. Preferring a local one over + // the remotes seems like a sensible thing to do. + // + const package_location* pl (nullptr); + + for (const package_location& l: ap->locations) + { + const repository_location& rl (l.repository.load ()->location); + + if (rl.version_control_based () && (pl == nullptr || rl.local ())) + { + pl = &l; + + if (rl.local ()) + break; + } + } + + if (pl == nullptr) + fail << "package " << n << " " << v + << " is not available from a version control-based repository"; + + if (verb > 1) + text << "checking out " << pl->location.leaf () << " " + << "from " << pl->repository->name; + + const repository_location& rl (pl->repository->location); + + // Note: for now we assume this is a git repository. If/when we add other + // version control-based repositories, this will need adjustment. + // + + // Currently the git repository state already contains the checked out + // working tree so all we need to do is distribute it to the package + // directory. + // + dir_path sd (c / repos_dir); + + sd /= dir_path (sha256 (rl.canonical_name ()).abbreviated_string (16)); + sd /= dir_path (pl->state); + sd /= path_cast<dir_path> (pl->location); + + // Verify the package prerequisites are all configured since the dist + // meta-operation generally requires all imports to be resolvable. + // + pkg_configure_prerequisites (o, t, sd); + + // The temporary out of source directory that is required for the dist + // meta-operation. + // + auto_rmdir rmo (temp_dir / dir_path (n)); + const dir_path& od (rmo.path); + + if (exists (od)) + rm_r (od); + + // Form the buildspec. + // + string bspec ("dist("); + bspec += sd.representation (); + bspec += '@'; + bspec += od.representation (); + bspec += ')'; + + // Remove the resulting package distribution directory on failure. + // + auto_rmdir rmd (d); + + // Distribute. + // + // Note that on failure the package stays in the existing (working) state. + // + // At first it may seem we have a problem: an existing package with the + // same name will cause a conflict since we now have multiple package + // locations for the same package name. We are luck, however: subprojects + // are only loaded if used and since we don't support dependency cycles, + // the existing project should never be loaded by any of our dependencies. + // + run_b (o, + c, + bspec, + false /* quiet */, + strings ({"config.dist.root=" + c.representation ()})); + + if (p != nullptr) + { + // Clean up the source directory and archive of the package we are + // replacing. Once this is done, there is no going back. If things go + // badly, we can't simply abort the transaction. + // + pkg_purge_fs (c, t, p); + + p->version = move (v); + p->state = package_state::unpacked; + p->repository = rl; + p->src_root = d.leaf (); + p->purge_src = true; + + db.update (p); + } + else + { + // Add the package to the configuration. + // + p.reset (new selected_package { + move (n), + move (v), + package_state::unpacked, + package_substate::none, + false, // hold package + false, // hold version + rl, + nullopt, // No archive + false, + d.leaf (), // Source root. + true, // Purge directory. + nullopt, // No output directory yet. + {}}); // No prerequisites captured yet. + + db.persist (p); + } + + t.commit (); + + rmd.cancel (); + return p; + } + + int + pkg_checkout (const pkg_checkout_options& o, cli::scanner& args) + { + tracer trace ("pkg_checkout"); + + dir_path c (o.directory ()); + l4 ([&]{trace << "configuration: " << c;}); + + database db (open (c, trace)); + transaction t (db.begin ()); + session s; + + shared_ptr<selected_package> p; + + if (!args.more ()) + fail << "package name/version argument expected" << + info << "run 'bpkg help pkg-checkout' for more information"; + + const char* arg (args.next ()); + string n (parse_package_name (arg)); + version v (parse_package_version (arg)); + + if (v.empty ()) + fail << "package version expected" << + info << "run 'bpkg help pkg-checkout' for more information"; + + // Commits the transaction. + // + p = pkg_checkout (o, c, t, move (n), move (v), o.replace ()); + + if (verb) + text << "checked out " << *p; + + return 0; + } +} diff --git a/bpkg/pkg-checkout.hxx b/bpkg/pkg-checkout.hxx new file mode 100644 index 0000000..252c8cd --- /dev/null +++ b/bpkg/pkg-checkout.hxx @@ -0,0 +1,32 @@ +// file : bpkg/pkg-checkout.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PKG_CHECKOUT_HXX +#define BPKG_PKG_CHECKOUT_HXX + +#include <libbpkg/manifest.hxx> // version + +#include <bpkg/types.hxx> +#include <bpkg/forward.hxx> // transaction, selected_package +#include <bpkg/utility.hxx> + +#include <bpkg/pkg-checkout-options.hxx> + +namespace bpkg +{ + int + pkg_checkout (const pkg_checkout_options&, cli::scanner& args); + + // Check out the package from a repository and commit the transaction. + // + shared_ptr<selected_package> + pkg_checkout (const common_options&, + const dir_path& configuration, + transaction&, + string name, + version, + bool replace); +} + +#endif // BPKG_PKG_CHECKOUT_HXX diff --git a/bpkg/pkg-configure.cli b/bpkg/pkg-configure.cli index 0ee1586..c20c2e8 100644 --- a/bpkg/pkg-configure.cli +++ b/bpkg/pkg-configure.cli @@ -20,8 +20,8 @@ namespace bpkg \h|DESCRIPTION| 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. + (\l{bpkg-pkg-unpack(1)}, \l{bpkg-pkg-checkout(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 diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx index 2ed51e5..86aadcb 100644 --- a/bpkg/pkg-configure.cxx +++ b/bpkg/pkg-configure.cxx @@ -19,6 +19,93 @@ using namespace butl; namespace bpkg { + package_prerequisites + pkg_configure_prerequisites (const common_options& o, + transaction& t, + const dir_path& source) + { + package_prerequisites r; + package_manifest m (pkg_verify (source, true)); + + database& db (t.database ()); + + for (const dependency_alternatives& da: m.dependencies) + { + assert (!da.conditional); //@@ TODO + + bool satisfied (false); + for (const dependency& d: da) + { + const string& n (d.name); + + if (da.buildtime) + { + // Handle special names. + // + if (n == "build2") + { + if (d.constraint) + satisfy_build2 (o, m.name, d); + + satisfied = true; + break; + } + else if (n == "bpkg") + { + if (d.constraint) + satisfy_bpkg (o, m.name, d); + + satisfied = true; + break; + } + // else + // + // @@ TODO: in the future we would need to at least make sure the + // build and target machines are the same. See also pkg-build. + } + + if (shared_ptr<selected_package> dp = db.find<selected_package> (n)) + { + if (dp->state != package_state::configured) + continue; + + if (!satisfies (dp->version, d.constraint)) + continue; + + auto p (r.emplace (dp, d.constraint)); + + // Currently we can only capture a single constraint, so if we + // already have a dependency on this package and one constraint is + // not a subset of the other, complain. + // + if (!p.second) + { + auto& c (p.first->second); + + bool s1 (satisfies (c, d.constraint)); + bool s2 (satisfies (d.constraint, c)); + + if (!s1 && !s2) + fail << "multiple dependencies on package " << n << + info << n << " " << *c << + info << n << " " << *d.constraint; + + if (s2 && !s1) + c = d.constraint; + } + + satisfied = true; + break; + } + } + + if (!satisfied) + fail << "no configured package satisfies dependency on " << da; + } + + return r; + } + void pkg_configure (const dir_path& c, const common_options& o, @@ -47,85 +134,8 @@ namespace bpkg // Verify all our prerequisites are configured and populate the // prerequisites list. // - { - assert (p->prerequisites.empty ()); - - package_manifest m (pkg_verify (src_root, true)); - - for (const dependency_alternatives& da: m.dependencies) - { - assert (!da.conditional); //@@ TODO - - bool satisfied (false); - for (const dependency& d: da) - { - const string& n (d.name); - - if (da.buildtime) - { - // Handle special names. - // - if (n == "build2") - { - if (d.constraint) - satisfy_build2 (o, m.name, d); - - satisfied = true; - break; - } - else if (n == "bpkg") - { - if (d.constraint) - satisfy_bpkg (o, m.name, d); - - satisfied = true; - break; - } - // else - // - // @@ TODO: in the future we would need to at least make sure the - // build and target machines are the same. See also pkg-build. - } - - if (shared_ptr<selected_package> dp = db.find<selected_package> (n)) - { - if (dp->state != package_state::configured) - continue; - - if (!satisfies (dp->version, d.constraint)) - continue; - - auto r (p->prerequisites.emplace (dp, d.constraint)); - - // Currently we can only capture a single constraint, so if we - // already have a dependency on this package and one constraint is - // not a subset of the other, complain. - // - if (!r.second) - { - auto& c (r.first->second); - - bool s1 (satisfies (c, d.constraint)); - bool s2 (satisfies (d.constraint, c)); - - if (!s1 && !s2) - fail << "multiple dependencies on package " << n << - info << n << " " << *c << - info << n << " " << *d.constraint; - - if (s2 && !s1) - c = d.constraint; - } - - satisfied = true; - break; - } - } - - if (!satisfied) - fail << "no configured package satisfies dependency on " << da; - } - } + assert (p->prerequisites.empty ()); + p->prerequisites = pkg_configure_prerequisites (o, t, src_root); // Form the buildspec. // diff --git a/bpkg/pkg-configure.hxx b/bpkg/pkg-configure.hxx index 651daba..9ad36f4 100644 --- a/bpkg/pkg-configure.hxx +++ b/bpkg/pkg-configure.hxx @@ -30,6 +30,15 @@ namespace bpkg // shared_ptr<selected_package> pkg_configure_system (const string& name, const version&, transaction&); + + // Verify that a directory is a valid package and return its prerequisites. + // Fail if the directory is not a valid package or some of the prerequisites + // are not configured or don't satisfy the package's dependency constraints. + // + package_prerequisites + pkg_configure_prerequisites (const common_options&, + transaction&, + const dir_path& package); } #endif // BPKG_PKG_CONFIGURE_HXX diff --git a/bpkg/pkg-fetch.cli b/bpkg/pkg-fetch.cli index 55d2a22..4e1dda1 100644 --- a/bpkg/pkg-fetch.cli +++ b/bpkg/pkg-fetch.cli @@ -20,11 +20,14 @@ namespace bpkg \h|DESCRIPTION| The \cb{pkg-fetch} command fetches the archive for the specified package - name and version from one of the repositories (\l{bpkg-rep-add(1)}). If - the \cb{--replace|-r} option is specified, then \cb{pkg-fetch} will + name and version from one of the archive-based repositories + (\l{bpkg-rep-add(1)}). The resulting package state is \cb{fetched} + (\l{bpkg-pkg-status(1)}). + + If the \cb{--replace|-r} option is specified, then \cb{pkg-fetch} will replace the archive of a package that is already in the \cb{fetched} or - \cb{unpacked} state (\l{bpkg-pkg-status(1)}). Otherwise, \cb{pkg-fetch} - expects the package to not exist in the configuration. + \cb{unpacked} state. Otherwise, \cb{pkg-fetch} expects the package to not + exist in the configuration. If the \cb{--existing|-e} option is used, then instead of the name and version arguments, \cb{pkg-fetch} expects a local path to an existing diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index 26f17d2..fbe5b5c 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -198,20 +198,28 @@ namespace bpkg if (ap == nullptr) fail << "package " << n << " " << v << " is not available"; - // Pick a repository. Preferring a local one over the remotes seems - // like a sensible thing to do. + // Pick an archive-based repository. Preferring a local one over the + // remotes seems like a sensible thing to do. // - const package_location* pl (&ap->locations.front ()); + const package_location* pl (nullptr); for (const package_location& l: ap->locations) { - if (!l.repository.load ()->location.remote ()) + const repository_location& rl (l.repository.load ()->location); + + if (rl.archive_based () && (pl == nullptr || rl.local ())) { pl = &l; - break; + + if (rl.local ()) + break; } } + if (pl == nullptr) + fail << "package " << n << " " << v + << " is not available from an archive-based repository"; + if (verb > 1) text << "fetching " << pl->location.leaf () << " " << "from " << pl->repository->name; diff --git a/bpkg/pkg-unpack.cli b/bpkg/pkg-unpack.cli index 87ac6e4..f4da2c2 100644 --- a/bpkg/pkg-unpack.cli +++ b/bpkg/pkg-unpack.cli @@ -19,19 +19,22 @@ namespace bpkg \h|DESCRIPTION| - The \cb{pkg-unpack} command unpacks the archive for the previously fetched - (\l{bpkg-pkg-fetch(1)}) package. If the \cb{--existing|-e} option is used, - then instead of the package name, \cb{pkg-unpack} expects a local path to - an existing package directory. In this case, \cb{bpkg} will use the - directory in place, without copying it to the configuration or package - cache directories. Also, unless the \cb{--purge|-p} option is specified, - \cb{bpkg} will not attempt to remove this directory when the package is - later purged with the \l{bpkg-pkg-purge(1)} command. + The \cb{pkg-unpack} command unpacks the archive for the previously + fetched (\l{bpkg-pkg-fetch(1)}) package. The resulting package state is + \cb{unpacked} (\l{bpkg-pkg-status(1)}). + + If the \cb{--existing|-e} option is used, then instead of the package + name, \cb{pkg-unpack} expects a local path to an existing package + directory. In this case, \cb{bpkg} will use the directory in place, + without copying it to the configuration or package cache directories. + Also, unless the \cb{--purge|-p} option is specified, \cb{bpkg} will not + attempt to remove this directory when the package is later purged with + the \l{bpkg-pkg-purge(1)} command. If \cb{--existing|-e} is specified together with the \cb{--replace|-r} option, then \cb{pkg-unpack} will replace the archive and/or source directory of a package that is already in the \cb{unpacked} or - \cb{fetched} state (\l{bpkg-pkg-status(1)})." + \cb{fetched} state." } class pkg_unpack_options: configuration_options diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx index 7afb62c..edd833d 100644 --- a/bpkg/pkg-unpack.cxx +++ b/bpkg/pkg-unpack.cxx @@ -150,9 +150,7 @@ namespace bpkg l4 ([&]{trace << "archive: " << a;}); - // Extract the package directory. Currently we always extract it - // into the configuration directory. But once we support package - // cache, this will need to change. + // Extract the package directory. // // Also, since we must have verified the archive during fetch, // here we can just assume what the resulting directory will be. diff --git a/bpkg/rep-fetch.cxx b/bpkg/rep-fetch.cxx index e78ae064..0458975 100644 --- a/bpkg/rep-fetch.cxx +++ b/bpkg/rep-fetch.cxx @@ -74,7 +74,15 @@ namespace bpkg authenticate_repository (co, conf, cert_pem, *cert, sm, rl); } - return rep_fetch_data {move (rms), move (pms), move (cert)}; + vector<rep_fetch_data::package> fps; + fps.reserve (pms.size ()); + + for (package_manifest& m: pms) + fps.emplace_back ( + rep_fetch_data::package {move (m), + string () /* repository_state */}); + + return rep_fetch_data {move (rms), move (fps), move (cert)}; } template <typename M> @@ -144,6 +152,9 @@ namespace bpkg // Clone or fetch the repository. // + // If changing the repository directory naming scheme, then don't forget + // to also update pkg_checkout(). + // dir_path h (sha256 (rl.canonical_name ()).abbreviated_string (16)); auto_rmdir rm (temp_dir / h); @@ -209,7 +220,10 @@ namespace bpkg } } - // Fill "skeleton" package manifests. + vector<rep_fetch_data::package> fps; + fps.reserve (pms.size ()); + + // Parse package manifests. // for (package_manifest& sm: pms) { @@ -246,7 +260,7 @@ namespace bpkg // Save the package manifest, preserving its location. // - m.location = move (sm.location); + m.location = move (*sm.location); sm = move (m); } catch (const manifest_parsing& e) @@ -327,8 +341,15 @@ namespace bpkg is.close (); + // If succeess then save the package manifest together with the + // repository state it belongs to and go to the next package. + // if (pr.wait ()) - continue; // Go to the next package. + { + fps.emplace_back (rep_fetch_data::package {move (sm), + nm.string ()}); + continue; + } // Fall through. } @@ -352,7 +373,7 @@ namespace bpkg } } - return rep_fetch_data {move (rms), move (pms), nullptr}; + return rep_fetch_data {move (rms), move (fps), nullptr}; } rep_fetch_data @@ -520,8 +541,10 @@ namespace bpkg session& s (session::current ()); session::reset_current (); - for (package_manifest& pm: rfd.packages) + for (rep_fetch_data::package& fp: rfd.packages) { + package_manifest& pm (fp.manifest); + // We might already have this package in the database. // bool persist (false); @@ -570,6 +593,7 @@ namespace bpkg // p->locations.push_back ( package_location {lazy_shared_ptr<repository> (db, r), + move (fp.repository_state), move (*pm.location)}); if (persist) diff --git a/bpkg/rep-fetch.hxx b/bpkg/rep-fetch.hxx index c54119f..cfd5f81 100644 --- a/bpkg/rep-fetch.hxx +++ b/bpkg/rep-fetch.hxx @@ -27,9 +27,20 @@ namespace bpkg struct rep_fetch_data { - std::vector<repository_manifest> repositories; - std::vector<package_manifest> packages; - shared_ptr<const bpkg::certificate> certificate; // Can be NULL. + using repository = repository_manifest; + + struct package + { + package_manifest manifest; + string repository_state; // See package_location::state. + }; + + std::vector<repository> repositories; + std::vector<package> packages; + + // For base repo (can be NULL). + // + shared_ptr<const bpkg::certificate> certificate; }; rep_fetch_data diff --git a/bpkg/rep-info.cxx b/bpkg/rep-info.cxx index 9fc7676..467c005 100644 --- a/bpkg/rep-info.cxx +++ b/bpkg/rep-info.cxx @@ -188,8 +188,8 @@ namespace bpkg // Note: serializing without any extra package_manifests info. // manifest_serializer s (cout, "STDOUT"); - for (const package_manifest& pm: rfd.packages) - pm.serialize (s); + for (const rep_fetch_data::package& p: rfd.packages) + p.manifest.serialize (s); s.next ("", ""); // End of stream. } else @@ -198,8 +198,8 @@ namespace bpkg // cout << endl; - for (const package_manifest& pm: rfd.packages) - cout << pm.name << "/" << pm.version << endl; + for (const rep_fetch_data::package& p: rfd.packages) + cout << p.manifest.name << "/" << p.manifest.version << endl; } } } diff --git a/bpkg/types.hxx b/bpkg/types.hxx index 3133696..82a6ac9 100644 --- a/bpkg/types.hxx +++ b/bpkg/types.hxx @@ -81,6 +81,8 @@ namespace bpkg using butl::basic_path; using butl::invalid_path; + using butl::path_cast; + using paths = std::vector<path>; using dir_paths = std::vector<dir_path>; @@ -57,9 +57,9 @@ compile "common" $o --output-suffix "-options" --class-doc bpkg::common_options= compile "bpkg" $o --output-prefix "" --suppress-undocumented --class-doc bpkg::commands=short --class-doc bpkg::topics=short pages="cfg-create help pkg-build pkg-clean pkg-configure pkg-disfigure \ -pkg-drop pkg-fetch pkg-install pkg-purge pkg-status pkg-test pkg-uninstall \ -pkg-unpack pkg-update pkg-verify rep-add rep-create rep-fetch rep-info \ -repository-signing" +pkg-drop pkg-fetch pkg-checkout pkg-install pkg-purge pkg-status pkg-test \ +pkg-uninstall pkg-unpack pkg-update pkg-verify rep-add rep-create rep-fetch \ +rep-info repository-signing" for p in $pages; do compile $p $o diff --git a/tests/common.test b/tests/common.test index 42234fb..b919da4 100644 --- a/tests/common.test +++ b/tests/common.test @@ -20,6 +20,7 @@ test.options += --build $recall($build.path) # cfg_create = $* cfg-create pkg_build = $* pkg-build +pkg_checkout = $* pkg-checkout pkg_configure = $* pkg-configure pkg_disfigure = $* pkg-disfigure pkg_drop = $* pkg-drop diff --git a/tests/common/git/state0/libbar.tar b/tests/common/git/state0/libbar.tar Binary files differindex 1046f4b..23cc354 100644 --- a/tests/common/git/state0/libbar.tar +++ b/tests/common/git/state0/libbar.tar diff --git a/tests/common/git/state0/libfoo.tar b/tests/common/git/state0/libfoo.tar Binary files differindex d917a68..9b44d03 100644 --- a/tests/common/git/state0/libfoo.tar +++ b/tests/common/git/state0/libfoo.tar diff --git a/tests/common/git/state0/style-basic.tar b/tests/common/git/state0/style-basic.tar Binary files differindex a16e009..1579f50 100644 --- a/tests/common/git/state0/style-basic.tar +++ b/tests/common/git/state0/style-basic.tar diff --git a/tests/common/git/state0/style.tar b/tests/common/git/state0/style.tar Binary files differindex 0d40071..1b75bc5 100644 --- a/tests/common/git/state0/style.tar +++ b/tests/common/git/state0/style.tar diff --git a/tests/common/git/state1/libbaz.tar b/tests/common/git/state1/libbaz.tar Binary files differindex 74661aa..4bccb1a 100644 --- a/tests/common/git/state1/libbaz.tar +++ b/tests/common/git/state1/libbaz.tar diff --git a/tests/common/git/state1/libfoo.tar b/tests/common/git/state1/libfoo.tar Binary files differindex 276e6ba..dc9d53f 100644 --- a/tests/common/git/state1/libfoo.tar +++ b/tests/common/git/state1/libfoo.tar diff --git a/tests/common/git/state1/style-basic.tar b/tests/common/git/state1/style-basic.tar Binary files differindex ad067f4..c18148b 100644 --- a/tests/common/git/state1/style-basic.tar +++ b/tests/common/git/state1/style-basic.tar diff --git a/tests/common/git/state1/style.tar b/tests/common/git/state1/style.tar Binary files differindex 67bcb87..080dc4d 100644 --- a/tests/common/git/state1/style.tar +++ b/tests/common/git/state1/style.tar diff --git a/tests/pkg-checkout.test b/tests/pkg-checkout.test new file mode 100644 index 0000000..663a900 --- /dev/null +++ b/tests/pkg-checkout.test @@ -0,0 +1,96 @@ +# file : tests/pkg-checkout.test +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include common.test config.test remote-git.test + +# Source repository: +# +# pkg-checkout +# `-- git +# |-- libbar.git -> style-basic.git (prerequisite) +# `-- style-basic.git + +# Prepare repositories used by tests if running in the local mode. +# ++if ($remote != true) + # Create git repositories. + # + $git_extract $src/git/libbar.tar + $git_extract $src/git/style-basic0.tar &$out_git/state0/*** + $git_extract $src/git/style-basic1.tar &$out_git/state1/*** +end + +: git-repos +: +if ($git_supported != true) +{ + # Skip git repository tests. + # +} +else +{ + rep = "$rep_git/state0" + + rep_add += -d cfg 2>! + rep_fetch += -d cfg 2>! + pkg_configure += -d cfg 2>! + pkg_status += -d cfg + + test.cleanups += &cfg/.bpkg/repositories/*/*** + + : unconfigured-dependency + : + $clone_root_cfg; + $rep_add "$rep/libbar.git#master"; + $rep_fetch; + + $* libmbar/1.0.0 2>>EOE != 0 + error: no configured package satisfies dependency on style-basic >= 1.0.0 + EOE + + : configured-dependency + : + $clone_root_cfg; + $rep_add "$rep/libbar.git#master" && $rep_add "$rep/style-basic.git#master"; + $rep_fetch; + + $pkg_status style-basic | sed -n -e 's/available ([^ ]+).+/\1/p' | set v; + + $* "style-basic/$v" 2>>"EOE" &cfg/style-basic-$v/***; + dist style-basic-$v + checked out style-basic/$v + EOE + + $pkg_configure style-basic; + + $* libmbar/1.0.0 2>>EOE &cfg/libmbar-1.0.0/*** + dist libmbar-1.0.0 + checked out libmbar/1.0.0 + EOE + + : replacement + : + # @@ Reduce to a single repository when multiple revisions can be specified + # in the repository URL fragment. + # + rep0 = "$rep_git/state0"; + rep1 = "$rep_git/state1"; + + $clone_root_cfg; + $rep_add "$rep0/style-basic.git#master"; + $rep_add "$rep1/style-basic.git#stable"; + $rep_fetch; + + $pkg_status style-basic | \ + sed -n -e 's/available ([^ ]+) +([^ ]+).+/\1 \2/p' | set vs; + + echo "$vs" | sed -e 's/([^ ]+).+/\1/' | set v0; + echo "$vs" | sed -e 's/([^ ]+) +([^ ]+)/\2/' | set v1; + + $* "style-basic/$v0" 2>!; + $pkg_status style-basic >~"/unpacked $v0;.+/"; + + $* --replace "style-basic/$v1" 2>! &cfg/style-basic-$v1/***; + $pkg_status style-basic >~"/unpacked $v1;.+/" +} diff --git a/tests/pkg-checkout/git/libbar.tar b/tests/pkg-checkout/git/libbar.tar new file mode 120000 index 0000000..67ccdb1 --- /dev/null +++ b/tests/pkg-checkout/git/libbar.tar @@ -0,0 +1 @@ +../../common/git/state0/libbar.tar
\ No newline at end of file diff --git a/tests/pkg-checkout/git/style-basic0.tar b/tests/pkg-checkout/git/style-basic0.tar new file mode 120000 index 0000000..2833f83 --- /dev/null +++ b/tests/pkg-checkout/git/style-basic0.tar @@ -0,0 +1 @@ +../../common/git/state0/style-basic.tar
\ No newline at end of file diff --git a/tests/pkg-checkout/git/style-basic1.tar b/tests/pkg-checkout/git/style-basic1.tar new file mode 120000 index 0000000..6db2c2f --- /dev/null +++ b/tests/pkg-checkout/git/style-basic1.tar @@ -0,0 +1 @@ +../../common/git/state1/style-basic.tar
\ No newline at end of file diff --git a/tests/pkg-status.test b/tests/pkg-status.test index 511cab2..9a72784 100644 --- a/tests/pkg-status.test +++ b/tests/pkg-status.test @@ -183,7 +183,6 @@ if ($git_supported != true) else { rep = "$rep_git/state0" - rep_add += -d cfg 2>! test.cleanups += &cfg/.bpkg/repositories/*/*** : complement-cycle |