diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2022-11-28 21:39:25 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2022-12-06 13:47:43 +0300 |
commit | 1b5f0457e8708a57bd081257c8a18a7ae02f6516 (patch) | |
tree | 4ae80a825dd2a2c4010664017296f6c61e898c12 | |
parent | 1bcba2c1b09f440e4017d5aeb411e5efaf08b809 (diff) |
Add support for --target-config and --package-config to bdep-ci command
-rw-r--r-- | bdep/buildfile | 2 | ||||
-rw-r--r-- | bdep/ci-parsers.cxx | 46 | ||||
-rw-r--r-- | bdep/ci-types.cxx | 28 | ||||
-rw-r--r-- | bdep/ci-types.hxx | 26 | ||||
-rw-r--r-- | bdep/ci.cli | 108 | ||||
-rw-r--r-- | bdep/ci.cxx | 445 | ||||
-rw-r--r-- | bdep/project.cxx | 13 | ||||
-rw-r--r-- | bdep/project.hxx | 6 | ||||
-rw-r--r-- | tests/ci.testscript | 273 |
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 } } |