From 523168b187b55085ff47064585838d321eb724a5 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 28 Sep 2023 22:15:02 +0300 Subject: Add support for *-build-*email package manifest values and their overrides --- libbpkg/manifest.cxx | 261 +++++++++++++++++++++++++++++++++++++++++---- libbpkg/manifest.hxx | 58 ++++++++-- tests/manifest/testscript | 79 +++++++++++++- tests/overrides/testscript | 80 +++++++++++--- 4 files changed, 434 insertions(+), 44 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index aeb9b0e..559e1c3 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -3557,10 +3557,21 @@ namespace bpkg }; // Return the package build configuration with the specified name, if - // already exists, or the newly created configuration otherwise. - // - auto build_conf = [&m] (string&& nm) -> build_package_config& - { + // already exists. If no configuration matches, then create one, if + // requested, and throw manifest_parsing otherwise. If the new + // configuration creation is not allowed, then the description for a + // potential manifest_parsing exception needs to also be specified. + // + auto build_conf = [&m, &bad_name] (string&& nm, + bool create = true, + const string& desc = "") + -> build_package_config& + { + // The error description must only be specified if the creation of the + // package build configuration is not allowed. + // + assert (desc.empty () == create); + small_vector& cs (m.build_configs); auto i (find_if (cs.begin (), cs.end (), @@ -3570,6 +3581,9 @@ namespace bpkg if (i != cs.end ()) return *i; + if (!create) + bad_name (desc + ": no build package configuration '" + nm + '\''); + // Add the new build configuration (arguments, builds, etc will come // later). // @@ -3601,6 +3615,16 @@ namespace bpkg vector changes; optional changes_type; + // It doesn't make sense for only emails to be specified for a package + // build configuration. Thus, we will cache the build configuration email + // manifest values to parse them later, after all other build + // configuration values are parsed, and to make sure that the build + // configurations they refer to are also specified. + // + vector build_config_emails; + vector build_config_warning_emails; + vector build_config_error_emails; + m.build_configs.emplace_back ("default"); for (nv = next (); !nv.empty (); nv = next ()) @@ -4033,6 +4057,24 @@ namespace bpkg bc.constraints.push_back ( parse_build_constraint (nv, true /* exclusion */, name)); } + else if (n.size () > 12 && + n.compare (n.size () - 12, 12, "-build-email") == 0) + { + n.resize (n.size () - 12); + build_config_emails.push_back (move (nv)); + } + else if (n.size () > 20 && + n.compare (n.size () - 20, 20, "-build-warning-email") == 0) + { + n.resize (n.size () - 20); + build_config_warning_emails.push_back (move (nv)); + } + else if (n.size () > 18 && + n.compare (n.size () - 18, 18, "-build-error-email") == 0) + { + n.resize (n.size () - 18); + build_config_error_emails.push_back (move (nv)); + } // @@ TMP time to drop *-0.14.0? // else if (n == "tests" || n == "tests-0.14.0" || @@ -4387,6 +4429,59 @@ namespace bpkg } } + // Parse the build configuration emails. + // + // Note: the argument can only be one of the build_config_*emails + // variables (see above) to distinguish between the email kinds. + // + auto parse_build_config_emails = [&name, + &nv, + &build_config_emails, + &build_config_warning_emails, + &build_config_error_emails, + &build_conf, + &parse_email] + (vector&& emails) + { + enum email_kind {build, warning, error}; + + email_kind ek ( + &emails == &build_config_emails ? email_kind::build : + &emails == &build_config_warning_emails ? email_kind::warning : + email_kind::error); + + // The argument can only be one of the build_config_*emails variables. + // + assert (ek != email_kind::error || &emails == &build_config_error_emails); + + for (name_value& e: emails) + { + // Restore as bad_name() and bad_value() use its line/column. + // + nv = move (e); + + build_package_config& bc ( + build_conf (move (nv.name), + false /* create */, + "stray build notification email")); + + parse_email ( + nv, + (ek == email_kind::build ? bc.email : + ek == email_kind::warning ? bc.warning_email : + bc.error_email), + (ek == email_kind::build ? "build configuration" : + ek == email_kind::warning ? "build configuration warning" : + "build configuration error"), + name, + ek == email_kind::build /* empty */); + } + }; + + parse_build_config_emails (move (build_config_emails)); + parse_build_config_emails (move (build_config_warning_emails)); + parse_build_config_emails (move (build_config_error_emails)); + // Now, when the version manifest value is parsed, we can parse the // dependencies and complete their constraints, if requested. // @@ -4712,14 +4807,22 @@ namespace bpkg // The first {build-*email} override value. // - const manifest_name_value* be (nullptr); + const manifest_name_value* cbe (nullptr); + + // The first {*-build-*email} override value. + // + const manifest_name_value* pbe (nullptr); - // List of indexes of the overridden build configurations together with - // flags which indicate if the *-builds override value was encountered for - // this configuration. + // List of indexes of the build configurations with the overridden build + // constraints together with flags which indicate if the *-builds override + // value was encountered for this configuration. // vector> obcs; + // List of indexes of the build configurations with the overridden emails. + // + vector obes; + // Apply overrides. // for (const manifest_name_value& nv: nvs) @@ -4768,7 +4871,8 @@ namespace bpkg // otherwise. // // The n argument specifies the length of the configuration name in - // *-build-config, *-builds, and *-build-{include,exclude} values. + // *-build-config, *-builds, *-build-{include,exclude}, and + // *-build-*email values. // auto build_conf = [&nv, &bad_name, &m] (size_t n, bool create) -> build_package_config& @@ -4869,19 +4973,77 @@ namespace bpkg return r; }; - // Reset the {build-*email} value group on the first call. + // Reset the {build-*email} value group on the first call but throw if + // any of the {*-build-*email} override values are already encountered. // - auto reset_build_emails = [&be, &nv, &m] () + auto reset_build_emails = [&cbe, &pbe, &nv, &bad_name, &m] () { - if (be == nullptr) + if (cbe == nullptr) { + if (pbe != nullptr) + bad_name ('\'' + nv.name + "' override specified together with '" + + pbe->name + "' override"); + m.build_email = nullopt; m.build_warning_email = nullopt; m.build_error_email = nullopt; - be = &nv; + cbe = &nv; } }; + // Return the reference to the package build configuration which matches + // the build config-specific emails group value override, if exists. If + // no configuration matches, then throw manifest_parsing, except for the + // validate-only mode in which case just add an empty configuration with + // this name and return the reference to it. + // + auto build_conf_email = + [&pbe, &cbe, &nv, &obes, &bad_name, &build_conf, &m, validate_only] + (size_t n) -> build_package_config& + { + const string& nm (nv.name); + + // If this is the first build config override value, then save its + // address. But first verify that no common build emails group value + // overrides are applied yet and throw if that's not the case. + // + if (pbe == nullptr) + { + if (cbe != nullptr) + bad_name ('\'' + nm + "' override specified together with '" + + cbe->name + "' override"); + + pbe = &nv; + } + + small_vector& cs (m.build_configs); + + // Find the build package configuration. If there is no such a + // configuration then throw, except for the validate-only mode in + // which case just add an empty configuration with this name. + // + // Note that we are using indexes rather then configuration addresses + // due to potential reallocations. + // + build_package_config& r (build_conf (n, validate_only)); + size_t ci (&r - cs.data ()); + + // If this is the first encountered {*-build-*email} override for this + // build config, then clear this config' email members and add an + // entry to the overridden configs list. + // + if (find (obes.begin (), obes.end (), ci) == obes.end ()) + { + r.email = nullopt; + r.warning_email = nullopt; + r.error_email = nullopt; + + obes.push_back (ci); + } + + return r; + }; + const string& n (nv.name); if (n == "builds") @@ -4954,6 +5116,29 @@ namespace bpkg reset_build_emails (); m.build_error_email = parse_email (nv, "build error", name); } + else if (n.size () > 12 && + n.compare (n.size () - 12, 12, "-build-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 12)); + + bc.email = parse_email ( + nv, "build configuration", name, true /* empty */); + } + else if (n.size () > 20 && + n.compare (n.size () - 20, 20, "-build-warning-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 20)); + + bc.warning_email = parse_email ( + nv, "build configuration warning", name); + } + else if (n.size () > 18 && + n.compare (n.size () - 18, 18, "-build-error-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 18)); + + bc.error_email = parse_email (nv, "build configuration error", name); + } else bad_name ("cannot override '" + n + "' value"); } @@ -4964,8 +5149,8 @@ namespace bpkg assert (cbc == nullptr || pbc == nullptr); // Now, if not in the validate-only mode, as all the potential build - // constraint overrides are applied, perform the final adjustments to the - // build config constraints. + // constraint/email overrides are applied, perform the final adjustments + // to the build config constraints/emails. // if (!validate_only) { @@ -4993,6 +5178,30 @@ namespace bpkg } } } + + if (cbe != nullptr) // Common build emails are overridden? + { + for (build_package_config& c: m.build_configs) + { + c.email = nullopt; + c.warning_email = nullopt; + c.error_email = nullopt; + } + } + else if (pbe != nullptr) // Build config emails are overridden? + { + for (size_t i (0); i != m.build_configs.size (); ++i) + { + if (find (obes.begin (), obes.end (), i) == obes.end ()) + { + build_package_config& c (m.build_configs[i]); + + c.email = email (); + c.warning_email = nullopt; + c.error_email = nullopt; + } + } + } } } @@ -5303,10 +5512,6 @@ namespace bpkg for (const build_package_config& bc: m.build_configs) { - if (!bc.arguments.empty () || !bc.comment.empty ()) - s.next (bc.name + "-build-config", - serializer::merge_comment (bc.arguments, bc.comment)); - if (!bc.builds.empty ()) { string n (bc.name + "-builds"); @@ -5326,6 +5531,24 @@ namespace bpkg : c.config + '/' + *c.target, c.comment)); } + + if (!bc.arguments.empty () || !bc.comment.empty ()) + s.next (bc.name + "-build-config", + serializer::merge_comment (bc.arguments, bc.comment)); + + if (bc.email) + s.next (bc.name + "-build-email", + serializer::merge_comment (*bc.email, bc.email->comment)); + + if (bc.warning_email) + s.next (bc.name + "-build-warning-email", + serializer::merge_comment (*bc.warning_email, + bc.warning_email->comment)); + + if (bc.error_email) + s.next (bc.name + "-build-error-email", + serializer::merge_comment (*bc.error_email, + bc.error_email->comment)); } bool an (m.alt_naming && *m.alt_naming); diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index dce0870..8439cdf 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -980,12 +980,15 @@ namespace bpkg return os << bce.string (); } - // Package build configuration. Includes comment and optional target build - // configuration class expressions/constraints overrides. + // Package build configuration. Includes comment and optional overrides for + // target build configuration class expressions/constraints and build + // notification emails. // class build_package_config { public: + using email_type = bpkg::email; + std::string name; // Whitespace separated list of potentially double/single-quoted package @@ -999,6 +1002,10 @@ namespace bpkg butl::small_vector builds; std::vector constraints; + butl::optional email; + butl::optional warning_email; + butl::optional error_email; + build_package_config () = default; // Built incrementally. @@ -1024,6 +1031,31 @@ namespace bpkg { return !builds.empty () || !constraints.empty () ? constraints : common; } + + // Return the configuration's build notification emails if they override + // the specified common build notification emails and return the latter + // otherwise (see package_manifest::override() for the override semantics + // details). + // + const butl::optional& + effective_email (const butl::optional& common) const noexcept + { + return email || warning_email || error_email ? email : common; + } + + const butl::optional& + effective_warning_email (const butl::optional& common) const + noexcept + { + return email || warning_email || error_email ? warning_email : common; + } + + const butl::optional& + effective_error_email (const butl::optional& common) const + noexcept + { + return email || warning_email || error_email ? error_email : common; + } }; enum class test_dependency_type @@ -1330,14 +1362,15 @@ namespace bpkg // {builds, build-{include,exclude}} // {*-builds, *-build-{include,exclude}} // {*-build-config} + // {*-build-*email} // // Throw manifest_parsing if the configuration specified by the build - // package configuration-specific build constraints group value overrides - // doesn't exists. In contrast, for the build config override add a new - // configuration if it doesn't exist and update the arguments of the - // existing configuration otherwise. In the former case, all the potential - // build constraints overrides for such a newly added configuration must - // follow the respective *-build-config override. + // package configuration-specific build constraints or emails group value + // overrides doesn't exists. In contrast, for the build config override + // add a new configuration if it doesn't exist and update the arguments of + // the existing configuration otherwise. In the former case, all the + // potential build constraints and emails overrides for such a newly added + // configuration must follow the respective *-build-config override. // // Note that the build constraints group values (both common and build // config-specific) are overridden hierarchically so that the @@ -1351,6 +1384,15 @@ namespace bpkg // constraints are overridden, then for the remaining configs the build // constraints are reset to `builds: none`. // + // Similar to the build constraints groups, the common and build + // config-specific build emails group value overrides are mutually + // exclusive. If the common build emails are overridden, then all the + // build config-specific emails are reset to nullopt. Otherwise, if some + // build config-specific emails are overridden, then for the remaining + // configs the email is reset to the empty value and the warning and error + // emails are reset to nullopt (which effectively disables email + // notifications for such configurations). + // // 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's diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 7d808e3..3179005 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -1060,8 +1060,8 @@ version: 2.0.0 summary: Modern C++ parser license: LGPLv2 - bar-build-config: config.foo.bar = true; Bar. bar-builds: all + bar-build-config: config.foo.bar = true; Bar. baz-build-config: config.foo.baz = true; Baz. EOF } @@ -1111,6 +1111,81 @@ bar-builds: all EOI } + + : email + : + { + : override + : + { + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-email: package@example.com + build-email: build@example.com + build-warning-email: build-warning@example.com + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-email: bar-build@example.com + bar-build-warning-email: bar-build-warning@example.com + bar-build-error-email: bar-build-error@example.com + EOF + } + + : disabled + : + { + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-email: package@example.com + build-email: build@example.com + build-warning-email: build-warning@example.com + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-email: + EOF + } + + : unrecognized + : + { + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-error-email: build-error@example.com + bar-build-email: bar-build@example.com + EOI + stdin:7:1: error: stray build notification email: no build package configuration 'bar' + EOE + } + + : empty + : + { + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-warning-email: ; Empty + EOI + stdin:8:26: error: empty build configuration warning email + EOE + } + } } : distribution @@ -4447,10 +4522,10 @@ build-include: linux* build-include: freebsd* build-exclude: *; Only supports Linux and FreeBSD. - network-build-config: config.libfoo.network=true; Enable networking API. network-builds: default network-build-include: linux* network-build-exclude: *; Only supports Linux. + network-build-config: config.libfoo.network=true; Enable networking API. bootstrap-build:\ project = libfoo diff --git a/tests/overrides/testscript b/tests/overrides/testscript index 07c1451..ba66b7f 100644 --- a/tests/overrides/testscript +++ b/tests/overrides/testscript @@ -15,6 +15,10 @@ build-email: foo@example.com build-error-email: error@example.com build-warning-email: warning@example.com + network-build-config: config.libfoo.network=true + network-build-email: network-foo@example.com + network-build-error-email: network-error@example.com + network-build-warning-email: network-warning@example.com EOI : 1 name: libfoo @@ -22,6 +26,7 @@ summary: Modern C++ parser license: LGPLv2 build-email: bar@example.com + network-build-config: config.libfoo.network=true EOO : builds @@ -99,6 +104,8 @@ : build-configs : $* 'network-builds: all' 'network-build-include: windows*' 'network-build-exclude: *' \ + 'network-build-warning-email: network-warning@example.com' 'sys-build-email:' \ + 'cache-build-error-email: cache-error@example.com' \ 'cache-build-include: freebsd*' 'cache-build-exclude: *' 'cache-builds: legacy' \ 'cache-build-config: config.libfoo.cache=true config.libfoo.buffer=1028' \ 'deprecated-api-build-config: config.libfoo.deprecated_api=true' 'deprecated-api-builds: windows' \ @@ -110,63 +117,80 @@ 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 builds: all build-include: linux* build-include: macos* build-include: freebsd* build-exclude: * - network-build-config: config.libfoo.network=true network-builds: default network-build-include: linux* network-build-exclude: * - cache-build-config: config.libfoo.cache=true + network-build-config: config.libfoo.network=true + network-build-error-email: network-error@example.com cache-builds: default cache-build-include: macos* cache-build-exclude: * - sys-build-config: ?sys:libcrypto + cache-build-config: config.libfoo.cache=true + cache-build-email: cache@example.com sys-builds: default sys-build-include: freebsd* sys-build-exclude: * - older-build-config: ?libbar/1.0.0 + sys-build-config: ?sys:libcrypto + sys-build-email: sys@example.com older-builds: default older-build-include: windows* older-build-exclude: * - fancy-build-config: config.libfoo.fancy=true + older-build-config: ?libbar/1.0.0 fancy-builds: default fancy-build-include: windows* fancy-build-exclude: * + fancy-build-config: config.libfoo.fancy=true EOI : 1 name: libfoo version: 2.0.0 summary: Modern C++ parser license: LGPLv2 + build-email: foo@example.com + build-warning-email: warning@example.com + build-error-email: error@example.com builds: all build-include: linux* build-include: macos* build-include: freebsd* build-exclude: * default-builds: none - network-build-config: config.libfoo.network=true + default-build-email: network-builds: all network-build-include: windows* network-build-exclude: * - cache-build-config: config.libfoo.cache=true config.libfoo.buffer=1028 + network-build-config: config.libfoo.network=true + network-build-warning-email: network-warning@example.com cache-builds: legacy cache-build-include: freebsd* cache-build-exclude: * - sys-build-config: ?sys:libcrypto + cache-build-config: config.libfoo.cache=true config.libfoo.buffer=1028 + cache-build-error-email: cache-error@example.com sys-builds: default sys-build-include: linux* sys-build-exclude: * - older-build-config: ?libbar/1.0.0 + sys-build-config: ?sys:libcrypto + sys-build-email: older-builds: none - fancy-build-config: config.libfoo.fancy=true + older-build-config: ?libbar/1.0.0 + older-build-email: fancy-builds: gcc - deprecated-api-build-config: config.libfoo.deprecated_api=true + fancy-build-config: config.libfoo.fancy=true + fancy-build-email: deprecated-api-builds: windows - experimental-api-build-config: config.libfoo.experimental_api=true + deprecated-api-build-config: config.libfoo.deprecated_api=true + deprecated-api-build-email: experimental-api-builds: none + experimental-api-build-config: config.libfoo.experimental_api=true + experimental-api-build-email: EOO : build-config-default @@ -177,8 +201,8 @@ version: 2.0.0 summary: Modern C++ parser license: LGPLv2 - network-build-config: config.libfoo.network=true network-builds: all + network-build-config: config.libfoo.network=true EOI : 1 name: libfoo @@ -188,8 +212,8 @@ default-builds: all default-build-include: windows* default-build-exclude: * - network-build-config: config.libfoo.network=true network-builds: none + network-build-config: config.libfoo.network=true EOO : add-build-config @@ -208,8 +232,8 @@ version: 2.0.0 summary: Modern C++ parser license: LGPLv2 - network-build-config: config.libfoo.network=true network-builds: all + network-build-config: config.libfoo.network=true experimental-api-build-config: config.libfoo.experimental_api=true EOO @@ -345,4 +369,30 @@ EOI cannot override 'deprecated-api-builds' value: no build package configuration 'deprecated-api' EOE + + : config-email-after-email + : + $* 'build-email: foo@example.com' 'network-build-warning-email: warning@example.com' <>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'network-build-warning-email' override specified together with 'build-email' override + EOE + + : email-after-config-email + : + $* 'network-build-warning-email: warning@example.com' 'build-email: foo@example.com' <>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'build-email' override specified together with 'network-build-warning-email' override + EOE } -- cgit v1.1