aboutsummaryrefslogtreecommitdiff
path: root/load
diff options
context:
space:
mode:
Diffstat (limited to 'load')
-rw-r--r--load/buildfile9
-rw-r--r--load/load.cli67
-rw-r--r--load/load.cxx986
-rw-r--r--load/types-parsers.cxx1
-rw-r--r--load/types-parsers.hxx1
5 files changed, 809 insertions, 255 deletions
diff --git a/load/buildfile b/load/buildfile
index 493d067..4278f20 100644
--- a/load/buildfile
+++ b/load/buildfile
@@ -1,5 +1,4 @@
# file : load/buildfile
-# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
import libs = libodb%lib{odb}
@@ -12,6 +11,10 @@ include ../libbrep/
exe{brep-load}: {hxx ixx cxx}{* -load-options} {hxx ixx cxx}{load-options} \
../libbrep/lib{brep} $libs
+# Build options.
+#
+obj{load}: cxx.poptions += -DBREP_COPYRIGHT=\"$copyright\"
+
# Generated options parser.
#
if $cli.configured
@@ -20,8 +23,8 @@ if $cli.configured
cli.options += --std c++11 -I $src_root --include-with-brackets \
--include-prefix load --guard-prefix LOAD --generate-specifier \
---cxx-prologue "#include <load/types-parsers.hxx>" --page-usage print_ \
---ansi-color --long-usage
+--generate-modifier --cxx-prologue "#include <load/types-parsers.hxx>" \
+--page-usage print_ --ansi-color --long-usage
# Include the generated cli files into the distribution and don't remove
# them when cleaning in src (so that clean results in a state identical to
diff --git a/load/load.cli b/load/load.cli
index 74c91f2..99d76f6 100644
--- a/load/load.cli
+++ b/load/load.cli
@@ -1,5 +1,4 @@
// file : load/load.cli
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
include <vector>;
@@ -54,9 +53,18 @@ class options
bool --shallow
{
"Don't load package information from prerequisite or complement
- repositories."
+ repositories, don't fail if unable to resolve a package dependency, and
+ don't detect package dependency cycles."
};
+ bool --ignore-unresolved-tests
+ {
+ "Ignore tests, examples, and benchmarks package manifest entries which
+ cannot be resolved from the main package's complement repositories,
+ recursively. Note that in contrast to --shallow option, such entries will
+ be removed from the main package manifests outright."
+ }
+
std::string --tenant
{
"<id>",
@@ -64,6 +72,41 @@ class options
specified, then the single-tenant mode is assumed."
};
+ bool --private
+ {
+ "Display the tenant packages in the web interface only in the tenant view
+ mode."
+ };
+
+ std::string --interactive
+ {
+ "<bkp>",
+ "Build the tenant packages interactively, stopping builds at the specified
+ breakpoint. Implies \cb{--private}."
+ };
+
+ std::string --service-id
+ {
+ "<id>",
+ "Third party service information to associate with the being created
+ tenant. Requires the \cb{--tenant} and \cb{--service-type} options to be
+ specified."
+ };
+
+ std::string --service-type
+ {
+ "<type>",
+ "Type of the service to associate with the being created tenant. Requires
+ the \cb{--service-id} option to be specified."
+ };
+
+ std::string --service-data
+ {
+ "<data>",
+ "Service data to associate with the being created tenant. Requires the
+ \cb{--service-id} option to be specified."
+ };
+
brep::path --overrides-file
{
"<file>",
@@ -124,6 +167,22 @@ class options
this option to specify multiple package manager options."
}
+ brep::path openssl = "openssl"
+ {
+ "<path>",
+ "The openssl program to be used for crypto operations. You can also
+ specify additional options that should be passed to the openssl program
+ with \cb{openssl-option}. If the openssl program is not explicitly
+ specified, then \cb{brep-load} will use \cb{openssl} by default."
+ }
+
+ brep::strings openssl-option
+ {
+ "<opt>",
+ "Additional option to be passed to the openssl program (see \cb{openssl}
+ for details). Repeat this option to specify multiple openssl options."
+ }
+
std::string --pager // String to allow empty value.
{
"<path>",
@@ -162,8 +221,8 @@ Fatal error.|
\li|\cb{2}
-An instance of \cb{brep-load} or \l{brep-migrate(1)} is already running. Try
-again.|
+An instance of \cb{brep-load} or some other \cb{brep} utility is already
+running. Try again.|
\li|\cb{3}
diff --git a/load/load.cxx b/load/load.cxx
index 83cc9e6..474b443 100644
--- a/load/load.cxx
+++ b/load/load.cxx
@@ -1,13 +1,13 @@
// file : load/load.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
#include <signal.h> // signal()
#include <cerrno>
-#include <cstring> // strncmp()
+#include <chrono>
+#include <thread> // this_thread::sleep_for()
+#include <cstring> // strncmp()
#include <iostream>
-#include <algorithm> // find(), find_if()
#include <odb/session.hxx>
#include <odb/database.hxx>
@@ -17,13 +17,14 @@
#include <odb/pgsql/database.hxx>
-#include <libbutl/pager.mxx>
-#include <libbutl/sha256.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/tab-parser.mxx>
-#include <libbutl/manifest-parser.mxx>
+#include <libbutl/pager.hxx>
+#include <libbutl/sha256.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/openssl.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx>
+#include <libbutl/tab-parser.hxx>
+#include <libbutl/manifest-parser.hxx>
#include <libbpkg/manifest.hxx>
@@ -37,6 +38,7 @@ using std::cout;
using std::cerr;
using std::endl;
+using namespace std::this_thread;
using namespace odb::core;
using namespace butl;
using namespace bpkg;
@@ -54,6 +56,17 @@ static const char* help_info (
static const path packages ("packages.manifest");
static const path repositories ("repositories.manifest");
+// Retry executing bpkg on recoverable errors for about 10 seconds.
+//
+// Should we just exit with some "bpkg recoverable" code instead and leave it
+// to the caller to perform retries? Feels like it's better to handle such
+// errors ourselves rather than to complicate every caller. Note that having
+// some frequently updated prerequisite repository can make these errors quite
+// probable, even if the internal repositories are rarely updated.
+//
+static const size_t bpkg_retries (10);
+static const std::chrono::seconds bpkg_retry_timeout (1);
+
struct internal_repository
{
repository_location location;
@@ -249,7 +262,7 @@ load_repositories (path p)
bad_line ("invalid buildable option value");
}
else
- bad_line ("invalid option '" + nv + "'");
+ bad_line ("invalid option '" + nv + '\'');
}
// For now cache option is mandatory.
@@ -352,23 +365,22 @@ repository_info (const options& lo, const string& rl, const cstrings& options)
// the repository. Should be called once per repository.
//
static void
-load_packages (const shared_ptr<repository>& rp,
+load_packages (const options& lo,
+ const shared_ptr<repository>& rp,
+ const repository_location& cl,
database& db,
bool ignore_unknown,
- const manifest_name_values& overrides)
+ const manifest_name_values& overrides,
+ const string& overrides_name)
{
// packages_timestamp other than timestamp_nonexistent signals the
// repository packages are already loaded.
//
assert (rp->packages_timestamp == timestamp_nonexistent);
- // Only locally accessible repositories allowed until package manager API is
- // ready.
- //
- assert (!rp->cache_location.empty ());
-
vector<package_manifest> pms;
- const repository_location& cl (rp->cache_location);
+
+ assert (!cl.empty ());
path p (cl.path () / packages);
@@ -395,8 +407,8 @@ load_packages (const shared_ptr<repository>& rp,
mp,
move (nv),
ignore_unknown,
- false /* complete_depends */,
- package_manifest_flags::forbid_incomplete_dependencies);
+ false /* complete_values */,
+ package_manifest_flags::forbid_incomplete_values);
}
else
pms = pkg_package_manifests (mp, ignore_unknown);
@@ -408,11 +420,15 @@ load_packages (const shared_ptr<repository>& rp,
}
using brep::dependency;
+ using brep::dependency_alternative;
+ using brep::dependency_alternatives;
+
+ const string& tenant (rp->tenant);
for (package_manifest& pm: pms)
{
shared_ptr<package> p (
- db.find<package> (package_id (rp->tenant, pm.name, pm.version)));
+ db.find<package> (package_id (tenant, pm.name, pm.version)));
// sha256sum should always be present if the package manifest comes from
// the packages.manifest file belonging to the pkg repository.
@@ -421,115 +437,296 @@ load_packages (const shared_ptr<repository>& rp,
if (p == nullptr)
{
- if (rp->internal)
+ // Apply the package manifest overrides.
+ //
+ if (!overrides.empty ())
+ try
{
- try
- {
- pm.override (overrides, "" /* name */);
- }
- catch (const manifest_parsing&)
+ pm.override (overrides, overrides_name);
+ }
+ catch (const manifest_parsing& e)
+ {
+ cerr << "error: unable to override " << pm.name << ' ' << pm.version
+ << " manifest: " << e << endl;
+
+ throw failed ();
+ }
+
+ // Convert the package manifest build configurations (contain public
+ // keys data) into the brep's build package configurations (contain
+ // public key object lazy pointers). Keep the bot key lists empty if
+ // the package is not buildable.
+ //
+ package_build_configs build_configs;
+
+ if (!pm.build_configs.empty ())
+ {
+ build_configs.reserve (pm.build_configs.size ());
+
+ for (bpkg::build_package_config& c: pm.build_configs)
{
- // Overrides are already validated (see below).
- //
- assert (false);
+ build_configs.emplace_back (move (c.name),
+ move (c.arguments),
+ move (c.comment),
+ move (c.builds),
+ move (c.constraints),
+ move (c.auxiliaries),
+ package_build_bot_keys (),
+ move (c.email),
+ move (c.warning_email),
+ move (c.error_email));
}
+ }
+ if (rp->internal)
+ {
// Create internal package object.
//
- optional<string> dsc;
- optional<text_type> dst;
-
- if (pm.description)
+ // Return nullopt if the text is in a file (can happen if the
+ // repository is of a type other than pkg) or if the type is not
+ // recognized (can only happen in the "ignore unknown" mode).
+ //
+ auto to_typed_text = [&cl, ignore_unknown] (typed_text_file&& v)
{
+ optional<typed_text> r;
+
// The description value should not be of the file type if the
// package manifest comes from the pkg repository.
//
- assert (!pm.description->file || cl.type () != repository_type::pkg);
+ assert (!v.file || cl.type () != repository_type::pkg);
- if (!pm.description->file)
+ if (!v.file)
{
- dst = pm.effective_description_type (ignore_unknown);
+ // Cannot throw since the manifest parser has already verified the
+ // effective type in the same "ignore unknown" mode.
+ //
+ optional<text_type> t (v.effective_type (ignore_unknown));
// If the description type is unknown (which may be the case for
// some "transitional" period and only if --ignore-unknown is
// specified) we just silently drop the description.
//
- assert (dst || ignore_unknown);
+ assert (t || ignore_unknown);
- if (dst)
- dsc = move (pm.description->text);
+ if (t)
+ r = typed_text {move (v.text), *t};
}
- }
- string chn;
+ return r;
+ };
+
+ // Convert descriptions.
+ //
+ optional<typed_text> ds (
+ pm.description
+ ? to_typed_text (move (*pm.description))
+ : optional<typed_text> ());
+
+ optional<typed_text> pds (
+ pm.package_description
+ ? to_typed_text (move (*pm.package_description))
+ : optional<typed_text> ());
+
+ // Merge changes into a single typed text object.
+ //
+ // If the text type is not recognized for any changes entry or some
+ // entry refers to a file, then assume that no changes are specified.
+ //
+ optional<typed_text> chn;
+
for (auto& c: pm.changes)
{
- // The changes value should not be of the file type if the package
- // manifest comes from the pkg repository.
- //
- assert (!c.file || cl.type () != repository_type::pkg);
+ optional<typed_text> tc (to_typed_text (move (c)));
- if (!c.file)
+ if (!tc)
{
- if (chn.empty ())
- chn = move (c.text);
- else
- {
- if (chn.back () != '\n')
- chn += '\n'; // Always have a blank line as a separator.
-
- chn += "\n" + c.text;
- }
+ chn = nullopt;
+ break;
}
- }
- dependencies ds;
-
- for (auto& pda: pm.dependencies)
- {
- // Ignore special build2 and bpkg dependencies. We may not have
- // packages for them and also showing them for every package is
- // probably not very helpful.
- //
- if (pda.buildtime && !pda.empty ())
+ if (!chn)
{
- const package_name& n (pda.front ().name);
- if (n == "build2" || n == "bpkg")
- continue;
+ chn = move (*tc);
}
+ else
+ {
+ // Should have failed while parsing the manifest otherwise.
+ //
+ assert (tc->type == chn->type);
- ds.emplace_back (pda.conditional, pda.buildtime, move (pda.comment));
+ string& v (chn->text);
- for (auto& pd: pda)
- // The package member will be assigned during dependency
- // resolution procedure.
- //
- ds.back ().push_back (dependency {move (pd.name),
- move (pd.constraint),
- nullptr /* package */});
+ assert (!v.empty ()); // Changes manifest value cannot be empty.
+
+ if (v.back () != '\n')
+ v += '\n'; // Always have a blank line as a separator.
+
+ v += '\n';
+ v += tc->text;
+ }
}
- auto deps = [] (small_vector<bpkg::dependency, 1>&& ds)
+ dependencies tds;
+
+ for (auto& das: pm.dependencies)
{
- small_vector<dependency, 1> r;
+ dependency_alternatives tdas (das.buildtime, move (das.comment));
- if (!ds.empty ())
+ for (auto& da: das)
{
- r.reserve (ds.size ());
+ dependency_alternative tda (move (da.enable),
+ move (da.reflect),
+ move (da.prefer),
+ move (da.accept),
+ move (da.require));
+
+ for (auto& d: da)
+ {
+ package_name& n (d.name);
+
+ // Ignore special build2 and bpkg dependencies. We may not have
+ // packages for them and also showing them for every package is
+ // probably not very helpful.
+ //
+ if (das.buildtime && (n == "build2" || n == "bpkg"))
+ continue;
+
+ // The package member will be assigned during dependency
+ // resolution procedure.
+ //
+ tda.push_back (dependency {move (n),
+ move (d.constraint),
+ nullptr /* package */});
+ }
- for (bpkg::dependency& d: ds)
- r.push_back (dependency {move (d.name),
- move (d.constraint),
- nullptr /* package */});
+ if (!tda.empty ())
+ tdas.push_back (move (tda));
}
- return r;
- };
+ if (!tdas.empty ())
+ tds.push_back (move (tdas));
+ }
+
+ small_vector<brep::test_dependency, 1> ts;
+
+ if (!pm.tests.empty ())
+ {
+ ts.reserve (pm.tests.size ());
+
+ for (bpkg::test_dependency& td: pm.tests)
+ ts.emplace_back (move (td.name),
+ td.type,
+ td.buildtime,
+ move (td.constraint),
+ move (td.enable),
+ move (td.reflect));
+ }
// Cache before the package name is moved.
//
package_name project (pm.effective_project ());
+ // If the package is buildable, then save the package manifest's
+ // common and build configuration-specific bot keys into the database
+ // and translate the key data lists into the lists of the public key
+ // object lazy pointers.
+ //
+ package_build_bot_keys bot_keys;
+
+ if (rp->buildable)
+ {
+ // Save the specified bot keys into the database as public key
+ // objects, unless they are already persisted. Translate these keys
+ // into the public key object lazy pointers.
+ //
+ auto keys_to_objects = [&lo,
+ &pm,
+ &tenant,
+ &db] (strings&& keys)
+ {
+ package_build_bot_keys r;
+
+ if (keys.empty ())
+ return r;
+
+ r.reserve (keys.size ());
+
+ for (string& key: keys)
+ {
+ // Calculate the key fingerprint.
+ //
+ string fp;
+
+ try
+ {
+ openssl os (path ("-"), path ("-"), 2,
+ lo.openssl (),
+ "pkey",
+ lo.openssl_option (), "-pubin", "-outform", "DER");
+
+ os.out << key;
+ os.out.close ();
+
+ fp = sha256 (os.in).string ();
+ os.in.close ();
+
+ if (!os.wait ())
+ {
+ cerr << "process " << lo.openssl () << ' ' << *os.exit
+ << endl;
+
+ throw io_error ("");
+ }
+ }
+ catch (const io_error&)
+ {
+ cerr << "error: unable to convert custom build bot public key "
+ << "for package " << pm.name << ' ' << pm.version << endl
+ << " info: key:" << endl
+ << key << endl;
+
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ cerr << "error: unable to convert custom build bot public key "
+ << "for package " << pm.name << ' ' << pm.version << ": "
+ << e << endl;
+
+ throw failed ();
+ }
+
+ // Try to find the public_key object for the calculated
+ // fingerprint. If it doesn't exist, then create and persist the
+ // new object.
+ //
+ public_key_id id (tenant, move (fp));
+ shared_ptr<public_key> k (db.find<public_key> (id));
+
+ if (k == nullptr)
+ {
+ k = make_shared<public_key> (move (id.tenant),
+ move (id.fingerprint),
+ move (key));
+
+ db.persist (k);
+ }
+
+ r.push_back (move (k));
+ }
+
+ return r;
+ };
+
+ bot_keys = keys_to_objects (move (pm.build_bot_keys));
+
+ assert (build_configs.size () == pm.build_configs.size ());
+
+ for (size_t i (0); i != build_configs.size (); ++i)
+ build_configs[i].bot_keys =
+ keys_to_objects (move (pm.build_configs[i].bot_keys));
+ }
+
p = make_shared<package> (
move (pm.name),
move (pm.version),
@@ -540,8 +737,8 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.license_alternatives),
move (pm.topics),
move (pm.keywords),
- move (dsc),
- move (dst),
+ move (ds),
+ move (pds),
move (chn),
move (pm.url),
move (pm.doc_url),
@@ -552,13 +749,14 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.build_email),
move (pm.build_warning_email),
move (pm.build_error_email),
- move (ds),
+ move (tds),
move (pm.requirements),
- deps (move (pm.tests)),
- deps (move (pm.examples)),
- deps (move (pm.benchmarks)),
+ move (ts),
move (pm.builds),
move (pm.build_constraints),
+ move (pm.build_auxiliaries),
+ move (bot_keys),
+ move (build_configs),
move (pm.location),
move (pm.fragment),
move (pm.sha256sum),
@@ -567,7 +765,13 @@ load_packages (const shared_ptr<repository>& rp,
else
// Create external package object.
//
- p = make_shared<package> (move (pm.name), move (pm.version), rp);
+ p = make_shared<package> (move (pm.name),
+ move (pm.version),
+ move (pm.builds),
+ move (pm.build_constraints),
+ move (pm.build_auxiliaries),
+ move (build_configs),
+ rp);
db.persist (p);
}
@@ -601,9 +805,14 @@ load_packages (const shared_ptr<repository>& rp,
// A non-stub package is buildable if belongs to at least one
// buildable repository (see libbrep/package.hxx for details).
+ // Note that if this is an external test package it will be marked as
+ // unbuildable later (see resolve_dependencies() for details).
//
- if (!p->stub () && !p->buildable)
- p->buildable = rp->buildable;
+ if (rp->buildable && !p->buildable && !p->stub ())
+ {
+ p->buildable = true;
+ p->unbuildable_reason = nullopt;
+ }
}
p->other_repositories.push_back (rp);
@@ -620,7 +829,9 @@ load_packages (const shared_ptr<repository>& rp,
// changed members. Should be called once per persisted internal repository.
//
static void
-load_repositories (const shared_ptr<repository>& rp,
+load_repositories (const options& lo,
+ const shared_ptr<repository>& rp,
+ const repository_location& cl,
database& db,
bool ignore_unknown,
bool shallow)
@@ -630,11 +841,6 @@ load_repositories (const shared_ptr<repository>& rp,
//
assert (rp->repositories_timestamp == timestamp_nonexistent);
- // Only locally accessible repositories allowed until package manager API is
- // ready.
- //
- assert (!rp->cache_location.empty ());
-
const string& tenant (rp->tenant);
// Repository is already persisted by the load_packages() function call.
@@ -644,7 +850,9 @@ load_repositories (const shared_ptr<repository>& rp,
pkg_repository_manifests rpm;
- path p (rp->cache_location.path () / repositories);
+ assert (!cl.empty ());
+
+ path p (cl.path () / repositories);
try
{
@@ -653,6 +861,9 @@ load_repositories (const shared_ptr<repository>& rp,
manifest_parser mp (ifs, p.string ());
rpm = pkg_repository_manifests (mp, ignore_unknown);
+
+ if (rpm.empty ())
+ rpm.emplace_back (repository_manifest ()); // Add the base repository.
}
catch (const io_error& e)
{
@@ -779,15 +990,19 @@ load_repositories (const shared_ptr<repository>& rp,
pr = make_shared<repository> (tenant, move (rl));
- // If the prerequsite repository location is a relative path, then
- // calculate its cache location.
+ // If the base repository is internal and the prerequsite repository
+ // location is a relative path, then calculate its cache location.
//
- if (rm.location.relative ())
+ if (rp->internal && rm.location.relative ())
{
+ // For an internal repository the cache location always comes from the
+ // loadtab file.
+ //
+ assert (cl.path () == rp->cache_location.path ());
+
try
{
- pr->cache_location =
- repository_location (rm.location, rp->cache_location);
+ pr->cache_location = repository_location (rm.location, cl);
}
catch (const invalid_argument&)
{
@@ -795,21 +1010,162 @@ load_repositories (const shared_ptr<repository>& rp,
<< "repository '" << rm.location << "'" << endl
<< " info: base (internal) repository location is "
<< rp->location << endl
- << " info: base repository cache location is "
- << rp->cache_location << endl;
+ << " info: base repository cache location is " << cl << endl;
throw failed ();
}
}
+ // If the (external) prerequisite repository cache location is empty, then
+ // check if the repository is local and, if that's the case, use its
+ // location as a cache location. Otherwise, fetch the repository
+ // information creating a temporary cache for it.
+ //
+ auto_rmdir cdr; // Remove the temporary cache after the repo load.
+ repository_location cl; // Repository temporary cache location.
+
+ if (pr->cache_location.empty ())
+ {
+ if (pr->location.local ())
+ {
+ pr->cache_location = pr->location;
+ }
+ else
+ {
+ dir_path cd;
+
+ try
+ {
+ cd = dir_path::temp_path ("brep-load-cache");
+ }
+ catch (const system_error& e)
+ {
+ cerr << "unable to obtain temporary directory: " << e;
+ throw failed ();
+ }
+
+ // It's highly unlikely but still possible that the temporary cache
+ // directory already exists. This can only happen due to the unclean
+ // loader termination. Let's remove it and retry.
+ //
+ try
+ {
+ if (try_mkdir (cd) == mkdir_status::already_exists)
+ {
+ try_rmdir_r (cd);
+
+ if (try_mkdir (cd) == mkdir_status::already_exists)
+ throw_generic_error (EEXIST);
+ }
+ }
+ catch (const system_error& e)
+ {
+ cerr << "unable to create directory '" << cd << "': " << e;
+ throw failed ();
+ }
+
+ cdr = auto_rmdir (cd);
+
+ path rf (cd / repositories);
+ path pf (cd / packages);
+
+ // Note that the fetch timeout can be overridden via --bpkg-option.
+ //
+ cstrings args {
+ "--fetch-timeout", "60", // 1 minute.
+ "--deep",
+ "--manifest",
+ "--repositories",
+ "--repositories-file", rf.string ().c_str (),
+ "--packages",
+ "--packages-file", pf.string ().c_str ()};
+
+ if (rm.trust)
+ {
+ args.push_back ("--trust");
+ args.push_back (rm.trust->c_str ());
+ }
+
+ // Always add it, so bpkg won't try to prompt for a certificate
+ // authentication if the fingerprint doesn't match.
+ //
+ args.push_back ("--trust-no");
+
+ // Retry bpkg-rep-info on recoverable errors, for a while.
+ //
+ for (size_t i (0);; ++i)
+ {
+ if (i != 0)
+ {
+ // Let's follow up the bpkg's diagnostics with the number of
+ // retries left.
+ //
+ cerr << bpkg_retries - i + 1 << " retries left" << endl;
+ sleep_for (bpkg_retry_timeout);
+ }
+
+ process p (repository_info (lo, pr->location.string (), args));
+
+ try
+ {
+ // Bail out from the retry loop on success.
+ //
+ if (p.wait ())
+ break;
+
+ // Assume the child issued diagnostics if terminated normally.
+ //
+ if (p.exit->normal ())
+ {
+ // Retry the manifests fetch on a recoverable error, unless the
+ // retries limit is reached.
+ //
+ if (p.exit->code () == 2 && i != bpkg_retries)
+ continue;
+ }
+ else
+ cerr << "process " << lo.bpkg () << " " << *p.exit << endl;
+
+ cerr << "error: unable to fetch manifests for "
+ << pr->canonical_name << endl
+ << " info: base repository location is "
+ << rp->location << endl;
+
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ cerr << "error: unable to fetch manifests for "
+ << pr->canonical_name << ": " << e << endl;
+
+ throw failed ();
+ }
+ }
+
+ // Note that this is a non-pkg repository cache and so we create the
+ // dir repository location (see load_repositories(path) for details).
+ //
+ cl = repository_location (repository_url (cd.string ()),
+ repository_type::dir);
+ }
+ }
+
// We don't apply overrides to the external packages.
//
- load_packages (pr,
+ load_packages (lo,
+ pr,
+ !pr->cache_location.empty () ? pr->cache_location : cl,
db,
ignore_unknown,
- manifest_name_values () /* overrides */);
-
- load_repositories (pr, db, ignore_unknown, false /* shallow */);
+ manifest_name_values () /* overrides */,
+ "" /* overrides_name */);
+
+ load_repositories (lo,
+ pr,
+ !pr->cache_location.empty () ? pr->cache_location : cl,
+ db,
+ ignore_unknown,
+ false /* shallow */);
}
db.update (rp);
@@ -850,29 +1206,36 @@ find (const lazy_shared_ptr<repository>& r,
return false;
}
-// Resolve package run-time dependencies, tests, examples, and benchmarks.
-// Make sure that the best matching dependency belongs to the package
-// repositories, their complements, recursively, or their immediate
-// prerequisite repositories (only for run-time dependencies). Should be
-// called once per internal package.
+// Resolve package regular dependencies and external tests. Make sure that the
+// best matching dependency belongs to the package repositories, their
+// complements, recursively, or their immediate prerequisite repositories
+// (only for regular dependencies). Set the buildable flag to false for the
+// resolved external tests packages. Fail if unable to resolve a regular
+// dependency, unless ignore_unresolved is true in which case leave this
+// dependency NULL. Fail if unable to resolve an external test, unless
+// ignore_unresolved or ignore_unresolved_tests is true in which case leave
+// this dependency NULL, if ignore_unresolved_tests is false, and remove the
+// respective tests manifest entry otherwise. Should be called once per
+// internal package.
//
static void
-resolve_dependencies (package& p, database& db)
+resolve_dependencies (package& p,
+ database& db,
+ bool ignore_unresolved,
+ bool ignore_unresolved_tests)
{
using brep::dependency;
+ using brep::dependency_alternative;
using brep::dependency_alternatives;
// Resolve dependencies for internal packages only.
//
assert (p.internal ());
- if (p.dependencies.empty () &&
- p.tests.empty () &&
- p.examples.empty () &&
- p.benchmarks.empty ())
+ if (p.dependencies.empty () && p.tests.empty ())
return;
- auto resolve = [&p, &db] (dependency& d, bool prereq)
+ auto resolve = [&p, &db] (dependency& d, bool test)
{
// Dependency should not be resolved yet.
//
@@ -934,9 +1297,26 @@ resolve_dependencies (package& p, database& db)
for (const auto& pp: db.query<package> (q + order_by_version_desc (vm)))
{
- if (find (p.internal_repository, pp, prereq))
+ if (find (p.internal_repository, pp, !test))
{
d.package.reset (db, pp.id);
+
+ // If the resolved dependency is an external test, then mark it as
+ // such, unless it is a stub.
+ //
+ if (test)
+ {
+ shared_ptr<package> dp (d.package.load ());
+
+ if (!dp->stub ())
+ {
+ dp->buildable = false;
+ dp->unbuildable_reason = unbuildable_reason::test;
+
+ db.update (dp);
+ }
+ }
+
return true;
}
}
@@ -944,49 +1324,50 @@ resolve_dependencies (package& p, database& db)
return false;
};
- auto bail = [&p] (const dependency& d, const char* what)
+ auto bail = [&p] (const dependency& d, const string& what)
{
- cerr << "error: can't resolve " << what << " " << d << " for the package "
- << p.name << " " << p.version << endl
+ cerr << "error: can't resolve " << what << ' ' << d << " for the package "
+ << p.name << ' ' << p.version << endl
<< " info: repository " << p.internal_repository.load ()->location
<< " appears to be broken" << endl;
throw failed ();
};
- for (dependency_alternatives& da: p.dependencies)
+ for (dependency_alternatives& das: p.dependencies)
{
- for (dependency& d: da)
+ // Practically it is enough to resolve at least one dependency alternative
+ // to build a package. Meanwhile here we consider an error specifying in
+ // the manifest file an alternative which can't be resolved, unless
+ // unresolved dependencies are allowed.
+ //
+ for (dependency_alternative& da: das)
{
- // Practically it is enough to resolve at least one dependency
- // alternative to build a package. Meanwhile here we consider an error
- // specifying in the manifest file an alternative which can't be
- // resolved.
- //
- if (!resolve (d, true /* prereq */))
- bail (d, "dependency");
+ for (dependency& d: da)
+ {
+ if (!resolve (d, false /* test */) && !ignore_unresolved)
+ bail (d, "dependency");
+ }
}
}
- // Should we allow tests, examples, and benchmarks packages to be
- // unresolvable? Let's forbid that until we see a use case for that.
- //
- for (dependency& d: p.tests)
+ for (auto i (p.tests.begin ()); i != p.tests.end (); )
{
- if (!resolve (d, false /* prereq */))
- bail (d, "tests");
- }
+ brep::test_dependency& td (*i);
- for (dependency& d: p.examples)
- {
- if (!resolve (d, false /* prereq */))
- bail (d, "examples");
- }
+ if (!resolve (td, true /* test */))
+ {
+ if (!ignore_unresolved && !ignore_unresolved_tests)
+ bail (td, to_string (td.type));
- for (dependency& d: p.benchmarks)
- {
- if (!resolve (d, false /* prereq */))
- bail (d, "benchmarks");
+ if (ignore_unresolved_tests)
+ {
+ i = p.tests.erase (i);
+ continue;
+ }
+ }
+
+ ++i;
}
db.update (p); // Update the package state.
@@ -1043,10 +1424,13 @@ detect_dependency_cycle (const package_id& id,
chain.push_back (id);
shared_ptr<package> p (db.load<package> (id));
- for (const auto& da: p->dependencies)
+ for (const auto& das: p->dependencies)
{
- for (const auto& d: da)
- detect_dependency_cycle (d.package.object_id (), chain, db);
+ for (const auto& da: das)
+ {
+ for (const auto& d: da)
+ detect_dependency_cycle (d.package.object_id (), chain, db);
+ }
}
chain.pop_back ();
@@ -1064,105 +1448,130 @@ certificate_info (const options& lo,
const repository_location& rl,
const optional<string>& fp)
{
- try
- {
- cstrings args {
- "--cert-fingerprint",
- "--cert-name",
- "--cert-organization",
- "--cert-email",
- "-q"}; // Don't print info messages.
+ cstrings args {
+ "--cert-fingerprint",
+ "--cert-name",
+ "--cert-organization",
+ "--cert-email",
+ "-q"}; // Don't print info messages.
- const char* trust ("--trust-no");
+ const char* trust ("--trust-no");
- if (fp)
+ if (fp)
+ {
+ if (!fp->empty ())
{
- if (!fp->empty ())
- {
- args.push_back ("--trust");
- args.push_back (fp->c_str ());
- }
- else
- trust = "--trust-yes";
+ args.push_back ("--trust");
+ args.push_back (fp->c_str ());
+ }
+ else
+ trust = "--trust-yes";
- if (!rl.remote ())
- {
- args.push_back ("--auth");
- args.push_back ("all");
- }
+ if (!rl.remote ())
+ {
+ args.push_back ("--auth");
+ args.push_back ("all");
}
+ }
- args.push_back (trust);
+ args.push_back (trust);
- process pr (repository_info (lo, rl.string (), args));
+ // Retry bpkg-rep-info on recoverable errors, for a while.
+ //
+ for (size_t i (0);; ++i)
+ {
+ if (i != 0)
+ {
+ // Let's follow up the bpkg's diagnostics with the number of retries
+ // left.
+ //
+ cerr << bpkg_retries - i + 1 << " retries left" << endl;
+ sleep_for (bpkg_retry_timeout);
+ }
try
{
- ifdstream is (
- move (pr.in_ofd),
- ifdstream::failbit | ifdstream::badbit | ifdstream::eofbit);
+ process pr (repository_info (lo, rl.string (), args));
- optional<certificate> cert;
+ try
+ {
+ ifdstream is (
+ move (pr.in_ofd),
+ ifdstream::failbit | ifdstream::badbit | ifdstream::eofbit);
- string fingerprint;
- getline (is, fingerprint);
+ optional<certificate> cert;
- if (!fingerprint.empty ())
- {
- cert = certificate ();
- cert->fingerprint = move (fingerprint);
- getline (is, cert->name);
- getline (is, cert->organization);
- getline (is, cert->email);
+ string fingerprint;
+ getline (is, fingerprint);
+
+ if (!fingerprint.empty ())
+ {
+ cert = certificate ();
+ cert->fingerprint = move (fingerprint);
+ getline (is, cert->name);
+ getline (is, cert->organization);
+ getline (is, cert->email);
+ }
+ else
+ {
+ // Read out empty lines.
+ //
+ string s;
+ getline (is, s); // Name.
+ getline (is, s); // Organization.
+ getline (is, s); // Email.
+ }
+
+ // Check that EOF is successfully reached.
+ //
+ is.exceptions (ifdstream::failbit | ifdstream::badbit);
+ if (is.peek () != ifdstream::traits_type::eof ())
+ throw io_error ("");
+
+ is.close ();
+
+ if (pr.wait ())
+ return cert;
+
+ // Fall through.
+ //
}
- else
+ catch (const io_error&)
{
- // Read out empty lines.
+ // Child exit status doesn't matter. Just wait for the process
+ // completion and fall through.
//
- string s;
- getline (is, s); // Name.
- getline (is, s); // Organization.
- getline (is, s); // Email.
+ pr.wait ();
}
- // Check that EOF is successfully reached.
+ // Assume the child issued diagnostics if terminated normally.
//
- is.exceptions (ifdstream::failbit | ifdstream::badbit);
- if (is.peek () != ifdstream::traits_type::eof ())
- throw io_error ("");
-
- is.close ();
+ if (pr.exit->normal ())
+ {
+ // Retry the certificate fetch on a recoverable error, unless the
+ // retries limit is reached.
+ //
+ if (pr.exit->code () == 2 && i != bpkg_retries)
+ continue;
+ }
+ else
+ cerr << "process " << lo.bpkg () << " " << *pr.exit << endl;
- if (pr.wait ())
- return cert;
+ cerr << "error: unable to fetch certificate information for "
+ << rl.canonical_name () << endl;
// Fall through.
- //
}
- catch (const io_error&)
+ catch (const process_error& e)
{
- // Child exit status doesn't matter. Just wait for the process
- // completion and fall through.
- //
- pr.wait ();
- }
-
- // Assume the child issued diagnostics.
- //
- cerr << "error: unable to fetch certificate information for "
- << rl.canonical_name () << endl;
+ cerr << "error: unable to fetch certificate information for "
+ << rl.canonical_name () << ": " << e << endl;
- // Fall through.
- }
- catch (const process_error& e)
- {
- cerr << "error: unable to fetch certificate information for "
- << rl.canonical_name () << ": " << e << endl;
+ // Fall through.
+ }
- // Fall through.
+ throw failed ();
}
-
- throw failed ();
}
int
@@ -1193,7 +1602,7 @@ try
<< "libbbot " << LIBBBOT_VERSION_ID << endl
<< "libbpkg " << LIBBPKG_VERSION_ID << endl
<< "libbutl " << LIBBUTL_VERSION_ID << endl
- << "Copyright (c) 2014-2019 Code Synthesis Ltd" << endl
+ << "Copyright (c) " << BREP_COPYRIGHT << "." << endl
<< "This is free software released under the MIT license." << endl;
return 0;
@@ -1241,8 +1650,46 @@ try
throw failed ();
}
+ // Verify the --service-* options.
+ //
+ if (ops.service_id_specified ())
+ {
+ if (!ops.tenant_specified ())
+ {
+ cerr << "error: --service-id requires --tenant" << endl;
+ throw failed ();
+ }
+
+ if (ops.service_type ().empty ())
+ {
+ cerr << "error: --service-id requires --service-type"
+ << endl;
+ throw failed ();
+ }
+ }
+ else
+ {
+ if (ops.service_type_specified ())
+ {
+ cerr << "error: --service-type requires --service-id"
+ << endl;
+ throw failed ();
+ }
+
+ if (ops.service_data_specified ())
+ {
+ cerr << "error: --service-data requires --service-id"
+ << endl;
+ throw failed ();
+ }
+ }
+
// Parse and validate overrides, if specified.
//
+ // Note that here we make sure that the overrides manifest is valid.
+ // Applying overrides to a specific package manifest may still fail (see
+ // package_manifest::validate_overrides() for details).
+ //
manifest_name_values overrides;
if (ops.overrides_file_specified ())
@@ -1277,7 +1724,7 @@ try
ops.db_port (),
"options='-c default_transaction_isolation=serializable'");
- // Prevent several brep-load/migrate instances from updating DB
+ // Prevent several brep utility instances from updating the package database
// simultaneously.
//
database_lock l (db);
@@ -1294,6 +1741,11 @@ try
throw failed ();
}
+ // Note: the interactive tenant implies private.
+ //
+ if (ops.interactive_specified ())
+ ops.private_ (true);
+
// Load the description of all the internal repositories from the
// configuration file.
//
@@ -1313,6 +1765,7 @@ try
{
db.erase_query<package> ();
db.erase_query<repository> ();
+ db.erase_query<public_key> ();
db.erase_query<tenant> ();
}
else // Multi-tenant mode.
@@ -1325,13 +1778,39 @@ try
db.erase_query<repository> (
query<repository>::id.tenant.in_range (ts.begin (), ts.end ()));
+ db.erase_query<public_key> (
+ query<public_key>::id.tenant.in_range (ts.begin (), ts.end ()));
+
db.erase_query<tenant> (
query<tenant>::id.in_range (ts.begin (), ts.end ()));
}
// Persist the tenant.
//
- db.persist (tenant (tnt));
+ // Note that if the tenant service is specified and some tenant with the
+ // same service id and type is already persisted, then we will end up with
+ // the `object already persistent` error and terminate with the exit code
+ // 1 (fatal error). We could potentially dedicate a special exit code for
+ // such a case, so that the caller may recognize it and behave accordingly
+ // (CI request handler can treat it as a client error rather than an
+ // internal error, etc). However, let's first see if it ever becomes a
+ // problem.
+ //
+ optional<tenant_service> service;
+
+ if (ops.service_id_specified ())
+ service = tenant_service (ops.service_id (),
+ ops.service_type (),
+ (ops.service_data_specified ()
+ ? ops.service_data ()
+ : optional<string> ()));
+
+ db.persist (tenant (tnt,
+ ops.private_ (),
+ (ops.interactive_specified ()
+ ? ops.interactive ()
+ : optional<string> ()),
+ move (service)));
// On the first pass over the internal repositories we load their
// certificate information and packages.
@@ -1356,7 +1835,13 @@ try
ir.buildable,
priority++));
- load_packages (r, db, ops.ignore_unknown (), overrides);
+ load_packages (ops,
+ r,
+ r->cache_location,
+ db,
+ ops.ignore_unknown (),
+ overrides,
+ ops.overrides_file ().string ());
}
// On the second pass over the internal repositories we load their
@@ -1369,12 +1854,17 @@ try
db.load<repository> (
repository_id (tnt, ir.location.canonical_name ())));
- load_repositories (r, db, ops.ignore_unknown (), ops.shallow ());
+ load_repositories (ops,
+ r,
+ r->cache_location,
+ db,
+ ops.ignore_unknown (),
+ ops.shallow ());
}
- // Resolve internal packages dependencies unless this is a shallow load.
+ // Resolve internal packages dependencies and, unless this is a shallow
+ // load, make sure there are no package dependency cycles.
//
- if (!ops.shallow ())
{
session s;
using query = query<package>;
@@ -1383,16 +1873,20 @@ try
db.query<package> (
query::id.tenant == tnt &&
query::internal_repository.canonical_name.is_not_null ()))
- resolve_dependencies (p, db);
+ resolve_dependencies (p,
+ db,
+ ops.shallow (),
+ ops.ignore_unresolved_tests ());
- // Make sure there is no package dependency cycles.
- //
- package_ids chain;
- for (const auto& p:
- db.query<package> (
- query::id.tenant == tnt &&
- query::internal_repository.canonical_name.is_not_null ()))
- detect_dependency_cycle (p.id, chain, db);
+ if (!ops.shallow ())
+ {
+ package_ids chain;
+ for (const auto& p:
+ db.query<package> (
+ query::id.tenant == tnt &&
+ query::internal_repository.canonical_name.is_not_null ()))
+ detect_dependency_cycle (p.id, chain, db);
+ }
}
}
diff --git a/load/types-parsers.cxx b/load/types-parsers.cxx
index bc829f3..4c4ea9d 100644
--- a/load/types-parsers.cxx
+++ b/load/types-parsers.cxx
@@ -1,5 +1,4 @@
// file : load/types-parsers.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
#include <load/types-parsers.hxx>
diff --git a/load/types-parsers.hxx b/load/types-parsers.hxx
index de7f001..1d2a6c9 100644
--- a/load/types-parsers.hxx
+++ b/load/types-parsers.hxx
@@ -1,5 +1,4 @@
// file : load/types-parsers.hxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
// CLI parsers, included into the generated source files.