aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2022-11-28 21:39:25 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2022-12-06 13:47:43 +0300
commit1b5f0457e8708a57bd081257c8a18a7ae02f6516 (patch)
tree4ae80a825dd2a2c4010664017296f6c61e898c12
parent1bcba2c1b09f440e4017d5aeb411e5efaf08b809 (diff)
Add support for --target-config and --package-config to bdep-ci command
-rw-r--r--bdep/buildfile2
-rw-r--r--bdep/ci-parsers.cxx46
-rw-r--r--bdep/ci-types.cxx28
-rw-r--r--bdep/ci-types.hxx26
-rw-r--r--bdep/ci.cli108
-rw-r--r--bdep/ci.cxx445
-rw-r--r--bdep/project.cxx13
-rw-r--r--bdep/project.hxx6
-rw-r--r--tests/ci.testscript273
9 files changed, 829 insertions, 118 deletions
diff --git a/bdep/buildfile b/bdep/buildfile
index 6b5f74d..4496241 100644
--- a/bdep/buildfile
+++ b/bdep/buildfile
@@ -160,7 +160,7 @@ if $cli.configured
--keep-separator --generate-specifier --generate-modifier \
--generate-description --generate-parse --generate-merge \
--page-usage 'bdep::print_$name$_' --ansi-color --ascii-tree \
---include-base-last --suppress-undocumented --option-length 24
+--include-base-last --suppress-undocumented --option-length 25
cli.cxx{common-options}: cli.options += --short-usage --long-usage # Both.
cli.cxx{bdep-options}: cli.options += --short-usage
diff --git a/bdep/ci-parsers.cxx b/bdep/ci-parsers.cxx
index 7cf8d62..cca8237 100644
--- a/bdep/ci-parsers.cxx
+++ b/bdep/ci-parsers.cxx
@@ -1,14 +1,12 @@
// file : bdep/ci-parsers.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <bdep/ci-parsers.hxx>
+
#include <sstream>
#include <bdep/utility.hxx> // trim()
-#include <bdep/ci-parsers.hxx>
-
-#include <bdep/ci-options.hxx> // bdep::cli namespace
-
namespace bdep
{
namespace cli
@@ -19,12 +17,17 @@ namespace bdep
void parser<cmd_ci_override>::
parse (cmd_ci_override& r, bool& xs, scanner& s)
{
- auto add = [&r] (string&& n, string&& v)
+ using origin = cmd_ci_override_origin;
+
+ auto add = [&r] (string&& n, string&& v, origin o)
{
+ uint64_t orig (static_cast<uint64_t> (o));
+
r.push_back (
- manifest_name_value {move (n),
- move (v),
- 0, 0, 0, 0, 0, 0, 0}); // Locations, etc.
+ manifest_name_value {move (n), move (v), // Name and value.
+ orig, 0, // Name line and column.
+ orig, 0, // Value line and column.
+ 0, 0, 0}); // File positions.
};
string o (s.next ());
@@ -48,13 +51,23 @@ namespace bdep
{
validate_value ();
- add ("build-email", move (v));
+ add ("build-email", move (v), origin::build_email);
}
else if (o == "--builds")
{
validate_value ();
- add ("builds", move (v));
+ size_t n (v.find ('/'));
+
+ if (n != string::npos)
+ {
+ if (n == 0)
+ throw invalid_value (o, v, "no package configuration");
+
+ add (string (v, 0, n) + "-builds", string (v, n + 1), origin::builds);
+ }
+ else
+ add ("builds", move (v), origin::builds);
}
else if (o == "--override")
{
@@ -82,7 +95,7 @@ namespace bdep
if (vn.empty ())
throw invalid_value (o, v, "empty value name");
- add (move (vn), move (vv));
+ add (move (vn), move (vv), origin::override);
}
else if (o == "--overrides-file")
{
@@ -101,8 +114,19 @@ namespace bdep
//
manifest_parser p (is, "" /* name */);
+ size_t i (r.size ());
parse_manifest (p, r);
is.close ();
+
+ // Set the origin information for the just parsed value overrides.
+ //
+ for (; i != r.size (); ++i)
+ {
+ uint64_t orig (static_cast<uint64_t> (origin::overrides_file));
+
+ manifest_name_value& nv (r[i]);
+ nv.name_line = nv.value_line = orig;
+ }
}
catch (const manifest_parsing& e)
{
diff --git a/bdep/ci-types.cxx b/bdep/ci-types.cxx
new file mode 100644
index 0000000..00b4098
--- /dev/null
+++ b/bdep/ci-types.cxx
@@ -0,0 +1,28 @@
+// file : bdep/ci-types.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <bdep/ci-types.hxx>
+
+namespace bdep
+{
+ const char*
+ to_string (cmd_ci_override_origin o)
+ {
+ using orig = cmd_ci_override_origin;
+
+ switch (o)
+ {
+ case orig::override: return "--override option value";
+ case orig::overrides_file: return "file referenced by --overrides-file option";
+ case orig::build_email: return "--build-email option value";
+ case orig::builds: return "--builds option value";
+ case orig::target_config: return "--target-config option value";
+ case orig::build_config: return "--build-config option value";
+ case orig::package_config: return "--package-config option value";
+ case orig::interactive: return "--interactive|-i option value";
+ }
+
+ assert (false); // Can't be here.
+ return "";
+ }
+}
diff --git a/bdep/ci-types.hxx b/bdep/ci-types.hxx
index 1e8feea..fb496ba 100644
--- a/bdep/ci-types.hxx
+++ b/bdep/ci-types.hxx
@@ -11,11 +11,35 @@
namespace bdep
{
// This type is intended for accumulating package manifest override values
- // from all the override-related options (see ci-parsers.hxx for details).
+ // from all the override-related options (see ci-parsers.hxx for details) to
+ // then validate it as a whole.
//
class cmd_ci_override: public vector<butl::manifest_name_value>
{
};
+
+ // While handling a potential validation error we need to be able to
+ // attribute this error back to the option which resulted with an invalid
+ // override. For that we will encode the value's origin option into the
+ // name/value line.
+ //
+ enum class cmd_ci_override_origin: uint64_t
+ {
+ override = 1, // --override
+ overrides_file, // --overrides-file
+ build_email, // --build-email
+ builds, // --builds
+ target_config, // --target-config
+ build_config, // --build-config
+ package_config, // --package-config
+ interactive // --interactive
+ };
+
+ // Return the override origin description as, for example,
+ // `--override option value`.
+ //
+ const char*
+ to_string (cmd_ci_override_origin);
}
#endif // BDEP_CI_TYPES_HXX
diff --git a/bdep/ci.cli b/bdep/ci.cli
index ed1e80d..832f01a 100644
--- a/bdep/ci.cli
+++ b/bdep/ci.cli
@@ -58,28 +58,40 @@ namespace bdep
Some package manifest values can be overridden as part of the CI request
submission using the \cb{--override} and \cb{--overrides-file} options as
- well as their \cb{--builds}, \cb{--build-config}, and \cb{--build-email}
- shortcuts. This is primarily useful for specifying alternative build
- configurations and/or build notification emails. For example:
+ well as their \cb{--builds}, \cb{--build-config}, \cb{--target-config},
+ \cb{--package-config}, and \cb{--build-email} shortcuts. This is
+ primarily useful for specifying alternative build configurations and/or
+ build notification emails. For example:
\
$ bdep ci --builds gcc
- $ bdep ci --build-config 'linux*-gcc*'
+ $ bdep ci --target-config 'linux*-gcc*'
+ $ bdep ci --package-config network
+ $ bdep ci --build-config 'network/linux*-gcc*'
\
- Note that manifest overrides override the entire value group that they
- belong to. Currently, the following value groups can be overridden with
- the \cb{build*-email} group overridden by default as if by specifying
- an empty build email.
+ Manifest overrides override the entire value group that they belong
+ to. Currently, the following value groups can be overridden. The
+ \cb{build-*email} group is overridden by default as if by specifying an
+ empty build email.
\
build-email build-{warning,error}-email
builds build-{include,exclude}
+ *-builds *-build-{include,exclude}
\
- Note also that the build constraints group values are overridden
- hierarchically so that the \cb{build-{include,exclude\}} overrides don't
- affect the \cb{builds} values.
+ Note that the build constraints group values (both common and build
+ package configuration-specific) are overridden hierarchically so that the
+ \c{[\b{*-}]\b{build-}{\b{include},\b{exclude}\}} overrides don't affect
+ the respective \c{[\b{*-}]\b{builds}} values.
+
+ Note also that the common and build package configuration-specific build
+ constraints group value overrides are mutually exclusive. If the common
+ build constraints are overridden, then all the configuration-specific
+ constraints are removed. Otherwise, if any configuration-specific
+ constraints are overridden, then for the remaining configurations the
+ build constraints are reset to \cb{builds:\ none}.
If supported by the CI service, a package can be tested interactively
in a specific build configuration using the \c{\b{--interactive}|\b{-i}}
@@ -113,14 +125,26 @@ namespace bdep
string --interactive|-i
{
- "<cf>[/<bp>]",
+ "<cf>[:<bp>]",
"Test the package interactively in the specified build configuration,
- pausing the execution at the specified breakpoint. Valid breakpoint
- values are \cb{none} (don't stop), \cb{error} (stop after first error),
- \cb{warning} (stop after first warning), as well as the CI
- service-specific step ids in which case the execution stops before
- performing the specified step (see \l{bbot#arch-worker \cb{bbot} worker
- step ids}). If no breakpoint is specified, then \cb{error} is assumed."
+ pausing the execution at the specified breakpoint. The build
+ configuration is a target configuration (\ci{tc}), optionally for a
+ specific package configuration (\ci{pc}) and/or for a specific target
+ (\ci{tg}):
+
+ \c{<cf> = [\i{pc}\b{/}]\i{tc} | \i{pc}\b{/}\i{tc}\b{/}\i{tg}}
+
+ Refer to the \cb{--build-config} option for details on the build
+ configuration component semantics. Note that for interactive testing
+ they should identify a single build configuration. Failed that, the
+ test request will be aborted.
+
+ Valid breakpoint values are \cb{none} (don't stop), \cb{error} (stop
+ after first error), \cb{warning} (stop after first warning), as well as
+ the CI service-specific step ids in which case the execution stops
+ before performing the specified step (see \l{bbot#arch-worker \cb{bbot}
+ worker step ids}). If no breakpoint is specified, then \cb{error} is
+ assumed."
}
url --server
@@ -154,26 +178,62 @@ namespace bdep
strings --builds
{
- "<class-expr>",
- "Shortcut for \c{\b{--override\ builds:}<class-expr>}."
+ "[<pc>/]<class-expr>",
+ "Shortcut for the following option:
+
+ \c{\b{--override\ }[<pc>\b{-}]\b{builds:}<class-expr>}
+
+ Repeat this option to specify multiple build target configuration
+ classes."
}
strings --build-config
{
- "<cf>[/<tg>]",
+ "<pc>/<tc>[/<tg>]",
+ "Shortcut for the following options sequence:
+
+ \c{\b{--override\ }<pc>\b{-builds:all}}\n
+ \c{\b{--override\ }<pc>\b{-build-include:}<tc>[\b{/}<tg>]}\n
+ \c{\b{--override\ }<pc>\b{-build-exclude:**}}
+
+ Repeat this option to specify multiple build configurations."
+ }
+
+ strings --target-config
+ {
+ "<tc>[/<tg>]",
"Shortcut for the following options sequence:
\c{\b{--override\ builds:all}}\n
- \c{\b{--override\ build-include:}<cf>[/<tg>]}\n
+ \c{\b{--override\ build-include:}<tc>[\b{/}<tg>]}\n
\c{\b{--override\ build-exclude:**}}
- Repeat this option to specify multiple build configurations."
+ Repeat this option to specify multiple build target configurations."
+ }
+
+ strings --package-config
+ {
+ "<pc>",
+ "Shortcut for the following options sequence:
+
+ \c{\b{--override\ }<pc>\b{-builds:}...}\n
+ \c{\b{--override\ }<pc>\b{-build-include:}...}\n
+ \c{\b{--override\ }<pc>\b{-build-exclude:}...}
+
+ Where the override values are the build constraints for the specified
+ build package configuration from the package manifest.
+
+ Repeat this option to specify multiple build package configurations."
}
string --build-email
{
"<email>",
- "Shortcut for \c{\b{--override\ build-email:}<email>}."
+ "Shortcut for the following option:
+
+ \c{\b{--override\ build-email:}<email>}
+
+ "
}
// All the overrides-related options are handled with a common parser and
diff --git a/bdep/ci.cxx b/bdep/ci.cxx
index 4afa7d9..5f93872 100644
--- a/bdep/ci.cxx
+++ b/bdep/ci.cxx
@@ -4,6 +4,7 @@
#include <bdep/ci.hxx>
#include <sstream>
+#include <algorithm> // equal()
#include <libbutl/path-pattern.hxx>
#include <libbutl/manifest-types.hxx>
@@ -12,6 +13,7 @@
#include <bdep/git.hxx>
#include <bdep/project.hxx>
+#include <bdep/ci-types.hxx>
#include <bdep/database.hxx>
#include <bdep/diagnostics.hxx>
#include <bdep/http-service.hxx>
@@ -187,75 +189,110 @@ namespace bdep
fail << n << " specified together with --forward";
}
+ // Collect the packages manifest value overrides parsing the --override,
+ // etc options and verify that the resulting overrides list contains valid
+ // package manifest values and is semantically correct.
+ //
+ // Note that if we end up with any build package configuration-specific
+ // overrides, then we will need to verify the overrides using the package
+ // manifests to make sure that the specified build configurations are
+ // valid for the specified packages.
+ //
vector<manifest_name_value> overrides;
- auto override = [&overrides] (string n, string v)
+ using origin = cmd_ci_override_origin;
+
+ auto override = [&overrides] (string n, string v, origin o)
{
+ uint64_t orig (static_cast<uint64_t> (o));
+
overrides.push_back (
- manifest_name_value {move (n), move (v), // Name and value.
- 0, 0, 0, 0, 0, 0, 0}); // Locations, etc.
+ manifest_name_value {move (n), move (v), // Name and value.
+ orig, 0, // Name line and column.
+ orig, 0, // Value line and column.
+ 0, 0, 0}); // File positions.
};
// Add the default overrides.
//
- override ("build-email", "");
+ override ("build-email", "", origin::build_email);
- // Validate and append the specified overrides.
+ // Append the overrides specified by --override, --overrides-file,
+ // --build-email, and --builds which are all handled by
+ // cli::parser<cmd_ci_override>. But first verify that they don't clash
+ // with the other build constraints-related options. Also detect if any of
+ // these overrides are build package configuration-specific.
//
+ bool pkg_config_ovr (o.build_config_specified () ||
+ o.package_config_specified () ||
+ (o.interactive_specified () &&
+ o.interactive ().find ('/') != string::npos));
+
if (o.overrides_specified ())
- try
{
- if (o.interactive_specified () || o.build_config_specified ())
+ const char* co (o.target_config_specified () ? "--target-config" :
+ o.build_config_specified () ? "--build-config" :
+ o.package_config_specified () ? "--package-config" :
+ o.interactive_specified () ? "--interactive|-i" :
+ nullptr);
+
+ for (const manifest_name_value& nv: o.overrides ())
{
- for (const manifest_name_value& nv: o.overrides ())
- {
- if (nv.name == "builds" ||
- nv.name == "build-include" ||
- nv.name == "build-exclude")
- fail << "'" << nv.name << "' override specified together with "
- << (o.interactive_specified ()
- ? "--interactive|-i"
- : "--build-config");
- }
- }
+ const string& n (nv.name);
- package_manifest::validate_overrides (o.overrides (), "" /* name */);
+ // True if the name is one of {*-builds, *-build-{include,exclude}}.
+ //
+ bool pco ((n.size () > 7 &&
+ n.compare (n.size () - 7, 7, "-builds") == 0) ||
+ (n.size () > 14 &&
+ n.compare (n.size () - 14, 14, "-build-include") == 0) ||
+ (n.size () > 14 &&
+ n.compare (n.size () - 14, 14, "-build-exclude") == 0));
+
+ if (pco)
+ pkg_config_ovr = true;
+
+ if (co != nullptr &&
+ (pco ||
+ n == "builds" ||
+ n == "build-include" ||
+ n == "build-exclude"))
+ fail << "invalid " << to_string (static_cast<origin> (nv.name_line))
+ << ": " << "'" << n << "' override specified together with "
+ << co <<
+ info << "override: " << n << ": " << nv.value;
+ }
overrides.insert (overrides.end (),
o.overrides ().begin (),
o.overrides ().end ());
}
- catch (const manifest_parsing& e)
- {
- fail << "invalid overrides: " << e;
- }
- // Validate the --build-config option values and convert them into build
- // manifest value overrides.
+ // Append the overrides specified by --target-config, but first verify
+ // that they don't clash with the other build constraints-related options.
//
- if (o.build_config_specified ())
- try
+ if (o.target_config_specified ())
{
- if (o.interactive_specified ())
- fail << "--build-config specified together with --interactive|-i";
+ if (o.build_config_specified ())
+ fail << "--target-config specified together with --build-config";
- override ("builds", "all");
+ if (o.package_config_specified ())
+ fail << "--target-config specified together with --package-config";
- for (const string& c: o.build_config ())
- override ("build-include", c);
+ if (o.interactive_specified ())
+ fail << "--target-config specified together with --interactive|-i";
- override ("build-exclude", "**");
+ override ("builds", "all", origin::target_config);
- // Note that some of the overrides are knowingly valid (builds:all,
- // etc), but let's keep it simple and validate all of them.
- //
- package_manifest::validate_overrides (overrides, "" /* name */);
- }
- catch (const manifest_parsing& e)
- {
- fail << "invalid --build-config option value: " << e;
+ for (const string& c: o.target_config ())
+ override ("build-include", c, origin::target_config);
+
+ override ("build-exclude", "**", origin::target_config);
}
+ // Note that we will add the build package configuration-specific
+ // overrides after the packages being CI-ed are determined.
+
// If we are submitting the entire project, then we have two choices: we
// can list all the packages in the project or we can only do so for
// packages that were initialized in the specified configurations.
@@ -300,7 +337,8 @@ namespace bdep
{
package_name name;
string version;
- shared_ptr<configuration> config; // NULL in the forward mode.
+ shared_ptr<configuration> config; // NULL in the forward mode.
+ dir_path src_root;
};
vector<package> pkgs;
@@ -334,7 +372,8 @@ namespace bdep
info << "package source directory is " << d;
}
- pkgs.push_back (package {move (n), move (pi.version_string), nullptr});
+ pkgs.push_back (package {
+ move (n), move (pi.version_string), nullptr, move (pi.src_root)});
};
for (package_location& p: pp.packages)
@@ -383,8 +422,17 @@ namespace bdep
info << *c;
}
- standard_version v (package_version (o, c->path, n));
- pkgs.push_back (package {move (n), v.string (), move (c)});
+ package_info pi (package_b_info (o,
+ dir_path (c->path) /= n.string (),
+ false /* ext_mods */));
+
+ verify_package_info (pi, n);
+
+ pkgs.push_back (package {
+ move (n),
+ pi.version.string (),
+ move (c),
+ move (pi.src_root)});
};
if (pp.packages.empty ())
@@ -425,6 +473,194 @@ namespace bdep
}
}
+ // If there are any build package configuration-specific overrides, then
+ // load the package manifests to use them later for validation of the
+ // complete override list. Note that we also need these manifests for
+ // producing the --package-config overrides.
+ //
+ vector<package_manifest> override_manifests;
+
+ if (pkg_config_ovr)
+ {
+ override_manifests.reserve (pkgs.size ());
+
+ for (const package& p: pkgs)
+ {
+ path f (p.src_root / manifest_file);
+
+ if (!exists (f))
+ fail << "package manifest file " << f << " does not exist";
+
+ try
+ {
+ ifdstream is (f);
+ manifest_parser p (is, f.string ());
+ override_manifests.emplace_back (p);
+ }
+ catch (const manifest_parsing& e)
+ {
+ fail << "invalid package manifest: " << f << ':'
+ << e.line << ':' << e.column << ": " << e.description;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read " << f << ": " << e;
+ }
+ }
+ }
+
+ // Append the overrides specified by --build-config, but first verify that
+ // they don't clash with the other build constraints-related options. Also
+ // collect the names of the build package configs they involve.
+ //
+ strings package_configs;
+
+ if (o.build_config_specified ())
+ {
+ if (o.interactive_specified ())
+ fail << "--build-config specified together with --interactive|-i";
+
+ for (const string& bc: o.build_config ()) // <pc>/<tc>[/<tg>]
+ {
+ size_t n (bc.find ('/'));
+
+ if (n == string::npos)
+ fail << "invalid --build-config option value: no target "
+ << "configuration in '" << bc << '\'';
+
+ if (n == 0)
+ fail << "invalid --build-config option value: no package "
+ << "configuration in '" << bc << '\'';
+
+ string pc (bc, 0, n);
+
+ bool first (find (package_configs.begin (), package_configs.end (),
+ pc) == package_configs.end ());
+
+ if (first)
+ override (pc + "-builds", "all", origin::build_config);
+
+ override (pc + "-build-include",
+ string (bc, n + 1),
+ origin::build_config);
+
+ if (first)
+ package_configs.push_back (move (pc));
+ }
+
+ for (const string& pc: package_configs)
+ override (pc + "-build-exclude", "**", origin::build_config);
+ }
+
+ // Append the overrides specified by --package-config, but first verify
+ // that they don't clash with the other build constraints-related options.
+ //
+ // Note that we also need to make sure that we end up with the same
+ // overrides regardless of the package we use to produce them.
+ //
+ if (o.package_config_specified ())
+ {
+ if (o.interactive_specified ())
+ fail << "--package-config specified together with --interactive|-i";
+
+ for (const string& pc: o.package_config ())
+ {
+ if (find (package_configs.begin (), package_configs.end (), pc) !=
+ package_configs.end ())
+ fail << "package configuration " << pc << " is specified using "
+ << "both --package-config and --build-config";
+
+ using bpkg::build_package_config;
+ using bpkg::build_class_expr;
+ using bpkg::build_constraint;
+
+ auto override_builds = [&pc, &override]
+ (const small_vector<build_class_expr, 1>& bs,
+ const vector<build_constraint>& cs)
+ {
+ if (!bs.empty ())
+ {
+ string n (pc + "-builds");
+ for (const build_class_expr& e: bs)
+ override (n, e.string (), origin::package_config);
+ }
+
+ if (!cs.empty ())
+ {
+ string in (pc + "-build-include");
+ string en (pc + "-build-exclude");
+
+ for (const build_constraint& bc: cs)
+ override (bc.exclusion ? en : in,
+ (!bc.target
+ ? bc.config
+ : bc.config + '/' + *bc.target),
+ origin::package_config);
+ }
+ };
+
+ // Generate overrides based on the --package-config option for every
+ // package and verify that we always end up with the same overrides.
+ //
+ optional<vector<manifest_name_value>> overrides_initial;
+ optional<vector<manifest_name_value>> overrides_cache;
+
+ for (const package_manifest& m: override_manifests)
+ {
+ // Save/restore the initial overrides.
+ //
+ if (overrides_initial)
+ overrides = *overrides_initial;
+ else if (override_manifests.size () > 1) // Will we compare overrides?
+ overrides_initial = overrides;
+
+ const small_vector<build_package_config, 1>& cs (m.build_configs);
+
+ auto i (find_if (cs.begin (), cs.end (),
+ [&pc] (const build_package_config& c)
+ {return c.name == pc;}));
+
+ if (i == cs.end ())
+ fail << "invalid --package-config option value: package " << m.name
+ << " has no build configuration '" << pc << '\'';
+
+ // Override the package configuration with it's current build
+ // constraints, if present, and with the common build constraints
+ // otherwise.
+ //
+ const build_package_config& bc (*i);
+
+ if (!bc.builds.empty () || !bc.constraints.empty ())
+ override_builds (bc.builds, bc.constraints);
+ else if (!m.builds.empty () || !m.build_constraints.empty ())
+ override_builds (m.builds, m.build_constraints);
+ else
+ override (pc + "-builds", "default", origin::package_config);
+
+ // Save the overrides for the first package and verify they are equal
+ // to the saved one for the remaining packages.
+ //
+ if (!overrides_cache)
+ {
+ overrides_cache = move (overrides);
+ }
+ else if (!equal (overrides.begin (), overrides.end (),
+ overrides_cache->begin (), overrides_cache->end (),
+ [] (const auto& x, const auto& y)
+ {return x.name == y.name && x.value == y.value;}))
+ {
+ fail << "invalid --package-config option value: overrides for "
+ << "configuration '" << pc << "' differ for packages "
+ << override_manifests[0].name << " and " << m.name;
+ }
+ }
+
+ assert (overrides_cache); // Must be at least one package.
+
+ overrides = move (*overrides_cache);
+ }
+ }
+
// Extract the interactive mode configuration and breakpoint from the
// --interactive|-i option value, reducing the former to the build
// manifest value overrides.
@@ -442,7 +678,7 @@ namespace bdep
const string& s (o.interactive ());
validate_utf8_graphic (s, "--interactive|-i option value");
- size_t p (s.find ('/'));
+ size_t p (s.find (':'));
if (p != string::npos)
{
@@ -452,22 +688,123 @@ namespace bdep
else
icfg = s;
- if (icfg->empty ())
- fail << "invalid --interactive|-i option value '" << s
- << "': configuration name is empty";
+ p = icfg->find ('/');
+
+ string pc;
+ string tc;
+ string tg;
+
+ if (p != string::npos)
+ {
+ if (p == 0)
+ fail << "invalid --interactive|-i option value '" << s
+ << "': package configuration name is empty";
+
+ pc = string (*icfg, 0, p);
+
+ string t (*icfg, p + 1); // tc[/tg]
+
+ p = t.find ('/');
+
+ if (p != string::npos)
+ {
+ if (p == t.size () - 1)
+ fail << "invalid --interactive|-i option value '" << s
+ << "': target name is empty";
+
+ tc = string (t, 0, p);
+ tg = string (t, p + 1);
+ }
+ else
+ tc = move (t);
+ }
+ else
+ tc = *icfg;
- if (path_pattern (*icfg))
+ if (tc.empty ())
fail << "invalid --interactive|-i option value '" << s
- << "': configuration name is a pattern";
+ << "': target configuration name is empty";
- override ("builds", "all");
- override ("build-include", *icfg);
- override ("build-exclude", "**");
+ if (!pc.empty ())
+ pc += '-';
+
+ if (!tg.empty ())
+ tg = '/' + tg;
+
+ override (pc + "builds", "all", origin::interactive);
+ override (pc + "build-include", tc + tg, origin::interactive);
+ override (pc + "build-exclude", "**", origin::interactive);
if (!ibpk)
ibpk = "error";
}
+ // Verify the collected overrides.
+ //
+ if (!overrides.empty ())
+ {
+ // Let's also save the override value index as a name/value columns
+ // (similar to what we do with the origin options), so that we can
+ // attribute the potential error back to the override value and add it
+ // to the diagnostics.
+ //
+ for (uint64_t i (0); i != overrides.size (); ++i)
+ {
+ manifest_name_value& nv (overrides[i]);
+ nv.name_column = nv.value_column = i;
+ }
+
+ auto bad_ovr = [&overrides, &override_manifests]
+ (const manifest_parsing& e, const package_name& n = {})
+ {
+ assert (e.column < overrides.size ());
+
+ const manifest_name_value& nv (overrides[e.column]);
+
+ diag_record dr (fail);
+ dr << "invalid " << to_string (static_cast<origin> (e.line))
+ << ": " << e.description <<
+ info << "override: " << nv.name << ": " << nv.value;
+
+ if (!n.empty () && override_manifests.size () != 1)
+ dr << info << "package: " << n;
+ };
+
+ // If the package manifests are loaded (which happens if there are any
+ // build package configuration-specific overrides), then override them
+ // all. Otherwise, use package_manifest::validate_overrides().
+ //
+ // Specify the name argument for the override validation call to make
+ // sure the origin/value information (saved into the values'
+ // lines/columns) is provided in a potential exception.
+ //
+ if (!override_manifests.empty ())
+ {
+ for (package_manifest& m: override_manifests)
+ {
+ try
+ {
+ m.override (overrides, "options");
+ }
+ catch (const manifest_parsing& e)
+ {
+ bad_ovr (e, m.name);
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ package_manifest::validate_overrides (overrides, "options");
+ }
+ catch (const manifest_parsing& e)
+ {
+ bad_ovr (e);
+ }
+ }
+ }
+
// Get the server and repository URLs.
//
const url& srv (o.server_specified () ? o.server () : default_server);
diff --git a/bdep/project.cxx b/bdep/project.cxx
index 975454f..d671286 100644
--- a/bdep/project.cxx
+++ b/bdep/project.cxx
@@ -537,15 +537,18 @@ namespace bdep
package_info pi (package_b_info (o,
(dir_path (cfg) /= p.string ()),
false /* ext_mods */));
+ verify_package_info (pi, p);
- if (pi.version.empty ())
- fail << "package " << p << " does not use standard version";
+ return move (pi.version);
+ }
- // Verify the name for good measure.
- //
+ void
+ verify_package_info (const package_info& pi, const package_name& p)
+ {
if (pi.project != p)
fail << "name mismatch for package " << p;
- return move (pi.version);
+ if (pi.version.empty ())
+ fail << "package " << p << " does not use standard version";
}
}
diff --git a/bdep/project.hxx b/bdep/project.hxx
index c0fd1d8..e789fc2 100644
--- a/bdep/project.hxx
+++ b/bdep/project.hxx
@@ -300,6 +300,12 @@ namespace bdep
package_info
package_b_info (const common_options&, const dir_path&, bool ext_mods);
+
+ // Verify that the package name matches what we expect it to be and the
+ // package uses a standard version.
+ //
+ void
+ verify_package_info (const package_info&, const package_name&);
}
#endif // BDEP_PROJECT_HXX
diff --git a/tests/ci.testscript b/tests/ci.testscript
index 00c6f64..7cb6245 100644
--- a/tests/ci.testscript
+++ b/tests/ci.testscript
@@ -26,6 +26,14 @@ end
#
+sed -i -e 's/^(version:) .*$/\1 1.0.1/' prj/manifest
++cat <<EOI >+prj/build/root.build
+ config [bool] config.prj.network ?= false
+ EOI
+
++cat <<EOI >+prj/manifest
+ network-build-config: config.prj.network=true
+ EOI
+
g = [cmdline] git -C prj 2>! >&2
+$g config user.name 'Test Script'
@@ -142,7 +150,7 @@ windows = ($cxx.target.class == 'windows')
test.options += --no-progress
- : valid
+ : common-build-constraints
:
: Here we only test that bdep-ci does not fail for valid overrides. It
: seems to be impossible to verify the submitted overrides manifest.
@@ -166,6 +174,35 @@ windows = ($cxx.target.class == 'windows')
EOE
}
+ : package-build-constraints
+ :
+ {
+ $clone_prj;
+
+ $* --builds 'default/&gcc' \
+ --override 'default-build-include: linux*' \
+ --override 'default-build-exclude: *' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
+ EOE
+ }
+
+ : common-package-build-constraints
+ :
+ {
+ $clone_prj;
+
+ cat <<EOI >=overrides.manifest;
+ : 1
+ builds: all
+ EOI
+
+ $* --builds 'network/&gcc' --overrides-file overrides.manifest 2>>~%EOE% != 0
+ error: invalid file referenced by --overrides-file option: 'builds' override specified together with 'network-builds' override
+ info: override: builds: all
+ EOE
+ }
+
: invalid-option
:
{
@@ -220,7 +257,8 @@ windows = ($cxx.target.class == 'windows')
$clone_prj;
$* --override 'builds: all' --override 'builds: default : &gcc' 2>>EOE != 0
- error: invalid overrides: invalid package builds in 'default : &gcc': unexpected underlying class set
+ error: invalid --override option value: invalid package builds: unexpected underlying class set
+ info: override: builds: default : &gcc
EOE
}
@@ -235,10 +273,21 @@ windows = ($cxx.target.class == 'windows')
error: unknown option '--overrides'
EOE
}
+
+ : target-and-package-configs
+ :
+ {
+ $clone_prj;
+
+ $* --override 'builds: gcc' --override 'network-builds: linux' 2>>EOE != 0
+ error: invalid --override option value: 'network-builds' override specified together with 'builds' override
+ info: override: network-builds: linux
+ EOE
+ }
}
}
- : build-config
+ : target-config
:
{
+$clone_root_prj
@@ -251,7 +300,7 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --build-config 'linux**/x86_64**' --build-config 'freebsd**' 2>>~%EOE%
+ $* --target-config 'linux**/x86_64**' --target-config 'freebsd**' 2>>~%EOE%
%CI request is queued.*%
%reference: .+%
EOE
@@ -262,8 +311,133 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --build-config '/x86_64**' 2>>EOE != 0
- error: invalid --build-config option value: empty build configuration name pattern in '/x86_64**'
+ $* --target-config '/x86_64**' 2>>EOE != 0
+ error: invalid --target-config option value: empty build configuration name pattern
+ info: override: build-include: /x86_64**
+ EOE
+ }
+
+ : empty-target
+ :
+ {
+ $clone_prj;
+
+ $* --target-config 'linux**/' 2>>EOE != 0
+ error: invalid --target-config option value: empty build target pattern
+ info: override: build-include: linux**/
+ EOE
+ }
+
+ : overrides
+ :
+ {
+ $clone_prj;
+
+ $* --target-config 'linux_debian_8-gcc_4.9' --builds '&gcc' 2>>EOE != 0
+ error: invalid --builds option value: 'builds' override specified together with --target-config
+ info: override: builds: &gcc
+ EOE
+ }
+
+ : interactive
+ :
+ {
+ $clone_prj;
+
+ $* --target-config 'linux**' --interactive 'linux_debian_8-gcc_4.9' 2>>EOE != 0
+ error: --target-config specified together with --interactive|-i
+ EOE
+ }
+
+ : package-config
+ :
+ {
+ $clone_prj;
+
+ $* --target-config 'linux**' --package-config 'default' 2>>EOE != 0
+ error: --target-config specified together with --package-config
+ EOE
+ }
+ }
+
+ : build-config
+ :
+ {
+ +$clone_root_prj
+ +$init -C @cfg &prj-cfg/***
+
+ test.options += --no-progress
+
+ : multiple
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default/linux**/x86_64**' --build-config 'default/freebsd**' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
+ EOE
+ }
+
+ : invalid-package-config
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default/linux**/x86_64**' --package-config 'cache' 2>>EOE != 0
+ error: invalid --package-config option value: package prj has no build configuration 'cache'
+ EOE
+ }
+
+ : with-package-config-option
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default/linux**/x86_64**' --package-config 'network' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
+ EOE
+ }
+
+ : invalid-package-config-option
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default/linux**/x86_64**' --package-config 'default' 2>>EOE != 0
+ error: package configuration default is specified using both --package-config and --build-config
+ EOE
+ }
+
+ : no-target-config
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default' 2>>EOE != 0
+ error: invalid --build-config option value: no target configuration in 'default'
+ EOE
+ }
+
+ : empty-package-config
+ :
+ {
+ $clone_prj;
+
+ $* --build-config '/linux**/x86_64**' 2>>EOE != 0
+ error: invalid --build-config option value: no package configuration in '/linux**/x86_64**'
+ EOE
+ }
+
+ : empty-target-config
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'default//x86_64**' 2>>EOE != 0
+ error: invalid --build-config option value: empty build configuration name pattern
+ info: override: default-build-include: /x86_64**
EOE
}
@@ -272,8 +446,9 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --build-config 'linux**/' 2>>EOE != 0
- error: invalid --build-config option value: empty build target pattern in 'linux**/'
+ $* --build-config 'default/linux**/' 2>>EOE != 0
+ error: invalid --build-config option value: empty build target pattern
+ info: override: default-build-include: linux**/
EOE
}
@@ -282,8 +457,9 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --build-config 'linux_debian_8-gcc_4.9' --builds '&gcc' 2>>EOE != 0
- error: 'builds' override specified together with --build-config
+ $* --build-config 'default/linux_debian_8-gcc_4.9' --builds '&gcc' 2>>EOE != 0
+ error: invalid --builds option value: 'builds' override specified together with --build-config
+ info: override: builds: &gcc
EOE
}
@@ -292,13 +468,23 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --build-config 'linux**' --interactive 'linux_debian_8-gcc_4.9' 2>>EOE != 0
+ $* --build-config 'default/linux**' --interactive 'linux_debian_8-gcc_4.9' 2>>EOE != 0
error: --build-config specified together with --interactive|-i
EOE
}
+
+ : package-config
+ :
+ {
+ $clone_prj;
+
+ $* --build-config 'linux**' --target-config 'linux_debian_8-gcc_4.9' 2>>EOE != 0
+ error: --target-config specified together with --build-config
+ EOE
+ }
}
- : interactive
+ : package-config
:
{
+$clone_root_prj
@@ -311,40 +497,82 @@ windows = ($cxx.target.class == 'windows')
{
$clone_prj;
- $* --interactive 'linux_debian_8-gcc_4.9/warning' 2>>~%EOE%
+ $* --package-config 'network' 2>>~%EOE%
%CI request is queued.*%
%reference: .+%
EOE
}
- : def-breakpoint
+ : interactive
:
{
$clone_prj;
- $* --interactive 'linux_debian_8-gcc_4.9' 2>>~%EOE%
+ $* --package-config 'default' --interactive 'linux_debian_8-gcc_4.9' 2>>EOE != 0
+ error: --package-config specified together with --interactive|-i
+ EOE
+ }
+ }
+
+ : interactive
+ :
+ {
+ +$clone_root_prj
+ +$init -C @cfg &prj-cfg/***
+
+ test.options += --no-progress
+
+ : target-config
+ :
+ {
+ $clone_prj;
+
+ $* --interactive 'linux_debian_8-gcc_4.9:warning' 2>>~%EOE%
%CI request is queued.*%
%reference: .+%
EOE
}
- : config-empty
+ : package-target-config
:
{
$clone_prj;
- $* --interactive '/warning' 2>>EOE != 0
- error: invalid --interactive|-i option value '/warning': configuration name is empty
+ $* --interactive 'network/linux_debian_8-gcc_4.9:warning' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
EOE
}
- : config-pattern
+ : package-target-config-target
:
{
$clone_prj;
- $* --interactive 'linux_debian_8-gcc_4.*' 2>>EOE != 0
- error: invalid --interactive|-i option value 'linux_debian_8-gcc_4.*': configuration name is a pattern
+ $* --interactive 'network/linux_debian_8-gcc_4.9/aarch64**:warning' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
+ EOE
+ }
+
+ : def-breakpoint
+ :
+ {
+ $clone_prj;
+
+ $* --interactive 'linux_debian_8-gcc_4.9' 2>>~%EOE%
+ %CI request is queued.*%
+ %reference: .+%
+ EOE
+ }
+
+ : config-empty
+ :
+ {
+ $clone_prj;
+
+ $* --interactive '/warning' 2>>EOE != 0
+ error: invalid --interactive|-i option value '/warning': package configuration name is empty
EOE
}
@@ -354,7 +582,8 @@ windows = ($cxx.target.class == 'windows')
$clone_prj;
$* --interactive 'linux_debian_8-gcc_4.9' --builds '&gcc' 2>>EOE != 0
- error: 'builds' override specified together with --interactive|-i
+ error: invalid --builds option value: 'builds' override specified together with --interactive|-i
+ info: override: builds: &gcc
EOE
}
}