aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbpkg/buildfile-scanner.cxx38
-rw-r--r--libbpkg/buildfile-scanner.hxx96
-rw-r--r--libbpkg/buildfile-scanner.txx272
-rw-r--r--libbpkg/manifest.cxx1471
-rw-r--r--libbpkg/manifest.hxx248
-rw-r--r--tests/buildfile-scanner/buildfile7
-rw-r--r--tests/buildfile-scanner/driver.cxx106
-rw-r--r--tests/buildfile-scanner/testscript342
-rw-r--r--tests/manifest/driver.cxx29
-rw-r--r--tests/manifest/testscript2279
10 files changed, 4764 insertions, 124 deletions
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 <libbpkg/buildfile-scanner.hxx>
+
+#include <string>
+
+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 <string>
+#include <cstdint> // uint64_t
+#include <cstddef> // size_t
+#include <stdexcept> // runtime_error
+
+#include <libbutl/char-scanner.hxx>
+
+#include <libbpkg/export.hxx>
+
+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 <typename V, std::size_t N>
+ class buildfile_scanner
+ {
+ public:
+ // Note that name is stored by shallow reference.
+ //
+ buildfile_scanner (butl::char_scanner<V, N>& 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<V, N>;
+ 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 <libbpkg/buildfile-scanner.txx>
+
+#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 <cassert>
+
+#include <libbutl/optional.hxx>
+
+namespace bpkg
+{
+ template <typename V, std::size_t N>
+ typename buildfile_scanner<V, N>::xchar buildfile_scanner<V, N>::
+ peek ()
+ {
+ xchar c (scan_.peek (ebuf_));
+
+ if (scanner::invalid (c))
+ throw buildfile_scanning (name_, scan_.line, scan_.column, ebuf_);
+
+ return c;
+ }
+
+ template <typename V, std::size_t N>
+ char buildfile_scanner<V, N>::
+ 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<char> 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<char> (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 <typename V, std::size_t N>
+ std::string buildfile_scanner<V, N>::
+ scan_line (char stop)
+ {
+ std::string r;
+ scan_line (r, stop);
+ return r;
+ }
+
+ template <typename V, std::size_t N>
+ std::string buildfile_scanner<V, N>::
+ scan_eval ()
+ {
+ std::string r;
+ scan_line (r, ')');
+
+ if (peek () != ')')
+ throw buildfile_scanning (name_,
+ scan_.line,
+ scan_.column,
+ "unterminated evaluation context");
+
+ return r;
+ }
+
+ template <typename V, std::size_t N>
+ std::string buildfile_scanner<V, N>::
+ 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 <libbutl/url.hxx>
#include <libbutl/path.hxx>
+#include <libbutl/utf8.hxx>
#include <libbutl/base64.hxx>
#include <libbutl/utility.hxx> // icasecmp(), lcase(), alnum(),
// digit(), xdigit(), next_word()
#include <libbutl/filesystem.hxx> // dir_exist()
#include <libbutl/small-vector.hxx>
+#include <libbutl/char-scanner.hxx>
#include <libbutl/manifest-parser.hxx>
#include <libbutl/manifest-serializer.hxx>
#include <libbutl/standard-version.hxx>
+#include <libbpkg/buildfile-scanner.hxx>
+
using namespace std;
using namespace butl;
@@ -1123,141 +1127,1434 @@ namespace bpkg
// dependency_alternative
//
- dependency_alternative::
- dependency_alternative (const std::string& s)
+ string dependency_alternative::
+ string () const
{
- push_back (dependency (s)); // @@ DEP
+ 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;
}
- string dependency_alternative::
- string () const
+ bool dependency_alternative::
+ single_line () const
{
- assert (size () == 1); // @@ DEP
- return front ().string ();
+ return !prefer &&
+ !require &&
+ (!reflect || reflect->find ('\n') == string::npos);
}
// dependency_alternatives
//
- dependency_alternatives::
- dependency_alternatives (const std::string& s)
+ class dependency_alternatives_lexer: public char_scanner<utf8_validator>
{
- using std::string;
+ public:
+ enum class token_type
+ {
+ eos,
+ newline,
+ word,
+ buildfile,
+
+ question, // ?
+
+ lcbrace, // {
+ rcbrace, // }
+
+ lparen, // (
+ rparen, // )
+
+ lsbrace, // [
+ rsbrace, // ]
- // Allow specifying ?* in any order.
+ 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.
//
- 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);
+ 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<utf8_validator>;
+
+ 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<utf8_validator, 1> 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 ? "<end of stream>" : "";
+ case token_type::newline: return diag ? "<newline>" : "\n";
+ case token_type::word: return q + value + q;
+ case token_type::buildfile: return (diag
+ ? "<buildfile fragment>"
+ : 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 `<what> expected instead of <token>`
+ // 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<string>
+ {
+ 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<string> (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<string> (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<string> 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<string> 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<string> 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");
+
+ if (r.reflect)
+ fail_precede ("reflect");
+
+ r.prefer = parse_block ();
+
+ // The accept clause must follow, so parse it.
+ //
+ next (t, tt);
+
+ if (tt != type::word || t.value != "accept")
+ bad_token ("accept clause");
+
+ r.accept = parse_eval ();
+
+ next (t, tt);
+ expect_token (type::newline);
+ }
+ else if (v == "require")
+ {
+ if (requirements_)
+ fail_requirements ();
+
+ if (r.require)
+ fail_dup ();
+
+ if (r.prefer)
+ fail_conflict ("prefer");
+
+ if (r.reflect)
+ fail_precede ("reflect");
+
+ r.require = parse_block ();
+ }
+ else if (v == "reflect")
+ {
+ if (r.reflect)
+ fail_dup ();
+
+ r.reflect = parse_block ();
+ }
+ else if (v == "accept")
+ {
+ if (requirements_)
+ fail_requirements ();
+
+ 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);
+ }
+ }
+
+ return r;
+ }
+
+ dependency_alternatives_parser::token_type dependency_alternatives_parser::
+ next (token& t, token_type& tt)
+ {
+ t = lexer_->next ();
+ tt = t.type;
+ return tt;
+ }
+
+ dependency_alternatives_parser::token_type dependency_alternatives_parser::
+ next_eval (token& t, token_type& tt)
+ {
+ t = lexer_->next_eval ();
+ tt = t.type;
+ return tt;
+ }
+
+ dependency_alternatives_parser::token_type dependency_alternatives_parser::
+ next_line (token& t, token_type& tt)
+ {
+ t = lexer_->next_line ('|');
+ tt = t.type;
+ return tt;
+ }
+
+ dependency_alternatives_parser::token_type dependency_alternatives_parser::
+ next_block (token& t, token_type& tt)
+ {
+ t = lexer_->next_block ();
+ tt = t.type;
+ return tt;
+ }
+
+ dependency_alternatives::
+ dependency_alternatives (const std::string& s,
+ const package_name& dependent,
+ const std::string& name,
+ uint64_t line,
+ uint64_t column)
+ {
+ using std::string;
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));
- if (empty () && comment.empty ())
- throw invalid_argument ("empty package requirement specification");
+ 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));
+ }
+
+ // 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;
+
+ string r (buildtime ? "* " : "");
- bool f (true);
+ 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<name_value> dependencies;
+ vector<name_value> requirements;
small_vector<name_value, 1> 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 <string>
#include <vector>
#include <cassert>
-#include <cstdint> // uint16_t
+#include <cstdint> // uint*_t
#include <ostream>
#include <utility> // move()
#include <stdexcept> // logic_error
@@ -455,45 +455,175 @@ namespace bpkg
// depends
//
+ // The dependency alternative can be represented in one of the following
+ // forms.
+ //
+ // Single-line form:
+ //
+ // <dependencies> ['?' <enable-condition>] [<reflect-config>]
+ //
+ // <dependencies> = <dependency> |
+ // ({ <dependency> [ <dependency>]* } [<version-constraint>])
+ //
+ // <enable-condition> - buildfile evaluation context
+ // <reflect-config> - 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:
+ //
+ // <dependencies>
+ // {
+ // enable <enable-condition>
+ //
+ // prefer
+ // {
+ // <prefer-config>
+ // }
+ //
+ // accept <accept-condition>
+ //
+ // reflect
+ // {
+ // <reflect-config>
+ // }
+ // }
+ // |
+ // <dependencies>
+ // {
+ // enable <enable-condition>
+ //
+ // require
+ // {
+ // <require-config>
+ // }
+ //
+ // reflect
+ // {
+ // <reflect-config>
+ // }
+ // }
+ //
+ // <prefer-config> - buildfile fragment containing dependency packages
+ // configuration variables assignments
+ //
+ // <accept-condition> - buildfile evaluation context
+ //
+ // <require-config> - buildfile fragment containing dependency packages
+ // configuration variables assignments
+ //
+ // <reflect-config> - 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<dependency, 1>
{
public:
butl::optional<std::string> enable;
+ butl::optional<std::string> reflect;
+ butl::optional<std::string> prefer;
+ butl::optional<std::string> accept;
+ butl::optional<std::string> require;
dependency_alternative () = default;
- dependency_alternative (butl::optional<std::string> e)
- : enable (std::move (e)) {}
-
- // Parse the dependency alternative string representation.
+ dependency_alternative (butl::optional<std::string> e,
+ butl::optional<std::string> r,
+ butl::optional<std::string> p,
+ butl::optional<std::string> a,
+ butl::optional<std::string> 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<dependency_alternative, 1>
{
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
- // `[?][*] <dependency> [ '|' <dependency>]* [; <comment>]` form. Throw
- // std::invalid_argument if the value is invalid. @@ DEP @@ TMP update.
+ // Parse the dependency alternatives string representation in the form:
+ //
+ // [*] <alternative> [ '|' <alternative>]* [; <comment>]
+ //
+ // Where <alternative> 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<std::string, 1>
{
public:
butl::optional<std::string> enable;
+ butl::optional<std::string> reflect;
requirement_alternative () = default;
- requirement_alternative (butl::optional<std::string> e)
- : enable (std::move (e)) {}
+ requirement_alternative (butl::optional<std::string> e,
+ butl::optional<std::string> 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<requirement_alternative, 1>
{
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
- // `[?] [<requirement> [ '|' <requirement>]*] [; <comment>]` form. Throw
- // std::invalid_argument if the value is invalid. @@ DEP @@ TMP update.
+ // following forms:
+ //
+ // [*] <alternative> [ '|' <alternative>]* [; <comment>]
+ // [*] [<requirement-id>] [? [<enable-condition>]] ; <comment>
+ //
+ // 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> // ios_base::failbit, ios_base::badbit
+#include <string>
+#include <iostream>
+
+#include <libbutl/utf8.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream,exception)
+#include <libbutl/char-scanner.hxx>
+
+#include <libbpkg/buildfile-scanner.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace butl;
+using namespace bpkg;
+
+// Usages:
+//
+// argv[0] (-e|-l [<char>]|-b)
+//
+// Read and scan the buildfile from stdin and print the scan result to stdout.
+//
+// -e scan evaluation context
+// -l [<char>] 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<utf8_validator>;
+
+ scanner s (cin);
+
+ string bsn ("stdin");
+ buildfile_scanner<utf8_validator, 1> 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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>EOE != 0
+ ($cxx.target.class == #'windows'
+ EOI
+ stdin:1:33: error: unterminated evaluation context
+ EOE
+
+ : multiline-comment
+ :
+ $* <<EOI 2>>EOE != 0
+ ($cxx.target.class == #\
+ 'windows'
+ #\
+ EOI
+ stdin:3:3: error: unterminated evaluation context
+ EOE
+
+ : multiline-comment-unterminated
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI >>:EOO
+ foo = bar
+ EOI
+ foo = bar
+ EOO
+
+ : no-newline
+ :
+ $* <<:EOF >>:EOF
+ foo = bar
+ EOF
+
+ : eol
+ :
+ $* '|' <<EOI >:'foo = bar '
+ foo = bar | baz
+ EOI
+
+ : single-quoted
+ :
+ $* <<:EOF >>:EOF
+ foo = 'bar'
+ EOF
+
+ : single-quoted-newline
+ :
+ $* <<EOI >>:EOO
+ foo = 'b
+ ar'
+ EOI
+ foo = 'b
+ ar'
+ EOO
+
+ : unterminated-single-quoted
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI >>: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
+ :
+ $* <<EOI >>:EOO
+ foo = # bar
+ EOI
+ foo = # bar
+ EOO
+
+ : empty-comment
+ :
+ $* <<EOI >>:EOO
+ foo = #
+ EOI
+ foo = #
+ EOO
+
+ : multiline-comment
+ :
+ $* <<EOI >>:EOO
+ foo = #\
+ 'windows'
+ #\
+ EOI
+ foo = #\
+ 'windows'
+ #\
+ EOO
+
+ : multiline-comment-unterminated
+ :
+ $* <<EOI 2>>EOE != 0
+ foo = #\
+ bar
+ EOI
+ stdin:3:1: error: unterminated multi-line comment
+ EOE
+
+ : eval
+ :
+ $* <<:EOF >>:EOF
+ foo = bar(baz)fox
+ EOF
+
+ : eval-unterminated
+ :
+ $* <<EOI 2>>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 >>EOF
+ {
+ config.foo.bar = true
+ config.foo.baz = "baz"
+ }
+ EOF
+
+ : quoted
+ :
+ $* <<EOF >>EOF
+ {
+ config.foo.bar = true
+ config.foo.baz = "baz
+ }
+ bar
+ "
+ }
+ EOF
+
+ : nested
+ :
+ $* <<EOF >>EOF
+ {
+ config.foo.bar = true
+
+ if ($cxx.target.class == windows)
+ {
+ config.foo.win = true
+ }
+ else
+ {
+ config.foo.win = false
+ }
+ }
+ EOF
+
+ : comments
+ :
+ $* <<EOF >>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
+ :
+ $* <<EOI 2>>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 <version>
// 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 <<EOI 2>'stdin:6:10: error: invalid package constraint: min version is greater than max version' != 0
+ $* -c <<EOI 2>"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 >>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
+ :
+ $* <<EOI >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 <buildfile fragment>
+ EOE
+ }
+
+ : no-curly-braces
+ :
+ {
+ : dependency
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ depends: bar == 1.0.0
+ EOF
+
+ : invalid-constraint
+ :
+ $* <<EOI 2>>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 >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ depends: bar ? ($cxx.target.class == 'windows')
+ EOF
+
+ : version-constraint
+ :
+ $* <<EOF >>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
+ :
+ $* <<EOI 2>>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 >>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 >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ depends: bar config.foo.bar=true
+ EOF
+
+ : version-constraint
+ :
+ $* <<EOF >>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 >>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 >>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 >>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
+ :
+ $* <<EOI 2>>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 <buildfile fragment>
+ EOE
+ }
+ }
+
+ : multi-line
+ :
+ {
+ : surrounding-newlines
+ :
+ $* <<EOI >>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
+ :
+ $* <<EOI >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>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
+ :
+ $* <<EOF 2>>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
+ :
+ $* <<EOI 2>>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 >>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 >>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
+ :
+ $* <<EOF 2>>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
+ :
+ $* <<EOF 2>>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 >>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<path-to-qmake> 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 <<EOI >$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)\(<feature-name>\)'
+ #
+ # 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 <double-conversion/fixed-dtoa.h> (in
+ # double-conversion/fixed-dtoa.cc)\; QtCore/3rdparty/double-conversion/include/
+ # for includes of <double-conversion/*>\; QtCore/3rdparty/harfbuzz/src/ for an
+ # include of <harfbuzz-shaper.h>\; and QtCore/3rdparty/forkfd/ for an include
+ # of <forkfd.h>.
+ #
+ 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 >>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 >>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
+ :
+ $* <<EOI 2>>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 >>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
+ :
+ $* <<EOI >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>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 >>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 >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ requires: * linux ? ($linux) | windows ? ($windows)
+ EOF
+
+ : enable-condition-reflect
+ :
+ $* <<EOF >>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 >>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
+ :
+ $* <<EOI 2>>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 >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ requires: ; X11 libs.
+ EOF
+
+ : no-comment
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI >>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 >>EOF
+ : 1
+ name: foo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ requires: ? ($windows); Only 64-bit.
+ EOF
+
+ : condition-no-comment
+ :
+ $* <<EOI 2>>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 >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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 >>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 <<EOI 2>'stdin:6:8: error: invalid package constraint: min version is greater than max version' != 0
+ $* -c <<EOI 2>"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 <regex>.
- 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 <regex>.
+ 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
:
- $* <<EOI 2>'stdin:8:10: error: invalid package constraint: $ not allowed' != 0
+ $* <<EOI 2>"stdin:8:10: error: invalid package constraint '== \$': \$ not allowed" != 0
: 1
sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
: