aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-02-19 21:47:04 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-02-21 17:42:43 +0300
commit89dd478de7cf075beac69d0145df46f914cf35cf (patch)
treebe960f2406d087135c6456cff654de1f24a91557
parentd75d1d9e1c5b03b17fdea1fa3638db2bfe5e8d12 (diff)
Add support for pkg-checkout
-rw-r--r--bpkg/bpkg.cli5
-rw-r--r--bpkg/bpkg.cxx2
-rw-r--r--bpkg/buildfile2
-rw-r--r--bpkg/package.hxx35
-rw-r--r--bpkg/package.xml1
-rw-r--r--bpkg/pkg-build.cxx9
-rw-r--r--bpkg/pkg-checkout.cli41
-rw-r--r--bpkg/pkg-checkout.cxx246
-rw-r--r--bpkg/pkg-checkout.hxx32
-rw-r--r--bpkg/pkg-configure.cli4
-rw-r--r--bpkg/pkg-configure.cxx168
-rw-r--r--bpkg/pkg-configure.hxx9
-rw-r--r--bpkg/pkg-fetch.cli11
-rw-r--r--bpkg/pkg-fetch.cxx18
-rw-r--r--bpkg/pkg-unpack.cli21
-rw-r--r--bpkg/pkg-unpack.cxx4
-rw-r--r--bpkg/rep-fetch.cxx36
-rw-r--r--bpkg/rep-fetch.hxx17
-rw-r--r--bpkg/rep-info.cxx8
-rw-r--r--bpkg/types.hxx2
-rwxr-xr-xdoc/cli.sh6
-rw-r--r--tests/common.test1
-rw-r--r--tests/common/git/state0/libbar.tarbin71680 -> 71680 bytes
-rw-r--r--tests/common/git/state0/libfoo.tarbin296960 -> 296960 bytes
-rw-r--r--tests/common/git/state0/style-basic.tarbin71680 -> 71680 bytes
-rw-r--r--tests/common/git/state0/style.tarbin133120 -> 133120 bytes
-rw-r--r--tests/common/git/state1/libbaz.tarbin61440 -> 61440 bytes
-rw-r--r--tests/common/git/state1/libfoo.tarbin378880 -> 378880 bytes
-rw-r--r--tests/common/git/state1/style-basic.tarbin71680 -> 71680 bytes
-rw-r--r--tests/common/git/state1/style.tarbin133120 -> 133120 bytes
-rw-r--r--tests/pkg-checkout.test96
l---------tests/pkg-checkout/git/libbar.tar1
l---------tests/pkg-checkout/git/style-basic0.tar1
l---------tests/pkg-checkout/git/style-basic1.tar1
-rw-r--r--tests/pkg-status.test1
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>;
diff --git a/doc/cli.sh b/doc/cli.sh
index 661d65c..13a94af 100755
--- a/doc/cli.sh
+++ b/doc/cli.sh
@@ -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
index 1046f4b..23cc354 100644
--- a/tests/common/git/state0/libbar.tar
+++ b/tests/common/git/state0/libbar.tar
Binary files differ
diff --git a/tests/common/git/state0/libfoo.tar b/tests/common/git/state0/libfoo.tar
index d917a68..9b44d03 100644
--- a/tests/common/git/state0/libfoo.tar
+++ b/tests/common/git/state0/libfoo.tar
Binary files differ
diff --git a/tests/common/git/state0/style-basic.tar b/tests/common/git/state0/style-basic.tar
index a16e009..1579f50 100644
--- a/tests/common/git/state0/style-basic.tar
+++ b/tests/common/git/state0/style-basic.tar
Binary files differ
diff --git a/tests/common/git/state0/style.tar b/tests/common/git/state0/style.tar
index 0d40071..1b75bc5 100644
--- a/tests/common/git/state0/style.tar
+++ b/tests/common/git/state0/style.tar
Binary files differ
diff --git a/tests/common/git/state1/libbaz.tar b/tests/common/git/state1/libbaz.tar
index 74661aa..4bccb1a 100644
--- a/tests/common/git/state1/libbaz.tar
+++ b/tests/common/git/state1/libbaz.tar
Binary files differ
diff --git a/tests/common/git/state1/libfoo.tar b/tests/common/git/state1/libfoo.tar
index 276e6ba..dc9d53f 100644
--- a/tests/common/git/state1/libfoo.tar
+++ b/tests/common/git/state1/libfoo.tar
Binary files differ
diff --git a/tests/common/git/state1/style-basic.tar b/tests/common/git/state1/style-basic.tar
index ad067f4..c18148b 100644
--- a/tests/common/git/state1/style-basic.tar
+++ b/tests/common/git/state1/style-basic.tar
Binary files differ
diff --git a/tests/common/git/state1/style.tar b/tests/common/git/state1/style.tar
index 67bcb87..080dc4d 100644
--- a/tests/common/git/state1/style.tar
+++ b/tests/common/git/state1/style.tar
Binary files differ
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