From 6a68b1fd2161357a5905b875e9d59609a2b829b1 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 8 Dec 2021 22:46:50 +0300 Subject: Add support for package dependency and requirement alternatives representation new syntax --- libbpkg/buildfile-scanner.cxx | 38 + libbpkg/buildfile-scanner.hxx | 96 ++ libbpkg/buildfile-scanner.txx | 272 +++++ libbpkg/manifest.cxx | 1737 +++++++++++++++++++++++---- libbpkg/manifest.hxx | 248 +++- tests/buildfile-scanner/buildfile | 7 + tests/buildfile-scanner/driver.cxx | 106 ++ tests/buildfile-scanner/testscript | 342 ++++++ tests/manifest/driver.cxx | 29 +- tests/manifest/testscript | 2279 +++++++++++++++++++++++++++++++++++- 10 files changed, 4897 insertions(+), 257 deletions(-) create mode 100644 libbpkg/buildfile-scanner.cxx create mode 100644 libbpkg/buildfile-scanner.hxx create mode 100644 libbpkg/buildfile-scanner.txx create mode 100644 tests/buildfile-scanner/buildfile create mode 100644 tests/buildfile-scanner/driver.cxx create mode 100644 tests/buildfile-scanner/testscript diff --git a/libbpkg/buildfile-scanner.cxx b/libbpkg/buildfile-scanner.cxx new file mode 100644 index 0000000..ec73d83 --- /dev/null +++ b/libbpkg/buildfile-scanner.cxx @@ -0,0 +1,38 @@ +// file : libbpkg/buildfile-scanner.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace bpkg +{ + // buildfile_scanning + // + static inline string + format (const string& n, uint64_t l, uint64_t c, const string& d) + { + string r; + if (!n.empty ()) + { + r += n; + r += ':'; + } + + r += to_string (l); + r += ':'; + r += to_string (c); + r += ": error: "; + r += d; + return r; + } + + buildfile_scanning:: + buildfile_scanning (const string& n, uint64_t l, uint64_t c, const string& d) + : runtime_error (format (n, l, c, d)), + name (n), line (l), column (c), description (d) + { + } +} diff --git a/libbpkg/buildfile-scanner.hxx b/libbpkg/buildfile-scanner.hxx new file mode 100644 index 0000000..8b5f0d7 --- /dev/null +++ b/libbpkg/buildfile-scanner.hxx @@ -0,0 +1,96 @@ +// file : libbpkg/buildfile-scanner.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBPKG_BUILDFILE_SCANNER_HXX +#define LIBBPKG_BUILDFILE_SCANNER_HXX + +#include +#include // uint64_t +#include // size_t +#include // runtime_error + +#include + +#include + +namespace bpkg +{ + // Scan buildfile fragments, respecting the single- and double-quoted + // character sequences, backslash-escaping, comments, evaluation + // contexts, and nested blocks. + // + class LIBBPKG_EXPORT buildfile_scanning: public std::runtime_error + { + public: + buildfile_scanning (const std::string& name, + std::uint64_t line, + std::uint64_t column, + const std::string& description); + + std::string name; + std::uint64_t line; + std::uint64_t column; + std::string description; + }; + + template + class buildfile_scanner + { + public: + // Note that name is stored by shallow reference. + // + buildfile_scanner (butl::char_scanner& s, const std::string& name) + : scan_ (s), name_ (name) {} + + // Scan the buildfile line and return the scanned fragment. Optionally, + // specify an additional stop character. Leave the newline (or the stop + // character) in the stream. Throw buildfile_scanning on error + // (unterminated quoted sequence, etc). + // + std::string + scan_line (char stop = '\0'); + + // Scan the buildfile line until an unbalanced ')' character is encountered + // and return the scanned fragment, leaving ')' in the stream. Throw + // buildfile_scanning on error or if eos or newline is reached. + // + std::string + scan_eval (); + + // Scan the buildfile block until an unbalanced block closing '}' character + // is encountered and return the scanned fragment. Throw buildfile_scanning + // on error or if eos is reached. + // + // Note that the block opening '{' and closing '}' characters are only + // considered as such, if they are the only characters on the line besides + // whitespaces and comments. Also note that the fragment terminating '}' + // line is consumed from the stream but is not included into the fragment. + // + std::string + scan_block (); + + private: + using scanner = butl::char_scanner; + using xchar = typename scanner::xchar; + + xchar + peek (); + + // Scan the buildfile line, saving the scanned characters into the + // specified string, leaving newline and the stop character, if specified, + // in the stream. Return '{' if this line is a block-opening curly brace, + // '}' if it is a block-closing curly brace, and '\0' otherwise. + // + char + scan_line (std::string& l, char stop = '\0'); + + private: + scanner& scan_; + const std::string& name_; + std::string ebuf_; // Error message buffer. + }; +} + +#include + +#endif // LIBBPKG_BUILDFILE_SCANNER_HXX diff --git a/libbpkg/buildfile-scanner.txx b/libbpkg/buildfile-scanner.txx new file mode 100644 index 0000000..acd9037 --- /dev/null +++ b/libbpkg/buildfile-scanner.txx @@ -0,0 +1,272 @@ +// file : libbpkg/buildfile-scanner.txx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +namespace bpkg +{ + template + typename buildfile_scanner::xchar buildfile_scanner:: + peek () + { + xchar c (scan_.peek (ebuf_)); + + if (scanner::invalid (c)) + throw buildfile_scanning (name_, scan_.line, scan_.column, ebuf_); + + return c; + } + + template + char buildfile_scanner:: + scan_line (std::string& l, char stop) + { + using namespace std; + + auto fail = [this] (const string& d) + { + throw buildfile_scanning (name_, scan_.line, scan_.column, d); + }; + + xchar c (peek ()); + + auto next = [&l, &c, this] () + { + l += c; + scan_.get (c); + }; + + butl::optional r; + bool double_quoted (false); + + for (; + !scanner::eos (c) && (double_quoted || (c != '\n' && c != stop)); + c = peek ()) + { + switch (c) + { + case '\"': + { + // Start or finish scanning the double-quoted sequence. + // + double_quoted = !double_quoted; + + r = '\0'; + break; + } + case '\\': + { + next (); + + c = peek (); + + if (scanner::eos (c)) + fail (double_quoted + ? "unterminated double-quoted sequence" + : "unterminated escape sequence"); + + r = '\0'; + break; + } + case '(': + { + next (); + + scan_line (l, ')'); + + c = peek (); + + if (c != ')') + fail ("unterminated evaluation context"); + + next (); + + r = '\0'; + continue; + } + case '\'': + { + if (!double_quoted) + { + next (); + + for (;;) + { + c = peek (); + + if (scanner::eos (c)) + fail ("unterminated single-quoted sequence"); + + next (); + + if (c == '\'') + break; + } + + r = '\0'; + continue; + } + + break; + } + case '#': + { + if (!double_quoted) + { + next (); + + // See if this is a multi-line comment in the form: + // + /* + #\ + ... + #\ + */ + auto ml = [&c, &next, this] () -> bool + { + if ((c = peek ()) == '\\') + { + next (); + + if ((c = peek ()) == '\n' || scanner::eos (c)) + return true; + } + + return false; + }; + + if (ml ()) + { + // Scan until we see the closing one. + // + for (;;) + { + if (c == '#' && ml ()) + break; + + if (scanner::eos (c = peek ())) + fail ("unterminated multi-line comment"); + + next (); + } + } + else + { + // Read until newline or eos. + // + for (; !scanner::eos (c) && c != '\n'; c = peek ()) + next (); + } + + continue; + } + + break; + } + case '{': + case '}': + { + if (!double_quoted) + r = !r ? static_cast (c) : '\0'; + + break; + } + default: + { + if (!double_quoted && c != ' ' && c != '\t') + r = '\0'; + + break; + } + } + + next (); + } + + if (double_quoted) + fail ("unterminated double-quoted sequence"); + + return r ? *r : '\0'; + } + + template + std::string buildfile_scanner:: + scan_line (char stop) + { + std::string r; + scan_line (r, stop); + return r; + } + + template + std::string buildfile_scanner:: + scan_eval () + { + std::string r; + scan_line (r, ')'); + + if (peek () != ')') + throw buildfile_scanning (name_, + scan_.line, + scan_.column, + "unterminated evaluation context"); + + return r; + } + + template + std::string buildfile_scanner:: + scan_block () + { + using namespace std; + + auto fail = [this] (const string& d) + { + throw buildfile_scanning (name_, scan_.line, scan_.column, d); + }; + + string r; + for (size_t level (0);; ) + { + if (scanner::eos (peek ())) + fail ("unterminated buildfile block"); + + size_t n (r.size ()); + char bc (scan_line (r)); + + xchar c (peek ()); + + // Append the newline unless this is eos. + // + if (c == '\n') + { + r += c; + scan_.get (c); + } + else + assert (scanner::eos (c)); + + if (bc == '{') + { + ++level; + } + else if (bc == '}') + { + // If this is the fragment terminating line, then strip it from the + // fragment and bail out. + // + if (level == 0) + { + r.resize (n); + break; + } + else + --level; + } + } + + return r; + } +} diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index bbe8749..6a5ce25 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -18,15 +18,19 @@ #include #include +#include #include #include // icasecmp(), lcase(), alnum(), // digit(), xdigit(), next_word() #include // dir_exist() #include +#include #include #include #include +#include + using namespace std; using namespace butl; @@ -967,297 +971,1590 @@ namespace bpkg optional sv ( parse_standard_version (vs, standard_version::allow_stub)); - if (!sv) - throw invalid_argument ("dependent version is not standard"); + if (!sv) + throw invalid_argument ("dependent version is not standard"); + + standard_version_constraint vc (min_open ? "~$" : "^$", *sv); + + try + { + assert (vc.min_version && vc.max_version); + + return version_constraint (version (vc.min_version->string ()), + vc.min_open, + version (vc.max_version->string ()), + vc.max_open); + } + catch (const invalid_argument&) + { + // There shouldn't be a reason for version_constraint() to throw. + // + assert (false); + } + } + + // Calculate effective constraint for a range. + // + return version_constraint ( + min_version && min_version->empty () ? v : min_version, min_open, + max_version && max_version->empty () ? v : max_version, max_open); + } + + std::string version_constraint:: + string () const + { + assert (!empty ()); + + auto ver = [] (const version& v) {return v.empty () ? "$" : v.string ();}; + + if (!min_version) + return (max_open ? "< " : "<= ") + ver (*max_version); + + if (!max_version) + return (min_open ? "> " : ">= ") + ver (*min_version); + + if (*min_version == *max_version) + { + const version& v (*min_version); + + if (!min_open && !max_open) + return "== " + ver (v); + + assert (v.empty () && (!min_open || !max_open)); + return min_open ? "~$" : "^$"; + } + + // If the range can potentially be represented as a range shortcut + // operator (^ or ~), having the [ ) + // form, then produce the resulting string using the standard version + // constraint code. + // + if (!min_open && + max_open && + !min_version->empty () && + !max_version->empty ()) + { + if (optional mnv = + parse_standard_version (min_version->string (), + standard_version::allow_earliest)) + { + if (optional mxv = + parse_standard_version (max_version->string (), + standard_version::allow_earliest)) + try + { + return standard_version_constraint ( + move (*mnv), min_open, move (*mxv), max_open).string (); + } + catch (const invalid_argument&) + { + // Invariants for both types of constraints are the same, so the + // conversion should never fail. + // + assert (false); + } + } + } + + // Represent as a range. + // + std::string r (min_open ? "(" : "["); + r += ver (*min_version); + r += ' '; + r += ver (*max_version); + r += max_open ? ')' : ']'; + return r; + } + + // dependency + // + dependency:: + dependency (std::string d) + { + using std::string; + using iterator = string::const_iterator; + + iterator b (d.begin ()); + iterator i (b); + iterator ne (b); // End of name. + iterator e (d.end ()); + + // Find end of name (ne). + // + // Grep for '=<>([~^' in the bpkg source code and update, if changed. + // + const string cb ("=<>([~^"); + for (char c; i != e && cb.find (c = *i) == string::npos; ++i) + { + if (!space (c)) + ne = i + 1; + } + + try + { + name = package_name (i == e ? move (d) : string (b, ne)); + } + catch (const invalid_argument& e) + { + throw invalid_argument (string ("invalid package name: ") + e.what ()); + } + + if (i != e) + try + { + constraint = version_constraint (string (i, e)); + } + catch (const invalid_argument& e) + { + throw invalid_argument (string ("invalid package constraint: ") + + e.what ()); + } + } + + std::string dependency:: + string () const + { + std::string r (name.string ()); + + if (constraint) + { + r += ' '; + r += constraint->string (); + } + + return r; + } + + // dependency_alternative + // + string dependency_alternative:: + string () const + { + std::string r (size () > 1 ? "{" : ""); + + bool first (true); + for (const dependency& d: *this) + { + if (!first) + r += ' '; + else + first = false; + + r += d.string (); + } + + if (size () > 1) + r += '}'; + + if (single_line ()) + { + if (enable) + { + r += " ? ("; + r += *enable; + r += ')'; + } + + if (reflect) + { + r += ' '; + r += *reflect; + } + } + else + { + // Add an extra newline between the clauses. + // + bool first (true); + + r += "\n{"; + + if (enable) + { + first = false; + + r += "\n enable ("; + r += *enable; + r += ')'; + } + + if (prefer) + { + if (!first) + r += '\n'; + else + first = false; + + r += "\n prefer\n {\n"; + r += *prefer; + r += " }"; + + assert (accept); + + r += "\n\n accept ("; + r += *accept; + r += ')'; + } + else if (require) + { + if (!first) + r += '\n'; + else + first = false; + + r += "\n require\n {\n"; + r += *require; + r += " }"; + } + + if (reflect) + { + if (!first) + r += '\n'; + else + first = false; + + r += "\n reflect\n {\n"; + r += *reflect; + r += " }"; + } + + r += "\n}"; + } + + return r; + } + + bool dependency_alternative:: + single_line () const + { + return !prefer && + !require && + (!reflect || reflect->find ('\n') == string::npos); + } + + // dependency_alternatives + // + class dependency_alternatives_lexer: public char_scanner + { + public: + enum class token_type + { + eos, + newline, + word, + buildfile, + + question, // ? + + lcbrace, // { + rcbrace, // } + + lparen, // ( + rparen, // ) + + lsbrace, // [ + rsbrace, // ] + + equal, // == + not_equal, // != + less, // < + greater, // > + less_equal, // <= + greater_equal, // >= + + tilde, // ~ + caret, // ^ + + bit_or // | + }; + + struct token + { + token_type type; + std::string value; + + uint64_t line; + uint64_t column; + + std::string + string (bool diag = true) const; + }; + + public: + // Note that name is stored by shallow reference. + // + dependency_alternatives_lexer (istream& is, + const string& name, + uint64_t line, + uint64_t column) + : char_scanner (is, + utf8_validator (codepoint_types::graphic, U"\n\r\t"), + true /* crlf */, + line, + column), + name_ (name), + buildfile_scan_ (*this, name_) {} + + // The following functions throw manifest_parsing on invalid UTF-8 + // sequence. + // + + // Peek the next non-whitespace character. + // + xchar + peek_char (); + + // Extract next token (other than of the buildfile type) from the stream. + // + // Note that it is ok to call next() again after getting eos. + // + token + next (); + + // The next_*() functions extract the buildfile token from the stream. + // Throw manifest_parsing on error (invalid buildfile fragment, etc). + // + // Note that they are just thin wrappers around the scan_*() functions + // (see buildfile-scanner.hxx for details). + // + token + next_eval (); + + token + next_line (char stop); + + token + next_block (); + + private: + using base = char_scanner; + + xchar + get () + { + xchar c (base::get (ebuf_)); + + if (invalid (c)) + throw parsing (name_, c.line, c.column, ebuf_); + + return c; + } + + void + get (const xchar& peeked) + { + base::get (peeked); + } + + xchar + peek () + { + xchar c (base::peek (ebuf_)); + + if (invalid (c)) + throw parsing (name_, c.line, c.column, ebuf_); + + return c; + } + + void + skip_spaces (); + + private: + const string& name_; + + // Buffer for a get()/peek() potential error. + // + string ebuf_; + + buildfile_scanner buildfile_scan_; + }; + + dependency_alternatives_lexer::token dependency_alternatives_lexer:: + next () + { + using type = token_type; + + skip_spaces (); + + uint64_t ln (line); + uint64_t cl (column); + + xchar c (get ()); + + auto make_token = [ln, cl] (type t, string v = string ()) + { + return token {t, move (v), ln, cl}; + }; + + if (eos (c)) + return make_token (type::eos); + + // NOTE: don't forget to also update the below separators list if changing + // anything here. + // + switch (c) + { + case '\n': return make_token (type::newline); + case '?': return make_token (type::question); + case '(': return make_token (type::lparen); + case ')': return make_token (type::rparen); + case '{': return make_token (type::lcbrace); + case '}': return make_token (type::rcbrace); + case '[': return make_token (type::lsbrace); + case ']': return make_token (type::rsbrace); + + case '=': + case '!': + { + if ((peek ()) == '=') + { + get (); + return make_token (c == '=' ? type::equal : type::not_equal); + } + break; + } + + case '<': + { + if ((c = peek ()) == '=') + { + get (c); + return make_token (type::less_equal); + } + else + return make_token (type::less); + } + + case '>': + { + if ((c = peek ()) == '=') + { + get (c); + return make_token (type::greater_equal); + } + else + return make_token (type::greater); + } + + case '~': return make_token (type::tilde); + case '^': return make_token (type::caret); + + case '|': return make_token (type::bit_or); + } + + // Otherwise it is a word. + // + // Starts with a non-whitespace character which has not been recognized as + // a part of some other token. + // + string r (1, c); + + // Add subsequent characters until eos or separator is encountered. + // + const char* s (" \n\t?(){}[]=!<>~^|"); + for (c = peek (); !eos (c) && strchr (s, c) == nullptr; c = peek ()) + { + r += c; + get (c); + } + + return make_token (type::word, move (r)); + } + + dependency_alternatives_lexer::token dependency_alternatives_lexer:: + next_eval () + { + skip_spaces (); + + uint64_t ln (line); + uint64_t cl (column); + + try + { + // Strip the trailing whitespaces. + // + return token {token_type::buildfile, + trim (buildfile_scan_.scan_eval ()), + ln, + cl}; + } + catch (const buildfile_scanning& e) + { + throw parsing (e.name, e.line, e.column, e.description); + } + } + + dependency_alternatives_lexer::token dependency_alternatives_lexer:: + next_line (char stop) + { + skip_spaces (); + + uint64_t ln (line); + uint64_t cl (column); + + try + { + // Strip the trailing whitespaces. + // + return token {token_type::buildfile, + trim (buildfile_scan_.scan_line (stop)), + ln, + cl}; + } + catch (const buildfile_scanning& e) + { + throw parsing (e.name, e.line, e.column, e.description); + } + } + + dependency_alternatives_lexer::token dependency_alternatives_lexer:: + next_block () + { + uint64_t ln (line); + uint64_t cl (column); + + try + { + // Don't trim the token value not to strip the potential block indenting + // on the first line. + // + return token {token_type::buildfile, + buildfile_scan_.scan_block (), + ln, + cl}; + } + catch (const buildfile_scanning& e) + { + throw parsing (e.name, e.line, e.column, e.description); + } + } + + dependency_alternatives_lexer::xchar dependency_alternatives_lexer:: + peek_char () + { + skip_spaces (); + return peek (); + } + + void dependency_alternatives_lexer:: + skip_spaces () + { + xchar c (peek ()); + bool start (c.column == 1); + + for (; !eos (c); c = peek ()) + { + switch (c) + { + case ' ': + case '\t': break; + + case '\n': + { + // Skip empty lines. + // + if (start) + break; + } + // Fall through. + default: return; + } + + get (c); + } + } + + std::string dependency_alternatives_lexer::token:: + string (bool diag) const + { + std::string q (diag ? "'" : ""); + + switch (type) + { + case token_type::eos: return diag ? "" : ""; + case token_type::newline: return diag ? "" : "\n"; + case token_type::word: return q + value + q; + case token_type::buildfile: return (diag + ? "" + : value); + case token_type::question: return q + "?" + q; + case token_type::lparen: return q + "(" + q; + case token_type::rparen: return q + ")" + q; + case token_type::lcbrace: return q + "{" + q; + case token_type::rcbrace: return q + "}" + q; + case token_type::lsbrace: return q + "[" + q; + case token_type::rsbrace: return q + "]" + q; + case token_type::equal: return q + "==" + q; + case token_type::not_equal: return q + "!=" + q; + case token_type::less: return q + "<" + q; + case token_type::greater: return q + ">" + q; + case token_type::less_equal: return q + "<=" + q; + case token_type::greater_equal: return q + ">=" + q; + case token_type::tilde: return q + "~" + q; + case token_type::caret: return q + "^" + q; + case token_type::bit_or: return q + "|" + q; + } + + assert (false); // Can't be here. + return ""; + } + + class dependency_alternatives_parser + { + public: + + // If the requirements flavor is specified, then only enable and reflect + // clauses are allowed in the multi-line representation. + // + explicit + dependency_alternatives_parser (bool requirements = false) + : requirements_ (requirements) {} + + // Throw manifest_parsing if representation is invalid. + // + void + parse (const package_name& dependent, + istream&, + const string& name, + uint64_t line, + uint64_t column, + dependency_alternatives&); + + private: + using lexer = dependency_alternatives_lexer; + using token = lexer::token; + using token_type = lexer::token_type; + + token_type + next (token&, token_type&); + + token_type + next_eval (token&, token_type&); + + token_type + next_line (token&, token_type&); + + token_type + next_block (token&, token_type&); + + // Receive the token/type from which it should start consuming and in + // return the token/type contains the first token that has not been + // consumed (normally eos, newline, or '|'). + // + dependency_alternative + parse_alternative (token&, token_type&, bool first); + + // Helpers. + // + // Throw manifest_parsing with the ` expected instead of ` + // description. + // + [[noreturn]] void + unexpected_token (const token&, string&& what); + + bool requirements_; + + const package_name* dependent_; + const string* name_; + lexer* lexer_; + dependency_alternatives* result_; + }; + + [[noreturn]] void dependency_alternatives_parser:: + unexpected_token (const token& t, string&& w) + { + w += " expected"; + + // Don't add the `instead of...` part, if the unexpected token is eos or + // an empty word/buildfile. + // + if (t.type != token_type::eos && + ((t.type != token_type::word && t.type != token_type::buildfile) || + !t.value.empty ())) + { + w += " instead of "; + w += t.string (); + } + + throw parsing (*name_, t.line, t.column, w); + } + + void dependency_alternatives_parser:: + parse (const package_name& dependent, + istream& is, + const string& name, + uint64_t line, + uint64_t column, + dependency_alternatives& result) + { + lexer lexer (is, name, line, column); + + dependent_ = &dependent; + name_ = &name; + lexer_ = &lexer; + result_ = &result; + + string what (requirements_ ? "requirement" : "dependency"); + + token t; + token_type tt; + next (t, tt); + + // Make sure the representation is not empty, unless we are in the + // requirements mode. In the latter case fallback to creating a simple + // unconditional requirement. Note that it's the caller's responsibility + // to verify that a non-empty comment is specified in this case. + // + if (tt == token_type::eos) + { + if (!requirements_) + unexpected_token (t, what + " alternatives"); + + dependency_alternative da; + da.push_back (dependency ()); + + result_->push_back (move (da)); + return; + } + + for (bool first (true); tt != token_type::eos; ) + { + dependency_alternative da (parse_alternative (t, tt, first)); + + // Skip newline after the dependency alternative, if present. + // + if (tt == token_type::newline) + next (t, tt); + + // Make sure that the simple requirement has the only alternative in the + // representation. + // + if (requirements_ && + da.size () == 1 && + (da[0].name.empty () || (da.enable && da.enable->empty ()))) + { + assert (first); + + if (tt != token_type::eos) + throw parsing (*name_, + t.line, + t.column, + "end of simple requirement expected"); + } + else + { + if (tt != token_type::eos && tt != token_type::bit_or) + unexpected_token (t, "end of " + what + " alternatives or '|'"); + } + + if (tt == token_type::bit_or) + { + next (t, tt); + + // Skip newline after '|', if present. + // + if (tt == token_type::newline) + next (t, tt); + + // Make sure '|' is not followed by eos. + // + if (tt == token_type::eos) + unexpected_token (t, move (what)); + } + + result_->push_back (move (da)); + + first = false; + } + } + + dependency_alternative dependency_alternatives_parser:: + parse_alternative (token& t, token_type& tt, bool first) + { + using type = token_type; + + dependency_alternative r; + + string what (requirements_ ? "requirement" : "dependency"); + string config ("config." + dependent_->variable () + "."); + + auto bad_token = [&t, this] (string&& what) + { + unexpected_token (t, move (what)); + }; + + // Check that the current token type matches the expected one. Throw + // manifest_parsing if that's not the case. Use the expected token type + // name for the error description or the custom name, if specified. For + // the word and buildfile token types the custom name must be specified. + // + // Only move from the custom name argument if throwing exception. + // + auto expect_token = [&tt, &bad_token] (type et, + string&& what = string ()) + { + assert ((et != type::word && et != type::buildfile) || !what.empty ()); + + if (tt != et) + { + if (what.empty ()) + { + token e {et, "", 0, 0}; + bad_token (e.string ()); + } + else + bad_token (move (what)); + } + }; + + // Parse dependencies. + // + // If the current token starts the version constraint, then read its + // tokens, rejoin them, and return the constraint string representation. + // Otherwise return nullopt. + // + // Note that normally the caller reads the dependency package name, reads + // the version constraint and, if present, appends it to the dependency, + // and then creates the dependency object with a single constructor call. + // + // Note: doesn't read token that follows the constraint. + // + auto try_scan_version_constraint = + [&t, &tt, &bad_token, &expect_token, this] () -> optional + { + switch (t.type) + { + case type::lparen: + case type::lsbrace: + { + string r (t.string (false /* diag */)); + + next (t, tt); + + expect_token (type::word, "version"); + + r += t.string (false /* diag */); + r += ' '; + + next (t, tt); + + expect_token (type::word, "version"); + + r += t.string (false /* diag */); + + next (t, tt); + + if (tt != type::rparen && tt != type::rsbrace) + bad_token ("')' or ']'"); + + r += t.string (false /* diag */); + + return optional (move (r)); + } + + case type::equal: + case type::not_equal: + case type::less: + case type::greater: + case type::less_equal: + case type::greater_equal: + case type::tilde: + case type::caret: + { + string r (t.string (false /* diag */)); + + next (t, tt); + + expect_token (type::word, "version"); + + r += t.string (false /* diag */); + + return optional (move (r)); + } + + default: return nullopt; + } + }; + + // Parse the evaluation context including the left and right parenthesis + // and return the enclosed buildfile fragment. + // + // Note: no token is read after terminating ')'. + // + auto parse_eval = [&t, &tt, &expect_token, &bad_token, this] () + { + next (t, tt); + expect_token (type::lparen); + + next_eval (t, tt); + + if (t.value.empty ()) + bad_token ("condition"); + + string r (move (t.value)); + + next (t, tt); + expect_token (type::rparen); + + return r; + }; + + const char* vccs ("([<>=!~^"); + + bool group (tt == type::lcbrace); // Dependency group. + + if (group) + { + next (t, tt); + + if (tt == type::rcbrace) + bad_token (move (what)); + + while (tt != type::rcbrace) + { + expect_token (type::word, what + " or '}'"); + + string d (move (t.value)); + uint64_t dl (t.line); + uint64_t dc (t.column); + + next (t, tt); + + optional vc (try_scan_version_constraint ()); + + if (vc) + { + d += *vc; + + next (t, tt); + } + + try + { + r.emplace_back (d); + } + catch (const invalid_argument& e) + { + throw parsing (*name_, dl, dc, e.what ()); + } + } + + // See if a common version constraint follows the dependency group and + // parse it if that's the case. + // + // Note that we need to be accurate not to consume what may end up to be + // a part of the reflect config. + // + lexer::xchar c (lexer_->peek_char ()); + + if (!lexer::eos (c) && strchr (vccs, c) != nullptr) + { + next (t, tt); + + uint64_t vcl (t.line); + uint64_t vcc (t.column); + + optional vc (try_scan_version_constraint ()); + + if (!vc) + bad_token ("version constraint"); + + try + { + version_constraint c (*vc); + + for (dependency& d: r) + { + if (!d.constraint) + d.constraint = c; + } + } + catch (const invalid_argument& e) + { + throw parsing (*name_, + vcl, + vcc, + string ("invalid version constraint: ") + e.what ()); + } + } + } + else // Single dependency. + { + // If we see the question mark instead of a word in the requirements + // mode, then this is a simple requirement. In this case parse the + // evaluation context, if present, and bail out. + // + if (requirements_ && first && tt == type::question) + { + r.emplace_back (dependency ()); + r.enable = lexer_->peek_char () == '(' ? parse_eval () : string (); + + next (t, tt); + return r; + } + + expect_token (type::word, move (what)); + + string d (move (t.value)); + uint64_t dl (t.line); + uint64_t dc (t.column); + + // See if a version constraint follows the dependency package name and + // parse it if that's the case. + // + lexer::xchar c (lexer_->peek_char ()); + + if (!lexer::eos (c) && strchr (vccs, c) != nullptr) + { + next (t, tt); + + optional vc (try_scan_version_constraint ()); + + if (!vc) + bad_token ("version constraint"); + + d += *vc; + } + + try + { + r.emplace_back (d); + } + catch (const invalid_argument& e) + { + throw parsing (*name_, dl, dc, e.what ()); + } + } + + // See if there is an enable condition and parse it if that's the case. + // + { + lexer::xchar c (lexer_->peek_char ()); + + if (c == '?') + { + next (t, tt); + expect_token (type::question); + + // If we don't see the opening parenthesis in the requirements mode, + // then this is a simple requirement. In this case set the enable + // condition to an empty string and bail out. + // + c = lexer_->peek_char (); + + if (requirements_ && first && !group && c != '(') + { + r.enable = ""; + + next (t, tt); + return r; + } + + r.enable = parse_eval (); + } + } + + // See if there is a reflect config and parse it if that's the case. + // + { + lexer::xchar c (lexer_->peek_char ()); + + if (!lexer::eos (c) && strchr ("|\n", c) == nullptr) + { + next_line (t, tt); + + string& l (t.value); + if (l.compare (0, config.size (), config) != 0) + bad_token (config + "* variable assignment"); + + r.reflect = move (l); + } + } + + // If the dependencies are terminated with the newline, then check if the + // next token is '{'. If that's the case, then this is a multi-line + // representation. + // + next (t, tt); + + if (tt == type::newline) + { + next (t, tt); + + if (tt == type::lcbrace) + { + if (r.enable) + throw parsing ( + *name_, + t.line, + t.column, + "multi-line " + what + " form with inline enable clause"); + + if (r.reflect) + throw parsing ( + *name_, + t.line, + t.column, + "multi-line " + what + " form with inline reflect clause"); + + next (t, tt); + expect_token (type::newline); + + // Parse the clauses. + // + for (next (t, tt); tt == type::word; next (t, tt)) + { + auto fail_dup = [&t, this] () + { + throw parsing (*name_, t.line, t.column, "duplicate clause"); + }; + + auto fail_precede = [&t, this] (const char* what) + { + throw parsing ( + *name_, + t.line, + t.column, + t.value + " clause should precede " + what + " clause"); + }; + + auto fail_conflict = [&t, this] (const char* what) + { + throw parsing ( + *name_, + t.line, + t.column, + t.value + " and " + what + " clauses are mutually exclusive"); + }; + + auto fail_requirements = [&t, this] () + { + throw parsing ( + *name_, + t.line, + t.column, + t.value + " clause is not permitted for requirements"); + }; + + // Parse the buildfile fragment block including the left and right + // curly braces (expected to be on the separate lines) and return + // the enclosed fragment. + // + auto parse_block = [&t, &tt, &expect_token, &bad_token, this] () + { + next (t, tt); + expect_token (type::newline); + + next (t, tt); + expect_token (type::lcbrace); + + next (t, tt); + expect_token (type::newline); + + next_block (t, tt); + + string r (move (t.value)); + + // Fail if the buildfile fragment is empty. + // + if (r.find_first_not_of (" \t\n") == string::npos) + bad_token ("buildfile fragment"); + + return r; + }; + + const string& v (t.value); + + if (v == "enable") + { + if (r.enable) + fail_dup (); + + if (r.prefer) + fail_precede ("prefer"); + + if (r.require) + fail_precede ("require"); + + if (r.reflect) + fail_precede ("reflect"); + + r.enable = parse_eval (); + + next (t, tt); + expect_token (type::newline); + } + else if (v == "prefer") + { + if (requirements_) + fail_requirements (); + + if (r.prefer) + fail_dup (); + + if (r.require) + fail_conflict ("require"); - standard_version_constraint vc (min_open ? "~$" : "^$", *sv); + if (r.reflect) + fail_precede ("reflect"); - try - { - assert (vc.min_version && vc.max_version); + r.prefer = parse_block (); - return version_constraint (version (vc.min_version->string ()), - vc.min_open, - version (vc.max_version->string ()), - vc.max_open); - } - catch (const invalid_argument&) - { - // There shouldn't be a reason for version_constraint() to throw. - // - assert (false); - } - } + // The accept clause must follow, so parse it. + // + next (t, tt); - // Calculate effective constraint for a range. - // - return version_constraint ( - min_version && min_version->empty () ? v : min_version, min_open, - max_version && max_version->empty () ? v : max_version, max_open); - } + if (tt != type::word || t.value != "accept") + bad_token ("accept clause"); - std::string version_constraint:: - string () const - { - assert (!empty ()); + r.accept = parse_eval (); - auto ver = [] (const version& v) {return v.empty () ? "$" : v.string ();}; + next (t, tt); + expect_token (type::newline); + } + else if (v == "require") + { + if (requirements_) + fail_requirements (); - if (!min_version) - return (max_open ? "< " : "<= ") + ver (*max_version); + if (r.require) + fail_dup (); - if (!max_version) - return (min_open ? "> " : ">= ") + ver (*min_version); + if (r.prefer) + fail_conflict ("prefer"); - if (*min_version == *max_version) - { - const version& v (*min_version); + if (r.reflect) + fail_precede ("reflect"); - if (!min_open && !max_open) - return "== " + ver (v); + r.require = parse_block (); + } + else if (v == "reflect") + { + if (r.reflect) + fail_dup (); - assert (v.empty () && (!min_open || !max_open)); - return min_open ? "~$" : "^$"; - } + r.reflect = parse_block (); + } + else if (v == "accept") + { + if (requirements_) + fail_requirements (); - // If the range can potentially be represented as a range shortcut - // operator (^ or ~), having the [ ) - // form, then produce the resulting string using the standard version - // constraint code. - // - if (!min_open && - max_open && - !min_version->empty () && - !max_version->empty ()) - { - if (optional mnv = - parse_standard_version (min_version->string (), - standard_version::allow_earliest)) - { - if (optional mxv = - parse_standard_version (max_version->string (), - standard_version::allow_earliest)) - try - { - return standard_version_constraint ( - move (*mnv), min_open, move (*mxv), max_open).string (); - } - catch (const invalid_argument&) - { - // Invariants for both types of constraints are the same, so the - // conversion should never fail. - // - assert (false); + throw parsing (*name_, + t.line, + t.column, + "accept clause should follow prefer clause"); + } + else + bad_token (what + " alternative clause"); } + + expect_token (type::rcbrace); + next (t, tt); } } - // Represent as a range. - // - std::string r (min_open ? "(" : "["); - r += ver (*min_version); - r += ' '; - r += ver (*max_version); - r += max_open ? ')' : ']'; return r; } - // dependency - // - dependency:: - dependency (std::string d) + dependency_alternatives_parser::token_type dependency_alternatives_parser:: + next (token& t, token_type& tt) { - using std::string; - using iterator = string::const_iterator; - - iterator b (d.begin ()); - iterator i (b); - iterator ne (b); // End of name. - iterator e (d.end ()); - - // Find end of name (ne). - // - // Grep for '=<>([~^' in the bpkg source code and update, if changed. - // - const string cb ("=<>([~^"); - for (char c; i != e && cb.find (c = *i) == string::npos; ++i) - { - if (!space (c)) - ne = i + 1; - } - - try - { - name = package_name (i == e ? move (d) : string (b, ne)); - } - catch (const invalid_argument& e) - { - throw invalid_argument (string ("invalid package name: ") + e.what ()); - } - - if (i != e) - try - { - constraint = version_constraint (string (i, e)); - } - catch (const invalid_argument& e) - { - throw invalid_argument (string ("invalid package constraint: ") + - e.what ()); - } + t = lexer_->next (); + tt = t.type; + return tt; } - std::string dependency:: - string () const + dependency_alternatives_parser::token_type dependency_alternatives_parser:: + next_eval (token& t, token_type& tt) { - std::string r (name.string ()); - - if (constraint) - { - r += ' '; - r += constraint->string (); - } - - return r; + t = lexer_->next_eval (); + tt = t.type; + return tt; } - // dependency_alternative - // - dependency_alternative:: - dependency_alternative (const std::string& s) + dependency_alternatives_parser::token_type dependency_alternatives_parser:: + next_line (token& t, token_type& tt) { - push_back (dependency (s)); // @@ DEP + t = lexer_->next_line ('|'); + tt = t.type; + return tt; } - string dependency_alternative:: - string () const + dependency_alternatives_parser::token_type dependency_alternatives_parser:: + next_block (token& t, token_type& tt) { - assert (size () == 1); // @@ DEP - return front ().string (); + t = lexer_->next_block (); + tt = t.type; + return tt; } - // dependency_alternatives - // dependency_alternatives:: - dependency_alternatives (const std::string& s) + dependency_alternatives (const std::string& s, + const package_name& dependent, + const std::string& name, + uint64_t line, + uint64_t column) { using std::string; - // Allow specifying ?* in any order. - // - size_t n (s.size ()); - size_t cond ((n > 0 && s[0] == '?') || (n > 1 && s[1] == '?') ? 1 : 0); - size_t btim ((n > 0 && s[0] == '*') || (n > 1 && s[1] == '*') ? 1 : 0); - auto vc (parser::split_comment (s)); - conditional = (cond != 0); - buildtime = (btim != 0); - comment = move (vc.second); + comment = move (vc.second); - const string& vl (vc.first); + const string& v (vc.first); + buildtime = (v[0] == '*'); - string::const_iterator b (vl.begin ()); - string::const_iterator e (vl.end ()); + string::const_iterator b (v.begin ()); + string::const_iterator e (v.end ()); - if (conditional || buildtime) + if (buildtime) { - string::size_type p (vl.find_first_not_of (spaces, cond + btim)); + string::size_type p (v.find_first_not_of (spaces, 1)); b = p == string::npos ? e : b + p; } - list_parser lp (b, e, '|'); - for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - push_back (dependency_alternative (lv)); + dependency_alternatives_parser p; + istringstream is (b == v.begin () ? v : string (b, e)); + p.parse (dependent, is, name, line, column, *this); } string dependency_alternatives:: string () const { - std::string r (conditional - ? (buildtime ? "?* " : "? ") - : (buildtime ? "* " : "")); + std::string r (buildtime ? "* " : ""); - bool f (true); + const dependency_alternative* prev (nullptr); for (const dependency_alternative& da: *this) { - r += (f ? (f = false, "") : " | "); + if (prev != nullptr) + { + r += prev->single_line () ? " |" : "\n|"; + r += !da.single_line () || !prev->single_line () ? '\n' : ' '; + } + r += da.string (); + prev = &da; } return serializer::merge_comment (r, comment); } - // requirement_alternative - // - requirement_alternative:: - requirement_alternative (const std::string& s) + bool dependency_alternatives:: + conditional () const { - push_back (s); // @@ DEP + for (const dependency_alternative& da: *this) + { + if (da.enable) + return true; + } + + return false; } + // requirement_alternative + // string requirement_alternative:: string () const { - assert (size () == 1); // @@ DEP - return front (); + using std::string; + + string r (size () > 1 ? "{" : ""); + + bool first (true); + for (const string& rq: *this) + { + if (!first) + r += ' '; + else + first = false; + + r += rq; + } + + if (size () > 1) + r += '}'; + + if (single_line ()) + { + if (enable) + { + if (!simple ()) + { + r += " ? ("; + r += *enable; + r += ')'; + } + else + { + // Note that the (single) requirement id may or may not be empty. + // + if (!r.empty ()) + r += ' '; + + r += '?'; + + if (!enable->empty ()) + { + r += " ("; + r += *enable; + r += ')'; + } + } + } + + if (reflect) + { + r += ' '; + r += *reflect; + } + } + else + { + r += "\n{"; + + if (enable) + { + r += "\n enable ("; + r += *enable; + r += ')'; + } + + if (reflect) + { + if (enable) + r += '\n'; + + r += "\n reflect\n {\n"; + r += *reflect; + r += " }"; + } + + r += "\n}"; + } + + return r; + } + + bool requirement_alternative:: + single_line () const + { + return !reflect || reflect->find ('\n') == string::npos; } // requirement_alternatives // requirement_alternatives:: - requirement_alternatives (const std::string& v) + requirement_alternatives (const std::string& s, + const package_name& dependent, + const std::string& name, + uint64_t line, + uint64_t column) { using std::string; - // Allow specifying ?* in any order. - // - size_t n (v.size ()); - size_t cond ((n > 0 && v[0] == '?') || (n > 1 && v[1] == '?') ? 1 : 0); - size_t btim ((n > 0 && v[0] == '*') || (n > 1 && v[1] == '*') ? 1 : 0); - - auto vc (parser::split_comment (v)); + auto vc (parser::split_comment (s)); - conditional = (cond != 0); - buildtime = (btim != 0); - comment = move (vc.second); + comment = move (vc.second); - const string& vl (vc.first); + const string& v (vc.first); + buildtime = (v[0] == '*'); - string::const_iterator b (vl.begin ()); - string::const_iterator e (vl.end ()); + string::const_iterator b (v.begin ()); + string::const_iterator e (v.end ()); - if (conditional || buildtime) + if (buildtime) { - string::size_type p (vl.find_first_not_of (spaces, cond + btim)); + string::size_type p (v.find_first_not_of (spaces, 1)); b = p == string::npos ? e : b + p; } - list_parser lp (b, e, '|'); - for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - push_back (requirement_alternative (lv)); + // We will use the dependency alternatives parser to parse the + // representation into a temporary dependency alternatives in the + // requirements mode. Then we will move the dependency alternatives into + // the requirement alternatives using the string representation of the + // dependencies. + // + dependency_alternatives_parser p (true /* requirements */); + istringstream is (b == v.begin () ? v : string (b, e)); + + dependency_alternatives das; + p.parse (dependent, is, name, line, column, das); + + for (dependency_alternative& da: das) + { + requirement_alternative ra (move (da.enable), move (da.reflect)); + + // Also handle the simple requirement. + // + for (dependency& d: da) + ra.push_back (!d.name.empty () ? d.string () : string ()); + + push_back (move (ra)); + } - if (empty () && comment.empty ()) - throw invalid_argument ("empty package requirement specification"); + // Make sure that the simple requirement is accompanied with a non-empty + // comment. + // + if (simple () && comment.empty ()) + { + // Let's describe the following error cases differently: + // + // requires: ? + // requires: + // + throw parsing (name, + line, + column, + (back ().enable + ? "no comment specified for simple requirement" + : "requirement or comment expected")); + } } std::string requirement_alternatives:: string () const { - std::string r (conditional - ? (buildtime ? "?* " : "? ") - : (buildtime ? "* " : "")); + using std::string; - bool f (true); + string r (buildtime ? "* " : ""); + + const requirement_alternative* prev (nullptr); for (const requirement_alternative& ra: *this) { - r += (f ? (f = false, "") : " | "); + if (prev != nullptr) + { + r += prev->single_line () ? " |" : "\n|"; + r += !ra.single_line () || !prev->single_line () ? '\n' : ' '; + } + r += ra.string (); + prev = &ra; } + // For better readability separate the comment from the question mark for + // the simple requirement with an empty condition. + // + if (simple () && conditional () && back ().enable->empty ()) + r += ' '; + return serializer::merge_comment (r, comment); } + bool requirement_alternatives:: + conditional () const + { + for (const requirement_alternative& ra: *this) + { + if (ra.enable) + return true; + } + + return false; + } + // build_class_term // build_class_term:: @@ -1966,9 +3263,11 @@ namespace bpkg // We will cache the depends and the test dependency manifest values to // parse and, if requested, complete the version constraints later, after - // the version value is parsed. + // the version value is parsed. We will also cache the requires values to + // parse them later, after the package name is parsed. // vector dependencies; + vector requirements; small_vector tests; // We will cache the description and its type values to validate them @@ -2254,16 +3553,13 @@ namespace bpkg m.license_alternatives.push_back (move (l)); } + else if (n == "depends") + { + dependencies.push_back (move (nv)); + } else if (n == "requires") { - try - { - m.requirements.push_back (requirement_alternatives (v)); - } - catch (const invalid_argument& e) - { - bad_value (e.what ()); - } + requirements.push_back (move (nv)); } else if (n == "builds") { @@ -2280,10 +3576,6 @@ namespace bpkg m.build_constraints.push_back ( parse_build_constraint (nv, true /* exclusion */, name)); } - else if (n == "depends") - { - dependencies.push_back (move (nv)); - } // @@ TMP time to drop *-0.14.0? // else if (n == "tests" || n == "tests-0.14.0" || @@ -2467,8 +3759,8 @@ namespace bpkg } catch (const invalid_argument& e) { - throw invalid_argument (string ("invalid package constraint: ") + - e.what ()); + throw invalid_argument ("invalid package constraint '" + + dep.constraint->string () + "': " + e.what ()); } return move (dep); @@ -2480,16 +3772,15 @@ namespace bpkg { nv = move (d); // Restore as bad_value() uses its line/column. - const string& v (nv.value); - // Parse dependency alternatives. // try { - dependency_alternatives das (v); - - if (das.empty ()) - bad_value ("empty package dependency specification"); + dependency_alternatives das (nv.value, + m.name, + name, + nv.value_line, + nv.value_column); for (dependency_alternative& da: das) { @@ -2505,6 +3796,18 @@ namespace bpkg } } + // Parse the requirements. + // + for (const name_value& r: requirements) + { + m.requirements.push_back ( + requirement_alternatives (r.value, + m.name, + name, + r.value_line, + r.value_column)); + } + // Parse the test dependencies. // for (name_value& t: tests) diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index d72a55e..499d64d 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -8,7 +8,7 @@ #include #include #include -#include // uint16_t +#include // uint*_t #include #include // move() #include // logic_error @@ -455,45 +455,175 @@ namespace bpkg // depends // + // The dependency alternative can be represented in one of the following + // forms. + // + // Single-line form: + // + // ['?' ] [] + // + // = | + // ({ [ ]* } []) + // + // - buildfile evaluation context + // - dependent package configuration variable assignment + // + // If the version constraint is specified after the dependency group, it + // only applies to dependencies without a version constraint. + // + // Multi-line forms: + // + // + // { + // enable + // + // prefer + // { + // + // } + // + // accept + // + // reflect + // { + // + // } + // } + // | + // + // { + // enable + // + // require + // { + // + // } + // + // reflect + // { + // + // } + // } + // + // - buildfile fragment containing dependency packages + // configuration variables assignments + // + // - buildfile evaluation context + // + // - buildfile fragment containing dependency packages + // configuration variables assignments + // + // - buildfile fragment containing dependent package + // configuration variables assignments + // + // The dependency alternative is only considered by bpkg if the enable + // condition evaluates to true. If the enable clause is not specified, then + // it is always considered. + // + // The prefer clause specifies the preferred dependency package + // configuration that may potentially differ from the resulting + // configuration after the preferred/required configurations from all the + // selected dependency alternatives of all the dependent packages are + // "negotiated" by bpkg. The accept clause is used to verify that the + // resulting configuration is still acceptable for the dependent + // package. The accept clause must always be specified if the prefer clause + // is specified. + // + // The require clause specifies the only acceptable dependency packages + // configuration. It is a shortcut for specifying the prefer/accept clauses, + // where the accept condition verifies all the variable values assigned in + // the prefer clause. The require clause and the prefer/accept clause pair + // are optional and are mutually exclusive. + // + // The reflect clause specifies the dependent package configuration that + // should be used if the alternative is selected. + // + // All clauses are optional but at least one of them must be specified. + // class dependency_alternative: public butl::small_vector { public: butl::optional enable; + butl::optional reflect; + butl::optional prefer; + butl::optional accept; + butl::optional require; dependency_alternative () = default; - dependency_alternative (butl::optional e) - : enable (std::move (e)) {} - - // Parse the dependency alternative string representation. + dependency_alternative (butl::optional e, + butl::optional r, + butl::optional p, + butl::optional a, + butl::optional q) + : enable (std::move (e)), + reflect (std::move (r)), + prefer (std::move (p)), + accept (std::move (a)), + require (std::move (q)) {} + + // Return the single-line representation if possible (the prefer and + // require clauses are absent and the reflect clause either absent or + // contains no newlines). // - explicit LIBBPKG_EXPORT - dependency_alternative (const std::string&); - LIBBPKG_EXPORT std::string string () const; + + // Return true if the string() function would return the single-line + // representation. + // + LIBBPKG_EXPORT bool + single_line () const; }; class dependency_alternatives: public butl::small_vector { public: - bool conditional; bool buildtime; std::string comment; dependency_alternatives () = default; - dependency_alternatives (bool d, bool b, std::string c) - : conditional (d), buildtime (b), comment (std::move (c)) {} + dependency_alternatives (bool b, std::string c) + : buildtime (b), comment (std::move (c)) {} - // Parse the dependency alternatives string representation in the - // `[?][*] [ '|' ]* [; ]` form. Throw - // std::invalid_argument if the value is invalid. @@ DEP @@ TMP update. + // Parse the dependency alternatives string representation in the form: + // + // [*] [ '|' ]* [; ] + // + // Where can be single or multi-line (see above). Note also + // that leading `*` and trailing comment can be on separate lines. Throw + // manifest_parsing if the value is invalid. + // + // Use the dependent package name to verify that the reflect clauses in + // the dependency alternative representations refer to the dependent + // package configuration variable. + // + // Optionally, specify the stream name to use when creating the + // manifest_parsing exception. The start line and column arguments can be + // used to align the exception information with a containing stream. This + // is useful when the alternatives representation is a part of some larger + // text (manifest, etc). + // + // Note that semicolons inside alternatives must be escaped with the + // backslash (not to be treated as the start of a comment). Backslashes at + // the end of buildfile fragment lines need to also be escaped, if + // dependency alternatives representation comes from the manifest file + // (since trailing backslashes in manifest lines has special semantics). // explicit LIBBPKG_EXPORT - dependency_alternatives (const std::string&); + dependency_alternatives (const std::string&, + const package_name& dependent, + const std::string& name = std::string (), + std::uint64_t line = 1, + std::uint64_t column = 1); LIBBPKG_EXPORT std::string string () const; + + // Return true if there is a conditional alternative in the list. + // + LIBBPKG_EXPORT bool + conditional () const; }; inline std::ostream& @@ -504,45 +634,109 @@ namespace bpkg // requires // + // The requirement alternative string representation is similar to that of + // the dependency alternative with the following differences: + // + // - The requirement id (with or without version) can mean anything (but + // must still be a valid package name). + // + // - Only the enable and reflect clauses are permitted (reflect is allowed + // for potential future support of recognized requirement alternatives, + // for example, C++ standard). + // + // - The simplified representation syntax, where the comment carries the + // main information and thus is mandatory, is also supported (see + // requirement_alternatives for details). For example: + // + // requires: ; X11 libs. + // requires: ? ($windows) ; Only 64-bit. + // requires: ? ; Only 64-bit if on Windows. + // requires: x86_64 ? ; Only if on Windows. + // class requirement_alternative: public butl::small_vector { public: butl::optional enable; + butl::optional reflect; requirement_alternative () = default; - requirement_alternative (butl::optional e) - : enable (std::move (e)) {} + requirement_alternative (butl::optional e, + butl::optional r) + : enable (std::move (e)), reflect (std::move (r)) {} - // Parse the requirement alternative string representation. + // Return the single-line representation if possible (the reflect clause + // either absent or contains no newlines). // - explicit LIBBPKG_EXPORT - requirement_alternative (const std::string&); - LIBBPKG_EXPORT std::string string () const; + + // Return true if the string() function would return the single-line + // representation. + // + LIBBPKG_EXPORT bool + single_line () const; + + // Return true if this is a single requirement with an empty id or an + // empty enable condition. + // + bool + simple () const + { + return size () == 1 && (back ().empty () || (enable && enable->empty ())); + } }; class requirement_alternatives: public butl::small_vector { public: - bool conditional; bool buildtime; std::string comment; requirement_alternatives () = default; - requirement_alternatives (bool d, bool b, std::string c) - : conditional (d), buildtime (b), comment (std::move (c)) {} + requirement_alternatives (bool b, std::string c) + : buildtime (b), comment (std::move (c)) {} // Parse the requirement alternatives string representation in the - // `[?] [ [ '|' ]*] [; ]` form. Throw - // std::invalid_argument if the value is invalid. @@ DEP @@ TMP update. + // following forms: + // + // [*] [ '|' ]* [; ] + // [*] [] [? []] ; + // + // Parsing the second form ends up with a single alternative with a single + // potentially empty requirement id, potentially with an enable condition + // with potentially empty value (see examples above). + // + // Throw manifest_parsing if the value is invalid. + // + // Optionally, specify the stream name to use when creating the + // manifest_parsing exception. The start line and column arguments can be + // used to align the exception information with a containing stream. This + // is useful when the alternatives representation is a part of some larger + // text (manifest, etc). // explicit LIBBPKG_EXPORT - requirement_alternatives (const std::string&); + requirement_alternatives (const std::string&, + const package_name& dependent, + const std::string& name = std::string (), + std::uint64_t line = 1, + std::uint64_t column = 1); LIBBPKG_EXPORT std::string string () const; + + // Return true if there is a conditional alternative in the list. + // + LIBBPKG_EXPORT bool + conditional () const; + + // Return true if this is a single simple requirement alternative. + // + bool + simple () const + { + return size () == 1 && back ().simple (); + } }; inline std::ostream& diff --git a/tests/buildfile-scanner/buildfile b/tests/buildfile-scanner/buildfile new file mode 100644 index 0000000..82f7ace --- /dev/null +++ b/tests/buildfile-scanner/buildfile @@ -0,0 +1,7 @@ +# file : tests/buildfile-scanner/buildfile +# 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/buildfile-scanner/driver.cxx b/tests/buildfile-scanner/driver.cxx new file mode 100644 index 0000000..fec15bd --- /dev/null +++ b/tests/buildfile-scanner/driver.cxx @@ -0,0 +1,106 @@ +// file : tests/buildfile-scanner/driver.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include // ios_base::failbit, ios_base::badbit +#include +#include + +#include +#include // operator<<(ostream,exception) +#include + +#include + +#undef NDEBUG +#include + +using namespace std; +using namespace butl; +using namespace bpkg; + +// Usages: +// +// argv[0] (-e|-l []|-b) +// +// Read and scan the buildfile from stdin and print the scan result to stdout. +// +// -e scan evaluation context +// -l [] scan single line, optionally terminated with the stop character +// -b scan buildfile block +// +int +main (int argc, char* argv[]) +{ + assert (argc >= 2); + + string mode (argv[1]); + + cin.exceptions (ios_base::failbit | ios_base::badbit); + cout.exceptions (ios_base::failbit | ios_base::badbit); + + using scanner = char_scanner; + + scanner s (cin); + + string bsn ("stdin"); + buildfile_scanner bs (s, bsn); + + try + { + string r; + + if (mode == "-e") + { + scanner::xchar c (s.get ()); + assert (c == '('); + + r += c; + r += bs.scan_eval (); + + c = s.get (); + assert (c == ')'); + + r += c; + } + else if (mode == "-l") + { + char stop ('\0'); + + if (argc == 3) + { + const char* chr (argv[2]); + assert (chr[0] != '\0' && chr[1] == '\0'); + + stop = chr[0]; + } + + r += bs.scan_line (stop); + + scanner::xchar c (s.get ()); + assert (scanner::eos (c) || c == '\n' || (stop != '\0' && c == stop)); + } + else if (mode == "-b") + { + scanner::xchar c (s.get ()); + assert (c == '{'); + + r += c; + r += bs.scan_block (); + + assert (scanner::eos (s.peek ())); + + r += "}\n"; + } + else + assert (false); + + cout << r; + } + catch (const buildfile_scanning& e) + { + cerr << e << endl; + return 1; + } + + return 0; +} diff --git a/tests/buildfile-scanner/testscript b/tests/buildfile-scanner/testscript new file mode 100644 index 0000000..c62d6ce --- /dev/null +++ b/tests/buildfile-scanner/testscript @@ -0,0 +1,342 @@ +# file : tests/buildfile-scanner/testscript +# license : MIT; see accompanying LICENSE file + +: eval +: +{ + test.options += -e + + : basic + : + $* <<:EOF >>:EOF + ($cxx.target.class == windows) + EOF + + : single-quoted + : + $* <<:EOF >>:EOF + ($cxx.target.class == 'windows') + EOF + + : unterminated + : + $* <<:EOI 2>>EOE != 0 + ($cxx.target.class == 'windows' + EOI + stdin:1:32: error: unterminated evaluation context + EOE + + : newline + : + $* <<:EOI 2>>EOE != 0 + ($cxx.target.class == 'windows' + + EOI + stdin:1:32: error: unterminated evaluation context + EOE + + : single-quoted-newline + : + $* <<:EOF >>:EOF + ($foo == 'b + ar') + EOF + + : unterminated-single-quoted + : + $* <>EOE != 0 + ($cxx.target.class == 'windows + EOI + stdin:2:1: error: unterminated single-quoted sequence + EOE + + : double-quoted + : + $* <<:EOF >>:EOF + ($foo == "b'a\"\\)r") + EOF + + : double-quoted-newline + : + $* <<:EOF >>:EOF + ($foo == "ba + r") + EOF + + : unterminated-double-quoted + : + $* <<:EOI 2>>EOE != 0 + ($cxx.target.class == "windows + EOI + stdin:1:31: error: unterminated double-quoted sequence + EOE + + : unterminated-escape + : + $* <<:EOI 2>>EOE != 0 + (foo == windows\ + EOI + stdin:1:17: error: unterminated escape sequence + EOE + + : comment + : + $* <>EOE != 0 + ($cxx.target.class == #'windows' + EOI + stdin:1:33: error: unterminated evaluation context + EOE + + : multiline-comment + : + $* <>EOE != 0 + ($cxx.target.class == #\ + 'windows' + #\ + EOI + stdin:3:3: error: unterminated evaluation context + EOE + + : multiline-comment-unterminated + : + $* <>EOE != 0 + ($cxx.target.class == #\ + 'windows' + EOI + stdin:3:1: error: unterminated multi-line comment + EOE + + : nested + : + $* <<:EOF >>:EOF + (foo != bar(baz)fox) + EOF + + : nested-double-quoted + : + $* <<:EOF >>:EOF + (foo != "bar(b"a"z)fox") + EOF +} + +: line +: +{ + test.options += -l + + : assignment + : + $* <>:EOO + foo = bar + EOI + foo = bar + EOO + + : no-newline + : + $* <<:EOF >>:EOF + foo = bar + EOF + + : eol + : + $* '|' <:'foo = bar ' + foo = bar | baz + EOI + + : single-quoted + : + $* <<:EOF >>:EOF + foo = 'bar' + EOF + + : single-quoted-newline + : + $* <>:EOO + foo = 'b + ar' + EOI + foo = 'b + ar' + EOO + + : unterminated-single-quoted + : + $* <>EOE != 0 + foo = 'bar + EOI + stdin:2:1: error: unterminated single-quoted sequence + EOE + + : double-quoted + : + $* <<:EOF >>:EOF + foo = "b'a\"\\)r" + EOF + + : double-quoted-newline + : + $* <>:EOO + foo == "ba + r" + EOI + foo == "ba + r" + EOO + + : unterminated-double-quoted + : + $* <<:EOI 2>>EOE != 0 + foo = "bar + EOI + stdin:1:11: error: unterminated double-quoted sequence + EOE + + : unterminated-escape + : + $* <<:EOI 2>>EOE != 0 + foo = bar\ + EOI + stdin:1:11: error: unterminated escape sequence + EOE + + : comment + : + $* <>:EOO + foo = # bar + EOI + foo = # bar + EOO + + : empty-comment + : + $* <>:EOO + foo = # + EOI + foo = # + EOO + + : multiline-comment + : + $* <>:EOO + foo = #\ + 'windows' + #\ + EOI + foo = #\ + 'windows' + #\ + EOO + + : multiline-comment-unterminated + : + $* <>EOE != 0 + foo = #\ + bar + EOI + stdin:3:1: error: unterminated multi-line comment + EOE + + : eval + : + $* <<:EOF >>:EOF + foo = bar(baz)fox + EOF + + : eval-unterminated + : + $* <>EOE != 0 + foo = bar(baz + EOI + stdin:1:14: error: unterminated evaluation context + EOE + + : eval-double-quoted + : + $* <<:EOF >>:EOF + foo = "bar($baz ? b"a"z : 'bar')fox" + EOF +} + +: block +: +{ + test.options += -b + + : basic + : + $* <>EOF + { + config.foo.bar = true + config.foo.baz = "baz" + } + EOF + + : quoted + : + $* <>EOF + { + config.foo.bar = true + config.foo.baz = "baz + } + bar + " + } + EOF + + : nested + : + $* <>EOF + { + config.foo.bar = true + + if ($cxx.target.class == windows) + { + config.foo.win = true + } + else + { + config.foo.win = false + } + } + EOF + + : comments + : + $* <>EOF + { + config.foo.bar = true + + if ($cxx.target.class == windows) + { # single line + config.foo.win = true + } + else + { #\ + Multi + line + #\ + config.foo.win = false + } + } + EOF + + : non-spaces + : + $* <>EOE != 0 + { + config.foo.bar = true + + box } + } box + }{ + }} + "}" + '}' + \} + }\ + (}) + EOI + stdin:13:1: error: unterminated buildfile block + EOE +} diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index 273dce5..c0d8693 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -20,8 +20,8 @@ using namespace bpkg; // Usages: // -// argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) -// argv[0] -p -c -i +// argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) [-l] +// argv[0] -p [-c] [-i] [-l] // argv[0] -ec // argv[0] -v // @@ -45,6 +45,9 @@ using namespace bpkg; // // Note: the above options should go after -p on the command line. // +// -l +// Don't break long lines while serializing a manifest. +// // In the third form read and parse dependency constraints from stdin and // roundtrip them to stdout together with their effective constraints, // calculated using version passed as an argument. @@ -65,8 +68,7 @@ main (int argc, char* argv[]) return 0; } - manifest_parser p (cin, "stdin"); - manifest_serializer s (cout, "stdout"); + manifest_parser p (cin, "stdin"); try { @@ -74,6 +76,7 @@ main (int argc, char* argv[]) { bool complete_dependencies (false); bool ignore_unknown (false); + bool long_lines (false); for (int i (2); i != argc; ++i) { @@ -83,10 +86,14 @@ main (int argc, char* argv[]) complete_dependencies = true; else if (o == "-i") ignore_unknown = true; + else if (o == "-l") + long_lines = true; else assert (false); } + manifest_serializer s (cout, "stdout", long_lines); + cin.exceptions (ios_base::failbit | ios_base::badbit); package_manifest ( @@ -130,7 +137,19 @@ main (int argc, char* argv[]) } else { - assert (argc == 2); + bool long_lines (false); + + for (int i (2); i != argc; ++i) + { + string o (argv[i]); + + if (o == "-l") + long_lines = true; + else + assert (false); + } + + manifest_serializer s (cout, "stdout", long_lines); cin.exceptions (ios_base::failbit | ios_base::badbit); diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 58a18d8..9f8a476 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -557,7 +557,7 @@ : invalid-version-range : - $* -c <'stdin:6:10: error: invalid package constraint: min version is greater than max version' != 0 + $* -c <"stdin:6:10: error: invalid package constraint '[\$ 1.0.0]': min version is greater than max version" != 0 : 1 name: foo version: 2.0.0 @@ -628,7 +628,7 @@ license: LGPLv2 depends: bar ~$ EOI - stdin:6:10: error: invalid package constraint: dependent version is not standard + stdin:6:10: error: invalid package constraint '~$': dependent version is not standard EOE : latest-snapshot @@ -661,6 +661,2269 @@ license: LGPLv2 depends: bar == $ | libbaz ~$ | libbox ^$ | libfox [1.0 $) EOF + + : single-line + : + { + : curly-braces + : + { + : multiple-dependencies + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 baz [1.0.0 1.3.0-) libfoo} + EOF + + : common-version-constraint + : + { + : valid + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} >= 2.0.0 + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo >= 2.0.0} + EOO + + : bad-operation + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} =+ 2.0.0 + EOI + stdin:6:30: error: version constraint expected instead of '=+' + EOE + + : bad-version + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} == 2-0-0 + EOI + stdin:6:30: error: invalid version constraint: invalid version: unexpected '-' character position + EOE + } + + : unterminated + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo + EOI + stdin:6:28: error: dependency or '}' expected + EOE + + : missing + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ^1.0.0 libfoo + EOI + stdin:6:21: error: config.foo.* variable assignment expected instead of + EOE + } + + : no-curly-braces + : + { + : dependency + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == 1.0.0 + EOF + + : invalid-constraint + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == 1-0-0 + EOI + stdin:6:10: error: invalid package constraint: invalid version: unexpected '-' character position + EOE + } + + : enable-condition + : + { + : no-version-constraint + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows') + EOF + + : version-constraint + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == 1.0.0 ? ($cxx.target.class == 'windows') + EOF + + : missed-dependency + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: ? ($cxx.target.class == 'windows') + EOI + stdin:6:10: error: dependency expected instead of '?' + EOE + + : multiple-dependencies + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} ? ($cxx.target.class == 'windows') + EOF + + : multiple-alternatives + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} ? ($cxx.target.class == 'windows') | baz + EOF + + : empty + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? () + EOI + stdin:6:17: error: condition expected + EOE + + : unterminated1 + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows' + EOI + stdin:6:47: error: unterminated evaluation context + EOE + + : unterminated2 + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows' | baz + EOI + stdin:6:53: error: unterminated evaluation context + EOE + } + + : reflect + : + { + : no-version-constraint + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar config.foo.bar=true + EOF + + : version-constraint + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == 1.0.0 config.foo.bar=true + EOF + + : multiple-dependencies + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: {bar ^1.0.0 libfoo} config.foo.bar=true + EOF + + : enable-condition + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows') config.foo.bar=true + EOF + + : multiple-alternatives + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows') config.foo.bar=true | baz + EOF + + : expected-config + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar baz config.foo.bar=true + EOI + stdin:6:14: error: config.foo.* variable assignment expected instead of + EOE + } + } + + : multi-line + : + { + : surrounding-newlines + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + * + + bar + + ; Comment. + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: * bar; Comment. + EOO + + : enable-clause + : + { + : single-line + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + bar + { + enable ($cxx.target.class == 'windows') + } + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar ? ($cxx.target.class == 'windows') + EOO + + : empty + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + bar + { + enable () + } + \ + EOI + stdin:10:11: error: condition expected + EOE + + : inline-enable + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + bar ? ($windows) + { + enable ($windows) + } + \ + EOI + stdin:9:1: error: multi-line dependency form with inline enable clause + EOE + + : unterminated + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + bar + { + enable ($cxx.target.class == 'windows' + } + \ + EOI + stdin:10:41: error: unterminated evaluation context + EOE + } + + : prefer-clause + : + { + : accept-clause + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + } + \ + EOF + + : no-accept-clause + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + config.bar.frame=4016 + } + } + \ + EOI + stdin:13:1: error: accept clause expected instead of '}' + EOE + + : empty + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + + } + } + \ + EOI + stdin:11:1: error: buildfile fragment expected + EOE + + : enable-clause + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + enable ($cxx.target.class == 'windows') + + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + } + \ + EOF + + : wrong-order + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + + enable ($cxx.target.class == 'windows') + } + \ + EOF + stdin:17:3: error: enable clause should precede prefer clause + EOE + + : require-clause + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOI + stdin:17:3: error: require and prefer clauses are mutually exclusive + EOE + } + + : require-clause + : + { + : valid + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOF + + : enable-clause + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + enable ($cxx.target.class == 'windows') + + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOF + + : prefer-clause + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOF + stdin:15:3: error: prefer and require clauses are mutually exclusive + EOE + + : unescaped-semicolon + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + config.bar.baz=a;bc + } + } + \ + EOF + stdin:11:21: error: unterminated buildfile block + EOE + + : huge + : + $* -l <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + # @@ TMP Version tagging? See + # upstream/qtbase/src/corelib/global/minimum-linux{.S,_p.h}, + # global/qversiontagging.cpp, and QtCore.version (linker version script) in + # the upstream build. + # + # Precompiled headers? + + intf_libs = # Interface dependencies. + import impl_libs = libicuuc%lib{icuuc} + import impl_libs += libicui18n%lib{icui18n} + import impl_libs += libpcre2%lib{pcre2-16} + import impl_libs += libtinycbor%lib{tinycbor} + import impl_libs += libz%lib{z} + + import! [metadata] moc = Qt5Moc%exe{qt5moc} + + windows = ($cxx.target.class == 'windows') + unix = ($cxx.target.class != 'windows') + linux = ($cxx.target.class == 'linux') + freebsd = ($cxx.target.system == 'freebsd') + openbsd = ($cxx.target.system == 'openbsd') + netbsd = ($cxx.target.system == 'netbsd') + bsd = ($cxx.target.class == 'bsd') + macos = ($cxx.target.class == 'macos') + + x86 = ($cxx.target.cpu == 'x86_64' || $cxx.target.cpu == 'i686') + + # True if the CPU and compiler support the F16C instructions. F16C has been + # supported since Intel's Ivy Bridge (2012) and AMD's Bulldozer (2011). Added + # to GCC 4.6.4 (2013) at the latest, so assume it's supported on all i686 and + # x86_64 compilers. + # + f16c = $x86 + + # Source files. + # + src = animation/qabstractanimation \\ + animation/qvariantanimation \\ + animation/qpropertyanimation \\ + animation/qanimationgroup \\ + animation/qsequentialanimationgroup \\ + animation/qparallelanimationgroup \\ + animation/qpauseanimation \\ + codecs/qicucodec \\ + codecs/qisciicodec \\ + codecs/qlatincodec \\ + codecs/qsimplecodec \\ + codecs/qtextcodec \\ + codecs/qtsciicodec \\ + codecs/qutfcodec \\ + global/archdetect \\ + global/qendian \\ + global/qglobal \\ + global/qlibraryinfo \\ + global/qmalloc \\ + global/qnumeric \\ + global/qfloat16 \\ + global/qoperatingsystemversion \\ + global/qlogging \\ + global/qrandom \\ + global/qhooks \\ + io/qabstractfileengine \\ + io/qbuffer \\ + io/qdataurl \\ + io/qtldurl \\ + io/qdebug \\ + io/qdir \\ + io/qdiriterator \\ + io/qfile \\ + io/qfiledevice \\ + io/qfileinfo \\ + io/qfilesystemwatcher \\ + io/qfilesystemwatcher_polling \\ + io/qipaddress \\ + io/qiodevice \\ + io/qlockfile \\ + io/qnoncontiguousbytedevice \\ + io/qprocess \\ + io/qsettings \\ + io/qstorageinfo \\ + io/qtemporarydir \\ + io/qtemporaryfile \\ + io/qresource \\ + io/qresource_iterator \\ + io/qsavefile \\ + io/qstandardpaths \\ + io/qurl \\ + io/qurlidna \\ + io/qurlquery \\ + io/qurlrecode \\ + io/qfsfileengine \\ + io/qfsfileengine_iterator \\ + io/qfilesystementry \\ + io/qfilesystemengine \\ + io/qfileselector \\ + io/qloggingcategory \\ + io/qloggingregistry \\ + itemmodels/qabstractitemmodel \\ + itemmodels/qabstractproxymodel \\ + itemmodels/qconcatenatetablesproxymodel \\ + itemmodels/qidentityproxymodel \\ + itemmodels/qitemselectionmodel \\ + itemmodels/qsortfilterproxymodel \\ + itemmodels/qstringlistmodel \\ + itemmodels/qtransposeproxymodel \\ + kernel/qabstracteventdispatcher \\ + kernel/qabstractnativeeventfilter \\ + kernel/qbasictimer \\ + kernel/qcoreapplication \\ + kernel/qcoreevent \\ + kernel/qcoreglobaldata \\ + kernel/qdeadlinetimer \\ + kernel/qelapsedtimer \\ + kernel/qeventloop \\ + kernel/qmetaobject \\ + kernel/qmetaobjectbuilder \\ + kernel/qmetatype \\ + kernel/qmath \\ + kernel/qmimedata \\ + kernel/qobject \\ + kernel/qobjectcleanuphandler \\ + kernel/qpointer \\ + kernel/qsharedmemory \\ + kernel/qsignalmapper \\ + kernel/qsocketnotifier \\ + kernel/qsystemerror \\ + kernel/qsystemsemaphore \\ + kernel/qtestsupport_core \\ + kernel/qtimer \\ + kernel/qtranslator \\ + kernel/qvariant \\ + mimetypes/qmimedatabase \\ + mimetypes/qmimetype \\ + mimetypes/qmimemagicrulematcher \\ + mimetypes/qmimetypeparser \\ + mimetypes/qmimemagicrule \\ + mimetypes/qmimeglobpattern \\ + mimetypes/qmimeprovider \\ + plugin/qelfparser_p \\ + plugin/qfactoryinterface \\ + plugin/qfactoryloader \\ + plugin/qlibrary \\ + plugin/qmachparser \\ + plugin/qpluginloader \\ + plugin/quuid \\ + serialization/qbinaryjson \\ + serialization/qbinaryjsonarray \\ + serialization/qbinaryjsonobject \\ + serialization/qbinaryjsonvalue \\ + serialization/qcborcommon \\ + serialization/qcbordiagnostic \\ + serialization/qcborstreamreader \\ + serialization/qcborstreamwriter \\ + serialization/qcborvalue \\ + serialization/qdatastream \\ + serialization/qjsoncbor \\ + serialization/qjsondocument \\ + serialization/qjsonobject \\ + serialization/qjsonarray \\ + serialization/qjsonvalue \\ + serialization/qjsonwriter \\ + serialization/qjsonparser \\ + serialization/qtextstream \\ + serialization/qxmlstream \\ + serialization/qxmlutils \\ + statemachine/qstatemachine \\ + statemachine/qabstractstate \\ + statemachine/qeventtransition \\ + statemachine/qstate \\ + statemachine/qfinalstate \\ + statemachine/qhistorystate \\ + statemachine/qabstracttransition \\ + statemachine/qsignaltransition \\ + text/qbytearray \\ + text/qbytearraylist \\ + text/qbytearraymatcher \\ + text/qcollator \\ + text/qcollator_icu \\ + text/qharfbuzz \\ + text/qlocale \\ + text/qlocale_icu \\ + text/qlocale_tools \\ + text/qregexp \\ + text/qregularexpression \\ + text/qstring \\ + text/qstringbuilder \\ + text/qstringlist \\ + text/qstringview \\ + text/qtextboundaryfinder \\ + text/qunicodetools \\ + text/qvsnprintf \\ + thread/qatomic \\ + thread/qexception \\ + thread/qfutureinterface \\ + thread/qfuturewatcher \\ + thread/qmutex \\ + thread/qreadwritelock \\ + thread/qresultstore \\ + thread/qrunnable \\ + thread/qsemaphore \\ + thread/qthread \\ + thread/qthreadpool \\ + thread/qthreadstorage \\ + time/qdatetime \\ + time/qdatetimeparser \\ + time/qcalendar \\ + time/qgregoriancalendar \\ + time/qhijricalendar \\ + time/qislamiccivilcalendar \\ + time/qjalalicalendar \\ + time/qjuliancalendar \\ + time/qmilankoviccalendar \\ + time/qromancalendar \\ + time/qtimezone \\ + time/qtimezoneprivate \\ + tools/qarraydata \\ + tools/qbitarray \\ + tools/qcommandlineoption \\ + tools/qcommandlineparser \\ + tools/qcryptographichash \\ + tools/qeasingcurve \\ + tools/qfreelist \\ + tools/qhash \\ + tools/qline \\ + tools/qlinkedlist \\ + tools/qlist \\ + tools/qpoint \\ + tools/qmap \\ + tools/qmargins \\ + tools/qmessageauthenticationcode \\ + tools/qcontiguouscache \\ + tools/qrect \\ + tools/qrefcount \\ + tools/qringbuffer \\ + tools/qshareddata \\ + tools/qsharedpointer \\ + tools/qsimd \\ + tools/qsize \\ + tools/qtimeline \\ + tools/qversionnumber + + # Headers to be moc'ed. + # + moc_hdr = qabstracteventdispatcher \\ + qabstractanimation \\ + qabstractitemmodel \\ + qabstractproxymodel \\ + qabstractstate \\ + qabstracttransition \\ + qanimationgroup \\ + qbuffer \\ + qcalendar \\ + qcborcommon \\ + qcborstreamreader \\ + qcborvalue \\ + qconcatenatetablesproxymodel \\ + qcoreapplication \\ + qcoreevent \\ + qcryptographichash \\ + qeasingcurve \\ + qeventloop \\ + qeventtransition \\ + qfile \\ + qfiledevice \\ + qfileselector \\ + qfilesystemwatcher \\ + qfinalstate \\ + qfuturewatcher \\ + qhistorystate \\ + qidentityproxymodel \\ + qiodevice \\ + qitemselectionmodel \\ + qlocale \\ + qlibrary \\ + qmimedata \\ + qmimetype \\ + qnamespace \\ + qobject \\ + qobjectcleanuphandler \\ + qparallelanimationgroup \\ + qpauseanimation \\ + qpluginloader \\ + qprocess \\ + qpropertyanimation \\ + qsavefile \\ + qsequentialanimationgroup \\ + qsettings \\ + qsignalmapper \\ + qsignaltransition \\ + qsharedmemory \\ + qsocketnotifier \\ + qsortfilterproxymodel \\ + qstandardpaths \\ + qstate \\ + qstatemachine \\ + qstringlistmodel \\ + qtemporaryfile \\ + qthread \\ + qthreadpool \\ + qtimeline \\ + qtimer \\ + qtranslator \\ + qtransposeproxymodel \\ + qvariantanimation \\ + private/qabstractanimation_p \\ + private/qeventdispatcher_cf_p \\ + private/qeventdispatcher_unix_p \\ + private/qeventdispatcher_win_p \\ + private/qfactoryloader_p \\ + private/qfilesystemwatcher_fsevents_p \\ + private/qfilesystemwatcher_inotify_p \\ + private/qfilesystemwatcher_kqueue_p \\ + private/qfilesystemwatcher_p \\ + private/qfilesystemwatcher_polling_p \\ + private/qfilesystemwatcher_win_p \\ + private/qnoncontiguousbytedevice_p \\ + private/qwindowspipereader_p \\ + private/qwindowspipewriter_p \\ + private/qtextstream_p + + # Source files to be moc'ed. + # + moc_src = io/qfilesystemwatcher_win \\ + kernel/qtimer \\ + statemachine/qhistorystate \\ + statemachine/qstatemachine \\ + thread/qthreadpool + + moc_mm = kernel/qeventdispatcher_cf + + # UNIX source files. + # + unix_src = io/forkfd_qt \\ + io/qfsfileengine_unix \\ + io/qfilesystemengine_unix \\ + io/qlockfile_unix \\ + io/qfilesystemiterator_unix \\ + io/qprocess_unix \\ + kernel/qcore_unix \\ + kernel/qeventdispatcher_unix \\ + kernel/qsharedmemory_posix \\ + kernel/qsharedmemory_systemv \\ + kernel/qsharedmemory_unix \\ + kernel/qsystemsemaphore_posix \\ + kernel/qsystemsemaphore_systemv \\ + kernel/qsystemsemaphore_unix \\ + kernel/qtimerinfo_unix \\ + plugin/qlibrary_unix \\ + thread/qthread_unix \\ + thread/qwaitcondition_unix + + # Linux source files. + # + linux_src = io/qfilesystemwatcher_inotify + + # Windows source files. + # + win_src = global/qoperatingsystemversion_win \\ + io/qfilesystemwatcher_win \\ + io/qprocess_win \\ + io/qsettings_win \\ + io/qfsfileengine_win \\ + io/qlockfile_win \\ + io/qfilesystemengine_win \\ + io/qfilesystemiterator_win \\ + io/qstandardpaths_win \\ + io/qstorageinfo_win \\ + io/qwindowspipereader \\ + io/qwindowspipewriter \\ + kernel/qcoreapplication_win \\ + kernel/qelapsedtimer_win \\ + kernel/qeventdispatcher_win \\ + kernel/qsharedmemory_win \\ + kernel/qsystemsemaphore_win \\ + kernel/qwineventnotifier \\ + kernel/qwinregistry \\ + plugin/qsystemlibrary \\ + plugin/qlibrary_win \\ + text/qlocale_win \\ + thread/qthread_win \\ + thread/qwaitcondition_win + + # Mac OS source files. + # + macos_src = io/qsettings_mac \\ + io/qstorageinfo_mac \\ + kernel/qcfsocketnotifier \\ + kernel/qcoreapplication_mac \\ + kernel/qelapsedtimer_mac + + # Define the Objective-C++ source file type. + # + define mm: file + mm{*}: extension = mm + + macos_mm = global/qoperatingsystemversion_darwin \\ + io/qfilesystemengine_mac \\ + io/qfilesystemwatcher_fsevents \\ + io/qprocess_darwin \\ + io/qstandardpaths_mac \\ + kernel/qcore_foundation \\ + kernel/qcore_mac \\ + kernel/qeventdispatcher_cf \\ + text/qlocale_mac \\ + time/qtimezoneprivate_mac + + # C sources from the Harfbuzz library. + # + # @@ TMP Package this? (And double-conversion?) + # + harfbuzz_src = harfbuzz-buffer \\ + harfbuzz-gdef \\ + harfbuzz-gsub \\ + harfbuzz-gpos \\ + harfbuzz-impl \\ + harfbuzz-open \\ + harfbuzz-stream + + # Generated headers. + # + gen_hdr = qconfig private/qconfig_p \\ + qtcore-config private/qtcore-config_p + + # Force headers that are included by C source files to be C headers otherwise + # they are most likely to be detected as C++ headers due to being included + # most often from C++ source files, which could break build2's header + # dependency extraction. + # + c_hdr = qglobal qsystemdetection qprocessordetection qcompilerdetection \\ + $gen_hdr + + # Headers, source files, and libraries. + # + lib{Qt5Core}: hxx{** -{$c_hdr} +Q*.} h{$c_hdr} cxx{$src} \\ + 3rdparty/harfbuzz/src/h{harfbuzz} \\ + 3rdparty/harfbuzz/src/c{$harfbuzz_src} \\ + 3rdparty/harfbuzz/src/cxx{harfbuzz-shaper-all} \\ + 3rdparty/double-conversion/cxx{*.cc} \\ + 3rdparty/double-conversion/hxx{**} + + # Ensure included source files are distributed. + # + # @@ TMP thread/qmutex_{linux,unix,mac,win}.cpp is included by + # thread/qmutex.cpp, but thread/thread.pri adds it to SOURCES, meaning + # the upstream build should compile it as well (which would lead to + # multiple definition errors). This is confirmed by the fact that the + # makefile generated by `qmake + # upstream/qtbase/src/corelib/thread/thread.pri` has qmutex_linux.cpp + # in SOURCES. + # + # However, the upstream build, when run, doesn't actually compile it, + # and its makefile doesn't have qmutex_linux.cpp in SOURCES either. + # + # No idea what's going on but what we have here looks right and works + # and matches what the upstream build actually ends up doing. + # + # @@ TMP mimetypes/qmimeprovider_database.cpp (note: ~2MB) is generated by + # mimetypes/mime/generate.{pl,bat} from + # mimetypes/mime/packages/freedesktop.org.xml which is from + # http://www.freedesktop.org/wiki/Software/shared-mime-info/. This doesn't + # seem like it should change often so perhaps it's not necessary for us to + # generate it? + # + lib{Qt5Core}: cxx{** -{$src}} \\ + 3rdparty/{h hxx c cxx}{** -harfbuzz/**} \\ + 3rdparty/harfbuzz/{cxx c}{** \\ + -harfbuzz-shaper-all -{$harfbuzz_src}} \\ + 3rdparty/harfbuzz/h{** -harfbuzz} \\ + 3rdparty/sha3/file{**.macros} \\ + testlib/3rdparty/h{valgrind_p}: \\ + include = adhoc + + # The "metadata library": its purpose is to make sure all the imported + # libraries are resolved for the ad hoc .mm compilation rules below. + # + # @@ TMP Using cxx{dummy} as link rule hint (added `using c` recently). + # + libul{Qt5CoreMeta}: mkspecs/features/data/cxx{dummy} $impl_libs $intf_libs + + # Generated headers and source files. + # + lib{Qt5Core}: global/cxx{qconfig-install}: for_install = true + lib{Qt5Core}: global/cxx{qconfig-develop}: for_install = false + + # Platform-specific source files. + # + lib{Qt5Core}: cxx{$unix_src}: include = $unix + lib{Qt5Core}: cxx{$linux_src}: include = $linux + lib{Qt5Core}: cxx{$win_src}: include = $windows + lib{Qt5Core}: cxx{$macos_src}: include = $macos + lib{Qt5Core}: cxx{io/qfilesystemwatcher_kqueue}: include = $bsd + lib{Qt5Core}: cxx{io/qstandardpaths_unix \\ + io/qstorageinfo_unix \\ + kernel/qelapsedtimer_unix \\ + text/qlocale_unix \\ + time/qtimezoneprivate_tz}: include = ($unix && ! $macos) + lib{Qt5Core}: cxx{time/qtimezoneprivate_icu}: include = (! $macos) + + # Declare the dependency of the library target on the Objective-C++ source + # files via the corresponding object files. + # + for n: $macos_mm + { + obja{$(n).a.o}: mm{$n} + objs{$(n).so.o}: mm{$n} + } + + liba{Qt5Core}: obja{$regex.apply($macos_mm,'(.+)','\1.a.o')}: \\ + include = $macos + libs{Qt5Core}: objs{$regex.apply($macos_mm,'(.+)','\1.so.o')}: \\ + include = $macos + + # Rules for compiling Objective-C++ source files into object files. + # + # Note: these rules are only used on Mac OS (so no -fPIC, etc). + # Note: exclude libua{Qt5CoreMeta} from update during match not to mess + # up its for-install'ness. + # + obja{~'/(.*).a/'}: mm{~'/\1/'} libua{Qt5CoreMeta} + {{ + dep_poptions = $cxx.lib_poptions(libua{Qt5CoreMeta}, obja) + depdb hash $dep_poptions + depdb dyndep "-I$out_root/QtCore" "-I$src_root/QtCore" \\ + --what=header --default-type=h \\ + --update-exclude libua{Qt5CoreMeta} \\ + -- $cxx.path $cc.poptions $cxx.poptions $dep_poptions \\ + $cc.coptions $cxx.coptions $cxx.mode -M -MG $path($<[0]) + diag obj-c++ ($<[0]) + $cxx.path $cc.poptions $cxx.poptions $dep_poptions \\ + $cc.coptions $cxx.coptions $cxx.mode \\ + -o $path($>) -c -x objective-c++ $path($<[0]) + }} + + objs{~'/(.*).so/'}: mm{~'/\1/'} libus{Qt5CoreMeta} + {{ + dep_poptions = $cxx.lib_poptions(libus{Qt5CoreMeta}, objs) + depdb hash $dep_poptions + depdb dyndep "-I$out_root/QtCore" "-I$src_root/QtCore" \\ + --what=header --default-type=h \\ + --update-exclude libus{Qt5CoreMeta} \\ + -- $cxx.path $cc.poptions $cxx.poptions $dep_poptions \\ + $cc.coptions $cxx.coptions $cxx.mode -M -MG $path($<[0]) + diag obj-c++ ($<[0]) + $cxx.path $cc.poptions $cxx.poptions $dep_poptions \\ + $cc.coptions $cxx.coptions $cxx.mode \\ + -o $path($>) -c -x objective-c++ $path($<[0]) + }} + + # Rule to generate a header with C++ compiler's predefined macros. It is used + # to make them available to moc. + # + # This is necessary because moc's (built-in) preprocessor evaluates conditions + # and expands macros and thus the set of defined macros can affect its + # output. For example, signals and/or slots can be defined conditionally. + # + # Not all compilers have a mode that produces such a macro list. GCC, Clang, + # and the Intel compiler do, but MSVC (for one) does not. + # + # The upstream build does not generate this header unless the compiler + # supports such a mode (that is, moc gets no macro definitions at all). The + # only exception is MSVC in which case Qt5 uses qmake to produce the required + # output (by passing /Bx to cl.exe, specifying an alternative + # preprocessor executable), and Qt6 (via CMake) does nothing except for adding + # the WIN32 macro to moc's definitions. We emulate the Qt6 behavior here for + # now. + # + # Note also that this approach is not limited to the Qt libraries themselves: + # applications may have preprocessor directives that affect the moc output and + # thus will either need to do something similar or supply the necessary macro + # definitions manually. As a result, it may make sense to provide a utility + # (probably as part of the moc package, maybe call it moc-predefs) that knows + # how to extract this list for various compilers (we could just pass to it the + # compiler id and maybe the target followed by the compiler command line and + # it can decide on the best method, potentially invoking the compiler, + # including for MSVC). + # + hxx{moc_predefs}: mkspecs/features/data/cxx{dummy} + % + if ($cxx.id != 'msvc') + {{ + diag gen ($>[0]) + $cxx.path $cc.poptions $cxx.poptions $cc.coptions $cxx.coptions $cxx.mode \\ + -o $path($>) -dM -E $path($<) + }} + else + {{ + diag gen ($>[0]) + cat <$path($>) + #define _WIN32 + #define WIN32 + EOI + }} + + # Dependencies involving source files generated by moc from headers. + # + # @@ Have to list hxx{moc_predefs} here to make dist work -- why? + # + # @@ TMP Is this comment still relevant? moc_predefs is now only a + # dependency in the ad hoc rule below. + # + for s: hxx{$moc_hdr} + { + n = $name($s) + lib{Qt5Core}: cxx{moc_$n}: include = adhoc + cxx{moc_$n}: $s + } + + # Target type for source file generated by moc from a source file. + # + define moc: cxx + moc{*}: extension = moc + + # Dependencies involving source files generated by moc from source files. + # + for s: cxx{$moc_src} mm{$moc_mm} + { + n = $name($s) + lib{Qt5Core}: moc{$n}: include = adhoc + moc{$n}: $s + } + + # Rule to run moc on a header file. + # + # Use -f to override the path moc uses to #include the input file, which is + # relative to the output directory, with just the name of the input file. + # + # Explicitly list the generated headers as dependencies in case the + # moc-generated file includes any of them (if only indirectly). Note that, + # because moc's `--output-dep-file` mode (which produces a list of header + # dependencies for the input file) silently omits non-existent headers from + # its output, it does not support generated headers and thus there is no + # choice but to declare these dependencies manually. + # + # Note that hxx{moc_predefs} must always be in position 1. + # + cxx{~'/moc_(.*)/'}: hxx{~'/\1/'} hxx{moc_predefs} h{$gen_hdr} $moc + {{ + o = $path($>[0]) + t = $(o).t + + depdb dyndep \\ + --byproduct --drop-cycles --what=header --default-type=h --file $t + + diag moc ($<[0]) + + s = $path($<[0]) + + $moc --include $path($<[1]) $cc.poptions $cxx.poptions -f $leaf($s) \\ + --output-dep-file --dep-file-path $t -o $o $s + }} + + # Rule to run moc on a source file. + # + # See the header-input moc rule regarding the header prerequisites. + # + moc{~'/(.*)/'}: cxx{~'/\1/'} hxx{moc_predefs} h{$gen_hdr} $moc + {{ + o = $path($>[0]) + t = $(o).t + + depdb dyndep \\ + --byproduct --drop-cycles --what=header --default-type=h --file $t + + diag moc ($<[0]) + + s = $path($<[0]) + + $moc --include $path($<[1]) $cc.poptions $cxx.poptions \\ + --output-dep-file --dep-file-path $t -o $o $s + }} + + # Rule to run moc on an Objective-C++ source file. + # + # See the header-input moc rule regarding the header prerequisites. + # + moc{~'/(.*)/'}: mm{~'/\1/'} hxx{moc_predefs} h{$gen_hdr} $moc + {{ + o = $path($>[0]) + t = $(o).t + + depdb dyndep \\ + --byproduct --drop-cycles --what=header --default-type=h --file $t + + diag moc ($<[0]) + + s = $path($<[0]) + + $moc --include $path($<[1]) $cc.poptions $cxx.poptions \\ + --output-dep-file --dep-file-path $t -o $o $s + }} + + # Ensure that a moc-generated target's moc-generated dependencies are updated + # before it itself is updated. This is necessary because a moc target's + # dependencies are extracted at the same time as it is updated. + # + # moc{foo} depends on cxx{moc_foo} because moc{foo} is generated from cxx{foo} + # which includes cxx{moc_foo}. (Note that other generated dependencies common + # to all moc-generated targets are declared above.) + # + moc{qeventdispatcher_cf}: cxx{moc_qeventdispatcher_cf_p} + moc{qhistorystate}: cxx{moc_qhistorystate} + moc{qstatemachine}: cxx{moc_qstatemachine} + moc{qthreadpool}: cxx{moc_qthreadpool} + moc{qtimer}: cxx{moc_qtimer} + + # A small minority of moc-generated files need to be compiled (most are + # included). And that's not where the inconsistency ends: + # qfilesystemwatcher_win.cpp includes qfilesystemwatcher_win.moc but not + # moc_qfilesystemwatcher_win.cpp, so the latter has to be compiled. + # + lib{Qt5Core}: cxx{moc_qcalendar moc_qmimetype} + + lib{Qt5Core}: cxx{moc_qeventdispatcher_win_p \\ + moc_qfilesystemwatcher_win_p \\ + moc_qwindowspipereader_p \\ + moc_qwindowspipewriter_p}: include = $windows + + lib{Qt5Core}: cxx{moc_qfilesystemwatcher_kqueue_p}: include = $bsd + lib{Qt5Core}: cxx{moc_qfilesystemwatcher_fsevents_p}: include = $macos + + lib{Qt5Core}: libul{Qt5CoreMeta} + + # Generated headers. + # + # In the Qt source code the features (QT_FEATURE_foo macros) are checked using + # the QT_CONFIG(foo) macro (defined in qglobal.h) which will not compile + # unless the macro is defined and has a value of 1 or -1. + # + # @@ To find usage instances, grep under ../upstream/qtbase/ for + # '(qtConfig|QT_CONFIG)\(\)' + # + # The files qtbase/configure.json and qtbase/src/corelib/configure.json are + # the sources used by qtbase/mkspecs/features/qt_configure.prf to generate + # qtconfig{,_p}.h and qtcore-config{,_p}.h, respectively. Entries in the + # `features` array in configure.json files correspond to .prf files under + # upstream/qtbase/mkspecs/features/. See https://wiki.qt.io/Qt5_Build_System. + # + # The configure.json files declare dependencies between features so be sure to + # consult them before disabling or enabling any features. + # + + # General public configuration header. Included by other Qt libraries via + # QtCore/qglobal.h. + # + # See the .in files for more information. + # + h{qconfig}: in{qconfig} + { + QT_VERSION_STR="$version.major.$version.minor.$version.patch" + QT_VERSION_MAJOR="$version.major" + QT_VERSION_MINOR="$version.minor" + QT_VERSION_PATCH="$version.patch" + + QT_FEATURE_framework = ($macos ? 1 : -1) + + # Given that SSE4.2 support was added to GCC in version 4.3 (2008), and + # AVX512 support in version 4.9 (2014), assume all versions of SSE and all + # versions of AVX are supported on all i686 and x86_64 compilers. Disable + # SSE and AVX on all other targets. + # + compiler_supports_sse_avx = ($x86 ? 1 : -1) + + QT_COMPILER_SUPPORTS_F16C = ($f16c ? 1 : -1) + } + + # General private configuration header. Included by a few other Qt libraries + # via QtCore/qglobal_p.h. + # + # See the .in file for more information. + # + private/h{qconfig_p}: private/in{qconfig_p} + { + QT_FEATURE_dlopen = ($unix ? 1 : -1) + QT_FEATURE_posix_fallocate = ($linux || $freebsd || $openbsd ? 1 : -1) + } + + # Public QtCore library configuration (mostly high-level features). Included + # by other Qt libraries via QtCore/qglobal.h. + # + # See the .in file for more information. + # + h{qtcore-config}: in{qtcore-config} + + # Private QtCore library configuration (lower-level features and + # settings). Included by a few other Qt libraries via QtCore/qglobal_p.h. + # + # See the .in file for more information. + # + private/h{qtcore-config_p}: private/in{qtcore-config_p} + { + QT_FEATURE_futimens = ($windows ? -1 : 1) + QT_FEATURE_getauxval = ($linux ? 1 : -1) + QT_FEATURE_glibc = ($linux ? 1 : -1) + QT_FEATURE_inotify = ($linux ? 1 : -1) + QT_FEATURE_linkat = ($linux ? 1 : -1) + QT_FEATURE_syslog = ($windows ? -1 : 1) + + QT_FEATURE_poll_select = -1 + QT_FEATURE_poll_ppoll = ($linux || $freebsd || $openbsd ? 1 : -1) + QT_FEATURE_poll_poll = ($macos ? 1 : -1) + QT_FEATURE_poll_pollts = ($netbsd ? 1 : -1) + } + + # global/qconfig.cpp: + # + # This file provides information relating to installation paths. It is + # included by global/qlibraryinfo.cpp (so we don't compile it). In upstream it + # is generated during the configure step (qtbase/configure.pri). + # + # In order to support different installation paths for the installed and + # uninstalled cases in build2 we turn qconfig.cpp into a real file (that is, + # not generated) and turn its static variable and macro definitions into + # extern variable declarations. Then we generate two source files, + # qconfig-install.cpp and qconfig-develop.cpp, that define the variables to + # values appropriate for the installed and uninstalled cases, respectively, + # and we use (above) the `for_install` prerequisite-specific variable to + # control which of the two gets linked in each case. + # + # See also comments in qconfig.cpp.in. + # + lib{Qt5Core}: global/cxx{qconfig}: include = adhoc + + # Installed case: Here the installation prefix is an absolute path and the + # other paths are all relative (to the prefix). + # + # Note that while this object file should only be linked when we are + # installing, it will be compiled even in the uninstalled case where we may + # have no install.root. + # + global/cxx{qconfig-install}: + { + i = ($install.root != [null]) + + # Values of the installation paths. Make the $install.* paths relative to + # $install.root. + # + prefix = [dir_path] ($i ? $install.root : .) + doc = [dir_path] ($i ? $leaf($install.resolve($install.doc), $prefix) : .) + incl = [dir_path] ($i ? $leaf($install.resolve($install.include), $prefix) : .) + lib = [dir_path] ($i ? $leaf($install.resolve($install.lib), $prefix) : .) + bin = [dir_path] ($i ? $leaf($install.resolve($install.bin), $prefix) : .) + libexec = [dir_path] ($i ? $leaf($install.resolve($windows ? $install.bin : $install.libexec), $prefix) : .) + plugins = [dir_path] $lib + imports = [dir_path] $lib + qml = [dir_path] $lib + data = [dir_path] ($i ? $leaf($install.resolve($install.data), $prefix) : .) + arch_data = [dir_path] $data + translations = [dir_path] translations + examples = [dir_path] examples + tests = [dir_path] tests + + # A path appended to the path of the directory containing the installed + # QtCore library to get to the prefix path. + # + # For example, if the prefix is /usr/local and the library is installed in + # /usr/local/lib, upstream would do "/usr/local/lib" + "/" + "../" to get + # "/usr/local". + # + lib_to_prefix = [dir_path] ($i ? $relative($prefix, $install.resolve($install.lib)) : .) + } + + # Uninstalled case: Here the installation prefix is the filesystem root and + # the other directories are all absolute paths (because the source and output + # directories may not have a longer common prefix). + # + assert ($root_directory($out_root) == $root_directory($src_root)) \\ + "out_root and src_root must have common filesystem root" + + global/cxx{qconfig-develop}: + { + # Values of the installation paths. + # + prefix = [dir_path] $root_directory($out_root) + doc = [dir_path] $out_root/doc + incl = [dir_path] $src_root + lib = [dir_path] $out_root + libexec = [dir_path] $out_root + bin = [dir_path] $out_root + plugins = [dir_path] $out_root/plugins + imports = [dir_path] $out_root + qml = [dir_path] $out_root + arch_data = [dir_path] $src_root + data = [dir_path] $src_root + translations = [dir_path] $out_root/translations + examples = [dir_path] $src_root/examples + tests = [dir_path] $src_root/tests + + # See comment in the installed case above. + # + lib_to_prefix = [dir_path] $relative($prefix, $lib) + } + + # Installed and uninstalled cases. + # + global/cxx{qconfig-install qconfig-develop}: global/in{qconfig} + { + # The directory path containing user application settings. Ignored on + # Windows where the registry is used instead. + # + settingspath = ($macos ? /Library/Preferences/Qt : etc/xdg) + + # The offsets of the beginning of each substring within the + # qt_configure_strs string which contains the installation path values + # defined above. + # + o_doc = [uint64] 0 + o_incl = $size($doc) + o_incl += 1 + o_incl += $o_doc + o_lib = $size($incl) + o_lib += 1 + o_lib += $o_incl + o_libexec = $size($lib) + o_libexec += 1 + o_libexec += $o_lib + o_bin = $size($libexec) + o_bin += 1 + o_bin += $o_libexec + o_plugins = $size($bin) + o_plugins += 1 + o_plugins += $o_bin + o_imports = $size($plugins) + o_imports += 1 + o_imports += $o_plugins + o_qml = $size($imports) + o_qml += 1 + o_qml += $o_imports + o_arch_data = $size($qml) + o_arch_data += 1 + o_arch_data += $o_qml + o_data = $size($arch_data) + o_data += 1 + o_data += $o_arch_data + o_translations = $size($data) + o_translations += 1 + o_translations += $o_data + o_examples = $size($translations) + o_examples += 1 + o_examples += $o_translations + o_tests = $size($examples) + o_tests += 1 + o_tests += $o_examples + + # Escape backslashes on Windows. Note: must be done after offset + # calculations. + # + prefix = [dir_path] $regex.replace($prefix, '\\', '\\\\') + doc = [dir_path] $regex.replace($doc, '\\', '\\\\') + incl = [dir_path] $regex.replace($incl, '\\', '\\\\') + lib = [dir_path] $regex.replace($lib, '\\', '\\\\') + libexec = [dir_path] $regex.replace($libexec, '\\', '\\\\') + bin = [dir_path] $regex.replace($bin, '\\', '\\\\') + plugins = [dir_path] $regex.replace($plugins, '\\', '\\\\') + imports = [dir_path] $regex.replace($imports, '\\', '\\\\') + qml = [dir_path] $regex.replace($qml, '\\', '\\\\') + arch_data = [dir_path] $regex.replace($arch_data, '\\', '\\\\') + data = [dir_path] $regex.replace($data, '\\', '\\\\') + translations = [dir_path] $regex.replace($translations, '\\', '\\\\') + examples = [dir_path] $regex.replace($examples, '\\', '\\\\') + tests = [dir_path] $regex.replace($tests, '\\', '\\\\') + + lib_to_prefix = [dir_path] $regex.replace($lib_to_prefix, '\\', '\\\\') + } + + # Build options. + # + # Add QtCore/3rdparty/ for include of (in + # double-conversion/fixed-dtoa.cc)\; QtCore/3rdparty/double-conversion/include/ + # for includes of \; QtCore/3rdparty/harfbuzz/src/ for an + # include of \; and QtCore/3rdparty/forkfd/ for an include + # of . + # + cxx.poptions =+ "-I$out_root" "-I$src_root" \\ + "-I$out_root/QtCore" "-I$src_root/QtCore" \\ + "-I$out_root/QtCore/private" "-I$src_root/QtCore/private" \\ + "-I$src_root/QtCore/3rdparty" \\ + "-I$src_root/QtCore/3rdparty/double-conversion/include" \\ + "-I$src_root/QtCore/3rdparty/harfbuzz/src" \\ + "-I$src_root/QtCore/3rdparty/forkfd" + + cxx.poptions += -DQT_BUILDING_QT -DQT_BUILD_CORE_LIB \\ + -DQT_NO_LINKED_LIST \\ + -DQT_NO_JAVA_STYLE_ITERATORS \\ + -DQT_NO_USING_NAMESPACE \\ + -DQT_NO_FOREACH \\ + -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT \\ + -DQT_ASCII_CAST_WARNINGS \\ + -DQT_MOC_COMPAT \\ + -DQT_USE_QSTRINGBUILDER \\ + -DQT_DEPRECATED_WARNINGS \\ + -D_REENTRANT \\ + -DQT_DEPRECATED_WARNINGS_SINCE=0x060000 \\ + -DQT_NO_VERSION_TAGGING + + # @@ TMP Are the hbmi* of any use? + # + {hbmia obja}{*}: cxx.poptions += -DQT_STATIC + {hbmis objs}{*}: cxx.poptions += -DQT_SHARED + + # @@ TODO -DQT_NO_DEBUG (as one would expect, upstream defines it for release + # builds). + # + + # Add platform-specific header directories, libraries, compilation options, + # and macro definitions. + # + if $windows + { + cxx.poptions += -DQT_DISABLE_DEPRECATED_BEFORE=0x040800 \\ + -D_CRT_SECURE_NO_WARNINGS \\ + -D_USE_MATH_DEFINES \\ + -DUNICODE \\ + -D_UNICODE \\ + -DWIN32 + } + else + { + cxx.poptions += -DQT_DISABLE_DEPRECATED_BEFORE=0x050000 \\ + -DQT_NO_CAST_TO_ASCII \\ + -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE + # @@ TODO Boost only adds -ldl if on Linux. + # + lib{Qt5Core}: cxx.libs += -ldl + } + + # @@ TMP Not sure if/when this is necessary. + # + # cxx.libs += -latomic + + # libexecinfo is required for backtrace(3) on BSD (see global/qlogging.cpp). + # + # @@ TMP When we did libboost-stacktrace we considered packaging libbacktrace: + # build2-packaging/boost/downstream/libs/stacktrace/src/buildfile. + # + if $bsd + lib{Qt5Core}: cxx.libs += -lexecinfo + + # If the compiler is always generating F16C code (no special options + # required), have global/qgloat16.cpp include global/qfloat16_f16c.c, + # otherwise compile it separately. Details including the Clang exception are + # explained in global/global.pri but see also the SSE/AVX comments in + # qconfig.h.in. + # + # @@ TMP Assuming the comments regarding special compiler options for + # SSE/AVX/NEON in qconfig.h.in apply to F16C as well. global/global.pri + # seems to make this connection as well (if only for GCC\; x86SimdAlways in + # global.pri corresponds to QT_COMPILER_SUPPORTS_SIMD_ALWAYS in + # qconfig.h.in). + # + # @@ TODO This logic is probably not correct at the moment. qfloat16_f16c.c + # should be included if F16C code is generated without any special compiler + # options, but the upstream Qt6 build chose to compile it on my machine (in + # contradiction of the configure report) which contradicts the logic + # currently used in this buildfile. + # + if ($f16c && ! $regex.match($cxx.id, 'clang.*')) + { + lib{Qt5Core}: global/c{qfloat16_f16c}: include = false + cxx.poptions += -DQFLOAT16_INCLUDE_FAST + } + else + { + lib{Qt5Core}: global/c{qfloat16_f16c}: include = true + + obj{global/qfloat16_f16c}: + { + c.poptions += $cxx.poptions + c.coptions += -mf16c + } + } + + # Every directory under mkspecs/ contains a unique `qplatformdefs.h`. + # + # Note that Mac OS with GCC is not supported by upstream (see README-DEV for + # details). + # + switch $cxx.target.class, $cxx.id, $cxx.target.system + { + case 'linux', 'gcc' + cxx.poptions += "-I$src_root/QtCore/mkspecs/linux-g++" + case 'linux', 'clang' + cxx.poptions += "-I$src_root/QtCore/mkspecs/linux-clang" + case 'macos', 'clang-apple' + { + cxx.poptions += "-I$src_root/QtCore/mkspecs/macx-clang" + lib{Qt5Core}: cxx.libs += -framework AppKit \\ + -framework CoreServices \\ + -framework DiskArbitration \\ + -framework Foundation \\ + -framework IOKit \\ + -framework Security + } + case 'bsd', 'clang', 'freebsd' + cxx.poptions += "-I$src_root/QtCore/mkspecs/freebsd-clang" + case 'bsd', 'gcc', 'openbsd' + cxx.poptions += "-I$src_root/QtCore/mkspecs/openbsd-g++" + case 'bsd', 'gcc', 'netbsd' + cxx.poptions += "-I$src_root/QtCore/mkspecs/netbsd-g++" + case 'windows', 'msvc' + { + cxx.poptions += "-I$src_root/QtCore/mkspecs/win32-msvc" \\ + -D_ENABLE_EXTENDED_ALIGNED_STORAGE + + lib{Qt5Core}: cxx.libs += ole32.lib advapi32.lib shell32.lib \\ + mpr.lib netapi32.lib userenv.lib \\ + version.lib winmm.lib ws2_32.lib + } + case 'windows', 'msvc-clang' | 'clang' + { + cxx.poptions += "-I$src_root/QtCore/mkspecs/win32-clang-msvc" \\ + -D_ENABLE_EXTENDED_ALIGNED_STORAGE + + lib{Qt5Core}: cxx.libs += ole32.lib advapi32.lib shell32.lib \\ + mpr.lib netapi32.lib userenv.lib \\ + version.lib winmm.lib ws2_32.lib + + # @@ The upstream build says it's only clang-cl that needs special + # compiler options "for anything above SSE2" (see + # mkspecs/win32-clang-msvc/qmake.conf) but plain clang does need + # `-msse4.2` at least. + # + obj{tools/qhash}: cxx.coptions += -msse4.2 + } + case 'windows', 'gcc', 'mingw32' + { + cxx.poptions += "-I$src_root/QtCore/mkspecs/win32-g++" \\ + -DMINGW_HAS_SECURE_API=1 + cxx.coptions += -fno-keep-inline-dllexport + + lib{Qt5Core}: cxx.libs += -lole32 -ladvapi32 -lshell32 -lmpr \\ + -lnetapi32 -luuid -luserenv -lversion \\ + -lwinmm -lws2_32 + } + } + + # Export options. + # + lib{Qt5Core}: + { + cxx.export.poptions = "-I$out_root" "-I$src_root" \\ + "-I$out_root/QtCore" "-I$src_root/QtCore" \\ + -DQT_NO_VERSION_TAGGING + cxx.export.libs = $intf_libs + } + + liba{Qt5Core}: cxx.export.poptions += -DQT_STATIC + libs{Qt5Core}: cxx.export.poptions += -DQT_SHARED + + # For pre-releases use the complete version to make sure they cannot be used + # in place of another pre-release or the final version. See the version module + # for details on the version.* variable values. + # + if $version.pre_release + lib{Qt5Core}: bin.lib.version = "-$version.project_id" + else + lib{Qt5Core}: bin.lib.version = "-$version.major.$version.minor" + + # Install public headers into the QtCore/ subdirectory of, say, /usr/include/. + # + private/{hxx h}{*}: install = false + {hxx h}{*}: + { + install = include/QtCore/ + } + } + } + \ + EOF + } + + : reflect-clause + : + { + : valid + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + reflect + { + config.foo.bar=true + config.foo.baz=false + } + } + \ + EOF + + : enable-clause + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + enable ($cxx.target.class == 'windows') + + reflect + { + config.foo.bar=true + config.foo.baz=false + } + } + \ + EOF + + : inline-reflect + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + \ + bar config.foo.bar=true + { + reflect + { + config.foo.bar=true + } + } + \ + EOI + stdin:9:1: error: multi-line dependency form with inline reflect clause + EOE + } + } + + : multiple-alternatives + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + baz | + bar + { + require + { + config.bar.frame=4016 + } + } + | + box + \ + EOF + + : newlines + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + + baz + + | + + bar + + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: baz | bar + EOO + + : missed-alternative + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: baz | + EOI + stdin:6:15: error: dependency expected + EOE + + : missed-dependencies + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: + EOI + stdin:6:9: error: dependency alternatives expected + EOE + + : missed-dependencies-newline + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + + \ + EOI + stdin:7:1: error: dependency alternatives expected + EOE + } + + : requires + : + { + : multiple-requirements + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: {linux zlib >= 1.2.0}; Should already have it. + EOF + + : enable-condition + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: zlib >= 1.2.0 ? ($cxx.target.class == 'linux') + EOF + + : multiple-alternatives + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: * linux ? ($linux) | windows ? ($windows) + EOF + + : enable-condition-reflect + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: zlib >= 1.2.0 ? ($cxx.target.class == 'linux') config.foo.zlib=true + EOF + + : enable-reflect-clauses + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires:\ + zlib >= 1.2.0 + { + enable ($cxx.target.class == 'linux') + + reflect + { + config.foo.zlib=true + } + } + \ + EOF + + : require-clause + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires:\ + libbar >= 1.2.0 + { + require + { + config.libbar.frame=4016 + } + } + \ + EOI + stdin:9:3: error: require clause is not permitted for requirements + EOE + + : simple + : + { + : comment + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ; X11 libs. + EOF + + : no-comment + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: + EOI + stdin:6:10: error: requirement or comment expected + EOE + + : comment-multiline + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires:\ + + ; X11 libs. + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ; X11 libs. + EOO + + : condition + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? ($windows); Only 64-bit. + EOF + + : condition-no-comment + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? ($windows) + EOI + stdin:6:11: error: no comment specified for simple requirement + EOE + + : no-condition + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? ; VC 15 or later if targeting Windows. + EOF + + : no-condition-no-comment + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? + EOI + stdin:6:11: error: no comment specified for simple requirement + EOE + + : no-condition-multiple + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? | foo ; VC 15 or later if targeting Windows. + EOI + stdin:6:13: error: end of simple requirement expected + EOE + + : reflect + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? config.foo.bar=true + EOI + stdin:6:13: error: end of simple requirement expected + EOE + + : with-id + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: x86_64 ? ; Only if on Windows. + EOF + + : with-id-no-comment + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: x86_64 ? + EOI + stdin:6:11: error: no comment specified for simple requirement + EOE + + : with-id-multiple + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: x86_64 ? | foo ; Only if on Windows. + EOI + stdin:6:20: error: end of simple requirement expected + EOE + + : with-id-junk + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: x86_64 ? foo=bar | foo ; Only if on Windows. + EOI + stdin:6:20: error: end of simple requirement expected + EOE + + : buildtime-no-condition + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: * ? ; VC 15 or later if targeting Windows. + EOF + } } : tests @@ -679,7 +2942,7 @@ : invalid-version-range-incomplete : - $* -c <'stdin:6:8: error: invalid package constraint: min version is greater than max version' != 0 + $* -c <"stdin:6:8: error: invalid package constraint '[\$ 1.0.0]': min version is greater than max version" != 0 : 1 name: foo version: 2.0.0 @@ -765,7 +3028,7 @@ license: LGPLv2 tests: bar ~$ EOI - stdin:6:8: error: invalid package constraint: dependent version is not standard + stdin:6:8: error: invalid package constraint '~$': dependent version is not standard EOE } @@ -820,9 +3083,9 @@ build-warning-email: libfoo-issues@example.org; Email for libfoo issues. depends: libz ~1.0.0 | libz ^2.0.0 depends: libgnutls <= 1.2.3 | libopenssl >= 2.3.4 - depends: ? libboost-regex >= 1.52.0; Only if C++ compiler does not support\ - C++11 . - depends: ? libqtcore >= 5.0.0; Only if GUI is enabled. + depends: libboost-regex >= 1.52.0 ? (!$cxx_regex); Only if C++ compiler does\ + not support C++11 . + depends: libqtcore >= 5.0.0 ? ($config.libfoo.gui); Only if GUI is enabled. requires: linux | windows | macosx; symbian is coming. requires: c++11 requires: ? ; VC++ 12.0 or later if targeting Windows. @@ -871,7 +3134,7 @@ { : incomplete : - $* <'stdin:8:10: error: invalid package constraint: $ not allowed' != 0 + $* <"stdin:8:10: error: invalid package constraint '== \$': \$ not allowed" != 0 : 1 sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 : -- cgit v1.1