aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-04-16 22:47:22 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-04-25 20:07:44 +0300
commit45218bf14ea1e8041b303bea313c939e1ec77a91 (patch)
tree26caa86adcc1df65199408f4edd2979f17901149
parenta792e92355b40b66b53908fb29cf6bb5cd18a083 (diff)
Add package_manifest::override() overriding build* values
-rw-r--r--libbpkg/manifest.cxx272
-rw-r--r--libbpkg/manifest.hxx27
-rw-r--r--tests/overrides/buildfile8
-rw-r--r--tests/overrides/driver.cxx95
-rw-r--r--tests/overrides/testscript122
5 files changed, 453 insertions, 71 deletions
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<string, string> 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<string, string> 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<string> tg (p != string::npos
+ ? optional<string> (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<string, string> 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<email>& 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<string> tg (p != string::npos
- ? optional<string> (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<translate_function> (), iu, cd, fl)
- {
- }
+ {
+ }
package_manifest::
package_manifest (manifest_parser& p,
@@ -2008,6 +2053,93 @@ namespace bpkg
p, move (nv), function<translate_function> (), iu, cd, fl, *this);
}
+ void package_manifest::
+ override (const vector<manifest_name_value>& 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<manifest_name_value>& 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<butl::manifest_name_value>&,
+ const std::string& source_name);
+
+ // Validate the overrides without applying them to any manifest.
+ //
+ static void
+ validate_overrides (const std::vector<butl::manifest_name_value>&,
+ 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> // ios_base::failbit, ios_base::badbit
+#include <string>
+#include <vector>
+#include <cstddef> // size_t
+#include <cstdint> // uint64_t
+#include <cassert>
+#include <iostream>
+
+#include <libbutl/utility.mxx> // trim()
+#include <libbutl/manifest-parser.mxx>
+#include <libbutl/manifest-serializer.mxx>
+
+#include <libbpkg/manifest.hxx>
+
+using namespace std;
+using namespace butl;
+using namespace bpkg;
+
+// Usages: argv[0] [-n] (<name>:<value>)*
+//
+// 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<manifest_name_value> 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' <<EOI >>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' <<EOI >>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.' <<EOI >>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
+ :
+ $* <<EOI >>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' <<EOI 2>"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' <<EOI 2>>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' <<EOI 2>>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
+}