aboutsummaryrefslogtreecommitdiff
path: root/bpkg/pkg-checkout.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/pkg-checkout.cxx')
-rw-r--r--bpkg/pkg-checkout.cxx394
1 files changed, 280 insertions, 114 deletions
diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx
index 05875a4..81efdc2 100644
--- a/bpkg/pkg-checkout.cxx
+++ b/bpkg/pkg-checkout.cxx
@@ -10,23 +10,26 @@
#include <bpkg/package-odb.hxx>
#include <bpkg/database.hxx>
#include <bpkg/checksum.hxx>
+#include <bpkg/rep-mask.hxx>
#include <bpkg/diagnostics.hxx>
#include <bpkg/manifest-utility.hxx>
#include <bpkg/pkg-purge.hxx>
#include <bpkg/pkg-verify.hxx>
-#include <bpkg/pkg-configure.hxx>
using namespace std;
using namespace butl;
namespace bpkg
{
+ // pkg_checkout()
+ //
static void
checkout (const common_options& o,
const repository_location& rl,
const dir_path& dir,
- const shared_ptr<available_package>& ap)
+ const shared_ptr<available_package>& ap,
+ database& db)
{
switch (rl.type ())
{
@@ -41,9 +44,9 @@ namespace bpkg
// Print the progress indicator to attribute the possible fetching
// progress.
//
- if (verb && !o.no_progress ())
+ if ((verb && !o.no_progress ()) || o.progress ())
text << "checking out "
- << package_string (ap->id.name, ap->version);
+ << package_string (ap->id.name, ap->version) << db;
git_checkout_submodules (o, rl, dir);
}
@@ -58,19 +61,23 @@ namespace bpkg
// For some platforms/repository types the working tree needs to be
// temporary "fixed up" for the build2 operations to work properly on it.
//
- static bool
+ static optional<bool>
fixup (const common_options& o,
const repository_location& rl,
const dir_path& dir,
- bool revert = false)
+ bool revert = false,
+ bool ie = false)
{
- bool r (false);
+ optional<bool> r;
switch (rl.type ())
{
case repository_type::git:
{
- r = git_fixup_worktree (o, dir, revert);
+ if (!revert && !ie)
+ git_verify_symlinks (o, dir);
+
+ r = git_fixup_worktree (o, dir, revert, ie);
break;
}
case repository_type::pkg:
@@ -80,23 +87,30 @@ namespace bpkg
return r;
}
- shared_ptr<selected_package>
- pkg_checkout (const common_options& o,
- const dir_path& c,
+ // Return the selected package object which may replace the existing one.
+ //
+ static shared_ptr<selected_package>
+ pkg_checkout (pkg_checkout_cache& cache,
+ const common_options& o,
+ database& pdb,
+ database& rdb,
transaction& t,
package_name n,
version v,
+ const optional<dir_path>& output_root,
bool replace,
+ bool purge,
bool simulate)
{
tracer trace ("pkg_checkout");
- database& db (t.database ());
- tracer_guard tg (db, trace);
+ tracer_guard tg (pdb, trace); // NOTE: sets tracer for the whole cluster.
+
+ const dir_path& c (pdb.config_orig);
// See if this package already exists in this configuration.
//
- shared_ptr<selected_package> p (db.find<selected_package> (n));
+ shared_ptr<selected_package> p (pdb.find<selected_package> (n));
if (p != nullptr)
{
@@ -117,13 +131,13 @@ namespace bpkg
}
}
- check_any_available (c, t);
+ check_any_available (rdb, t);
- // Note that here we compare including the revision (see pkg-fetch()
+ // 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)));
+ rdb.find<available_package> (available_package_id (n, v)));
if (ap == nullptr)
fail << "package " << n << " " << v << " is not available";
@@ -135,14 +149,17 @@ namespace bpkg
for (const package_location& l: ap->locations)
{
- const repository_location& rl (l.repository_fragment.load ()->location);
-
- if (rl.version_control_based () && (pl == nullptr || rl.local ()))
+ if (!rep_masked_fragment (l.repository_fragment))
{
- pl = &l;
+ const repository_location& rl (l.repository_fragment.load ()->location);
- if (rl.local ())
- break;
+ if (rl.version_control_based () && (pl == nullptr || rl.local ()))
+ {
+ pl = &l;
+
+ if (rl.local ())
+ break;
+ }
}
}
@@ -157,8 +174,8 @@ namespace bpkg
const repository_location& rl (pl->repository_fragment->location);
auto_rmdir rmd;
- optional<string> mc;
- dir_path d (c / dir_path (n.string () + '-' + v.string ()));
+ const dir_path& ord (output_root ? *output_root : c);
+ dir_path d (ord / dir_path (n.string () + '-' + v.string ()));
// An incomplete checkout may result in an unusable repository state
// (submodule fetch is interrupted, working tree fix up failed in the
@@ -167,10 +184,7 @@ namespace bpkg
// (or interruption) the user will need to run bpkg-rep-fetch to restore
// the missing repository.
//
- bool fs_changed (false);
-
if (!simulate)
- try
{
if (exists (d))
fail << "package directory " << d << " already exists";
@@ -179,57 +193,84 @@ namespace bpkg
// if the previous checkout have failed or been interrupted.
//
dir_path sd (repository_state (rl));
- dir_path rd (c / repos_dir / sd);
+ dir_path rd (rdb.config_orig / repos_dir / sd);
- if (!exists (rd))
- fail << "missing repository directory for package " << n << " " << v
- << " in configuration " << c <<
- info << "run 'bpkg rep-fetch' to repair";
+ // Use the temporary directory from the repository information source
+ // configuration, so that we can always move the repository into and out
+ // of it (note that if they appear on different filesystems that won't
+ // be possible).
+ //
+ auto ti (tmp_dirs.find (rdb.config_orig));
+ assert (ti != tmp_dirs.end ());
+ const dir_path& tdir (ti->second);
- // The repository temporary directory.
+ // Try to reuse the cached repository (moved to the temporary directory
+ // with some fragment checked out and fixed up).
//
- auto_rmdir rmt (temp_dir / sd);
- const dir_path& td (rmt.path);
+ pkg_checkout_cache::state_map& cm (cache.map_);
+ auto i (cm.find (rd));
+
+ if (i == cm.end () || i->second.rl.fragment () != rl.fragment ())
+ {
+ // Restore the repository if some different fragment is checked out.
+ //
+ if (i != cm.end ())
+ cache.erase (i);
+
+ // Checkout and cache the fragment.
+ //
+ if (!exists (rd))
+ fail << "missing repository directory for package " << n << " " << v
+ << " in its repository information configuration "
+ << rdb.config_orig <<
+ info << "run 'bpkg rep-fetch' to repair";
+
+ // The repository temporary directory.
+ //
+ auto_rmdir rmt (tdir / sd, !keep_tmp);
+
+ // Move the repository to the temporary directory.
+ //
+ {
+ const dir_path& td (rmt.path);
+
+ if (exists (td))
+ rm_r (td);
+
+ mv (rd, td);
+ }
+
+ // Pre-insert the incomplete repository entry into the cache and
+ // "finalize" it by setting the fixed up value later, after the
+ // repository fragment checkout succeeds. Until then the repository
+ // may not be restored in its permanent place.
+ //
+ using state = pkg_checkout_cache::state;
- if (exists (td))
- rm_r (td);
+ i = cm.emplace (rd, state {move (rmt), rl, nullopt}).first;
+
+ // Checkout the repository fragment and fix up the working tree.
+ //
+ state& s (i->second);
+ const dir_path& td (s.rmt.path);
+
+ checkout (o, rl, td, ap, pdb);
+ s.fixedup = fixup (o, rl, td);
+ }
// The temporary out of source directory that is required for the dist
// meta-operation.
//
- auto_rmdir rmo (temp_dir / dir_path (n.string ()));
+ auto_rmdir rmo (tdir / dir_path (n.string ()), !keep_tmp);
const dir_path& od (rmo.path);
if (exists (od))
rm_r (od);
- // Finally, move the repository to the temporary directory and proceed
- // with the checkout.
- //
- mv (rd, td);
- fs_changed = true;
-
- // Checkout the repository fragment and fix up the working tree.
- //
- checkout (o, rl, td, ap);
- bool fixedup (fixup (o, rl, td));
-
// Calculate the package path that points into the checked out fragment
// directory.
//
- dir_path pd (td / 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.
- //
- package_manifest m (pkg_verify (pd,
- true /* ignore_unknown */,
- [&ap] (version& v) {v = ap->version;}));
-
- pkg_configure_prerequisites (o,
- t,
- convert (move (m.dependencies)),
- m.name);
+ dir_path pd (i->second.rmt.path / path_cast<dir_path> (pl->location));
// Form the buildspec.
//
@@ -245,7 +286,11 @@ namespace bpkg
// Distribute.
//
- // Note that on failure the package stays in the existing (working)
+ // Note that we are using the bootstrap distribution mode (and also skip
+ // bootstrapping external modules) to make sure a package can be checked
+ // out without its dependencies being present.
+ //
+ // Note also 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
@@ -256,43 +301,18 @@ namespace bpkg
// of our dependencies.
//
- // At verbosity level 1 we want our (nicer) progress header but the
- // build system's actual progress.
+ // If the verbosity level is less than 2, then we want our (nicer)
+ // progress header but the build system's actual progress.
//
- if (verb == 1 && !o.no_progress ())
- text << "distributing " << n << '/' << v;
+ if ((verb == 1 && !o.no_progress ()) || (verb == 0 && o.progress ()))
+ text << "distributing " << n << '/' << v << pdb;
run_b (o,
verb_b::progress,
- strings ({"config.dist.root='" + c.representation () + "'"}),
+ "--no-external-modules",
+ "!config.dist.bootstrap=true",
+ "config.dist.root='" + ord.representation () + '\'',
bspec);
-
- // Revert the fix-ups.
- //
- if (fixedup)
- fixup (o, rl, td, true /* revert */);
-
- // Manipulations over the repository are now complete, so we can return
- // it to its permanent location.
- //
- mv (td, rd);
- fs_changed = false;
-
- rmt.cancel ();
-
- mc = sha256 (o, d / manifest_file);
- }
- catch (const failed&)
- {
- if (fs_changed)
- {
- // We assume that the diagnostics has already been issued.
- //
- warn << "repository state is now broken" <<
- info << "run 'bpkg rep-fetch' to repair";
- }
-
- throw;
}
if (p != nullptr)
@@ -301,7 +321,7 @@ namespace bpkg
// 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, simulate);
+ pkg_purge_fs (pdb, t, p, simulate);
// Note that if the package name spelling changed then we need to update
// it, to make sure that the subsequent commands don't fail and the
@@ -310,21 +330,34 @@ namespace bpkg
//
if (p->name.string () != n.string ())
{
- db.erase (p);
+ pdb.erase (p);
p = nullptr;
}
}
+ // Make the package path absolute and normalized. If the package is inside
+ // the configuration, use the relative path. This way we can move the
+ // configuration around.
+ //
+ normalize (d, "package");
+
+ if (d.sub (pdb.config))
+ d = d.leaf (pdb.config);
+
if (p != nullptr)
{
+ // Note: we can be replacing an external package and thus we reset the
+ // manifest/subprojects and buildfiles checksums.
+ //
p->version = move (v);
p->state = package_state::unpacked;
p->repository_fragment = rl;
- p->src_root = d.leaf ();
- p->purge_src = true;
- p->manifest_checksum = move (mc);
+ p->src_root = move (d);
+ p->purge_src = purge;
+ p->manifest_checksum = nullopt;
+ p->buildfiles_checksum = nullopt;
- db.update (p);
+ pdb.update (p);
}
else
{
@@ -340,13 +373,14 @@ namespace bpkg
rl,
nullopt, // No archive
false,
- d.leaf (), // Source root.
- true, // Purge directory.
- move (mc),
+ move (d), // Source root.
+ purge, // Purge directory.
+ nullopt, // No manifest/subprojects checksum.
+ nullopt, // No buildfiles checksum.
nullopt, // No output directory yet.
{}}); // No prerequisites captured yet.
- db.persist (p);
+ pdb.persist (p);
}
t.commit ();
@@ -355,6 +389,56 @@ namespace bpkg
return p;
}
+ shared_ptr<selected_package>
+ pkg_checkout (pkg_checkout_cache& cache,
+ const common_options& o,
+ database& pdb,
+ database& rdb,
+ transaction& t,
+ package_name n,
+ version v,
+ const dir_path& d,
+ bool replace,
+ bool purge,
+ bool simulate)
+ {
+ return pkg_checkout (cache,
+ o,
+ pdb,
+ rdb,
+ t,
+ move (n),
+ move (v),
+ optional<dir_path> (d),
+ replace,
+ purge,
+ simulate);
+ }
+
+ shared_ptr<selected_package>
+ pkg_checkout (pkg_checkout_cache& cache,
+ const common_options& o,
+ database& pdb,
+ database& rdb,
+ transaction& t,
+ package_name n,
+ version v,
+ bool replace,
+ bool simulate)
+ {
+ return pkg_checkout (cache,
+ o,
+ pdb,
+ rdb,
+ t,
+ move (n),
+ move (v),
+ nullopt /* output_root */,
+ replace,
+ true /* purge */,
+ simulate);
+ }
+
int
pkg_checkout (const pkg_checkout_options& o, cli::scanner& args)
{
@@ -363,7 +447,7 @@ namespace bpkg
dir_path c (o.directory ());
l4 ([&]{trace << "configuration: " << c;});
- database db (open (c, trace));
+ database db (c, trace, true /* pre_attach */);
transaction t (db);
session s;
@@ -381,19 +465,101 @@ namespace bpkg
fail << "package version expected" <<
info << "run 'bpkg help pkg-checkout' for more information";
+ pkg_checkout_cache checkout_cache (o);
+
// Commits the transaction.
//
- p = pkg_checkout (o,
- c,
- t,
- move (n),
- move (v),
- o.replace (),
- false /* simulate */);
+ if (o.output_root_specified ())
+ p = pkg_checkout (checkout_cache,
+ o,
+ db /* pdb */,
+ db /* rdb */,
+ t,
+ move (n),
+ move (v),
+ o.output_root (),
+ o.replace (),
+ o.output_purge (),
+ false /* simulate */);
+ else
+ p = pkg_checkout (checkout_cache,
+ o,
+ db /* pdb */,
+ db /* rdb */,
+ t,
+ move (n),
+ move (v),
+ o.replace (),
+ false /* simulate */);
+
+ checkout_cache.clear (); // Detect errors.
if (verb && !o.no_result ())
text << "checked out " << *p;
return 0;
}
+
+ // pkg_checkout_cache
+ //
+ pkg_checkout_cache::
+ ~pkg_checkout_cache ()
+ {
+ if (!map_.empty () && !clear (true /* ignore_errors */))
+ {
+ // We assume that the diagnostics has already been issued.
+ //
+ warn << "repository state is now broken" <<
+ info << "run 'bpkg rep-fetch' to repair";
+ }
+ }
+
+ bool pkg_checkout_cache::
+ clear (bool ie)
+ {
+ while (!map_.empty ())
+ {
+ if (!erase (map_.begin (), ie))
+ return false;
+ }
+
+ return true;
+ }
+
+ bool pkg_checkout_cache::
+ erase (state_map::iterator i, bool ie)
+ {
+ state& s (i->second);
+
+ // Bail out if the entry is incomplete.
+ //
+ if (!s.fixedup)
+ {
+ assert (ie); // Only makes sense in the ignore errors mode.
+ return false;
+ }
+
+ // Revert the fix-ups.
+ //
+ // But first make the entry incomplete, so on error we don't try to
+ // restore the partially restored repository later.
+ //
+ bool f (*s.fixedup);
+
+ s.fixedup = nullopt;
+
+ if (f && !fixup (options_, s.rl, s.rmt.path, true /* revert */, ie))
+ return false;
+
+ // Manipulations over the repository are now complete, so we can return it
+ // to the permanent location.
+ //
+ if (!mv (s.rmt.path, i->first, ie))
+ return false;
+
+ s.rmt.cancel ();
+
+ map_.erase (i);
+ return true;
+ }
}