aboutsummaryrefslogtreecommitdiff
path: root/bpkg/rep-fetch.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/rep-fetch.cxx')
-rw-r--r--bpkg/rep-fetch.cxx708
1 files changed, 561 insertions, 147 deletions
diff --git a/bpkg/rep-fetch.cxx b/bpkg/rep-fetch.cxx
index 3239421..d02a064 100644
--- a/bpkg/rep-fetch.cxx
+++ b/bpkg/rep-fetch.cxx
@@ -6,7 +6,7 @@
#include <map>
#include <set>
-#include <libbutl/manifest-parser.mxx>
+#include <libbutl/manifest-parser.hxx>
#include <bpkg/auth.hxx>
#include <bpkg/fetch.hxx>
@@ -15,7 +15,10 @@
#include <bpkg/package-odb.hxx>
#include <bpkg/database.hxx>
#include <bpkg/rep-remove.hxx>
+#include <bpkg/pkg-verify.hxx>
#include <bpkg/diagnostics.hxx>
+#include <bpkg/satisfaction.hxx>
+#include <bpkg/package-query.hxx>
#include <bpkg/manifest-utility.hxx>
using namespace std;
@@ -49,9 +52,11 @@ namespace bpkg
static rep_fetch_data
rep_fetch_pkg (const common_options& co,
const dir_path* conf,
+ database* db,
const repository_location& rl,
const optional<string>& dependent_trust,
- bool ignore_unknown)
+ bool ignore_unknown,
+ bool ignore_toolchain)
{
// First fetch the repositories list and authenticate the base's
// certificate.
@@ -71,7 +76,7 @@ namespace bpkg
if (a)
{
cert = authenticate_certificate (
- co, conf, cert_pem, rl, dependent_trust);
+ co, conf, db, cert_pem, rl, dependent_trust);
a = !cert->dummy ();
}
@@ -113,6 +118,18 @@ namespace bpkg
authenticate_repository (co, conf, cert_pem, *cert, sm, rl);
}
+ // If requested, verify that the packages are compatible with the current
+ // toolchain.
+ //
+ if (!ignore_toolchain)
+ {
+ for (const package_manifest& m: fr.packages)
+ {
+ for (const dependency_alternatives& das: m.dependencies)
+ toolchain_buildtime_dependency (co, das, &m.name);
+ }
+ }
+
return rep_fetch_data {{move (fr)}, move (cert_pem), move (cert)};
}
@@ -160,7 +177,8 @@ namespace bpkg
M r;
if (exists (f))
r = parse_manifest<M> (f, iu, rl, fragment);
- else
+
+ if (r.empty ())
r.emplace_back (repository_manifest ()); // Add the base repository.
return r;
@@ -189,81 +207,194 @@ namespace bpkg
return r;
}
+ static void
+ print_package_info (diag_record& dr,
+ const dir_path& pl,
+ const repository_location& rl,
+ const optional<string>& fragment)
+ {
+ dr << "package ";
+
+ if (!pl.current ())
+ dr << "'" << pl.string () << "' "; // Strip trailing '/'.
+
+ dr << "in repository " << rl;
+
+ if (fragment)
+ dr << ' ' << *fragment;
+ }
+
// Parse package manifests referenced by the package directory manifests.
//
- static vector<package_manifest>
+ static pair<vector<package_manifest>, vector<package_info>>
parse_package_manifests (const common_options& co,
const dir_path& repo_dir,
- vector<package_manifest>&& sms,
+ vector<package_manifest>&& pms,
bool iu,
+ bool it,
const repository_location& rl,
const optional<string>& fragment) // For diagnostics.
{
- vector<package_manifest> r;
- r.reserve (sms.size ());
+ auto prn_package_info = [&rl, &fragment] (diag_record& dr,
+ const package_manifest& pm)
+ {
+ print_package_info (dr,
+ path_cast<dir_path> (*pm.location),
+ rl,
+ fragment);
+ };
- for (package_manifest& sm: sms)
+ // Verify that all the package directories contain the package manifest
+ // files and retrieve the package versions via the single `b info` call,
+ // but only if the current build2 version is satisfactory for all the
+ // repository packages. While at it cache the manifest paths for the
+ // future use.
+ //
+ // Note that if the package is not compatible with the toolchain, not to
+ // end up with an unfriendly build2 error message (referring a line in the
+ // bootstrap file issued by the version module), we need to verify the
+ // compatibility of the package manifests prior to calling `b info`. Also
+ // note that we cannot create the package manifest objects at this stage,
+ // since we need the package versions for that. Thus, we cache the
+ // respective name value lists instead.
+ //
+ optional<package_version_infos> pvs;
+ paths mfs;
+ vector<vector<manifest_name_value>> nvs;
{
- assert (sm.location);
+ mfs.reserve (pms.size ());
+ nvs.reserve (pms.size ());
- auto package_info = [&sm, &rl, &fragment] (diag_record& dr)
+ dir_paths pds;
+ pds.reserve (pms.size ());
+
+ // If true, then build2 version is satisfactory for all the repository
+ // packages.
+ //
+ bool bs (true);
+
+ for (const package_manifest& pm: pms)
{
- dr << "package ";
+ assert (pm.location);
- if (!sm.location->current ())
- dr << "'" << sm.location->string () << "' "; // Strip trailing '/'.
+ dir_path d (repo_dir / path_cast<dir_path> (*pm.location));
+ d.normalize (); // In case location is './'.
- dr << "in repository " << rl;
+ path f (d / manifest_file);
+ if (!exists (f))
+ {
+ diag_record dr (fail);
+ dr << "no manifest file for ";
+ prn_package_info (dr, pm);
+ }
- if (fragment)
- dr << ' ' << *fragment;
- };
+ // Provide the context if the package compatibility verification fails.
+ //
+ auto g (
+ make_exception_guard (
+ [&pm, &prn_package_info] ()
+ {
+ diag_record dr (info);
- auto failure = [&package_info] (const char* desc)
- {
- diag_record dr (fail);
- dr << desc << " for ";
- package_info (dr);
- };
+ dr << "while retrieving information for ";
+ prn_package_info (dr, pm);
+ }));
+
+ try
+ {
+ ifdstream ifs (f);
+ manifest_parser mp (ifs, f.string ());
- dir_path d (repo_dir / path_cast<dir_path> (*sm.location));
- d.normalize (); // In case location is './'.
+ // Note that the package directory points to something temporary
+ // (e.g., .bpkg/tmp/6f746365314d/) and it's probably better to omit
+ // it entirely (the above exception guard will print all we've got).
+ //
+ pkg_verify_result r (pkg_verify (co, mp, it, dir_path ()));
+
+ if (bs &&
+ r.build2_dependency &&
+ !satisfy_build2 (co, *r.build2_dependency))
+ {
+ bs = false;
+ pds.clear (); // Won't now be used.
+ }
+
+ nvs.push_back (move (r));
+ }
+ catch (const manifest_parsing& e)
+ {
+ fail (e.name, e.line, e.column) << e.description;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read from " << f << ": " << e;
+ }
- path f (d / manifest_file);
- if (!exists (f))
- failure ("no manifest file");
+ mfs.push_back (move (f));
+
+ if (bs)
+ pds.push_back (move (d));
+ }
+
+ // Note that for the directory-based repositories we also query
+ // subprojects since the package information will be used for the
+ // subsequent package_iteration() call (see below).
+ //
+ if (bs)
+ pvs = package_versions (co, pds,
+ (rl.directory_based ()
+ ? b_info_flags::subprojects
+ : b_info_flags::none));
+ }
+
+ // Parse package manifests, fixing up their versions.
+ //
+ pair<vector<package_manifest>, vector<package_info>> r;
+ r.first.reserve (pms.size ());
+
+ if (pvs)
+ r.second.reserve (pms.size ());
+
+ for (size_t i (0); i != pms.size (); ++i)
+ {
+ package_manifest& pm (pms[i]);
+
+ assert (pm.location);
try
{
- ifdstream ifs (f);
- manifest_parser mp (ifs, f.string ());
-
package_manifest m (
- mp,
- [&co, &d] (version& v)
+ mfs[i].string (),
+ move (nvs[i]),
+ [&pvs, i] (version& v)
{
- if (optional<version> pv = package_version (co, d))
- v = move (*pv);
+ if (pvs)
+ {
+ optional<version>& pv ((*pvs)[i].version);
+
+ if (pv)
+ v = move (*pv);
+ }
},
iu);
// Save the package manifest, preserving its location.
//
- m.location = move (*sm.location);
- sm = move (m);
+ m.location = move (*pm.location);
+
+ pm = move (m);
}
catch (const manifest_parsing& e)
{
diag_record dr (fail (e.name, e.line, e.column));
dr << e.description << info;
- package_info (dr);
- }
- catch (const io_error& e)
- {
- fail << "unable to read from " << f << ": " << e;
+ prn_package_info (dr, pm);
}
- r.emplace_back (move (sm));
+ r.first.push_back (move (pm));
+
+ if (pvs)
+ r.second.push_back (move ((*pvs)[i].info));
}
return r;
@@ -287,11 +418,11 @@ namespace bpkg
ifdstream is (fp);
string s (is.read_text ());
- if (s.empty ())
+ if (s.empty () && name != "build-file")
fail << name << " manifest value in " << pkg / manifest_file
<< " references empty file " << rp <<
info << "repository " << rl
- << (!fragment.empty () ? " " + fragment : "");
+ << (!fragment.empty () ? ' ' + fragment : "");
return s;
}
@@ -301,7 +432,7 @@ namespace bpkg
<< name << " manifest value in " << pkg / manifest_file << ": "
<< e <<
info << "repository " << rl
- << (!fragment.empty () ? " " + fragment : "") << endf;
+ << (!fragment.empty () ? ' ' + fragment : "") << endf;
}
}
@@ -309,7 +440,9 @@ namespace bpkg
rep_fetch_dir (const common_options& co,
const repository_location& rl,
bool iu,
- bool ev)
+ bool it,
+ bool ev,
+ bool lb)
{
assert (rl.absolute ());
@@ -330,29 +463,76 @@ namespace bpkg
rl,
string () /* fragment */));
- fr.packages = parse_package_manifests (co,
- rd,
- move (pms),
- iu,
- rl,
- empty_string /* fragment */);
+ pair<vector<package_manifest>, vector<package_info>> pmi (
+ parse_package_manifests (co,
+ rd,
+ move (pms),
+ iu,
+ it,
+ rl,
+ empty_string /* fragment */));
+
+ fr.packages = move (pmi.first);
+ fr.package_infos = move (pmi.second);
- // Expand file-referencing package manifest values.
+ // If requested, expand file-referencing package manifest values and load
+ // the buildfiles into the respective *-build values.
//
- if (ev)
+ if (ev || lb)
{
for (package_manifest& m: fr.packages)
- m.load_files (
- [&m, &rd, &rl] (const string& n, const path& p)
- {
- return read_package_file (p,
- n,
- path_cast<dir_path> (*m.location),
- rd,
- rl,
- empty_string /* fragment */);
- },
- iu);
+ {
+ dir_path pl (path_cast<dir_path> (*m.location));
+
+ // Load *-file values.
+ //
+ try
+ {
+ m.load_files (
+ [ev, &rd, &rl, &pl]
+ (const string& n, const path& p) -> optional<string>
+ {
+ // Always expand the build-file values.
+ //
+ if (ev || n == "build-file")
+ {
+ return read_package_file (p,
+ n,
+ pl,
+ rd,
+ rl,
+ empty_string /* fragment */);
+ }
+ else
+ return nullopt;
+ },
+ iu);
+ }
+ catch (const manifest_parsing& e)
+ {
+ diag_record dr (fail);
+ dr << e << info;
+ print_package_info (dr, pl, rl, nullopt /* fragment */);
+ dr << endf;
+ }
+
+ // Load the bootstrap, root, and config/*.build buildfiles into the
+ // respective *-build values, if requested and if they are not already
+ // specified in the manifest.
+ //
+ if (lb)
+ try
+ {
+ load_package_buildfiles (m, rd / pl, true /* err_path_relative */);
+ }
+ catch (const runtime_error& e)
+ {
+ diag_record dr (fail);
+ dr << e << info;
+ print_package_info (dr, pl, rl, nullopt /* fragment */);
+ dr << endf;
+ }
+ }
}
return rep_fetch_data {{move (fr)},
@@ -365,16 +545,16 @@ namespace bpkg
const dir_path* conf,
const repository_location& rl,
bool iu,
- bool ev)
+ bool it,
+ bool ev,
+ bool lb)
{
- if (conf != nullptr && conf->empty ())
- conf = exists (bpkg_dir) ? &current_dir : nullptr;
-
- assert (conf == nullptr || !conf->empty ());
+ auto i (tmp_dirs.find (conf != nullptr ? *conf : empty_dir_path));
+ assert (i != tmp_dirs.end ());
dir_path sd (repository_state (rl));
- auto_rmdir rm (temp_dir / sd);
+ auto_rmdir rm (i->second / sd, !keep_tmp);
const dir_path& td (rm.path);
if (exists (td))
@@ -490,42 +670,91 @@ namespace bpkg
// Parse package manifests.
//
- fr.packages = parse_package_manifests (co,
- td,
- move (pms),
- iu,
- rl,
- fr.friendly_name);
-
- // Expand file-referencing package manifest values checking out
- // submodules, if required.
+ pair<vector<package_manifest>, vector<package_info>> pmi (
+ parse_package_manifests (co,
+ td,
+ move (pms),
+ iu,
+ it,
+ rl,
+ fr.friendly_name));
+
+ fr.packages = move (pmi.first);
+ fr.package_infos = move (pmi.second);
+
+ // If requested, expand file-referencing package manifest values
+ // checking out submodules, if required, and load the buildfiles into
+ // the respective *-build values.
//
- if (ev)
+ if (ev || lb)
{
for (package_manifest& m: fr.packages)
- m.load_files (
- [&m, &td, &rl, &fr, &checkout_submodules] (const string& n,
- const path& p)
- {
- // Note that this doesn't work for symlinks on Windows where git
- // normally creates filesystem-agnostic symlinks that are
- // indistinguishable from regular files (see fixup_worktree()
- // for details). It seems like the only way to deal with that is
- // to unconditionally checkout submodules on Windows. Let's not
- // pessimize things for now (if someone really wants this to
- // work, they can always enable real symlinks in git).
- //
- if (!exists (td / *m.location / p))
- checkout_submodules ();
-
- return read_package_file (p,
- n,
- path_cast<dir_path> (*m.location),
- td,
- rl,
- fr.friendly_name);
- },
- iu);
+ {
+ dir_path pl (path_cast<dir_path> (*m.location));
+
+ // Load *-file values.
+ //
+ try
+ {
+ m.load_files (
+ [ev, &td, &rl, &pl, &fr, &checkout_submodules]
+ (const string& n, const path& p) -> optional<string>
+ {
+ // Always expand the build-file values.
+ //
+ if (ev || n == "build-file")
+ {
+ // Check out submodules if the referenced file doesn't exist.
+ //
+ // Note that this doesn't work for symlinks on Windows where
+ // git normally creates filesystem-agnostic symlinks that
+ // are indistinguishable from regular files (see
+ // fixup_worktree() for details). It seems like the only way
+ // to deal with that is to unconditionally checkout
+ // submodules on Windows. Let's not pessimize things for now
+ // (if someone really wants this to work, they can always
+ // enable real symlinks in git).
+ //
+ if (!exists (td / pl / p))
+ checkout_submodules ();
+
+ return read_package_file (p,
+ n,
+ pl,
+ td,
+ rl,
+ fr.friendly_name);
+ }
+ else
+ return nullopt;
+ },
+ iu);
+ }
+ catch (const manifest_parsing& e)
+ {
+ diag_record dr (fail);
+ dr << e << info;
+ print_package_info (dr, pl, rl, fr.friendly_name);
+ dr << endf;
+ }
+
+ // Load the bootstrap, root, and config/*.build buildfiles into the
+ // respective *-build values, if requested and if they are not
+ // already specified in the manifest.
+ //
+ if (lb)
+ try
+ {
+ load_package_buildfiles (m, td / pl, true /* err_path_relative */);
+ }
+ catch (const runtime_error& e)
+ {
+ diag_record dr (fail);
+ dr << e << info;
+ print_package_info (dr, pl, rl, fr.friendly_name);
+ dr << endf;
+ }
+ }
}
np += fr.packages.size ();
@@ -556,16 +785,28 @@ namespace bpkg
static rep_fetch_data
rep_fetch (const common_options& co,
const dir_path* conf,
+ database* db,
const repository_location& rl,
const optional<string>& dt,
bool iu,
- bool ev)
+ bool it,
+ bool ev,
+ bool lb)
{
switch (rl.type ())
{
- case repository_type::pkg: return rep_fetch_pkg (co, conf, rl, dt, iu);
- case repository_type::dir: return rep_fetch_dir (co, rl, iu, ev);
- case repository_type::git: return rep_fetch_git (co, conf, rl, iu, ev);
+ case repository_type::pkg:
+ {
+ return rep_fetch_pkg (co, conf, db, rl, dt, iu, it);
+ }
+ case repository_type::dir:
+ {
+ return rep_fetch_dir (co, rl, iu, it, ev, lb);
+ }
+ case repository_type::git:
+ {
+ return rep_fetch_git (co, conf, rl, iu, it, ev, lb);
+ }
}
assert (false); // Can't be here.
@@ -577,9 +818,19 @@ namespace bpkg
const dir_path* conf,
const repository_location& rl,
bool iu,
- bool ev)
+ bool it,
+ bool ev,
+ bool lb)
{
- return rep_fetch (co, conf, rl, nullopt /* dependent_trust */, iu, ev);
+ return rep_fetch (co,
+ conf,
+ nullptr /* database */,
+ rl,
+ nullopt /* dependent_trust */,
+ iu,
+ it,
+ ev,
+ lb);
}
// Return an existing repository fragment or create a new one. Update the
@@ -591,7 +842,7 @@ namespace bpkg
static shared_ptr<repository_fragment>
rep_fragment (const common_options& co,
- const dir_path& conf,
+ database& db,
transaction& t,
const repository_location& rl,
rep_fetch_data::fragment&& fr,
@@ -601,7 +852,6 @@ namespace bpkg
{
tracer trace ("rep_fragment");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Calculate the fragment location.
@@ -852,10 +1102,15 @@ namespace bpkg
// details).
//
if (exists && !full_fetch)
- rep_remove_package_locations (t, rf->name);
+ rep_remove_package_locations (db, t, rf->name);
+
+ vector<package_manifest>& pms (fr.packages);
+ const vector<package_info>& pis (fr.package_infos);
- for (package_manifest& pm: fr.packages)
+ for (size_t i (0); i != pms.size (); ++i)
{
+ package_manifest& pm (pms[i]);
+
// Fix-up the external package version iteration number.
//
if (rl.directory_based ())
@@ -868,12 +1123,13 @@ namespace bpkg
optional<version> v (
package_iteration (
co,
- conf,
+ db,
t,
path_cast<dir_path> (rl.path () / *pm.location),
pm.name,
pm.version,
- false /* check_external */));
+ !pis.empty () ? &pis[i] : nullptr,
+ false /* check_external */));
if (v)
pm.version = move (*v);
@@ -956,7 +1212,7 @@ namespace bpkg
//
static void
rep_fetch (const common_options& co,
- const dir_path& conf,
+ database& db,
transaction& t,
const shared_ptr<repository>& r,
const optional<string>& dependent_trust,
@@ -970,7 +1226,6 @@ namespace bpkg
{
tracer trace ("rep_fetch(rep)");
- database& db (t.database ());
tracer_guard tg (db, trace);
// Check that the repository is not fetched yet and register it as fetched
@@ -990,7 +1245,8 @@ namespace bpkg
//
if (need_auth (co, r->location))
authenticate_certificate (co,
- &conf,
+ &db.config_orig,
+ &db,
r->certificate,
r->location,
dependent_trust);
@@ -1057,13 +1313,22 @@ namespace bpkg
// repository fragments list, as well as its prerequisite and complement
// repository sets.
//
+ // Note that we do this in the forward compatible manner ignoring
+ // unrecognized manifest values and unsatisfied build2 toolchain
+ // constraints in the package manifests. This approach allows older
+ // toolchains to work with newer repositories, successfully building the
+ // toolchain-satisfied packages and only failing for unsatisfied ones.
+ //
rep_fetch_data rfd (
rep_fetch (co,
- &conf,
+ &db.config_orig,
+ &db,
rl,
dependent_trust,
true /* ignore_unknow */,
- false /* expand_values */));
+ true /* ignore_toolchain */,
+ false /* expand_values */,
+ true /* load_buildfiles */));
// Save for subsequent certificate authentication for repository use by
// its dependents.
@@ -1079,7 +1344,7 @@ namespace bpkg
string nm (fr.friendly_name); // Don't move, still may be used.
shared_ptr<repository_fragment> rf (rep_fragment (co,
- conf,
+ db,
t,
rl,
move (fr),
@@ -1156,7 +1421,7 @@ namespace bpkg
rm (pr);
auto fetch = [&co,
- &conf,
+ &db,
&t,
&fetched_repositories,
&removed_repositories,
@@ -1171,7 +1436,7 @@ namespace bpkg
assert (i != repo_trust.end ());
rep_fetch (co,
- conf,
+ db,
t,
r,
i->second,
@@ -1206,7 +1471,7 @@ namespace bpkg
static void
rep_fetch (const common_options& o,
- const dir_path& conf,
+ database& db,
transaction& t,
const vector<lazy_shared_ptr<repository>>& repos,
bool shallow,
@@ -1215,7 +1480,6 @@ namespace bpkg
{
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
@@ -1243,7 +1507,7 @@ namespace bpkg
//
for (const lazy_shared_ptr<repository>& r: repos)
rep_fetch (o,
- conf,
+ db,
t,
r.load (),
nullopt /* dependent_trust */,
@@ -1258,7 +1522,14 @@ namespace bpkg
// Remove dangling repositories.
//
for (const shared_ptr<repository>& r: removed_repositories)
- rep_remove (conf, t, r);
+ {
+ // Prior to removing the repository we need to make sure it still
+ // exists, which may not be the case due to earlier removal of the
+ // dependent dangling repository.
+ //
+ if (db.find<repository> (r->name) != nullptr)
+ rep_remove (db, t, r);
+ }
// Remove dangling repository fragments.
//
@@ -1277,7 +1548,7 @@ namespace bpkg
//
assert (f == rf);
- rep_remove_fragment (conf, t, rf);
+ rep_remove_fragment (db, t, rf);
}
}
@@ -1346,12 +1617,17 @@ namespace bpkg
{
dependencies& ds (at.package->dependencies);
- // Note that the special test dependencies entry is always the last
- // one, if present.
+ // Note that there is only one special test dependencies entry in
+ // the test package.
//
- assert (!ds.empty () && ds.back ().type);
-
- ds.pop_back ();
+ for (auto i (ds.begin ()), e (ds.end ()); i != e; ++i)
+ {
+ if (i->type)
+ {
+ ds.erase (i);
+ break;
+ }
+ }
db.update (at.package);
}
@@ -1360,17 +1636,37 @@ namespace bpkg
// Go through the available packages that have external tests and add
// them as the special test dependencies to these test packages.
//
+ // Note that not being able to resolve the test package for a main
+ // package is not an error, since the test package absence doesn't
+ // affect the main package building and internal testing. Dropping of an
+ // external test package from a repository may, however, be intentional.
+ // Think of a private repository crafted as a subset of some public
+ // repository with the external examples packages omitted.
+ //
for (const auto& am: db.query<available_main> ())
{
const shared_ptr<available_package>& p (am.package);
+ const package_name& n (p->id.name);
+ const version& v (p->version);
vector<shared_ptr<repository_fragment>> rfs;
for (const package_location& pl: p->locations)
rfs.push_back (pl.repository_fragment.load ());
+ bool module (build2_module (n));
+
for (const test_dependency& td: p->tests)
{
+ // Verify that the package has no runtime tests if it is a build
+ // system module.
+ //
+ if (module && !td.buildtime)
+ fail << "run-time " << td.type << ' ' << td.name << " for build "
+ << "system module "
+ << package_string (n, v) <<
+ info << "build system modules cannot have run-time " << td.type;
+
vector<pair<shared_ptr<available_package>,
shared_ptr<repository_fragment>>> tps (
filter (rfs,
@@ -1386,11 +1682,122 @@ namespace bpkg
dependencies& ds (tp->dependencies);
- if (ds.empty () || !ds.back ().type)
- ds.push_back (dependency_alternatives_ex (td.type));
+ // Find the special test dependencies entry, if already present.
+ //
+ auto b (ds.begin ());
+ auto e (ds.end ());
+ auto oi (b); // Old entry location.
+ for (; oi != e && !oi->type; ++oi) ;
+
+ // Note that since we store all the primary packages as
+ // alternative dependencies (which must be all of the same
+ // dependency type) for the test package, it must either be a
+ // runtime or build-time dependency for all of them.
+ //
+ // Note that the test package alternative dependencies contain the
+ // `== <version>` constraints (see below), so we can use min
+ // version of such a constraint as the primary package version.
+ //
+ if (oi != e && oi->buildtime != td.buildtime)
+ {
+ dependency_alternatives_ex& das (*oi);
+ assert (!das.empty ()); // Cannot be empty if present.
+
+ const dependency_alternative& da (das[0]);
+
+ // We always add the primary package to the test package as a
+ // single-dependency alternative (see below).
+ //
+ assert (da.size () == 1);
+
+ fail << to_string (td.type) << " package " << td.name << " is a "
+ << "build-time dependency for one primary package and a "
+ << "run-time for another" <<
+ info << (das.buildtime ? "build-time for " : "run-time for ")
+ << package_string (da[0].name,
+ *da[0].constraint->min_version) <<
+ info << (td.buildtime ? "build-time for " : "run-time for ")
+ << package_string (n, v);
+ }
+
+ // Find the (new) location for the special test dependencies entry.
+ //
+ // Note that if the entry is already present, it can only be moved
+ // towards the end of the list.
+ //
+ auto ni (e);
+
+ // First, find the last depends clause that explicitly specifies
+ // this main package but goes after the special entry current
+ // location, if present. Note that we only consider clauses with
+ // the matching buildtime flag.
+ //
+ for (auto i (oi != e ? oi + 1 : b); i != e; ++i)
+ {
+ const dependency_alternatives_ex& das (*i);
+ if (das.buildtime == td.buildtime)
+ {
+ bool specifies (false);
+
+ for (const dependency_alternative& da: das)
+ {
+ for (const dependency& d: da)
+ {
+ if (d.name == n)
+ {
+ specifies = true;
+ break;
+ }
+ }
+
+ if (specifies)
+ break;
+ }
+
+ if (specifies)
+ ni = i;
+ }
+ }
+
+ // Now, set ni to refer to the special test dependencies entry,
+ // moving or creating one, if required.
+ //
+ if (oi != e) // The entry already exists?
+ {
+ if (ni != e) // Move the entry to the new location?
+ {
+ // Move the [oi + 1, ni] range 1 position to the left and
+ // move the *oi element to the now vacant ni slot.
+ //
+ rotate (oi, oi + 1, ni + 1);
+ }
+ else
+ ni = oi; // Leave the entry at the old location.
+ }
+ else // The entry doesn't exist.
+ {
+ if (ni != e) // Create the entry right after ni?
+ ++ni;
+ else
+ ni = b; // Create the entry at the beginning of the list.
+
+ ni = ds.emplace (ni, td.type, td.buildtime); // Create the entry.
+ }
+
+ // Finally, add the new dependency alternative to the special
+ // entry.
+ //
+ dependency_alternative da (td.enable,
+ td.reflect,
+ nullopt /* prefer */,
+ nullopt /* accept */,
+ nullopt /* require */);
- ds.back ().push_back (
- dependency {p->id.name, version_constraint (p->version)});
+ da.push_back (dependency {n, version_constraint (v)});
+
+ assert (ni != ds.end ()); // Must be deduced by now.
+
+ ni->push_back (move (da));
db.update (tp);
}
@@ -1409,7 +1816,7 @@ namespace bpkg
warn << "repository state is now broken and will be cleaned up" <<
info << "run 'bpkg rep-fetch' to update";
- rep_remove_clean (o, conf, t.database ());
+ rep_remove_clean (o, db);
}
throw;
@@ -1418,7 +1825,6 @@ namespace bpkg
void
rep_fetch (const common_options& o,
- const dir_path& conf,
database& db,
const vector<repository_location>& rls,
bool shallow,
@@ -1444,13 +1850,17 @@ namespace bpkg
// Add the repository, unless it is already a top-level one and has the
// same location.
//
+ // Note that on Windows we can overwrite the local repository location
+ // with the same location but some characters specified in a different
+ // case, which is ok.
+ //
if (ua.find (r) == ua.end () || r.load ()->location.url () != rl.url ())
- rep_add (o, t, rl);
+ rep_add (o, db, t, rl);
repos.emplace_back (r);
}
- rep_fetch (o, conf, t, repos, shallow, false /* full_fetch */, reason);
+ rep_fetch (o, db, t, repos, shallow, false /* full_fetch */, reason);
t.commit ();
}
@@ -1467,7 +1877,11 @@ namespace bpkg
//
vector<lazy_shared_ptr<repository>> repos;
- database db (open (c, trace));
+ // Pre-attach the explicitly linked databases since we call
+ // package_iteration().
+ //
+ database db (c, trace, true /* pre_attach */);
+
transaction t (db);
session s; // Repository dependencies can have cycles.
@@ -1531,7 +1945,7 @@ namespace bpkg
//
auto i (ua.find (r));
if (i == ua.end () || i->load ()->location.url () != rl.url ())
- r = lazy_shared_ptr<repository> (db, rep_add (o, t, rl));
+ r = lazy_shared_ptr<repository> (db, rep_add (o, db, t, rl));
}
repos.emplace_back (move (r));
@@ -1558,7 +1972,7 @@ namespace bpkg
}
}
- rep_fetch (o, c, t, repos, o.shallow (), full_fetch, reason);
+ rep_fetch (o, db, t, repos, o.shallow (), full_fetch, reason);
size_t rcount (0), pcount (0);
if (verb)