aboutsummaryrefslogtreecommitdiff
path: root/libbpkg
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-11-17 23:39:15 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-11-29 21:06:49 +0300
commitc8775bf46f337e2dca4d161251eb89595aef4051 (patch)
tree38593dff2e970ff3cb9afb9d4df662fea6584f8e /libbpkg
parent93c043d0bc755f6c9cffea489116ae742795a152 (diff)
Add support for builds manifest value
Diffstat (limited to 'libbpkg')
-rw-r--r--libbpkg/manifest.cxx363
-rw-r--r--libbpkg/manifest.hxx122
2 files changed, 482 insertions, 3 deletions
diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx
index e93f0cb..73d2311 100644
--- a/libbpkg/manifest.cxx
+++ b/libbpkg/manifest.cxx
@@ -17,8 +17,8 @@
#include <libbutl/url.mxx>
#include <libbutl/path.mxx>
#include <libbutl/base64.mxx>
-#include <libbutl/utility.mxx> // casecmp(), lcase(), alpha(),
- // digit(), xdigit()
+#include <libbutl/utility.mxx> // casecmp(), lcase(), alnum(),
+ // digit(), xdigit(), next_word()
#include <libbutl/manifest-parser.mxx>
#include <libbutl/manifest-serializer.mxx>
#include <libbutl/standard-version.mxx>
@@ -390,7 +390,7 @@ namespace bpkg
}
default:
{
- if (!digit (c) && !alpha (c))
+ if (!alnum (c))
bad_arg ("alpha-numeric characters expected in a component");
}
}
@@ -889,6 +889,340 @@ namespace bpkg
return o;
}
+ // build_class_term
+ //
+ build_class_term::
+ ~build_class_term ()
+ {
+ if (simple)
+ name.~string ();
+ else
+ expr.~vector<build_class_term> ();
+ }
+
+ build_class_term::
+ build_class_term (build_class_term&& t)
+ : operation (t.operation),
+ inverted (t.inverted),
+ simple (t.simple)
+ {
+ if (simple)
+ new (&name) string (move (t.name));
+ else
+ new (&expr) vector<build_class_term> (move (t.expr));
+ }
+
+ build_class_term::
+ build_class_term (const build_class_term& t)
+ : operation (t.operation),
+ inverted (t.inverted),
+ simple (t.simple)
+ {
+ if (simple)
+ new (&name) string (t.name);
+ else
+ new (&expr) vector<build_class_term> (t.expr);
+ }
+
+ build_class_term& build_class_term::
+ operator= (build_class_term&& t)
+ {
+ if (this != &t)
+ {
+ this->~build_class_term ();
+
+ // Assume noexcept move-construction.
+ //
+ new (this) build_class_term (move (t));
+ }
+ return *this;
+ }
+
+ build_class_term& build_class_term::
+ operator= (const build_class_term& t)
+ {
+ if (this != &t)
+ *this = build_class_term (t); // Reduce to move-assignment.
+ return *this;
+ }
+
+ bool build_class_term::
+ validate_name (const string& s)
+ {
+ if (s.empty ())
+ throw invalid_argument ("empty class name");
+
+ size_t i (0);
+ char c (s[i++]);
+
+ if (!(alnum (c) || c == '_'))
+ throw invalid_argument (
+ "class name '" + s + "' starts with '" + c + "'");
+
+ for (; i != s.size (); ++i)
+ {
+ if (!(alnum (c = s[i]) || c == '+' || c == '-' || c == '_' || c == '.'))
+ throw invalid_argument (
+ "class name '" + s + "' contains '" + c + "'");
+ }
+
+ return s[0] == '_';
+ }
+
+ // build_class_expr
+ //
+ // Parse the string representation of a space-separated, potentially empty
+ // build class expression.
+ //
+ // Calls itself recursively when a nested expression is encountered. In this
+ // case the second parameter points to the position at which the nested
+ // expression starts (right after the opening parenthesis). Updates the
+ // position to refer the nested expression end (right after the closing
+ // parenthesis).
+ //
+ static vector<build_class_term>
+ parse_build_class_expr (const string& s, size_t* p = nullptr)
+ {
+ vector<build_class_term> r;
+
+ bool root (p == nullptr);
+ size_t e (0);
+
+ if (root)
+ p = &e;
+
+ size_t n;
+ for (size_t b (0); (n = next_word (s, b, *p)); )
+ {
+ string t (s, b, n);
+
+ // Check for the nested expression end.
+ //
+ if (t == ")")
+ {
+ if (root)
+ throw invalid_argument ("class term expected instead of ')'");
+
+ break;
+ }
+
+ // Parse the term.
+ //
+ char op (t[0]); // Can be '\0'.
+
+ if (op != '+')
+ {
+ if (op != '-' && op != '&')
+ throw invalid_argument (
+ "class term '" + t + "' must start with '+', '-', or '&'");
+
+ // Only the root expression may start with a term having the '-' or
+ // '&' operation.
+ //
+ if (r.empty () && !root)
+ throw invalid_argument (
+ "class term '" + t + "' must start with '+'");
+ }
+
+ bool inv (t[1] == '!'); // Can be '\0'.
+ string nm (t, inv ? 2 : 1);
+
+ // Append the compound term.
+ //
+ if (nm == "(")
+ r.emplace_back (parse_build_class_expr (s, p), op, inv);
+
+ // Append the simple term.
+ //
+ else
+ {
+ build_class_term::validate_name (nm);
+ r.emplace_back (move (nm), op, inv);
+ }
+ }
+
+ // Verify that the nested expression is terminated with the closing
+ // parenthesis and is not empty.
+ //
+ if (!root)
+ {
+ // The zero-length of the last term means that we escaped the loop due
+ // to the eos.
+ //
+ if (n == 0)
+ throw invalid_argument (
+ "nested class expression must be closed with ')'");
+
+ if (r.empty ())
+ throw invalid_argument ("empty nested class expression");
+ }
+
+ return r;
+ }
+
+ build_class_expr::
+ build_class_expr (const std::string& s, std::string c)
+ : comment (move (c))
+ {
+ using std::string;
+
+ size_t eb (0); // Start of expression.
+
+ // Parse the underlying classes until the expression term, ':', or eos is
+ // encountered.
+ //
+ for (size_t b (0); next_word (s, b, eb); )
+ {
+ string nm (s, b, eb - b);
+
+ if (nm[0] == '+' || nm[0] == '-' || nm[0] == '&')
+ {
+ // Expression must always be separated with ':' from the underlying
+ // classes.
+ //
+ if (!underlying_classes.empty ())
+ throw invalid_argument ("class expression separator ':' expected");
+
+ eb = b; // Reposition to the term beginning.
+ break;
+ }
+ else if (nm == ":")
+ {
+ // The ':' separator must follow the underlying class set.
+ //
+ if (underlying_classes.empty ())
+ throw invalid_argument ("underlying class set expected");
+
+ break;
+ }
+
+ build_class_term::validate_name (nm);
+ underlying_classes.emplace_back (move (nm));
+ }
+
+ expr = parse_build_class_expr (eb == 0 ? s : string (s, eb));
+
+ // At least one of the expression or underlying class set should be
+ // present in the representation.
+ //
+ if (expr.empty () && underlying_classes.empty ())
+ throw invalid_argument ("empty class expression");
+ }
+
+ build_class_expr::
+ build_class_expr (const strings& cs, char op, std::string c)
+ : comment (move (c))
+ {
+ vector<build_class_term> r;
+
+ for (const std::string& c: cs)
+ r.emplace_back (c, op == '-' ? '-' : '+', false /* inverse */);
+
+ if (op == '&' && !r.empty ())
+ {
+ build_class_term t (move (r), '&', false /* inverse */);
+ r = vector<build_class_term> ({move (t)});
+ }
+
+ expr = move (r);
+ }
+
+ // Return string representation of the build class expression.
+ //
+ static string
+ to_string (const vector<build_class_term>& expr)
+ {
+ string r;
+ for (const build_class_term& t: expr)
+ {
+ if (!r.empty ())
+ r += ' ';
+
+ r += t.operation;
+
+ if (t.inverted)
+ r += '!';
+
+ r += t.simple ? t.name : "( " + to_string (t.expr) + " )";
+ }
+
+ return r;
+ }
+
+ string build_class_expr::
+ string () const
+ {
+ using std::string;
+
+ string r;
+ for (const string& c: underlying_classes)
+ {
+ if (!r.empty ())
+ r += ' ';
+
+ r += c;
+ }
+
+ if (!expr.empty ())
+ {
+ if (!r.empty ())
+ r += " : " + to_string (expr);
+ else
+ r = to_string (expr);
+ }
+
+ return r;
+ }
+
+ // Match build configuration classes against an expression, updating the
+ // result.
+ //
+ static void
+ match_classes (const strings& cs,
+ const vector<build_class_term>& expr,
+ bool& r)
+ {
+ for (const build_class_term& t: expr)
+ {
+ // Note that the '+' operation may only invert false and the '-' and '&'
+ // operations may only invert true (see below). So, let's optimize it a
+ // bit.
+ //
+ if ((t.operation == '+') == r)
+ continue;
+
+ bool m;
+
+ // We don't expect the class list to be long, so the linear search should
+ // be fine.
+ //
+ if (t.simple)
+ m = find (cs.begin (), cs.end (), t.name) != cs.end ();
+ else
+ {
+ m = false;
+ match_classes (cs, t.expr, m);
+ }
+
+ if (t.inverted)
+ m = !m;
+
+ switch (t.operation)
+ {
+ case '+': if (m) r = true; break;
+ case '-': if (m) r = false; break;
+ case '&': r &= m; break;
+ default: assert (false);
+ }
+ }
+ }
+
+ void build_class_expr::
+ match (const strings& cs, bool& r) const
+ {
+ match_classes (cs, expr, r);
+ }
+
// pkg_package_manifest
//
static void
@@ -1229,6 +1563,26 @@ namespace bpkg
m.requirements.push_back (move (ra));
}
+ 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 ());
+ }
+ }
else if (n == "build-include")
{
add_build_constraint (false, v);
@@ -1541,6 +1895,9 @@ namespace bpkg
: (r.buildtime ? "* " : "")) +
serializer::merge_comment (concatenate (r, " | "), r.comment));
+ for (const build_class_expr& e: m.builds)
+ s.next ("builds", serializer::merge_comment (e.string (), e.comment));
+
for (const auto& c: m.build_constraints)
s.next (c.exclusion ? "build-exclude" : "build-include",
serializer::merge_comment (!c.target
diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx
index 20c9caf..55e9d15 100644
--- a/libbpkg/manifest.hxx
+++ b/libbpkg/manifest.hxx
@@ -430,6 +430,127 @@ namespace bpkg
return x |= y;
}
+ // Build configuration class term.
+ //
+ class LIBBPKG_EXPORT build_class_term
+ {
+ public:
+ char operation; // '+', '-' or '&'
+ bool inverted; // Operation is followed by '!'.
+ bool simple; // Name if true, expr otherwise.
+ union
+ {
+ std::string name; // Class name.
+ std::vector<build_class_term> expr; // Parenthesized expression.
+ };
+
+ // Create the simple term object (class name).
+ //
+ build_class_term (std::string n, char o, bool i)
+ : operation (o), inverted (i), simple (true), name (std::move (n)) {}
+
+ // Create the compound term object (parenthesized expression).
+ //
+ build_class_term (std::vector<build_class_term> e, char o, bool i)
+ : operation (o), inverted (i), simple (false), expr (std::move (e)) {}
+
+ // Required by VC for some reason.
+ //
+ build_class_term ()
+ : operation ('\0'), inverted (false), simple (true), name () {}
+
+ build_class_term (build_class_term&&);
+ build_class_term (const build_class_term&);
+ build_class_term& operator= (build_class_term&&);
+ build_class_term& operator= (const build_class_term&);
+
+ ~build_class_term ();
+
+ // Check that the specified string is a valid class name, that is
+ // non-empty, containing only alpha-numeric characters, '_', '+', '-', '.'
+ // (except as the first character for the last three). Return true if the
+ // name is reserved (starts with '_'). Throw std::invalid_argument if
+ // invalid.
+ //
+ static bool
+ validate_name (const std::string&);
+ };
+
+ // Build configuration class expression. Includes comment and optional
+ // underlying set.
+ //
+ class LIBBPKG_EXPORT build_class_expr
+ {
+ public:
+ std::string comment;
+ strings underlying_classes;
+ std::vector<build_class_term> expr;
+
+ public:
+ build_class_expr () = default;
+
+ // Parse the string representation of a space-separated build class
+ // expression, potentially prepended with a space-separated underlying
+ // build class set, in which case the expression can be empty. If both,
+ // underlying class set and expression are present, then they should be
+ // separated with the semicolon. Throw std::invalid_argument if the
+ // representation is invalid. Some expression examples:
+ //
+ // +gcc
+ // -msvc -clang
+ // default leagacy
+ // default leagacy :
+ // default leagacy : -msvc
+ // default leagacy : &gcc
+ //
+ build_class_expr (const std::string&, std::string comment);
+
+ // Create the expression object from a class list (c1, c2, ...) using the
+ // specified operation (+/-/&) according to the following rules:
+ //
+ // + -> +c1 +c2 ...
+ // - -> -c1 -c2 ...
+ // & -> &( +c1 +c2 ... )
+ //
+ // An empty class list results in an empty expression.
+ //
+ // Note: it is assumed that the class names are valid.
+ //
+ build_class_expr (const strings& classes,
+ char operation,
+ std::string comment);
+
+ // Return the string representation of the build class expression,
+ // potentially prepended with the underlying class set.
+ //
+ std::string
+ string () const;
+
+ // Match a build configuration that belongs to the specified list of
+ // classes against the expression. Either return or update the result (the
+ // latter allows to sequentially matching against a list of expressions).
+ //
+ // Note: the underlying class set doesn't affect the match in any way (it
+ // should have been used to pre-filter the set of build configurations).
+ //
+ void
+ match (const strings&, bool& result) const;
+
+ bool
+ match (const strings& cs) const
+ {
+ bool r (false);
+ match (cs, r);
+ return r;
+ }
+ };
+
+ inline std::ostream&
+ operator<< (std::ostream& os, const build_class_expr& bce)
+ {
+ return os << bce.string ();
+ }
+
class LIBBPKG_EXPORT package_manifest
{
public:
@@ -458,6 +579,7 @@ namespace bpkg
butl::optional<email_type> build_error_email;
std::vector<dependency_alternatives> dependencies;
std::vector<requirement_alternatives> requirements;
+ std::vector<build_class_expr> builds;
std::vector<build_constraint> build_constraints;
// The following values are only valid in the manifest list (and only for