From 45218bf14ea1e8041b303bea313c939e1ec77a91 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 16 Apr 2019 22:47:22 +0300 Subject: Add package_manifest::override() overriding build* values --- libbpkg/manifest.cxx | 272 +++++++++++++++++++++++++++++++++------------ libbpkg/manifest.hxx | 27 ++++- tests/overrides/buildfile | 8 ++ tests/overrides/driver.cxx | 95 ++++++++++++++++ tests/overrides/testscript | 122 ++++++++++++++++++++ 5 files changed, 453 insertions(+), 71 deletions(-) create mode 100644 tests/overrides/buildfile create mode 100644 tests/overrides/driver.cxx create mode 100644 tests/overrides/testscript diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index 79e2977..55556f0 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -1393,6 +1393,100 @@ namespace bpkg // pkg_package_manifest // + static build_class_expr + parse_build_class_expr (const name_value& nv, + bool first, + const string& source_name) + { + pair vc (parser::split_comment (nv.value)); + string& v (vc.first); + string& c (vc.second); + + auto bad_value = [&v, &nv, &source_name] (const string& d, + const invalid_argument& e) + { + throw !source_name.empty () + ? parsing (source_name, + nv.value_line, nv.value_column, + d + ": " + e.what ()) + : parsing (d + " in '" + v + "': " + e.what ()); + }; + + build_class_expr r; + + try + { + r = build_class_expr (v, move (c)); + + // Underlying build configuration class set may appear only in the + // first builds value. + // + if (!r.underlying_classes.empty () && !first) + throw invalid_argument ("unexpected underlying class set"); + } + catch (const invalid_argument& e) + { + bad_value ("invalid package builds", e); + } + + return r; + } + + static build_constraint + parse_build_constraint (const name_value& nv, + bool exclusion, + const string& source_name) + { + pair vc (parser::split_comment (nv.value)); + string& v (vc.first); + string& c (vc.second); + + auto bad_value = [&v, &nv, &source_name] (const string& d) + { + throw !source_name.empty () + ? parsing (source_name, nv.value_line, nv.value_column, d) + : parsing (d + " in '" + v + "'"); + }; + + size_t p (v.find ('/')); + string nm (p != string::npos ? v.substr (0, p) : move (v)); + + optional tg (p != string::npos + ? optional (string (v, p + 1)) + : nullopt); + + if (nm.empty ()) + bad_value ("empty build configuration name pattern"); + + if (tg && tg->empty ()) + bad_value ("empty build target pattern"); + + return build_constraint (exclusion, move (nm), move (tg), move (c)); + } + + static email + parse_email (const name_value& nv, + const char* what, + const string& source_name, + bool empty = false) + { + auto bad_value = [&nv, &source_name] (const string& d) + { + throw !source_name.empty () + ? parsing (source_name, nv.value_line, nv.value_column, d) + : parsing (d); + }; + + pair vc (parser::split_comment (nv.value)); + string& v (vc.first); + string& c (vc.second); + + if (v.empty () && !empty) + bad_value (string ("empty ") + what + " email"); + + return email (move (v), move (c)); + } + static void parse_package_manifest ( parser& p, @@ -1417,25 +1511,16 @@ namespace bpkg if (nv.value != "1") bad_value ("unsupported format version"); - auto add_build_constraint = [&bad_value, &m] (bool e, const string& vc) + auto parse_email = [&bad_name] (const name_value& nv, + optional& r, + const char* what, + const string& source_name, + bool empty = false) { - auto vcp (parser::split_comment (vc)); - string v (move (vcp.first)); - string c (move (vcp.second)); - - size_t p (v.find ('/')); - string nm (p != string::npos ? v.substr (0, p) : move (v)); - optional tg (p != string::npos - ? optional (string (v, p + 1)) - : nullopt); - - if (nm.empty ()) - bad_value ("empty build configuration name pattern"); + if (r) + bad_name (what + string (" email redefinition")); - if (tg && tg->empty ()) - bad_value ("empty build target pattern"); - - m.build_constraints.emplace_back (e, move (nm), move (tg), move (c)); + r = bpkg::parse_email (nv, what, source_name, empty); }; auto parse_url = [&bad_value] (const string& v, const char* what) -> url @@ -1448,18 +1533,6 @@ namespace bpkg return url (move (p.first), move (p.second)); }; - auto parse_email = [&bad_value] (const string& v, - const char* what, - bool empty = false) -> email - { - auto p (parser::split_comment (v)); - - if (v.empty () && !empty) - bad_value (string ("empty ") + what + " email"); - - return email (move (p.first), move (p.second)); - }; - auto flag = [fl] (package_manifest_flags f) { return (fl & f) != package_manifest_flags::none; @@ -1641,10 +1714,7 @@ namespace bpkg } else if (n == "email") { - if (m.email) - bad_name ("project email redefinition"); - - m.email = parse_email (v, "project"); + parse_email (nv, m.email, "project", p.name ()); } else if (n == "doc-url") { @@ -1669,31 +1739,19 @@ namespace bpkg } else if (n == "package-email") { - if (m.package_email) - bad_name ("package email redefinition"); - - m.package_email = parse_email (v, "package"); + parse_email (nv, m.package_email, "package", p.name ()); } else if (n == "build-email") { - if (m.build_email) - bad_name ("build email redefinition"); - - m.build_email = parse_email (v, "build", true /* empty */); + parse_email (nv, m.build_email, "build", p.name (), true /* empty */); } else if (n == "build-warning-email") { - if (m.build_warning_email) - bad_name ("build warning email redefinition"); - - m.build_warning_email = parse_email (v, "build warning"); + parse_email (nv, m.build_warning_email, "build warning", p.name ()); } else if (n == "build-error-email") { - if (m.build_error_email) - bad_name ("build error email redefinition"); - - m.build_error_email = parse_email (v, "build error"); + parse_email (nv, m.build_error_email, "build error", p.name ()); } else if (n == "priority") { @@ -1759,31 +1817,18 @@ namespace bpkg } else if (n == "builds") { - try - { - auto vc (parser::split_comment (v)); - build_class_expr expr (vc.first, move (vc.second)); - - // Underlying build configuration class set may appear only in the - // first builds value. - // - if (!expr.underlying_classes.empty () && !m.builds.empty ()) - throw invalid_argument ("unexpected underlying class set"); - - m.builds.emplace_back (move (expr)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid package builds: ") + e.what ()); - } + m.builds.push_back ( + parse_build_class_expr (nv, m.builds.empty (), p.name ())); } else if (n == "build-include") { - add_build_constraint (false, v); + m.build_constraints.push_back ( + parse_build_constraint (nv, false /* exclusion */, p.name ())); } else if (n == "build-exclude") { - add_build_constraint (true, v); + m.build_constraints.push_back ( + parse_build_constraint (nv, true /* exclusion */, p.name ())); } else if (n == "depends") { @@ -1994,8 +2039,8 @@ namespace bpkg bool cd, package_manifest_flags fl) : package_manifest (p, function (), iu, cd, fl) - { - } + { + } package_manifest:: package_manifest (manifest_parser& p, @@ -2008,6 +2053,93 @@ namespace bpkg p, move (nv), function (), iu, cd, fl, *this); } + void package_manifest:: + override (const vector& nvs, const string& name) + { + // Reset the builds value group on the first call. + // + bool rb (true); + auto reset_builds = [&rb, this] () + { + if (rb) + { + builds.clear (); + build_constraints.clear (); + rb = false; + } + }; + + // Reset the build emails value group on the first call. + // + bool rbe (true); + auto reset_build_emails = [&rbe, this] () + { + if (rbe) + { + build_email = nullopt; + build_warning_email = nullopt; + build_error_email = nullopt; + rbe = false; + } + }; + + for (const manifest_name_value& nv: nvs) + { + const string& n (nv.name); + + if (n == "builds") + { + reset_builds (); + builds.push_back (parse_build_class_expr (nv, builds.empty (), name)); + } + else if (n == "build-include") + { + reset_builds (); + + build_constraints.push_back ( + parse_build_constraint (nv, false /* exclusion */, name)); + } + else if (n == "build-exclude") + { + reset_builds (); + + build_constraints.push_back ( + parse_build_constraint (nv, true /* exclusion */, name)); + } + else if (n == "build-email") + { + reset_build_emails (); + build_email = parse_email (nv, "build", name, true /* empty */); + } + else if (n == "build-warning-email") + { + reset_build_emails (); + build_warning_email = parse_email (nv, "build warning", name); + } + else if (n == "build-error-email") + { + reset_build_emails (); + build_error_email = parse_email (nv, "build error", name); + } + else + { + string d ("cannot override '" + n + "' value"); + + throw !name.empty () + ? parsing (name, nv.name_line, nv.name_column, d) + : parsing (d); + } + } + } + + void package_manifest:: + validate_overrides (const vector& nvs, + const string& name) + { + package_manifest p; + p.override (nvs, name); + } + static const string description_file ("description-file"); static const string changes_file ("changes-file"); diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index df9247b..8dca593 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -636,7 +636,7 @@ namespace bpkg effective_project () const noexcept {return project ? *project : name;} public: - package_manifest () = default; // VC export. + package_manifest () = default; // Create individual manifest. // @@ -681,6 +681,31 @@ namespace bpkg bool complete_depends, package_manifest_flags); + // Override manifest values with the specified. Throw manifest_parsing if + // any value is invalid, cannot be overridden, or its name is not + // recognized. + // + // The specified values override the whole groups they belong to, + // resetting all the group values prior to being applied. Currently, only + // the following value groups can be overridden: {build-*email} and + // {builds, build-{include,exclude}}. + // + // If a non-empty source name is specified, then the specified values are + // assumed to also include the line/column information and the possibly + // thrown manifest_parsing exception will contain the invalid value + // location information. Otherwise, the exception description will refer + // to the invalid value name instead. + // + void + override (const std::vector&, + const std::string& source_name); + + // Validate the overrides without applying them to any manifest. + // + static void + validate_overrides (const std::vector&, + const std::string& source_name); + void serialize (butl::manifest_serializer&) const; diff --git a/tests/overrides/buildfile b/tests/overrides/buildfile new file mode 100644 index 0000000..bcc0224 --- /dev/null +++ b/tests/overrides/buildfile @@ -0,0 +1,8 @@ +# file : tests/overrides/buildfile +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +import libs = libbutl%lib{butl} +import libs += libbpkg%lib{bpkg} + +exe{driver}: {hxx cxx}{*} $libs testscript diff --git a/tests/overrides/driver.cxx b/tests/overrides/driver.cxx new file mode 100644 index 0000000..fea4294 --- /dev/null +++ b/tests/overrides/driver.cxx @@ -0,0 +1,95 @@ +// file : tests/overrides/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // ios_base::failbit, ios_base::badbit +#include +#include +#include // size_t +#include // uint64_t +#include +#include + +#include // trim() +#include +#include + +#include + +using namespace std; +using namespace butl; +using namespace bpkg; + +// Usages: argv[0] [-n] (:)* +// +// Parse the package manifest from stdin, apply overrides passed as arguments, +// and serialize the resulting manifest to stdout. +// +// -n pass the stream name to manifest_parser::override() +// +int +main (int argc, char* argv[]) +{ + vector overrides; + + bool name (false); + + uint64_t l (1); + for (int i (1); i != argc; ++i) + { + string a (argv[i]); + + if (a == "-n") + { + name = true; + } + else + { + size_t p (a.find (':')); + + assert (p != string::npos); + + // Fill the values with the location information for the exception + // description testing. + // + manifest_name_value nv {string (a, 0, p), string (a, p + 1), + l /* name_line */, 1 /* name_column */, + l /* value_line */, p + 2 /* value_column */, + 0, 0, 0}; + + ++l; + + trim (nv.name); + trim (nv.value); + + assert (!nv.name.empty ()); + + overrides.push_back (move (nv)); + } + } + + cin.exceptions (ios_base::failbit | ios_base::badbit); + cout.exceptions (ios_base::failbit | ios_base::badbit); + + manifest_parser p (cin, "stdin"); + manifest_serializer s (cout, "stdout"); + + try + { + package_manifest m (p); + m.override (overrides, name ? "args" : string ()); + m.serialize (s); + } + catch (const manifest_parsing& e) + { + cerr << e << endl; + return 1; + } + catch (const manifest_serialization& e) + { + cerr << e << endl; + return 1; + } + + return 0; +} diff --git a/tests/overrides/testscript b/tests/overrides/testscript new file mode 100644 index 0000000..9a7472f --- /dev/null +++ b/tests/overrides/testscript @@ -0,0 +1,122 @@ +# file : tests/overrides/testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +: valid +: +{ + : build-email + : + $* 'build-email: bar@example.com' <>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: foo@example.com + build-error-email: error@example.com + build-warning-email: warning@example.com + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: bar@example.com + EOO + + : builds + : + $* 'builds: gcc' <>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + builds: default + build-include: linux* + build-exclude: *; Only supports Linux. + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + builds: gcc + EOO + + : build-include-exclude + : + $* 'build-include: linux*' 'build-exclude: *; Only supports Linux.' <>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + builds: default + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-include: linux* + build-exclude: *; Only supports Linux. + EOO + + : none + : + $* <>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: foo@example.com + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: foo@example.com + EOO +} + +: invalid +: +{ + : forbidden + : + $* 'name: libbar' <"cannot override 'name' value" != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + + : bad-value + : + $* 'builds: all' 'builds: default : -windows' <>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + invalid package builds in 'default : -windows': unexpected underlying class set + EOE + + : stream-name-specified + : + $* -n 'builds: all' 'builds: default : -windows' <>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + args:2:8: error: invalid package builds: unexpected underlying class set + EOE +} -- cgit v1.1