diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | build/root.build | 9 | ||||
-rw-r--r-- | libbpkg/buildfile-scanner.cxx | 38 | ||||
-rw-r--r-- | libbpkg/buildfile-scanner.hxx | 96 | ||||
-rw-r--r-- | libbpkg/buildfile-scanner.txx | 272 | ||||
-rw-r--r-- | libbpkg/manifest.cxx | 4240 | ||||
-rw-r--r-- | libbpkg/manifest.hxx | 1221 | ||||
-rw-r--r-- | libbpkg/manifest.ixx | 412 | ||||
-rw-r--r-- | libbpkg/package-name.hxx | 2 | ||||
-rw-r--r-- | manifest | 10 | ||||
-rw-r--r-- | tests/build-class-expr/driver.cxx | 7 | ||||
-rw-r--r-- | tests/build/root.build | 9 | ||||
-rw-r--r-- | tests/buildfile-scanner/buildfile | 7 | ||||
-rw-r--r-- | tests/buildfile-scanner/driver.cxx | 106 | ||||
-rw-r--r-- | tests/buildfile-scanner/testscript | 342 | ||||
-rw-r--r-- | tests/manifest/driver.cxx | 76 | ||||
-rw-r--r-- | tests/manifest/testscript | 4213 | ||||
-rw-r--r-- | tests/overrides/driver.cxx | 28 | ||||
-rw-r--r-- | tests/overrides/testscript | 423 | ||||
-rw-r--r-- | tests/package-version/driver.cxx | 28 | ||||
-rw-r--r-- | tests/repository-location/driver.cxx | 26 |
22 files changed, 10693 insertions, 880 deletions
@@ -5,10 +5,16 @@ *.d *.t *.i +*.i.* *.ii +*.ii.* *.o *.obj +*.gcm +*.pcm +*.ifc *.so +*.dylib *.dll *.a *.lib @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2020 the build2 authors (see the AUTHORS file). +Copyright (c) 2014-2024 the build2 authors (see the AUTHORS file). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build/root.build b/build/root.build index ad3f3a7..c9ab5dc 100644 --- a/build/root.build +++ b/build/root.build @@ -15,3 +15,12 @@ if ($cxx.target.system == 'win32-msvc') if ($cxx.class == 'msvc') cxx.coptions += /wd4251 /wd4275 /wd4800 +elif ($cxx.id == 'gcc') +{ + cxx.coptions += -Wno-maybe-uninitialized -Wno-free-nonheap-object # libbutl + + if ($cxx.version.major >= 13) + cxx.coptions += -Wno-dangling-reference +} +elif ($cxx.id.type == 'clang' && $cxx.version.major >= 15) + cxx.coptions += -Wno-unqualified-std-cast-call 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 016e88c..065f055 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -4,24 +4,32 @@ #include <libbpkg/manifest.hxx> #include <string> +#include <limits> #include <ostream> #include <sstream> #include <cassert> -#include <cstring> // strncmp(), strcmp() -#include <utility> // move() -#include <cstdint> // uint*_t, UINT16_MAX -#include <algorithm> // find(), find_if_not(), find_first_of(), replace() -#include <stdexcept> // invalid_argument - -#include <libbutl/url.mxx> -#include <libbutl/path.mxx> -#include <libbutl/base64.mxx> -#include <libbutl/utility.mxx> // icasecmp(), lcase(), alnum(), +#include <cstdlib> // strtoull() +#include <cstring> // strncmp(), strcmp(), strchr(), strcspn() +#include <utility> // move() +#include <cstdint> // uint*_t +#include <algorithm> // find(), find_if(), find_first_of(), replace() +#include <stdexcept> // invalid_argument +#include <type_traits> // remove_reference + +#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/small-vector.mxx> -#include <libbutl/manifest-parser.mxx> -#include <libbutl/manifest-serializer.mxx> -#include <libbutl/standard-version.mxx> +#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; @@ -171,12 +179,12 @@ namespace bpkg canonical_upstream ( data_type (upstream.c_str (), data_type::parse::upstream, - false /* fold_zero_revision */). + none). canonical_upstream), canonical_release ( data_type (release ? release->c_str () : nullptr, data_type::parse::release, - false /* fold_zero_revision */). + none). canonical_release) { // Check members constrains. @@ -253,9 +261,12 @@ namespace bpkg } version::data_type:: - data_type (const char* v, parse pr, bool fold_zero_rev) + data_type (const char* v, parse pr, version::flags fl) { - if (fold_zero_rev) + if ((fl & version::fold_zero_revision) != 0) + assert (pr == parse::full); + + if ((fl & version::allow_iteration) != 0) assert (pr == parse::full); // Otherwise compiler gets confused with string() member. @@ -270,32 +281,75 @@ namespace bpkg return; } - assert (v != nullptr); - - optional<uint16_t> ep; - auto bad_arg = [](const string& d) {throw invalid_argument (d);}; - auto uint16 = [&bad_arg](const string& s, const char* what) -> uint16_t + auto parse_uint = [&bad_arg](const string& s, auto& r, const char* what) { - try - { - uint64_t v (stoull (s)); + using type = typename remove_reference<decltype (r)>::type; - if (v <= UINT16_MAX) // From <cstdint>. - return static_cast<uint16_t> (v); - } - catch (const std::exception&) + if (!s.empty () && s[0] != '-' && s[0] != '+') // strtoull() allows these. { - // Fall through. + const char* b (s.c_str ()); + char* e (nullptr); + errno = 0; // We must clear it according to POSIX. + uint64_t v (strtoull (b, &e, 10)); // Can't throw. + + if (errno != ERANGE && + e == b + s.size () && + v <= numeric_limits<type>::max ()) + { + r = static_cast<type> (v); + return; + } } - bad_arg (string (what) + " should be 2-byte unsigned integer"); + bad_arg (string (what) + " should be " + + std::to_string (sizeof (type)) + "-byte unsigned integer"); + }; - assert (false); // Can't be here. - return 0; + auto parse_uint16 = [&parse_uint](const string& s, const char* what) + { + uint16_t r; + parse_uint (s, r, what); + return r; + }; + + auto parse_uint32 = [&parse_uint](const string& s, const char* what) + { + uint32_t r; + parse_uint (s, r, what); + return r; }; + assert (v != nullptr); + + // Parse the iteration, if allowed. + // + // Note that allowing iteration is not very common, so let's handle it in + // an ad hoc way not to complicate the subsequent parsing. + // + string storage; + if (pr == parse::full) + { + iteration = 0; + + // Note that if not allowed but the iteration is present, then the below + // version parsing code will fail with appropriate diagnostics. + // + if ((fl & version::allow_iteration) != 0) + { + if (const char* p = strchr (v, '#')) + { + iteration = parse_uint32 (p + 1, "iteration"); + + storage.assign (v, p - v); + v = storage.c_str (); + } + } + } + + optional<uint16_t> ep; + enum class mode {epoch, upstream, release, revision}; mode m (pr == parse::full ? (v[0] == '+' @@ -350,7 +404,7 @@ namespace bpkg if (lnn >= cb) // Contains non-digits. bad_arg ("epoch should be 2-byte unsigned integer"); - ep = uint16 (string (cb, p), "epoch"); + ep = parse_uint16 (string (cb, p), "epoch"); } else canon_part->add (cb, p, lnn < cb); @@ -423,9 +477,9 @@ namespace bpkg if (lnn >= cb) // Contains non-digits. bad_arg ("revision should be 2-byte unsigned integer"); - std::uint16_t rev (uint16 (cb, "revision")); + uint16_t rev (parse_uint16 (cb, "revision")); - if (rev != 0 || !fold_zero_rev) + if (rev != 0 || (fl & fold_zero_revision) == 0) revision = rev; } else if (cb != p) @@ -512,7 +566,7 @@ namespace bpkg } version& version:: - operator= (version&& v) + operator= (version&& v) noexcept { if (this != &v) { @@ -570,7 +624,8 @@ namespace bpkg } text_file:: - text_file (text_file&& f): file (f.file), comment (move (f.comment)) + text_file (text_file&& f) noexcept + : file (f.file), comment (move (f.comment)) { if (file) new (&path) path_type (move (f.path)); @@ -588,12 +643,12 @@ namespace bpkg } text_file& text_file:: - operator= (text_file&& f) + operator= (text_file&& f) noexcept { if (this != &f) { this->~text_file (); - new (this) text_file (move (f)); // Assume noexcept move-construction. + new (this) text_file (move (f)); // Rely on noexcept move-construction. } return *this; } @@ -606,6 +661,132 @@ namespace bpkg return *this; } + // text_type + // + string + to_string (text_type t) + { + switch (t) + { + case text_type::plain: return "text/plain"; + case text_type::github_mark: return "text/markdown;variant=GFM"; + case text_type::common_mark: return "text/markdown;variant=CommonMark"; + } + + assert (false); // Can't be here. + return string (); + } + + optional<text_type> + to_text_type (const string& t) + { + auto bad_type = [] (const string& d) {throw invalid_argument (d);}; + + // Parse the media type representation (see RFC2045 for details) into the + // type/subtype value and the parameter list. Note: we don't support + // parameter quoting and comments for simplicity. + // + size_t p (t.find (';')); + const string& tp (p != string::npos ? trim (string (t, 0, p)) : t); + + small_vector<pair<string, string>, 1> ps; + + while (p != string::npos) + { + // Extract parameter name. + // + size_t b (p + 1); + p = t.find ('=', b); + + if (p == string::npos) + bad_type ("missing '='"); + + string n (trim (string (t, b, p - b))); + + // Extract parameter value. + // + b = p + 1; + p = t.find (';', b); + + string v (trim (string (t, + b, + p != string::npos ? p - b : string::npos))); + + ps.emplace_back (move (n), move (v)); + } + + // Calculate the resulting text type, failing on unrecognized media type, + // unexpected parameter name or value. + // + // Note that type, subtype, and parameter names are matched + // case-insensitively. + // + optional<text_type> r; + + // Currently only the plain and markdown text types are allowed. Later we + // can potentially introduce some other text types. + // + if (icasecmp (tp, "text/plain") == 0) + { + // Currently, we don't expect parameters for plain text. Later we can + // potentially introduce some plain text variants. + // + if (ps.empty ()) + r = text_type::plain; + } + else if (icasecmp (tp, "text/markdown") == 0) + { + // Currently, a single optional variant parameter with the two possible + // values is allowed for markdown. Later we can potentially introduce + // some other markdown variants. + // + if (ps.empty () || + (ps.size () == 1 && icasecmp (ps[0].first, "variant") == 0)) + { + // Note that markdown variants are matched case-insensitively (see + // RFC7763 for details). + // + string v; + if (ps.empty () || icasecmp (v = move (ps[0].second), "GFM") == 0) + r = text_type::github_mark; + else if (icasecmp (v, "CommonMark") == 0) + r = text_type::common_mark; + } + } + else if (icasecmp (tp, "text/", 5) != 0) + bad_type ("text type expected"); + + return r; + } + + // typed_text_file + // + optional<text_type> typed_text_file:: + effective_type (bool iu) const + { + optional<text_type> r; + + if (type) + { + r = to_text_type (*type); + } + else if (file) + { + string ext (path.extension ()); + if (ext.empty () || icasecmp (ext, "txt") == 0) + r = text_type::plain; + else if (icasecmp (ext, "md") == 0 || icasecmp (ext, "markdown") == 0) + r = text_type::github_mark; + } + else + r = text_type::plain; + + if (!r && !iu) + throw invalid_argument ("unknown text type"); + + return r; + } + // manifest_url // manifest_url:: @@ -657,7 +838,7 @@ namespace bpkg if (mnv != "$") try { - min_version = version (mnv, false /* fold_zero_revision */); + min_version = version (mnv, version::none); } catch (const invalid_argument& e) { @@ -684,7 +865,7 @@ namespace bpkg if (mxv != "$") try { - max_version = version (mxv, false /* fold_zero_revision */); + max_version = version (mxv, version::none); } catch (const invalid_argument& e) { @@ -788,7 +969,7 @@ namespace bpkg // version. // if (vs != "$") - v = version (vs, false /* fold_zero_revision */); + v = version (vs, version::none); switch (operation) { @@ -1012,15 +1193,146 @@ namespace bpkg return r; } - std::string dependency:: + // dependency + // + dependency:: + dependency (std::string d) + { + using std::string; + using iterator = string::const_iterator; + + iterator b (d.begin ()); + iterator i (b); + iterator ne (b); // End of name. + iterator e (d.end ()); + + // Find end of name (ne). + // + // Grep for '=<>([~^' in the bpkg source code and update, if changed. + // + const string cb ("=<>([~^"); + for (char c; i != e && cb.find (c = *i) == string::npos; ++i) + { + if (!space (c)) + ne = i + 1; + } + + try + { + name = package_name (i == e ? move (d) : string (b, ne)); + } + catch (const invalid_argument& e) + { + throw invalid_argument (string ("invalid package name: ") + e.what ()); + } + + if (i != e) + try + { + constraint = version_constraint (string (i, e)); + } + catch (const invalid_argument& e) + { + throw invalid_argument (string ("invalid package constraint: ") + + e.what ()); + } + } + + // dependency_alternative + // + string dependency_alternative:: string () const { - std::string r (name.string ()); + std::string r (size () > 1 ? "{" : ""); - if (constraint) + bool first (true); + for (const dependency& d: *this) { - r += ' '; - r += constraint->string (); + 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; @@ -1028,26 +1340,1370 @@ namespace bpkg // dependency_alternatives // - ostream& - operator<< (ostream& o, const dependency_alternatives& as) + class dependency_alternatives_lexer: public char_scanner<utf8_validator> + { + public: + enum class token_type + { + eos, + newline, + word, + buildfile, + + question, // ? + + lcbrace, // { + rcbrace, // } + + lparen, // ( + rparen, // ) + + lsbrace, // [ + rsbrace, // ] + + 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; + }; + + // If true, then comments are allowed and are treated as whitespace + // characters. + // + bool comments = false; + + public: + // Note that name is stored by shallow reference. + // + dependency_alternatives_lexer (istream& is, + const string& name, + uint64_t line, + uint64_t column) + : char_scanner (is, + utf8_validator (codepoint_types::graphic, U"\n\r\t"), + true /* crlf */, + line, + column), + name_ (name), + buildfile_scan_ (*this, name_) {} + + // The following functions throw manifest_parsing on invalid UTF-8 + // sequence. + // + + // Peek the next non-whitespace character. + // + xchar + peek_char (); + + // Extract next token (other than of the buildfile type) from the stream. + // + // Note that it is ok to call next() again after getting eos. + // + token + next (); + + // The next_*() functions extract the buildfile token from the stream. + // Throw manifest_parsing on error (invalid buildfile fragment, etc). + // + // Note that they are just thin wrappers around the scan_*() functions + // (see buildfile-scanner.hxx for details). + // + token + next_eval (); + + token + next_line (char stop); + + token + next_block (); + + private: + using base = char_scanner<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 () { - if (as.conditional) - o << '?'; + using type = token_type; + + skip_spaces (); + + uint64_t ln (line); + uint64_t cl (column); - if (as.buildtime) - o << '*'; + xchar c (get ()); + + auto make_token = [ln, cl] (type t, string v = string ()) + { + return token {t, move (v), ln, cl}; + }; - if (as.conditional || as.buildtime) - o << ' '; + if (eos (c)) + return make_token (type::eos); - bool f (true); - for (const dependency& a: as) - o << (f ? (f = false, "") : " | ") << a; + // 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 '=': + { + if (peek () == '=') + { + get (); + return make_token (type::equal); + } + break; + } - if (!as.comment.empty ()) - o << "; " << as.comment; + case '<': + { + if ((c = peek ()) == '=') + { + get (c); + return make_token (type::less_equal); + } + else + return make_token (type::less); + } - return o; + 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 '#': + { + if (!comments) + return; + + get (c); + + // See if this is a multi-line comment in the form: + // + /* + #\ + ... + #\ + */ + auto ml = [&c, this] () -> bool + { + if ((c = peek ()) == '\\') + { + get (c); + if ((c = peek ()) == '\n' || eos (c)) + return true; + } + + return false; + }; + + if (ml ()) + { + // Scan until we see the closing one. + // + for (;;) + { + if (c == '#' && ml ()) + break; + + if (eos (c = peek ())) + throw parsing (name_, + c.line, c.column, + "unterminated multi-line comment"); + + get (c); + } + } + else + { + // Read until newline or eos. + // + for (; !eos (c) && c != '\n'; c = peek ()) + get (c); + } + + continue; + } + + 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::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::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 ()); + + bool eval (lexer_->peek_char () == '('); + r.enable = eval ? parse_eval () : string (); + + next (t, tt); + + // @@ TMP Treat requirements similar to `? cli` as `cli ?` until + // toolchain 0.15.0 and libodb-mssql 2.5.0-b.22 are both released. + // + // NOTE: don't forget to drop the temporary test in + // tests/manifest/testscript when dropping this workaround. + // + if (!eval && tt == type::word) + try + { + r.back ().name = package_name (move (t.value)); + next (t, tt); + } + catch (const invalid_argument&) {} + + 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"); + + // Allow comments. + // + lexer_->comments = true; + + 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. + // + // Note that an empty buildfile fragment is allowed. + // + auto parse_block = [&t, &tt, &expect_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); + + return move (t.value); + }; + + 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); + + // Disallow comments. + // + lexer_->comments = false; + + 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)); + + comment = move (vc.second); + + const string& v (vc.first); + buildtime = (v[0] == '*'); + + string::const_iterator b (v.begin ()); + string::const_iterator e (v.end ()); + + if (buildtime) + { + string::size_type p (v.find_first_not_of (spaces, 1)); + b = p == string::npos ? e : b + p; + } + + 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 (buildtime ? "* " : ""); + + const dependency_alternative* prev (nullptr); + for (const dependency_alternative& da: *this) + { + 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 + // + string requirement_alternative:: + string () const + { + 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; + } + + // requirement_alternatives + // + requirement_alternatives:: + requirement_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)); + + comment = move (vc.second); + + const string& v (vc.first); + buildtime = (v[0] == '*'); + + string::const_iterator b (v.begin ()); + string::const_iterator e (v.end ()); + + if (buildtime) + { + string::size_type p (v.find_first_not_of (spaces, 1)); + b = p == string::npos ? e : b + p; + } + + // We will use the dependency alternatives parser to parse the + // representation into a temporary dependency alternatives in the + // requirements mode. Then we will move the dependency alternatives into + // the requirement alternatives using the string representation of the + // dependencies. + // + dependency_alternatives_parser p (true /* requirements */); + istringstream is (b == v.begin () ? v : string (b, e)); + + dependency_alternatives das; + p.parse (dependent, is, name, line, column, das); + + for (dependency_alternative& da: das) + { + requirement_alternative ra (move (da.enable), move (da.reflect)); + + // Also handle the simple requirement. + // + for (dependency& d: da) + ra.push_back (!d.name.empty () ? d.string () : string ()); + + push_back (move (ra)); + } + + // 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 + { + using std::string; + + string r (buildtime ? "* " : ""); + + const requirement_alternative* prev (nullptr); + for (const requirement_alternative& ra: *this) + { + 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); } // build_class_term @@ -1062,7 +2718,7 @@ namespace bpkg } build_class_term:: - build_class_term (build_class_term&& t) + build_class_term (build_class_term&& t) noexcept : operation (t.operation), inverted (t.inverted), simple (t.simple) @@ -1086,13 +2742,13 @@ namespace bpkg } build_class_term& build_class_term:: - operator= (build_class_term&& t) + operator= (build_class_term&& t) noexcept { if (this != &t) { this->~build_class_term (); - // Assume noexcept move-construction. + // Rely on noexcept move-construction. // new (this) build_class_term (move (t)); } @@ -1118,13 +2774,13 @@ namespace bpkg if (!(alnum (c) || c == '_')) throw invalid_argument ( - "class name '" + s + "' starts with '" + c + "'"); + "class name '" + s + "' starts with '" + c + '\''); for (; i != s.size (); ++i) { if (!(alnum (c = s[i]) || c == '+' || c == '-' || c == '_' || c == '.')) throw invalid_argument ( - "class name '" + s + "' contains '" + c + "'"); + "class name '" + s + "' contains '" + c + '\''); } return s[0] == '_'; @@ -1413,102 +3069,42 @@ namespace bpkg match_classes (cs, im, expr, r); } - // text_type + // build_auxiliary // - string - to_string (text_type t) + optional<pair<string, string>> build_auxiliary:: + parse_value_name (const string& n) { - switch (t) - { - case text_type::plain: return "text/plain"; - case text_type::github_mark: return "text/markdown;variant=GFM"; - case text_type::common_mark: return "text/markdown;variant=CommonMark"; - } - - assert (false); // Can't be here. - return string (); - } - - optional<text_type> - to_text_type (const string& t) - { - auto bad_type = [] (const string& d) {throw invalid_argument (d);}; - - // Parse the media type representation (see RFC2045 for details) into the - // type/subtype value and the parameter list. Note: we don't support - // parameter quoting and comments for simplicity. + // Check if the value name matches exactly. // - size_t p (t.find (';')); - const string& tp (p != string::npos ? trim (string (t, 0, p)) : t); - - small_vector<pair<string, string>, 1> ps; + if (n == "build-auxiliary") + return make_pair (string (), string ()); - while (p != string::npos) + // Check if this is a *-build-auxiliary name. + // + if (n.size () > 16 && + n.compare (n.size () - 16, 16, "-build-auxiliary") == 0) { - // Extract parameter name. - // - size_t b (p + 1); - p = t.find ('=', b); - - if (p == string::npos) - bad_type ("missing '='"); - - string n (trim (string (t, b, p - b))); - - // Extract parameter value. - // - b = p + 1; - p = t.find (';', b); - - string v (trim (string (t, - b, - p != string::npos ? p - b : string::npos))); - - ps.emplace_back (move (n), move (v)); + return make_pair (string (n, 0, n.size () - 16), string ()); } - // Calculate the resulting text type, failing on unrecognized media type, - // unexpected parameter name or value. + // Check if this is a build-auxiliary-* name. // - // Note that type, subtype, and parameter names are matched - // case-insensitively. - // - optional<text_type> r; + if (n.size () > 16 && n.compare (0, 16, "build-auxiliary-") == 0) + return make_pair (string (), string (n, 16)); - // Currently only the plain and markdown text types are allowed. Later we - // can potentially introduce some other text types. + // Check if this is a *-build-auxiliary-* name. // - if (icasecmp (tp, "text/plain") == 0) - { - // Currently, we don't expect parameters for plain text. Later we can - // potentially introduce some plain text variants. - // - if (ps.empty ()) - r = text_type::plain; - } - else if (icasecmp (tp, "text/markdown") == 0) + size_t p (n.find ("-build-auxiliary-")); + + if (p != string::npos && + p != 0 && // Not '-build-auxiliary-*'? + p + 17 != n.size () && // Not '*-build-auxiliary-'? + n.find ("-build-auxiliary-", p + 17) == string::npos) // Unambiguous? { - // Currently, a single optional variant parameter with the two possible - // values is allowed for markdown. Later we can potentially introduce - // some other markdown variants. - // - if (ps.empty () || - (ps.size () == 1 && icasecmp (ps[0].first, "variant") == 0)) - { - // Note that markdown variants are matched case-insensitively (see - // RFC7763 for details). - // - string v; - if (ps.empty () || icasecmp (v = move (ps[0].second), "GFM") == 0) - r = text_type::github_mark; - else if (icasecmp (v, "CommonMark") == 0) - r = text_type::common_mark; - } + return make_pair (string (n, 0, p), string (n, p + 17)); } - else if (icasecmp (tp, "text/", 5) != 0) - bad_type ("text type expected"); - return r; + return nullopt; } // test_dependency_type @@ -1533,7 +3129,135 @@ namespace bpkg if (t == "tests") return test_dependency_type::tests; else if (t == "examples") return test_dependency_type::examples; else if (t == "benchmarks") return test_dependency_type::benchmarks; - else throw invalid_argument ("invalid test dependency type '" + t + "'"); + else throw invalid_argument ("invalid test dependency type '" + t + '\''); + } + + + // test_dependency + // + test_dependency:: + test_dependency (std::string v, test_dependency_type t) + : type (t) + { + using std::string; + + // We will use the dependency alternatives parser to parse the + // `<name> [<version-constraint>] ['?' <enable-condition>] [<reflect-config>]` + // representation into a temporary dependency alternatives object. Then we + // will verify that the result has no multiple alternatives/dependency + // packages and unexpected clauses and will move the required information + // (dependency, reflection, etc) into the being created test dependency + // object. + + // Verify that there is no newline characters to forbid the multi-line + // dependency alternatives representation. + // + if (v.find ('\n') != string::npos) + throw invalid_argument ("unexpected <newline>"); + + buildtime = (v[0] == '*'); + + size_t p (v.find_first_not_of (spaces, buildtime ? 1 : 0)); + + if (p == string::npos) + throw invalid_argument ("no package name specified"); + + string::const_iterator b (v.begin () + p); + string::const_iterator e (v.end ()); + + // Extract the dependency package name in advance, to pass it to the + // parser which will use it to verify the reflection variable name. + // + // Note that multiple packages can only be specified in {} to be accepted + // by the parser. In our case such '{' would be interpreted as a part of + // the package name and so would fail complaining about an invalid + // character. Let's handle this case manually to avoid the potentially + // confusing error description. + // + assert (b != e); // We would fail earlier otherwise. + + if (*b == '{') + throw invalid_argument ("only single package allowed"); + + package_name dn; + + try + { + p = v.find_first_of (" \t=<>[(~^", p); // End of the package name. + dn = package_name (string (b, p == string::npos ? e : v.begin () + p)); + } + catch (const invalid_argument& e) + { + throw invalid_argument (string ("invalid package name: ") + e.what ()); + } + + // Parse the value into the temporary dependency alternatives object. + // + dependency_alternatives das; + + try + { + dependency_alternatives_parser p; + istringstream is (b == v.begin () ? v : string (b, e)); + p.parse (dn, is, "" /* name */, 1, 1, das); + } + catch (const manifest_parsing& e) + { + throw invalid_argument (e.description); + } + + // Verify that there are no multiple dependency alternatives. + // + assert (!das.empty ()); // Enforced by the parser. + + if (das.size () != 1) + throw invalid_argument ("unexpected '|'"); + + dependency_alternative& da (das[0]); + + // Verify that there are no multiple dependencies in the alternative. + // + // The parser can never end up with no dependencies in an alternative and + // we already verified that there can't be multiple of them (see above). + // + assert (da.size () == 1); + + // Verify that there are no unexpected clauses. + // + // Note that the require, prefer, and accept clauses can only be present + // in the multi-line representation and we have already verified that this + // is not the case. So there is nothing to verify here. + + // Move the dependency and the enable and reflect clauses into the being + // created test dependency object. + // + static_cast<dependency&> (*this) = move (da[0]); + + enable = move (da.enable); + reflect = move (da.reflect); + } + + string test_dependency:: + string () const + { + std::string r (buildtime + ? "* " + dependency::string () + : dependency::string ()); + + if (enable) + { + r += " ? ("; + r += *enable; + r += ')'; + } + + if (reflect) + { + r += ' '; + r += *reflect; + } + + return r; } // pkg_package_manifest @@ -1590,7 +3314,7 @@ namespace bpkg { throw !source_name.empty () ? parsing (source_name, nv.value_line, nv.value_column, d) - : parsing (d + " in '" + v + "'"); + : parsing (d + " in '" + v + '\''); }; size_t p (v.find ('/')); @@ -1632,42 +3356,86 @@ namespace bpkg return email (move (v), move (c)); } + // Parse the [*-]build-auxiliary[-*] manifest value. + // + // Note that the environment name is expected to already be retrieved using + // build_auxiliary::parse_value_name(). + // + static build_auxiliary + parse_build_auxiliary (const name_value& nv, + string&& env_name, + const string& source_name) + { + auto bad_value = [&nv, &source_name] (const string& d) + { + throw !source_name.empty () + ? parsing (source_name, nv.value_line, nv.value_column, d) + : parsing (d); + }; + + pair<string, string> vc (parser::split_comment (nv.value)); + string& v (vc.first); + string& c (vc.second); + + if (v.empty ()) + bad_value ("empty build auxiliary configuration name pattern"); + + return build_auxiliary (move (env_name), move (v), move (c)); + } + const version stub_version (0, "0", nullopt, nullopt, 0); + // Parse until next() returns end-of-manifest value. + // static void parse_package_manifest ( - parser& p, - name_value nv, - const function<package_manifest::translate_function>& tf, + const string& name, + const function<name_value ()>& next, + const function<package_manifest::translate_function>& translate, bool iu, - bool cd, + bool cv, package_manifest_flags fl, package_manifest& m) { - auto bad_name ([&p, &nv](const string& d) { - throw parsing (p.name (), nv.name_line, nv.name_column, d);}); + name_value nv; - auto bad_value ([&p, &nv](const string& d) { - throw parsing (p.name (), nv.value_line, nv.value_column, d);}); + auto bad_name ([&name, &nv](const string& d) { + throw parsing (name, nv.name_line, nv.name_column, d);}); - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of package manifest expected"); + auto bad_value ([&name, &nv](const string& d) { + throw parsing (name, nv.value_line, nv.value_column, d);}); - if (nv.value != "1") - bad_value ("unsupported format version"); - - auto parse_email = [&bad_name] (const name_value& nv, - optional<email>& r, - const char* what, - const string& source_name, - bool empty = false) + auto parse_email = [&bad_name, &name] (const name_value& nv, + optional<email>& r, + const char* what, + bool empty = false) { if (r) bad_name (what + string (" email redefinition")); - r = bpkg::parse_email (nv, what, source_name, empty); + r = bpkg::parse_email (nv, what, name, empty); + }; + + // Parse the [*-]build-auxiliary[-*] manifest value and append it to the + // specified build auxiliary list. Make sure that the list contains not + // more than one entry with unspecified environment name and throw parsing + // if that's not the case. Also make sure that there are no entry + // redefinitions (multiple entries with the same environment name). + // + auto parse_build_auxiliary = [&bad_name, &name] (const name_value& nv, + string&& en, + vector<build_auxiliary>& r) + { + build_auxiliary a (bpkg::parse_build_auxiliary (nv, move (en), name)); + + if (find_if (r.begin (), r.end (), + [&a] (const build_auxiliary& ba) + { + return ba.environment_name == a.environment_name; + }) != r.end ()) + bad_name ("build auxiliary environment redefinition"); + + r.push_back (move (a)); }; auto parse_url = [&bad_value] (const string& v, @@ -1731,11 +3499,190 @@ namespace bpkg } }; + // Note: the n argument is the distribution name length. + // + auto parse_distribution = [&bad_name, &bad_value] (string&& nm, size_t n, + string&& vl) + { + size_t p (nm.find ('-')); + + // Distribution-related manifest value name always has a dash-starting + // suffix (-name, etc). + // + assert (p != string::npos); + + if (p < n) + bad_name ("distribution name '" + string (nm, 0, n) + "' contains '-'"); + + if (vl.empty ()) + bad_value ("empty package distribution value"); + + return distribution_name_value (move (nm), move (vl)); + }; + + auto add_distribution = [&m, &bad_name] (distribution_name_value&& nv, + bool unique) + { + vector<distribution_name_value>& dvs (m.distribution_values); + + if (unique && + find_if (dvs.begin (), dvs.end (), + [&nv] (const distribution_name_value& dnv) + {return dnv.name == nv.name;}) != dvs.end ()) + { + bad_name ("package distribution value redefinition"); + } + + dvs.push_back (move (nv)); + }; + auto flag = [fl] (package_manifest_flags f) { return (fl & f) != package_manifest_flags::none; }; + // Based on the buildfile path specified via the `*-build[2]` value name + // or the `build-file` value set the manifest's alt_naming flag if absent + // and verify that it doesn't change otherwise. If it does, then return + // the error description and nullopt otherwise. + // + auto alt_naming = [&m] (const string& p) -> optional<string> + { + assert (!p.empty ()); + + bool an (p.back () == '2'); + + if (!m.alt_naming) + m.alt_naming = an; + else if (*m.alt_naming != an) + return string (*m.alt_naming ? "alternative" : "standard") + + " buildfile naming scheme is already used"; + + return nullopt; + }; + + // Try to parse and verify the buildfile path specified via the + // `*-build[2]` value name or the `build-file` value and set the + // manifest's alt_naming flag. On success return the normalized path with + // the suffix stripped and nullopt and the error description + // otherwise. Expects that the prefix is not empty. + // + // Specifically, verify that the path doesn't contain backslashes, is + // relative, doesn't refer outside the packages's build subdirectory, and + // was not specified yet. Also verify that the file name is not empty. + // + auto parse_buildfile_path = + [&m, &alt_naming] (string&& p, string& err) -> optional<path> + { + if (optional<string> e = alt_naming (p)) + { + err = move (*e); + return nullopt; + } + + // Verify that the path doesn't contain backslashes which would be + // interpreted differently on Windows and POSIX. + // + if (p.find ('\\') != string::npos) + { + err = "backslash in package buildfile path"; + return nullopt; + } + + // Strip the '(-|.)build' suffix. + // + size_t n (*m.alt_naming ? 7 : 6); + assert (p.size () > n); + + p.resize (p.size () - n); + + try + { + path f (move (p)); + + // Fail if the value name is something like `config/-build`. + // + if (f.to_directory ()) + { + err = "empty package buildfile name"; + return nullopt; + } + + if (f.absolute ()) + { + err = "absolute package buildfile path"; + return nullopt; + } + + // Verify that the path refers inside the package's build/ + // subdirectory. + // + f.normalize (); // Note: can't throw since the path is relative. + + if (dir_path::traits_type::parent (*f.begin ())) + { + err = "package buildfile path refers outside build/ subdirectory"; + return nullopt; + } + + // Check for duplicates. + // + const vector<buildfile>& bs (m.buildfiles); + const vector<path>& bps (m.buildfile_paths); + + if (find_if (bs.begin (), bs.end (), + [&f] (const auto& v) {return v.path == f;}) + != bs.end () || + find (bps.begin (), bps.end (), f) != bps.end ()) + { + err = "package buildfile redefinition"; + return nullopt; + } + + return f; + } + catch (const invalid_path&) + { + err = "invalid package buildfile path"; + return nullopt; + } + }; + + // Return the package build configuration with the specified name, if + // already exists. If no configuration matches, then create one, if + // requested, and throw manifest_parsing otherwise. If the new + // configuration creation is not allowed, then the description for a + // potential manifest_parsing exception needs to also be specified. + // + auto build_conf = [&m, &bad_name] (string&& nm, + bool create = true, + const string& desc = "") + -> build_package_config& + { + // The error description must only be specified if the creation of the + // package build configuration is not allowed. + // + assert (desc.empty () == create); + + small_vector<build_package_config, 1>& cs (m.build_configs); + + auto i (find_if (cs.begin (), cs.end (), + [&nm] (const build_package_config& c) + {return c.name == nm;})); + + if (i != cs.end ()) + return *i; + + if (!create) + bad_name (desc + ": no build package configuration '" + nm + '\''); + + // Add the new build configuration (arguments, builds, etc will come + // later). + // + cs.emplace_back (move (nm)); + return cs.back (); + }; + // Cache the upstream version manifest value and validate whether it's // allowed later, after the version value is parsed. // @@ -1743,18 +3690,36 @@ 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 - // later, after both are parsed. + // We will cache the descriptions and changes and their type values to + // validate them later, after all are parsed. // optional<name_value> description; optional<name_value> description_type; + optional<name_value> package_description; + optional<name_value> package_description_type; + vector<name_value> changes; + optional<name_value> changes_type; + + // It doesn't make sense for only emails to be specified for a package + // build configuration. Thus, we will cache the build configuration email + // manifest values to parse them later, after all other build + // configuration values are parsed, and to make sure that the build + // configurations they refer to are also specified. + // + vector<name_value> build_config_emails; + vector<name_value> build_config_warning_emails; + vector<name_value> build_config_error_emails; - for (nv = p.next (); !nv.empty (); nv = p.next ()) + m.build_configs.emplace_back ("default"); + + for (nv = next (); !nv.empty (); nv = next ()) { string& n (nv.name); string& v (nv.value); @@ -1793,9 +3758,9 @@ namespace bpkg if (m.version.release && m.version.release->empty ()) bad_value ("invalid package version release"); - if (tf) + if (translate) { - tf (m.version); + translate (m.version); // Re-validate the version after the translation. // @@ -1821,6 +3786,55 @@ namespace bpkg upstream_version = move (nv); } + else if (n == "type") + { + if (m.type) + bad_name ("package type redefinition"); + + if (v.empty () || v.find (',') == 0) + bad_value ("empty package type"); + + m.type = move (v); + } + else if (n == "language") + { + // Strip the language extra information, if present. + // + size_t p (v.find (',')); + if (p != string::npos) + v.resize (p); + + // Determine the language impl flag. + // + bool impl (false); + p = v.find ('='); + if (p != string::npos) + { + string s (trim (string (v, p + 1))); + if (s != "impl") + bad_value (!s.empty () + ? "unexpected '" + s + "' value after '='" + : "expected 'impl' after '='"); + + impl = true; + + v.resize (p); + } + + // Finally, validate and add the language. + // + trim_right (v); + + if (v.empty ()) + bad_value ("empty package language"); + + if (find_if (m.languages.begin (), m.languages.end (), + [&v] (const language& l) {return l.name == v;}) != + m.languages.end ()) + bad_value ("duplicate package language"); + + m.languages.emplace_back (move (v), impl); + } else if (n == "project") { if (m.project) @@ -1877,28 +3891,28 @@ namespace bpkg if (description) { if (description->name == "description-file") - bad_name ("package description and description-file are " + bad_name ("project description and description file are " "mutually exclusive"); else - bad_name ("package description redefinition"); + bad_name ("project description redefinition"); } if (v.empty ()) - bad_value ("empty package description"); + bad_value ("empty project description"); description = move (nv); } else if (n == "description-file") { if (flag (package_manifest_flags::forbid_file)) - bad_name ("package description-file not allowed"); + bad_name ("project description file not allowed"); if (description) { if (description->name == "description-file") - bad_name ("package description-file redefinition"); + bad_name ("project description file redefinition"); else - bad_name ("package description-file and description are " + bad_name ("project description file and description are " "mutually exclusive"); } @@ -1907,32 +3921,69 @@ namespace bpkg else if (n == "description-type") { if (description_type) - bad_name ("package description-type redefinition"); + bad_name ("project description type redefinition"); description_type = move (nv); } + else if (n == "package-description") + { + if (package_description) + { + if (package_description->name == "package-description-file") + bad_name ("package description and description file are " + "mutually exclusive"); + else + bad_name ("package description redefinition"); + } + + if (v.empty ()) + bad_value ("empty package description"); + + package_description = move (nv); + } + else if (n == "package-description-file") + { + if (flag (package_manifest_flags::forbid_file)) + bad_name ("package description file not allowed"); + + if (package_description) + { + if (package_description->name == "package-description-file") + bad_name ("package description file redefinition"); + else + bad_name ("package description file and description are " + "mutually exclusive"); + } + + package_description = move (nv); + } + else if (n == "package-description-type") + { + if (package_description_type) + bad_name ("package description type redefinition"); + + package_description_type = move (nv); + } else if (n == "changes") { if (v.empty ()) bad_value ("empty package changes specification"); - m.changes.emplace_back (move (v)); + changes.emplace_back (move (nv)); } else if (n == "changes-file") { if (flag (package_manifest_flags::forbid_file)) bad_name ("package changes-file not allowed"); - auto vc (parser::split_comment (v)); - path p (move (vc.first)); - - if (p.empty ()) - bad_value ("no path in package changes-file"); - - if (p.absolute ()) - bad_value ("package changes-file path is absolute"); + changes.emplace_back (move (nv)); + } + else if (n == "changes-type") + { + if (changes_type) + bad_name ("package changes type redefinition"); - m.changes.emplace_back (move (p), move (vc.second)); + changes_type = move (nv); } else if (n == "url") { @@ -1943,7 +3994,7 @@ namespace bpkg } else if (n == "email") { - parse_email (nv, m.email, "project", p.name ()); + parse_email (nv, m.email, "project"); } else if (n == "doc-url") { @@ -1968,19 +4019,19 @@ namespace bpkg } else if (n == "package-email") { - parse_email (nv, m.package_email, "package", p.name ()); + parse_email (nv, m.package_email, "package"); } else if (n == "build-email") { - parse_email (nv, m.build_email, "build", p.name (), true /* empty */); + parse_email (nv, m.build_email, "build", true /* empty */); } else if (n == "build-warning-email") { - parse_email (nv, m.build_warning_email, "build warning", p.name ()); + parse_email (nv, m.build_warning_email, "build warning"); } else if (n == "build-error-email") { - parse_email (nv, m.build_error_email, "build error", p.name ()); + parse_email (nv, m.build_error_email, "build error"); } else if (n == "priority") { @@ -2031,60 +4082,210 @@ namespace bpkg m.license_alternatives.push_back (move (l)); } + else if (n == "depends") + { + dependencies.push_back (move (nv)); + } else if (n == "requires") { - // 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); - + requirements.push_back (move (nv)); + } + else if (n == "builds") + { + m.builds.push_back ( + parse_build_class_expr (nv, m.builds.empty (), name)); + } + else if (n == "build-include") + { + m.build_constraints.push_back ( + parse_build_constraint (nv, false /* exclusion */, name)); + } + else if (n == "build-exclude") + { + m.build_constraints.push_back ( + parse_build_constraint (nv, true /* exclusion */, name)); + } + else if (optional<pair<string, string>> ba = + build_auxiliary::parse_value_name (n)) + { + if (ba->first.empty ()) // build-auxiliary*? + { + parse_build_auxiliary (nv, move (ba->second), m.build_auxiliaries); + } + else // *-build-auxiliary* + { + build_package_config& bc (build_conf (move (ba->first))); + parse_build_auxiliary (nv, move (ba->second), bc.auxiliaries); + } + } + else if (n.size () > 13 && + n.compare (n.size () - 13, 13, "-build-config") == 0) + { auto vc (parser::split_comment (v)); - const string& vl (vc.first); - requirement_alternatives ra (cond != 0, btim != 0, move (vc.second)); + n.resize (n.size () - 13); - string::const_iterator b (vl.begin ()); - string::const_iterator e (vl.end ()); + build_package_config& bc (build_conf (move (n))); - if (ra.conditional || ra.buildtime) - { - string::size_type p (vl.find_first_not_of (spaces, cond + btim)); - b = p == string::npos ? e : b + p; - } + if (!bc.arguments.empty () || !bc.comment.empty ()) + bad_name ("build configuration redefinition"); - list_parser lp (b, e, '|'); - for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - ra.push_back (lv); + bc.arguments = move (vc.first); + bc.comment = move (vc.second); + } + else if (n.size () > 7 && n.compare (n.size () - 7, 7, "-builds") == 0) + { + n.resize (n.size () - 7); - if (ra.empty () && ra.comment.empty ()) - bad_value ("empty package requirement specification"); + build_package_config& bc (build_conf (move (n))); - m.requirements.push_back (move (ra)); + bc.builds.push_back ( + parse_build_class_expr (nv, bc.builds.empty (), name)); } - else if (n == "builds") + else if (n.size () > 14 && + n.compare (n.size () - 14, 14, "-build-include") == 0) { - m.builds.push_back ( - parse_build_class_expr (nv, m.builds.empty (), p.name ())); + n.resize (n.size () - 14); + + build_package_config& bc (build_conf (move (n))); + + bc.constraints.push_back ( + parse_build_constraint (nv, false /* exclusion */, name)); } - else if (n == "build-include") + else if (n.size () > 14 && + n.compare (n.size () - 14, 14, "-build-exclude") == 0) { - m.build_constraints.push_back ( - parse_build_constraint (nv, false /* exclusion */, p.name ())); + n.resize (n.size () - 14); + + build_package_config& bc (build_conf (move (n))); + + bc.constraints.push_back ( + parse_build_constraint (nv, true /* exclusion */, name)); } - else if (n == "build-exclude") + else if (n.size () > 12 && + n.compare (n.size () - 12, 12, "-build-email") == 0) { - m.build_constraints.push_back ( - parse_build_constraint (nv, true /* exclusion */, p.name ())); + n.resize (n.size () - 12); + build_config_emails.push_back (move (nv)); } - else if (n == "depends") + else if (n.size () > 20 && + n.compare (n.size () - 20, 20, "-build-warning-email") == 0) { - dependencies.push_back (move (nv)); + n.resize (n.size () - 20); + build_config_warning_emails.push_back (move (nv)); } - else if (n == "tests" || n == "examples" || n == "benchmarks") + else if (n.size () > 18 && + n.compare (n.size () - 18, 18, "-build-error-email") == 0) { + n.resize (n.size () - 18); + build_config_error_emails.push_back (move (nv)); + } + // @@ TMP time to drop *-0.14.0? + // + else if (n == "tests" || n == "tests-0.14.0" || + n == "examples" || n == "examples-0.14.0" || + n == "benchmarks" || n == "benchmarks-0.14.0") + { + // Strip the '-0.14.0' suffix from the value name, if present. + // + size_t p (n.find ('-')); + if (p != string::npos) + n.resize (p); + tests.push_back (move (nv)); } + else if (n == "bootstrap-build" || n == "bootstrap-build2") + { + if (optional<string> e = alt_naming (n)) + bad_name (*e); + + if (m.bootstrap_build) + bad_name ("package " + n + " redefinition"); + + m.bootstrap_build = move (v); + } + else if (n == "root-build" || n == "root-build2") + { + if (optional<string> e = alt_naming (n)) + bad_name (*e); + + if (m.root_build) + bad_name ("package " + n + " redefinition"); + + m.root_build = move (v); + } + else if ((n.size () > 6 && n.compare (n.size () - 6, 6, "-build") == 0) || + (n.size () > 7 && n.compare (n.size () - 7, 7, "-build2") == 0)) + { + string err; + if (optional<path> p = parse_buildfile_path (move (n), err)) + m.buildfiles.push_back (buildfile (move (*p), move (v))); + else + bad_name (err); + } + else if (n == "build-file") + { + if (flag (package_manifest_flags::forbid_file)) + bad_name ("package build-file not allowed"); + + // Verify that the buildfile extension is either build or build2. + // + if ((v.size () > 6 && v.compare (v.size () - 6, 6, ".build") == 0) || + (v.size () > 7 && v.compare (v.size () - 7, 7, ".build2") == 0)) + { + string err; + if (optional<path> p = parse_buildfile_path (move (v), err)) + { + // Verify that the resulting path differs from bootstrap and root. + // + const string& s (p->string ()); + if (s == "bootstrap" || s == "root") + bad_value (s + " not allowed"); + + m.buildfile_paths.push_back (move (*p)); + } + else + bad_value (err); + } + else + bad_value ("path with build or build2 extension expected"); + + } + else if (n.size () > 5 && n.compare (n.size () - 5, 5, "-name") == 0) + { + add_distribution ( + parse_distribution (move (n), n.size () - 5, move (v)), + false /* unique */); + } + // Note: must precede the check for the "-version" suffix. + // + else if (n.size () > 22 && + n.compare (n.size () - 22, 22, "-to-downstream-version") == 0) + { + add_distribution ( + parse_distribution (move (n), n.size () - 22, move (v)), + false /* unique */); + } + // Note: must follow the check for "upstream-version". + // + else if (n.size () > 8 && n.compare (n.size () - 8, 8, "-version") == 0) + { + // If the value is forbidden then throw, but only after the name is + // validated. Thus, check for that before we move the value from. + // + bool bad (v == "$" && + flag (package_manifest_flags::forbid_incomplete_values)); + + // Can throw. + // + distribution_name_value d ( + parse_distribution (move (n), n.size () - 8, move (v))); + + if (bad) + bad_value ("$ not allowed"); + + add_distribution (move (d), true /* unique */); + } else if (n == "location") { if (flag (package_manifest_flags::forbid_location)) @@ -2165,23 +4366,24 @@ namespace bpkg m.upstream_version = move (nv.value); } - // Verify that description is specified if the description type is - // specified. - // - if (description_type && !description) - bad_value ("no package description for specified description type"); - - // Validate (and set) description and its type. + // Parse and validate a text/file manifest value and its respective type + // value, if present. Return a typed_text_file object. // - if (description) + auto parse_text_file = [iu, &nv, &bad_value] (name_value&& text_file, + optional<name_value>&& type, + const char* what) + -> typed_text_file { + typed_text_file r; + // Restore as bad_value() uses its line/column. // - nv = move (*description); + nv = move (text_file); string& v (nv.value); + const string& n (nv.name); - if (nv.name == "description-file") + if (n.size () > 5 && n.compare (n.size () - 5, 5, "-file") == 0) { auto vc (parser::split_comment (v)); @@ -2192,112 +4394,223 @@ namespace bpkg } catch (const invalid_path& e) { - bad_value (string ("invalid package description file: ") + - e.what ()); + bad_value (string ("invalid ") + what + " file: " + e.what ()); } if (p.empty ()) - bad_value ("no path in package description-file"); + bad_value (string ("no path in ") + what + " file"); if (p.absolute ()) - bad_value ("package description-file path is absolute"); + bad_value (string (what) + " file path is absolute"); - m.description = text_file (move (p), move (vc.second)); + r = typed_text_file (move (p), move (vc.second)); } else - m.description = text_file (move (v)); + r = typed_text_file (move (v)); - if (description_type) - m.description_type = move (description_type->value); + if (type) + r.type = move (type->value); - // Verify the description type. + // Verify the text type. // try { - m.effective_description_type (iu); + r.effective_type (iu); } catch (const invalid_argument& e) { - if (description_type) + if (type) { - // Restore as bad_value() uses its line/column. + // Restore as bad_value() uses its line/column. Note that we don't + // need to restore the moved out type value. // - nv = move (*description_type); + nv = move (*type); - bad_value (string ("invalid package description type: ") + - e.what ()); + bad_value (string ("invalid ") + what + " type: " + e.what ()); } else - bad_value (string ("invalid package description file: ") + - e.what ()); + { + // Note that this can only happen due to inability to guess the + // type from the file extension. Let's help the user here a bit. + // + assert (r.file); + + bad_value (string ("invalid ") + what + " file: " + e.what () + + " (use " + string (n, 0, n.size () - 5) + + "-type manifest value to specify explicitly)"); + } } - } - // Now, when the version manifest value is parsed, we can parse the - // dependencies and complete their constraints, if requested. + return r; + }; + + // As above but also accepts nullopt as the text_file argument, in which + // case throws manifest_parsing if the type is specified and return + // nullopt otherwise. // - auto parse_dependency = [&m, cd, &flag, &bad_value] (string&& d, - const char* what) + auto parse_text_file_opt = [&nv, &bad_name, &parse_text_file] + (optional<name_value>&& text_file, + optional<name_value>&& type, + const char* what) -> optional<typed_text_file> { - using iterator = string::const_iterator; - - iterator b (d.begin ()); - iterator i (b); - iterator ne (b); // End of name. - iterator e (d.end ()); - - // Find end of name (ne). - // - // Grep for '=<>([~^' in the bpkg source code and update, if changed. + // Verify that the text/file value is specified if the type value is + // specified. // - const string cb ("=<>([~^"); - for (char c; i != e && cb.find (c = *i) == string::npos; ++i) + if (!text_file) { - if (!space (c)) - ne = i + 1; + if (type) + { + // Restore as bad_name() uses its line/column. + // + nv = move (*type); + + bad_name (string ("no ") + what + " for specified type"); + } + + return nullopt; } - package_name nm; + return parse_text_file (move (*text_file), move (type), what); + }; - try + // Parse the project/package descriptions/types. + // + m.description = parse_text_file_opt (move (description), + move (description_type), + "project description"); + + m.package_description = + parse_text_file_opt (move (package_description), + move (package_description_type), + "package description"); + + // Parse the package changes/types. + // + // Note: at the end of the loop the changes_type variable may contain + // value in unspecified state but we can still check for the value + // presence. + // + for (name_value& c: changes) + { + // Move the changes_type value from for the last changes entry. + // + m.changes.push_back ( + parse_text_file (move (c), + (&c != &changes.back () + ? optional<name_value> (changes_type) + : move (changes_type)), + "changes")); + } + + // If there are multiple changes and the changes type is not explicitly + // specified, then verify that all changes effective types are the same. + // Note that in the "ignore unknown" mode there can be unresolved + // effective types which we just skip. + // + if (changes.size () > 1 && !changes_type) + { + optional<text_type> type; + + for (size_t i (0); i != m.changes.size (); ++i) { - nm = package_name (i == e ? move (d) : string (b, ne)); + const typed_text_file& c (m.changes[i]); + + if (optional<text_type> t = c.effective_type (iu)) + { + if (!type) + { + type = *t; + } + else if (*t != *type) + { + // Restore as bad_value() uses its line/column. + // + nv = move (changes[i]); + + bad_value ("changes type '" + to_string (*t) + "' differs from " + + " previous type '" + to_string (*type) + "'"); + } + } } - catch (const invalid_argument& e) + } + + // Parse the build configuration emails. + // + // Note: the argument can only be one of the build_config_*emails + // variables (see above) to distinguish between the email kinds. + // + auto parse_build_config_emails = [&nv, + &build_config_emails, + &build_config_warning_emails, + &build_config_error_emails, + &build_conf, + &parse_email] + (vector<name_value>&& emails) + { + enum email_kind {build, warning, error}; + + email_kind ek ( + &emails == &build_config_emails ? email_kind::build : + &emails == &build_config_warning_emails ? email_kind::warning : + email_kind::error); + + // The argument can only be one of the build_config_*emails variables. + // + assert (ek != email_kind::error || &emails == &build_config_error_emails); + + for (name_value& e: emails) { - bad_value (string ("invalid ") + what + " package name: " + - e.what ()); + // Restore as bad_name() and bad_value() use its line/column. + // + nv = move (e); + + build_package_config& bc ( + build_conf (move (nv.name), + false /* create */, + "stray build notification email")); + + parse_email ( + nv, + (ek == email_kind::build ? bc.email : + ek == email_kind::warning ? bc.warning_email : + bc.error_email), + (ek == email_kind::build ? "build configuration" : + ek == email_kind::warning ? "build configuration warning" : + "build configuration error"), + ek == email_kind::build /* empty */); } + }; - dependency r; + parse_build_config_emails (move (build_config_emails)); + parse_build_config_emails (move (build_config_warning_emails)); + parse_build_config_emails (move (build_config_error_emails)); - if (i == e) - r = dependency {move (nm), nullopt}; - else + // Now, when the version manifest value is parsed, we can parse the + // dependencies and complete their constraints, if requested. + // + auto complete_constraint = [&m, cv, &flag] (auto&& dep) + { + if (dep.constraint) + try { - try - { - version_constraint vc (string (i, e)); + version_constraint& vc (*dep.constraint); - if (!vc.complete () && - flag (package_manifest_flags::forbid_incomplete_dependencies)) - bad_value ("$ not allowed"); + if (!vc.complete () && + flag (package_manifest_flags::forbid_incomplete_values)) + throw invalid_argument ("$ not allowed"); - // Complete the constraint. - // - if (cd) - vc = vc.effective (m.version); - - r = dependency {move (nm), move (vc)}; - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid ") + what + " package constraint: " + - e.what ()); - } + // Complete the constraint. + // + if (cv) + vc = vc.effective (m.version); + } + catch (const invalid_argument& e) + { + throw invalid_argument ("invalid package constraint '" + + dep.constraint->string () + "': " + e.what ()); } - return r; + return move (dep); }; // Parse the regular dependencies. @@ -2306,72 +4619,168 @@ namespace bpkg { nv = move (d); // Restore as bad_value() uses its line/column. - const string& v (nv.value); - - // Allow specifying ?* in any order. + // Parse dependency alternatives. // - 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)); - - const string& vl (vc.first); - dependency_alternatives da (cond != 0, btim != 0, move (vc.second)); + try + { + dependency_alternatives das (nv.value, + m.name, + name, + nv.value_line, + nv.value_column); - string::const_iterator b (vl.begin ()); - string::const_iterator e (vl.end ()); + for (dependency_alternative& da: das) + { + for (dependency& d: da) + d = complete_constraint (move (d)); + } - if (da.conditional || da.buildtime) + m.dependencies.push_back (move (das)); + } + catch (const invalid_argument& e) { - string::size_type p (vl.find_first_not_of (spaces, cond + btim)); - b = p == string::npos ? e : b + p; + bad_value (e.what ()); } + } - list_parser lp (b, e, '|'); - for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - da.push_back (parse_dependency (move (lv), "prerequisite")); - - if (da.empty ()) - bad_value ("empty package dependency specification"); - - m.dependencies.push_back (da); + // 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& v: tests) + for (name_value& t: tests) { - nv = move (v); // Restore as bad_value() uses its line/column. - - dependency d (parse_dependency (move (nv.value), nv.name.c_str ())); + nv = move (t); // Restore as bad_value() uses its line/column. try { - m.tests.emplace_back ( - move (d.name), - to_test_dependency_type (nv.name), - move (d.constraint)); + m.tests.push_back ( + complete_constraint ( + test_dependency (move (nv.value), + to_test_dependency_type (nv.name)))); } - catch (const invalid_argument&) + catch (const invalid_argument& e) { - // to_test_dependency_type() can't throw since the type string is - // already validated. - // - assert (false); + bad_value (e.what ()); } } - if (m.description && - !m.description_type && - flag (package_manifest_flags::require_description_type)) - bad_name ("no package description type specified"); + // Now, when the version manifest value is parsed, we complete the + // <distribution>-version values, if requested. + // + if (cv) + { + for (distribution_name_value& nv: m.distribution_values) + { + const string& n (nv.name); + string& v (nv.value); + + if (v == "$" && + (n.size () > 8 && n.compare (n.size () - 8, 8, "-version") == 0) && + n.find ('-') == n.size () - 8) + { + v = version (default_epoch (m.version), + move (m.version.upstream), + nullopt /* release */, + nullopt /* revision */, + 0 /* iteration */).string (); + } + } + } if (!m.location && flag (package_manifest_flags::require_location)) bad_name ("no package location specified"); if (!m.sha256sum && flag (package_manifest_flags::require_sha256sum)) bad_name ("no package sha256sum specified"); + + if (flag (package_manifest_flags::require_text_type)) + { + if (m.description && !m.description->type) + bad_name ("no project description type specified"); + + if (m.package_description && !m.package_description->type) + bad_name ("no package description type specified"); + + // Note that changes either all have the same explicitly specified type + // or have no type. + // + if (!m.changes.empty () && !m.changes.front ().type) + { + // @@ TMP To support older repositories allow absent changes type + // until toolchain 0.16.0 is released. + // + // Note that for such repositories the packages may not have + // changes values other than plan text. Thus, we can safely set + // this type, if they are absent, so that the caller can always + // be sure that these values are always present for package + // manifest lists. + //bad_name ("no package changes type specified"); + for (typed_text_file& c: m.changes) + c.type = "text/plain"; + } + } + + if (!m.bootstrap_build && + flag (package_manifest_flags::require_bootstrap_build)) + { + // @@ TMP To support older repositories allow absent bootstrap build + // and alt_naming until toolchain 0.15.0 is released. + // + // Note that for such repositories the packages may not have any + // need for the bootstrap buildfile (may not have any dependency + // clauses, etc). Thus, we can safely set the bootstrap build and + // alt_naming values to an empty string and false, respectively, + // if they are absent, so that the caller can always be sure that + // these values are always present for package manifest lists. + // + // Note: don't forget to uncomment no-bootstrap test in + // tests/manifest/testscript when removing this workaround. + // + // bad_name ("no package bootstrap build specified"); + m.bootstrap_build = "project = " + m.name.string () + '\n'; + m.alt_naming = false; + } + } + + static void + parse_package_manifest ( + parser& p, + name_value nv, + const function<package_manifest::translate_function>& tf, + bool iu, + bool cv, + package_manifest_flags fl, + package_manifest& m) + { + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "start of package manifest expected"); + + if (nv.value != "1") + throw parsing (p.name (), nv.value_line, nv.value_column, + "unsupported format version"); + + // Note that we rely on "small function object" optimization here. + // + parse_package_manifest (p.name (), + [&p] () {return p.next ();}, + tf, + iu, + cv, + fl, + m); } package_manifest @@ -2381,12 +4790,13 @@ namespace bpkg p, move (nv), iu, - false /* complete_depends */, + false /* complete_values */, package_manifest_flags::forbid_file | - package_manifest_flags::require_description_type | - package_manifest_flags::require_location | package_manifest_flags::forbid_fragment | - package_manifest_flags::forbid_incomplete_dependencies); + package_manifest_flags::forbid_incomplete_values | + package_manifest_flags::require_location | + package_manifest_flags::require_text_type | + package_manifest_flags::require_bootstrap_build); } // package_manifest @@ -2395,10 +4805,10 @@ namespace bpkg package_manifest (manifest_parser& p, const function<translate_function>& tf, bool iu, - bool cd, + bool cv, package_manifest_flags fl) { - parse_package_manifest (p, p.next (), tf, iu, cd, fl, *this); + parse_package_manifest (p, p.next (), tf, iu, cv, fl, *this); // Make sure this is the end. // @@ -2409,11 +4819,42 @@ namespace bpkg } package_manifest:: - package_manifest (manifest_parser& p, + package_manifest (const string& name, + vector<name_value>&& vs, + const function<translate_function>& tf, + bool iu, + bool cv, + package_manifest_flags fl) + { + auto i (vs.begin ()); + auto e (vs.end ()); + + // Note that we rely on "small function object" optimization here. + // + parse_package_manifest (name, + [&i, &e] () + { + return i != e ? move (*i++) : name_value (); + }, + tf, + iu, + cv, + fl, + *this); + } + + package_manifest:: + package_manifest (const string& name, + vector<name_value>&& vs, bool iu, - bool cd, + bool cv, package_manifest_flags fl) - : package_manifest (p, function<translate_function> (), iu, cd, fl) + : package_manifest (name, + move (vs), + function<translate_function> (), + iu, + cv, + fl) { } @@ -2421,197 +4862,644 @@ namespace bpkg package_manifest (manifest_parser& p, name_value nv, bool iu, - bool cd, + bool cv, package_manifest_flags fl) { parse_package_manifest ( - p, move (nv), function<translate_function> (), iu, cd, fl, *this); + p, move (nv), function<translate_function> (), iu, cv, fl, *this); } - optional<text_type> package_manifest:: - effective_description_type (bool iu) const + strings package_manifest:: + effective_type_sub_options (const optional<string>& t) { - if (!description) - throw logic_error ("absent description"); + strings r; - optional<text_type> r; - - if (description_type) - r = to_text_type (*description_type); - else if (description->file) + if (t) { - string ext (description->path.extension ()); - if (ext.empty () || icasecmp (ext, "txt") == 0) - r = text_type::plain; - else if (icasecmp (ext, "md") == 0 || icasecmp (ext, "markdown") == 0) - r = text_type::github_mark; + for (size_t b (0), e (0); next_word (*t, b, e, ','); ) + { + if (b != 0) + r.push_back (trim (string (*t, b, e - b))); + } } - else - r = text_type::plain; - - if (!r && !iu) - throw invalid_argument ("unknown text type"); return r; } - void package_manifest:: - override (const vector<manifest_name_value>& nvs, const string& name) + // If validate_only is true, then the package manifest is assumed to be + // default constructed and is used as a storage for convenience of the + // validation implementation. + // + static void + override (const vector<manifest_name_value>& nvs, + const string& name, + package_manifest& m, + bool validate_only) { - // Reset the build constraints value sub-group on the first call. + // The first {builds, build-{include,exclude}} override value. // - bool rbc (true); - auto reset_build_constraints = [&rbc, this] () - { - if (rbc) - { - build_constraints.clear (); - rbc = false; - } - }; + const manifest_name_value* cbc (nullptr); - // Reset the builds value group on the first call. + // The first builds override value. // - bool rb (true); - auto reset_builds = [&rb, &reset_build_constraints, this] () - { - if (rb) - { - builds.clear (); - reset_build_constraints (); - rb = false; - } - }; + const manifest_name_value* cb (nullptr); + + // The first {*-builds, *-build-{include,exclude}} override value. + // + const manifest_name_value* pbc (nullptr); + + // The first {build-*email} override value. + // + const manifest_name_value* cbe (nullptr); + + // The first {*-build-*email} override value. + // + const manifest_name_value* pbe (nullptr); - // Reset the build emails value group on the first call. + // List of indexes of the build configurations with the overridden build + // constraints together with flags which indicate if the *-builds override + // value was encountered for this configuration. // - bool rbe (true); - auto reset_build_emails = [&rbe, this] () + vector<pair<size_t, bool>> obcs; + + // List of indexes of the build configurations with the overridden emails. + // + vector<size_t> obes; + + // Return true if the specified package build configuration is newly + // created by the *-build-config override. + // + auto config_created = [&m, confs_num = m.build_configs.size ()] + (const build_package_config& c) { - if (rbe) - { - build_email = nullopt; - build_warning_email = nullopt; - build_error_email = nullopt; - rbe = false; - } + return &c >= m.build_configs.data () + confs_num; }; + // Apply overrides. + // for (const manifest_name_value& nv: nvs) { + auto bad_name = [&name, &nv] (const string& d) + { + throw !name.empty () + ? parsing (name, nv.name_line, nv.name_column, d) + : parsing (d); + }; + + // Reset the build-{include,exclude} value sub-group on the first call + // but throw if any of the {*-builds, *-build-{include,exclude}} + // override values are already encountered. + // + auto reset_build_constraints = [&cbc, &pbc, &nv, &bad_name, &m] () + { + if (cbc == nullptr) + { + if (pbc != nullptr) + bad_name ('\'' + nv.name + "' override specified together with '" + + pbc->name + "' override"); + + m.build_constraints.clear (); + cbc = &nv; + } + }; + + // Reset the {builds, build-{include,exclude}} value group on the first + // call. + // + auto reset_builds = [&cb, &nv, &reset_build_constraints, &m] () + { + if (cb == nullptr) + { + reset_build_constraints (); + + m.builds.clear (); + cb = &nv; + } + }; + + // Return the reference to the package build configuration which matches + // the build config value override, if exists. If no configuration + // matches, then create one, if requested, and throw manifest_parsing + // otherwise. + // + // The n argument specifies the length of the configuration name in + // *-build-config, *-builds, *-build-{include,exclude}, and + // *-build-*email values. + // + auto build_conf = + [&nv, &bad_name, &m] (size_t n, bool create) -> build_package_config& + { + const string& nm (nv.name); + small_vector<build_package_config, 1>& cs (m.build_configs); + + // Find the build package configuration. If no configuration is found, + // then create one, if requested, and throw otherwise. + // + auto i (find_if (cs.begin (), cs.end (), + [&nm, n] (const build_package_config& c) + {return nm.compare (0, n, c.name) == 0;})); + + if (i == cs.end ()) + { + string cn (nm, 0, n); + + if (create) + { + cs.emplace_back (move (cn)); + return cs.back (); + } + else + bad_name ("cannot override '" + nm + "' value: no build " + + "package configuration '" + cn + '\''); + } + + return *i; + }; + + // Return the reference to the package build configuration which matches + // the build config-specific builds group value override, if exists. If + // no configuration matches, then throw manifest_parsing, except for the + // validate-only mode in which case just add an empty configuration with + // this name and return the reference to it. + // + auto build_conf_constr = + [&pbc, &cbc, &nv, &obcs, &bad_name, &build_conf, &m, validate_only] + (size_t n) -> build_package_config& + { + const string& nm (nv.name); + + // If this is the first build config override value, then save its + // address. But first verify that no common build constraints group + // value overrides are applied yet and throw if that's not the case. + // + if (pbc == nullptr) + { + if (cbc != nullptr) + bad_name ('\'' + nm + "' override specified together with '" + + cbc->name + "' override"); + + pbc = &nv; + } + + small_vector<build_package_config, 1>& cs (m.build_configs); + + // Find the build package configuration. If there is no such a + // configuration then throw, except for the validate-only mode in + // which case just add an empty configuration with this name. + // + // Note that we are using indexes rather then configuration addresses + // due to potential reallocations. + // + build_package_config& r (build_conf (n, validate_only)); + size_t ci (&r - cs.data ()); + bool bv (nm.compare (n, nm.size () - n, "-builds") == 0); + + // If this is the first encountered + // {*-builds, *-build-{include,exclude}} override for this build + // config, then clear this config' constraints member and add an entry + // to the overridden configs list. + // + auto i (find_if (obcs.begin (), obcs.end (), + [ci] (const auto& c) {return c.first == ci;})); + + bool first (i == obcs.end ()); + + if (first) + { + r.constraints.clear (); + + obcs.push_back (make_pair (ci, bv)); + } + + // If this is the first encountered *-builds override, then also clear + // this config' builds member. + // + if (bv && (first || !i->second)) + { + r.builds.clear (); + + if (!first) + i->second = true; + } + + return r; + }; + + // Reset the {build-*email} value group on the first call but throw if + // any of the {*-build-*email} override values are already encountered. + // + auto reset_build_emails = [&cbe, &pbe, &nv, &bad_name, &m] () + { + if (cbe == nullptr) + { + if (pbe != nullptr) + bad_name ('\'' + nv.name + "' override specified together with '" + + pbe->name + "' override"); + + m.build_email = nullopt; + m.build_warning_email = nullopt; + m.build_error_email = nullopt; + cbe = &nv; + } + }; + + // Return the reference to the package build configuration which matches + // the build config-specific emails group value override, if exists. If + // no configuration matches, then throw manifest_parsing, except for the + // validate-only mode in which case just add an empty configuration with + // this name and return the reference to it. + // + auto build_conf_email = + [&pbe, &cbe, &nv, &obes, &bad_name, &build_conf, &m, validate_only] + (size_t n) -> build_package_config& + { + const string& nm (nv.name); + + // If this is the first build config override value, then save its + // address. But first verify that no common build emails group value + // overrides are applied yet and throw if that's not the case. + // + if (pbe == nullptr) + { + if (cbe != nullptr) + bad_name ('\'' + nm + "' override specified together with '" + + cbe->name + "' override"); + + pbe = &nv; + } + + small_vector<build_package_config, 1>& cs (m.build_configs); + + // Find the build package configuration. If there is no such a + // configuration then throw, except for the validate-only mode in + // which case just add an empty configuration with this name. + // + // Note that we are using indexes rather then configuration addresses + // due to potential reallocations. + // + build_package_config& r (build_conf (n, validate_only)); + size_t ci (&r - cs.data ()); + + // If this is the first encountered {*-build-*email} override for this + // build config, then clear this config' email members and add an + // entry to the overridden configs list. + // + if (find (obes.begin (), obes.end (), ci) == obes.end ()) + { + r.email = nullopt; + r.warning_email = nullopt; + r.error_email = nullopt; + + obes.push_back (ci); + } + + return r; + }; + + // Parse the [*-]build-auxiliary[-*] value override. If the mode is not + // validate-only, then override the matching value and throw + // manifest_parsing if no match. But throw only unless this is a + // configuration-specific override (build_config is not NULL) for a + // newly created configuration, in which case add the value instead. + // + auto override_build_auxiliary = + [&bad_name, + &name, + &config_created, + validate_only] (const name_value& nv, + string&& en, + vector<build_auxiliary>& r, + build_package_config* build_config = nullptr) + { + build_auxiliary a (bpkg::parse_build_auxiliary (nv, move (en), name)); + + if (!validate_only) + { + auto i (find_if (r.begin (), r.end (), + [&a] (const build_auxiliary& ba) + { + return ba.environment_name == a.environment_name; + })); + + if (i != r.end ()) + { + *i = move (a); + } + else + { + if (build_config != nullptr && config_created (*build_config)) + r.emplace_back (move (a)); + else + bad_name ("no match for '" + nv.name + "' value override"); + } + } + }; + const string& n (nv.name); if (n == "builds") { reset_builds (); - builds.push_back (parse_build_class_expr (nv, builds.empty (), name)); + + m.builds.push_back ( + parse_build_class_expr (nv, m.builds.empty (), name)); } else if (n == "build-include") { reset_build_constraints (); - build_constraints.push_back ( + m.build_constraints.push_back ( parse_build_constraint (nv, false /* exclusion */, name)); } else if (n == "build-exclude") { reset_build_constraints (); - build_constraints.push_back ( + m.build_constraints.push_back ( + parse_build_constraint (nv, true /* exclusion */, name)); + } + else if ((n.size () > 13 && + n.compare (n.size () - 13, 13, "-build-config") == 0)) + { + build_package_config& bc ( + build_conf (n.size () - 13, true /* create */)); + + auto vc (parser::split_comment (nv.value)); + + bc.arguments = move (vc.first); + bc.comment = move (vc.second); + } + else if (n.size () > 7 && n.compare (n.size () - 7, 7, "-builds") == 0) + { + build_package_config& bc (build_conf_constr (n.size () - 7)); + + bc.builds.push_back ( + parse_build_class_expr (nv, bc.builds.empty (), name)); + } + else if (n.size () > 14 && + n.compare (n.size () - 14, 14, "-build-include") == 0) + { + build_package_config& bc (build_conf_constr (n.size () - 14)); + + bc.constraints.push_back ( + parse_build_constraint (nv, false /* exclusion */, name)); + } + else if (n.size () > 14 && + n.compare (n.size () - 14, 14, "-build-exclude") == 0) + { + build_package_config& bc (build_conf_constr (n.size () - 14)); + + bc.constraints.push_back ( parse_build_constraint (nv, true /* exclusion */, name)); } else if (n == "build-email") { reset_build_emails (); - build_email = parse_email (nv, "build", name, true /* empty */); + m.build_email = parse_email (nv, "build", name, true /* empty */); } else if (n == "build-warning-email") { reset_build_emails (); - build_warning_email = parse_email (nv, "build warning", name); + m.build_warning_email = parse_email (nv, "build warning", name); } else if (n == "build-error-email") { reset_build_emails (); - build_error_email = parse_email (nv, "build error", name); + m.build_error_email = parse_email (nv, "build error", name); + } + else if (n.size () > 12 && + n.compare (n.size () - 12, 12, "-build-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 12)); + + bc.email = parse_email ( + nv, "build configuration", name, true /* empty */); + } + else if (n.size () > 20 && + n.compare (n.size () - 20, 20, "-build-warning-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 20)); + + bc.warning_email = parse_email ( + nv, "build configuration warning", name); + } + else if (n.size () > 18 && + n.compare (n.size () - 18, 18, "-build-error-email") == 0) + { + build_package_config& bc (build_conf_email (n.size () - 18)); + + bc.error_email = parse_email (nv, "build configuration error", name); + } + else if (optional<pair<string, string>> ba = + build_auxiliary::parse_value_name (n)) + { + if (ba->first.empty ()) // build-auxiliary*? + { + override_build_auxiliary (nv, move (ba->second), m.build_auxiliaries); + } + else // *-build-auxiliary* + { + build_package_config& bc ( + build_conf (ba->first.size (), validate_only)); + + override_build_auxiliary (nv, move (ba->second), bc.auxiliaries, &bc); + } } else + bad_name ("cannot override '" + n + "' value"); + } + + // Common build constraints and build config overrides are mutually + // exclusive. + // + assert (cbc == nullptr || pbc == nullptr); + + // Now, if not in the validate-only mode, as all the potential build + // constraint/email overrides are applied, perform the final adjustments + // to the build config constraints/emails. + // + if (!validate_only) + { + if (cbc != nullptr) // Common build constraints are overridden? { - string d ("cannot override '" + n + "' value"); + for (build_package_config& c: m.build_configs) + { + c.builds.clear (); + c.constraints.clear (); + } + } + else if (pbc != nullptr) // Build config constraints are overridden? + { + for (size_t i (0); i != m.build_configs.size (); ++i) + { + if (find_if (obcs.begin (), obcs.end (), + [i] (const auto& pc) {return pc.first == i;}) == + obcs.end ()) + { + build_package_config& c (m.build_configs[i]); - throw !name.empty () - ? parsing (name, nv.name_line, nv.name_column, d) - : parsing (d); + c.builds.clear (); + c.constraints.clear (); + c.builds.emplace_back ("none", "" /* comment */); + } + } + } + + if (cbe != nullptr) // Common build emails are overridden? + { + for (build_package_config& c: m.build_configs) + { + c.email = nullopt; + c.warning_email = nullopt; + c.error_email = nullopt; + } + } + else if (pbe != nullptr) // Build config emails are overridden? + { + for (size_t i (0); i != m.build_configs.size (); ++i) + { + if (find (obes.begin (), obes.end (), i) == obes.end ()) + { + build_package_config& c (m.build_configs[i]); + + c.email = email (); + c.warning_email = nullopt; + c.error_email = nullopt; + } + } } } } void package_manifest:: + override (const vector<manifest_name_value>& nvs, const string& name) + { + bpkg::override (nvs, name, *this, false /* validate_only */); + } + + void package_manifest:: validate_overrides (const vector<manifest_name_value>& nvs, const string& name) { package_manifest p; - p.override (nvs, name); + bpkg::override (nvs, name, p, true /* validate_only */); } - static const string description_file ("description-file"); - static const string changes_file ("changes-file"); + static const string description_file ("description-file"); + static const string package_description_file ("package-description-file"); + static const string changes_file ("changes-file"); + static const string build_file ("build-file"); void package_manifest:: load_files (const function<load_function>& loader, bool iu) { - auto load = [&loader] (const string& n, const path& p) + // If required, load a file and verify that its content is not empty, if + // the loader returns the content. Make the text type explicit. + // + auto load = [iu, &loader] (typed_text_file& text, + const string& file_value_name) { - string r (loader (n, p)); + // Make the type explicit. + // + optional<text_type> t; - if (r.empty ()) - throw parsing ("package " + n + " references empty file"); + // Convert the potential invalid_argument exception to the + // manifest_parsing exception similar to what we do in the manifest + // parser. + // + try + { + t = text.effective_type (iu); + } + catch (const invalid_argument& e) + { + if (text.type) + { + // Strip trailing "-file". + // + string prefix (file_value_name, 0, file_value_name.size () - 5); - return r; - }; + throw parsing ("invalid " + prefix + "-type package manifest " + + "value: " + e.what ()); + } + else + { + throw parsing ("invalid " + file_value_name + " package " + + "manifest value: " + e.what ()); + } + } - // Load the description-file manifest value. - // - if (description) - { - // Make the description type explicit. - // - optional<text_type> t (effective_description_type (iu)); // Can throw. assert (t || iu); // Can only be absent if we ignore unknown. - if (!description_type && t) - description_type = to_string (*t); + if (!text.type && t) + text.type = to_string (*t); - // At this point the description type can only be absent if the - // description comes from a file. Otherwise, we would end up with the - // plain text. + // At this point the type can only be absent if the text comes from a + // file. Otherwise, we would end up with the plain text. // - assert (description_type || description->file); + assert (text.type || text.file); - if (description->file) + if (text.file) { - if (!description_type) - description_type = "text/unknown; extension=" + - description->path.extension (); + if (!text.type) + text.type = "text/unknown; extension=" + text.path.extension (); - description = text_file (load (description_file, description->path)); + if (optional<string> fc = loader (file_value_name, text.path)) + { + if (fc->empty ()) + throw parsing ("package manifest value " + file_value_name + + " references empty file"); + + text = typed_text_file (move (*fc), move (text.type)); + } } - } + }; + + // Load the descriptions and changes, if present. + // + if (description) + load (*description, description_file); + + if (package_description) + load (*package_description, package_description_file); + + for (typed_text_file& c: changes) + load (c, changes_file); - // Load the changes-file manifest values. + // Load the build-file manifest values. // - for (text_file& c: changes) + if (!buildfile_paths.empty ()) { - if (c.file) - c = text_file (load (changes_file, c.path)); + // Must already be set if the build-file value is parsed. + // + assert (alt_naming); + + dir_path d (*alt_naming ? "build2" : "build"); + + for (auto i (buildfile_paths.begin ()); i != buildfile_paths.end (); ) + { + path& p (*i); + path f (d / p); + f += *alt_naming ? ".build2" : ".build"; + + if (optional<string> fc = loader (build_file, f)) + { + buildfiles.emplace_back (move (p), move (*fc)); + i = buildfile_paths.erase (i); // Moved to buildfiles. + } + else + ++i; + } } } static void - serialize_package_manifest (manifest_serializer& s, - const package_manifest& m, - bool header_only) + serialize_package_manifest ( + manifest_serializer& s, + const package_manifest& m, + bool header_only, + const optional<standard_version>& min_ver = nullopt) { // @@ Should we check that all non-optional values are specified ? // @@ Should we check that values are valid: version release is not empty, @@ -2635,6 +5523,12 @@ namespace bpkg if (m.upstream_version) s.next ("upstream-version", *m.upstream_version); + if (m.type) + s.next ("type", *m.type); + + for (const language& l: m.languages) + s.next ("language", !l.impl ? l.name : l.name + "=impl"); + if (m.project) s.next ("project", m.project->string ()); @@ -2662,26 +5556,46 @@ namespace bpkg if (!m.keywords.empty ()) s.next ("keywords", concatenate (m.keywords, " ")); - if (m.description) + auto serialize_text_file = [&s] (const text_file& v, const string& n) { - if (m.description->file) - s.next ("description-file", - serializer::merge_comment (m.description->path.string (), - m.description->comment)); + if (v.file) + s.next (n + "-file", + serializer::merge_comment (v.path.string (), v.comment)); else - s.next ("description", m.description->text); + s.next (n, v.text); + }; - if (m.description_type) - s.next ("description-type", *m.description_type); - } + auto serialize_description = [&s, &serialize_text_file] + (const optional<typed_text_file>& desc, + const char* prefix) + { + if (desc) + { + string p (prefix); + serialize_text_file (*desc, p + "description"); + + if (desc->type) + s.next (p + "description-type", *desc->type); + } + }; + + serialize_description (m.description, "" /* prefix */); + serialize_description (m.package_description, "package-"); for (const auto& c: m.changes) + serialize_text_file (c, "changes"); + + // If there are any changes, then serialize the type of the first + // changes entry, if present. Note that if it is present, then we assume + // that the type was specified explicitly and so it is the same for all + // entries. + // + if (!m.changes.empty ()) { - if (c.file) - s.next ("changes-file", - serializer::merge_comment (c.path.string (), c.comment)); - else - s.next ("changes", c.text); + const typed_text_file& c (m.changes.front ()); + + if (c.type) + s.next ("changes-type", *c.type); } if (m.url) @@ -2728,21 +5642,29 @@ namespace bpkg m.build_error_email->comment)); for (const dependency_alternatives& d: m.dependencies) - s.next ("depends", - (d.conditional - ? (d.buildtime ? "?* " : "? ") - : (d.buildtime ? "* " : "")) + - serializer::merge_comment (concatenate (d, " | "), d.comment)); + s.next ("depends", d.string ()); for (const requirement_alternatives& r: m.requirements) - s.next ("requires", - (r.conditional - ? (r.buildtime ? "?* " : "? ") - : (r.buildtime ? "* " : "")) + - serializer::merge_comment (concatenate (r, " | "), r.comment)); + s.next ("requires", r.string ()); - for (const test_dependency& p: m.tests) - s.next (to_string (p.type), p.string ()); + for (const test_dependency& t: m.tests) + { + string n (to_string (t.type)); + + // If we generate the manifest for parsing by clients of libbpkg + // versions less than 0.14.0-, then replace the introduced in 0.14.0 + // build-time tests, examples, and benchmarks values with + // tests-0.14.0, examples-0.14.0, and benchmarks-0.14.0, + // respectively. This way such clients will still be able to parse it, + // ignoring unknown values. + // + // @@ TMP time to drop? + // 0.14.0- + if (t.buildtime && min_ver && min_ver->version < 13999990001ULL) + n += "-0.14.0"; + + s.next (n, t.string ()); + } for (const build_class_expr& e: m.builds) s.next ("builds", serializer::merge_comment (e.string (), e.comment)); @@ -2751,9 +5673,86 @@ namespace bpkg s.next (c.exclusion ? "build-exclude" : "build-include", serializer::merge_comment (!c.target ? c.config - : c.config + "/" + *c.target, + : c.config + '/' + *c.target, c.comment)); + for (const build_auxiliary& ba: m.build_auxiliaries) + s.next ((!ba.environment_name.empty () + ? "build-auxiliary-" + ba.environment_name + : "build-auxiliary"), + serializer::merge_comment (ba.config, ba.comment)); + + for (const build_package_config& bc: m.build_configs) + { + if (!bc.builds.empty ()) + { + string n (bc.name + "-builds"); + for (const build_class_expr& e: bc.builds) + s.next (n, serializer::merge_comment (e.string (), e.comment)); + } + + if (!bc.constraints.empty ()) + { + string in (bc.name + "-build-include"); + string en (bc.name + "-build-exclude"); + + for (const build_constraint& c: bc.constraints) + s.next (c.exclusion ? en : in, + serializer::merge_comment (!c.target + ? c.config + : c.config + '/' + *c.target, + c.comment)); + } + + if (!bc.auxiliaries.empty ()) + { + string n (bc.name + "-build-auxiliary"); + + for (const build_auxiliary& ba: bc.auxiliaries) + s.next ((!ba.environment_name.empty () + ? n + '-' + ba.environment_name + : n), + serializer::merge_comment (ba.config, ba.comment)); + } + + if (!bc.arguments.empty () || !bc.comment.empty ()) + s.next (bc.name + "-build-config", + serializer::merge_comment (bc.arguments, bc.comment)); + + if (bc.email) + s.next (bc.name + "-build-email", + serializer::merge_comment (*bc.email, bc.email->comment)); + + if (bc.warning_email) + s.next (bc.name + "-build-warning-email", + serializer::merge_comment (*bc.warning_email, + bc.warning_email->comment)); + + if (bc.error_email) + s.next (bc.name + "-build-error-email", + serializer::merge_comment (*bc.error_email, + bc.error_email->comment)); + } + + bool an (m.alt_naming && *m.alt_naming); + + if (m.bootstrap_build) + s.next (an ? "bootstrap-build2" : "bootstrap-build", + *m.bootstrap_build); + + if (m.root_build) + s.next (an ? "root-build2" : "root-build", *m.root_build); + + for (const auto& bf: m.buildfiles) + s.next (bf.path.posix_string () + (an ? "-build2" : "-build"), + bf.content); + + for (const path& f: m.buildfile_paths) + s.next ("build-file", f.posix_string () + (an ? ".build2" : ".build")); + + for (const distribution_name_value& nv: m.distribution_values) + s.next (nv.name, nv.value); + if (m.location) s.next ("location", m.location->posix_string ()); @@ -2768,9 +5767,9 @@ namespace bpkg } void package_manifest:: - serialize (serializer& s) const + serialize (serializer& s, const optional<standard_version>& min_ver) const { - serialize_package_manifest (s, *this, false); + serialize_package_manifest (s, *this, false, min_ver); } void package_manifest:: @@ -2984,7 +5983,7 @@ namespace bpkg } void pkg_package_manifests:: - serialize (serializer& s) const + serialize (serializer& s, const optional<standard_version>& min_ver) const { // Serialize the package list manifest. // @@ -3002,21 +6001,32 @@ namespace bpkg { throw serialization ( s.name (), - d + " for " + p.name.string () + "-" + p.version.string ()); + d + " for " + p.name.string () + '-' + p.version.string ()); }; - if (p.description) + // Throw manifest_serialization if the text is in a file or untyped. + // + auto verify_text_file = [&bad_value] (const typed_text_file& v, + const string& n) { - if (p.description->file) - bad_value ("forbidden description-file"); + if (v.file) + bad_value ("forbidden " + n + "-file"); - if (!p.description_type) - bad_value ("no valid description-type"); - } + if (!v.type) + bad_value ("no valid " + n + "-type"); + }; + + if (p.description) + verify_text_file (*p.description, "description"); + + if (p.package_description) + verify_text_file (*p.package_description, "package-description"); for (const auto& c: p.changes) - if (c.file) - bad_value ("forbidden changes-file"); + verify_text_file (c, "changes"); + + if (!p.buildfile_paths.empty ()) + bad_value ("forbidden build-file"); if (!p.location) bad_value ("no valid location"); @@ -3024,7 +6034,7 @@ namespace bpkg if (!p.sha256sum) bad_value ("no valid sha256sum"); - pkg_package_manifest (s, p); + pkg_package_manifest (s, p, min_ver); } s.next ("", ""); // End of stream. @@ -3370,7 +6380,7 @@ namespace bpkg if (optional<repository_type> r = parse_repository_type (t)) return *r; - throw invalid_argument ("invalid repository type '" + t + "'"); + throw invalid_argument ("invalid repository type '" + t + '\''); } repository_type @@ -3766,6 +6776,19 @@ namespace bpkg // path sp; + // Convert the local repository location path to lower case on Windows. + // + // Note that we need to do that prior to stripping the special path + // components to match them case-insensitively, so, for example, the + // c:\pkg\1\stable and c:\Pkg\1\stable (or c:\repo.git and c:\repo.Git) + // repository locations end up with the same canonical name. + // + #ifdef _WIN32 + const path& p (local () ? path (lcase (up.string ())) : up); + #else + const path& p (up); + #endif + switch (type_) { case repository_type::pkg: @@ -3773,7 +6796,7 @@ namespace bpkg // Produce the pkg repository canonical name <prefix>/<path> part (see // the Repository Chaining documentation for more details). // - sp = strip_path (up, + sp = strip_path (p, remote () ? strip_mode::component : strip_mode::path); @@ -3783,7 +6806,7 @@ namespace bpkg // stripping just the version component. // if (absolute () && sp.empty ()) - sp = strip_path (up, strip_mode::version); + sp = strip_path (p, strip_mode::version); break; } @@ -3791,7 +6814,7 @@ namespace bpkg { // For dir repository we use the absolute (normalized) path. // - sp = up; + sp = p; break; } case repository_type::git: @@ -3799,7 +6822,7 @@ namespace bpkg // For git repository we use the absolute (normalized) path, stripping // the .git extension if present. // - sp = strip_path (up, strip_mode::extension); + sp = strip_path (p, strip_mode::extension); break; } } @@ -4016,7 +7039,8 @@ namespace bpkg parse_repository_manifest (parser& p, name_value nv, repository_type base_type, - bool iu) + bool iu, + bool verify_version = true) { auto bad_name ([&p, &nv](const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d);}); @@ -4026,11 +7050,16 @@ namespace bpkg // Make sure this is the start and we support the version. // - if (!nv.name.empty ()) - bad_name ("start of repository manifest expected"); + if (verify_version) + { + if (!nv.name.empty ()) + bad_name ("start of repository manifest expected"); - if (nv.value != "1") - bad_value ("unsupported format version"); + if (nv.value != "1") + bad_value ("unsupported format version"); + + nv = p.next (); + } repository_manifest r; @@ -4041,7 +7070,7 @@ namespace bpkg optional<repository_type> type; optional<name_value> location; - for (nv = p.next (); !nv.empty (); nv = p.next ()) + for (; !nv.empty (); nv = p.next ()) { string& n (nv.name); string& v (nv.value); @@ -4386,13 +7415,126 @@ namespace bpkg parse_repository_manifests (parser& p, repository_type base_type, bool iu, + optional<repositories_manifest_header>& header, vector<repository_manifest>& ms) { + // Return nullopt on eos. Otherwise, parse and verify the + // manifest-starting format version value and return the subsequent + // manifest value, that can potentially be empty (for an empty manifest). + // + // Also save the manifest-starting position (start_nv) for the + // diagnostics. + // + name_value start_nv; + auto next_manifest = [&p, &start_nv] () -> optional<name_value> + { + start_nv = p.next (); + + if (start_nv.empty ()) + return nullopt; + + // Make sure this is the start and we support the version. + // + if (!start_nv.name.empty ()) + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, + "start of repository manifest expected"); + + if (start_nv.value != "1") + throw parsing (p.name (), start_nv.value_line, start_nv.value_column, + "unsupported format version"); + + return p.next (); + }; + + optional<name_value> nv (next_manifest ()); + + if (!nv) + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, + "start of repository manifest expected"); + + auto bad_name ([&p, &nv](const string& d) { + throw parsing (p.name (), nv->name_line, nv->name_column, d);}); + + auto bad_value ([&p, &nv](const string& d) { + throw parsing (p.name (), nv->value_line, nv->value_column, d);}); + + // First check if this a header manifest, if any manifest is present. + // + // Note that if this is none of the known header values, then we assume + // this is a repository manifest (rather than a header that starts with an + // unknown value; so use one of the original names to make sure it's + // recognized as such, for example `compression:none`). + // + if (nv->name == "min-bpkg-version" || + nv->name == "compression") + { + header = repositories_manifest_header (); + + // First verify the version, if any. + // + if (nv->name == "min-bpkg-version") + try + { + const string& v (nv->value); + standard_version mbv (v, standard_version::allow_earliest); + + if (mbv > standard_version (LIBBPKG_VERSION_STR)) + bad_value ( + "incompatible repositories manifest: minimum bpkg version is " + v); + + header->min_bpkg_version = move (mbv); + + nv = p.next (); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid minimum bpkg version: ") + e.what ()); + } + + // Parse the remaining header values, failing if min-bpkg-version is + // encountered (should be first). + // + for (; !nv->empty (); nv = p.next ()) + { + const string& n (nv->name); + string& v (nv->value); + + if (n == "min-bpkg-version") + { + bad_name ("minimum bpkg version must be first in repositories " + "manifest header"); + } + else if (n == "compression") + { + header->compression = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in repositories manifest header"); + } + + nv = next_manifest (); + } + + // Parse the manifest list. + // + // Note that if nv is present, then it contains the manifest's first + // value, which can potentially be empty (for an empty manifest, which is + // recognized as a base manifest). + // + // Also note that if the header is present but is not followed by + // repository manifests (there is no ':' line after the header values), + // then the empty manifest list is returned (no base manifest is + // automatically added). + // bool base (false); - for (name_value nv (p.next ()); !nv.empty (); nv = p.next ()) + while (nv) { - ms.push_back (parse_repository_manifest (p, nv, base_type, iu)); + ms.push_back (parse_repository_manifest (p, + *nv, + base_type, + iu, + false /* verify_version */)); // Make sure that there is a single base repository manifest in the // list. @@ -4400,19 +7542,38 @@ namespace bpkg if (ms.back ().effective_role () == repository_role::base) { if (base) - throw parsing (p.name (), nv.name_line, nv.name_column, + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, "base repository manifest redefinition"); base = true; } + + nv = next_manifest (); } } // Serialize the repository manifest list. // static void - serialize_repository_manifests (serializer& s, - const vector<repository_manifest>& ms) + serialize_repository_manifests ( + serializer& s, + const optional<repositories_manifest_header>& header, + const vector<repository_manifest>& ms) { + if (header) + { + s.next ("", "1"); // Start of manifest. + + const repositories_manifest_header& h (*header); + + if (h.min_bpkg_version) + s.next ("min-bpkg-version", h.min_bpkg_version->string ()); + + if (h.compression) + s.next ("compression", *h.compression); + + s.next ("", ""); // End of manifest. + } + for (const repository_manifest& r: ms) r.serialize (s); @@ -4424,13 +7585,13 @@ namespace bpkg pkg_repository_manifests:: pkg_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::pkg, iu, *this); + parse_repository_manifests (p, repository_type::pkg, iu, header, *this); } void pkg_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // dir_repository_manifests @@ -4438,13 +7599,13 @@ namespace bpkg dir_repository_manifests:: dir_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::dir, iu, *this); + parse_repository_manifests (p, repository_type::dir, iu, header, *this); } void dir_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // git_repository_manifests @@ -4452,13 +7613,13 @@ namespace bpkg git_repository_manifests:: git_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::git, iu, *this); + parse_repository_manifests (p, repository_type::git, iu, header, *this); } void git_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // signature_manifest @@ -4560,4 +7721,41 @@ namespace bpkg s.next ("", ""); // End of manifest. } + + // extract_package_*() + // + package_name + extract_package_name (const char* s, bool allow_version) + { + if (!allow_version) + return package_name (s); + + // Calculate the package name length as a length of the prefix that + // doesn't contain spaces, slashes and the version constraint starting + // characters. Note that none of them are valid package name characters. + // + size_t n (strcspn (s, " /=<>([~^")); + return package_name (string (s, n)); + } + + version + extract_package_version (const char* s, version::flags fl) + { + using traits = string::traits_type; + + if (const char* p = traits::find (s, traits::length (s), '/')) + { + version r (p + 1, fl); + + if (r.release && r.release->empty ()) + throw invalid_argument ("earliest version"); + + if (r.compare (stub_version, true /* ignore_revision */) == 0) + throw invalid_argument ("stub version"); + + return r; + } + + return version (); + } } diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index b666716..4b0ccc6 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -8,16 +8,16 @@ #include <string> #include <vector> #include <cassert> -#include <cstdint> // uint16_t +#include <cstdint> // uint*_t #include <ostream> -#include <utility> // move() -#include <stdexcept> // logic_error +#include <utility> // move(), pair #include <functional> -#include <libbutl/url.mxx> -#include <libbutl/path.mxx> -#include <libbutl/optional.mxx> -#include <libbutl/small-vector.mxx> +#include <libbutl/url.hxx> +#include <libbutl/path.hxx> +#include <libbutl/optional.hxx> +#include <libbutl/small-vector.hxx> +#include <libbutl/standard-version.hxx> #include <libbutl/manifest-forward.hxx> #include <libbpkg/package-name.hxx> @@ -67,13 +67,20 @@ namespace bpkg // std::invalid_argument if the passed string is not a valid version // representation. // + enum flags + { + none = 0, + fold_zero_revision = 0x01, + allow_iteration = 0x02 + }; + explicit - version (const std::string& v, bool fold_zero_revision = true) - : version (v.c_str (), fold_zero_revision) {} + version (const std::string& v, flags fl = fold_zero_revision) + : version (v.c_str (), fl) {} explicit - version (const char* v, bool fold_zero_revision = true) - : version (data_type (v, data_type::parse::full, fold_zero_revision)) + version (const char* v, flags fl = fold_zero_revision) + : version (data_type (v, data_type::parse::full, fl)) { } @@ -94,7 +101,7 @@ namespace bpkg version (version&&) = default; version (const version&) = default; - version& operator= (version&&); + version& operator= (version&&) noexcept; version& operator= (const version&); // If the revision is ignored, then the iteration (that semantically @@ -103,23 +110,12 @@ namespace bpkg std::string string (bool ignore_revision = false, bool ignore_iteration = false) const; - bool - operator< (const version& v) const noexcept {return compare (v) < 0;} - - bool - operator> (const version& v) const noexcept {return compare (v) > 0;} - - bool - operator== (const version& v) const noexcept {return compare (v) == 0;} - - bool - operator<= (const version& v) const noexcept {return compare (v) <= 0;} - - bool - operator>= (const version& v) const noexcept {return compare (v) >= 0;} - - bool - operator!= (const version& v) const noexcept {return compare (v) != 0;} + bool operator< (const version& v) const noexcept; + bool operator> (const version& v) const noexcept; + bool operator== (const version& v) const noexcept; + bool operator<= (const version& v) const noexcept; + bool operator>= (const version& v) const noexcept; + bool operator!= (const version& v) const noexcept; // If the revision is ignored, then the iteration is also ignored, // regardless of the argument (see above for details). @@ -127,28 +123,7 @@ namespace bpkg int compare (const version& v, bool ignore_revision = false, - bool ignore_iteration = false) const noexcept - { - if (epoch != v.epoch) - return epoch < v.epoch ? -1 : 1; - - if (int c = canonical_upstream.compare (v.canonical_upstream)) - return c; - - if (int c = canonical_release.compare (v.canonical_release)) - return c; - - if (!ignore_revision) - { - if (revision != v.revision) - return revision < v.revision ? -1 : 1; - - if (!ignore_iteration && iteration != v.iteration) - return iteration < v.iteration ? -1 : 1; - } - - return 0; - } + bool ignore_iteration = false) const noexcept; bool empty () const noexcept @@ -168,7 +143,7 @@ namespace bpkg { enum class parse {full, upstream, release}; - data_type (const char*, parse, bool fold_zero_revision); + data_type (const char*, parse, flags); // Note that there is no iteration component as it can't be present in // the string representation passed to the ctor. @@ -177,6 +152,7 @@ namespace bpkg std::string upstream; butl::optional<std::string> release; butl::optional<std::uint16_t> revision; + std::uint32_t iteration; std::string canonical_upstream; std::string canonical_release; }; @@ -187,7 +163,7 @@ namespace bpkg upstream (std::move (d.upstream)), release (std::move (d.release)), revision (d.revision), - iteration (0), + iteration (d.iteration), canonical_upstream (std::move (d.canonical_upstream)), canonical_release (std::move (d.canonical_release)) {} }; @@ -198,6 +174,11 @@ namespace bpkg return os << (v.empty () ? "<empty-version>" : v.string ()); } + version::flags operator& (version::flags, version::flags); + version::flags operator| (version::flags, version::flags); + version::flags operator&= (version::flags&, version::flags); + version::flags operator|= (version::flags&, version::flags); + // priority // class priority @@ -214,11 +195,17 @@ namespace bpkg operator value_type () const {return value;} }; - // description - // description-file - // change - // change-file + // language // + struct language + { + std::string name; + bool impl; // True if implementation-only. + + language (): impl (false) {} + language (std::string n, bool i): name (std::move (n)), impl (i) {} + }; + class LIBBPKG_EXPORT text_file { public: @@ -244,14 +231,80 @@ namespace bpkg text_file (path_type p, std::string c) : file (true), path (std::move (p)), comment (std::move (c)) {} - text_file (text_file&&); + text_file (text_file&&) noexcept; text_file (const text_file&); - text_file& operator= (text_file&&); + text_file& operator= (text_file&&) noexcept; text_file& operator= (const text_file&); ~text_file (); }; + enum class text_type + { + plain, + common_mark, + github_mark + }; + + LIBBPKG_EXPORT std::string + to_string (text_type); + + // Throw std::invalid_argument if the argument is not a well-formed text + // type. Otherwise, return nullopt for an unknown text variant. + // + LIBBPKG_EXPORT butl::optional<text_type> + to_text_type (const std::string&); + + inline std::ostream& + operator<< (std::ostream& os, text_type t) + { + return os << to_string (t); + } + + // description + // description-file + // description-type + // package-description + // package-description-file + // package-description-type + // change + // change-file + // change-type + // + class LIBBPKG_EXPORT typed_text_file: public text_file + { + public: + butl::optional<std::string> type; + + // File text constructor. + // + explicit + typed_text_file (std::string s = "", + butl::optional<std::string> t = butl::nullopt) + : text_file (std::move (s)), type (std::move (t)) {} + + // File reference constructor. + // + typed_text_file (path_type p, + std::string c, + butl::optional<std::string> t = butl::nullopt) + : text_file (std::move (p), std::move (c)), type (std::move (t)) {} + + // Return the type value if present, text_type::github_mark if it refers + // to a file with the .md or .markdown extension and text_type::plain if + // it refers to a file with the .txt extension or no extension or the text + // does not come from a file. Depending on the ignore_unknown value either + // throw std::invalid_argument or return nullopt if the type value or the + // file extension is unknown. + // + // Note: also throws std::invalid_argument if the type is not well-formed. + // This, however, may not happen for an object created by the package + // manifest parser since it has already verified that. + // + butl::optional<text_type> + effective_type (bool ignore_unknown = false) const; + }; + // license // class licenses: public butl::small_vector<std::string, 1> @@ -274,7 +327,7 @@ namespace bpkg // - is not local (the scheme is not `file`) // - authority is present and is not empty // - // See libbutl/url.mxx for details. + // See libbutl/url.hxx for details. // // NOTE: this class must not be DLL-exported wholesale (non-exported base). // @@ -378,68 +431,366 @@ namespace bpkg } inline bool - operator== (const version_constraint& x, const version_constraint& y) - { - return x.min_version == y.min_version && x.max_version == y.max_version && - x.min_open == y.min_open && x.max_open == y.max_open; - } + operator== (const version_constraint&, const version_constraint&); inline bool - operator!= (const version_constraint& x, const version_constraint& y) - { - return !(x == y); - } + operator!= (const version_constraint&, const version_constraint&); struct LIBBPKG_EXPORT dependency { package_name name; butl::optional<version_constraint> constraint; + dependency () = default; + dependency (package_name n, butl::optional<version_constraint> c) + : name (std::move (n)), constraint (std::move (c)) {} + + // Parse the dependency string representation in the + // `<name> [<version-constraint>]` form. Throw std::invalid_argument if + // the value is invalid. + // + explicit + dependency (std::string); + std::string string () const; }; + std::ostream& + operator<< (std::ostream&, const dependency&); + + // 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 + // + // In the multi-line form the block may contain comments besides the + // clauses. The '#' character starts a single-line comment which spans + // until the end of the line. Unless it is followed with '\' followed by + // the newline in which case this is a multi-line comment which spans + // until the closing '#\' is encountered. + // + // 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, + 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)) {} + + // Can be used to copy a dependency alternative object, while omitting + // some clauses which are no longer needed. + // + 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, + butl::small_vector<dependency, 1> ds) + : small_vector<dependency, 1> (move (ds)), + 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). + // + LIBBPKG_EXPORT std::string + string () const; + + // Return true if the string() function would return the single-line + // representation. + // + bool + single_line () const + { + return !prefer && + !require && + (!reflect || reflect->find ('\n') == std::string::npos); + } + }; + inline std::ostream& - operator<< (std::ostream& os, const dependency& d) + operator<< (std::ostream& os, const dependency_alternative& da) { - return os << d.string (); + return os << da.string (); } - // depends - // - class dependency_alternatives: public butl::small_vector<dependency, 1> + 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 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&, + 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. + // + bool + conditional () const; }; - LIBBPKG_EXPORT std::ostream& - operator<< (std::ostream&, const dependency_alternatives&); + inline std::ostream& + operator<< (std::ostream& os, const dependency_alternatives& das) + { + return os << das.string (); + } // requires // - class requirement_alternatives: public butl::small_vector<std::string, 1> + // 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, + butl::optional<std::string> r) + : enable (std::move (e)), reflect (std::move (r)) {} + + // Return the single-line representation if possible (the reflect clause + // either absent or contains no newlines). + // + LIBBPKG_EXPORT std::string + string () const; + + // Return true if the string() function would return the single-line + // representation. + // + bool + single_line () const + { + return !reflect || reflect->find ('\n') == std::string::npos; + } + + // 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 + // 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&, + 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. + // + bool + conditional () const; + + // Return true if this is a single simple requirement alternative. + // + bool + simple () const + { + return size () == 1 && back ().simple (); + } }; + inline std::ostream& + operator<< (std::ostream& os, const requirement_alternatives& ra) + { + return os << ra.string (); + } + class build_constraint { public: - // If true, then the package should not be built for matching + // If true, then the package should not be built for matching target // configurations by automated build bots. // bool exclusion; @@ -474,48 +825,33 @@ namespace bpkg // enum class package_manifest_flags: std::uint16_t { - none = 0x00, - - forbid_file = 0x01, // Forbid *-file manifest values. - forbid_location = 0x02, - forbid_sha256sum = 0x04, - forbid_fragment = 0x08, - forbid_incomplete_dependencies = 0x10, - - require_location = 0x20, - require_sha256sum = 0x40, - require_description_type = 0x80 + none = 0x000, + + forbid_file = 0x001, // Forbid *-file manifest values. + forbid_location = 0x002, + forbid_sha256sum = 0x004, + forbid_fragment = 0x008, + forbid_incomplete_values = 0x010, // depends, <distribution>-version, etc. + + require_location = 0x020, + require_sha256sum = 0x040, + require_text_type = 0x080, // description-type, changes-type, etc. + require_bootstrap_build = 0x100 }; - inline package_manifest_flags - operator&= (package_manifest_flags& x, package_manifest_flags y) - { - return x = static_cast<package_manifest_flags> ( - static_cast<std::uint16_t> (x) & - static_cast<std::uint16_t> (y)); - } + package_manifest_flags + operator& (package_manifest_flags, package_manifest_flags); - inline package_manifest_flags - operator|= (package_manifest_flags& x, package_manifest_flags y) - { - return x = static_cast<package_manifest_flags> ( - static_cast<std::uint16_t> (x) | - static_cast<std::uint16_t> (y)); - } + package_manifest_flags + operator| (package_manifest_flags, package_manifest_flags); - inline package_manifest_flags - operator& (package_manifest_flags x, package_manifest_flags y) - { - return x &= y; - } + package_manifest_flags + operator&= (package_manifest_flags&, package_manifest_flags); - inline package_manifest_flags - operator| (package_manifest_flags x, package_manifest_flags y) - { - return x |= y; - } + package_manifest_flags + operator|= (package_manifest_flags&, package_manifest_flags); - // Build configuration class term. + // Target build configuration class term. // class LIBBPKG_EXPORT build_class_term { @@ -544,9 +880,9 @@ namespace bpkg build_class_term () : operation ('\0'), inverted (false), simple (true), name () {} - build_class_term (build_class_term&&); + build_class_term (build_class_term&&) noexcept; build_class_term (const build_class_term&); - build_class_term& operator= (build_class_term&&); + build_class_term& operator= (build_class_term&&) noexcept; build_class_term& operator= (const build_class_term&); ~build_class_term (); @@ -565,8 +901,8 @@ namespace bpkg // using build_class_inheritance_map = std::map<std::string, std::string>; - // Build configuration class expression. Includes comment and optional - // underlying set. + // Target build configuration class expression. Includes comment and + // optional underlying set. // class LIBBPKG_EXPORT build_class_expr { @@ -615,10 +951,10 @@ namespace bpkg std::string string () const; - // Match a build configuration that belongs to the specified list of - // classes (and recursively to their bases) against the expression. Either - // return or update the result (the latter allows to sequentially matching - // against a list of expressions). + // Match a target build configuration that belongs to the specified list + // of classes (and recursively to their bases) against the expression. + // Either return or update the result (the latter allows to sequentially + // matching against a list of expressions). // // Notes: // @@ -626,7 +962,8 @@ namespace bpkg // inheritance cycles, etc.). // // - The underlying class set doesn't affect the match in any way (it - // should have been used to pre-filter the set of build configurations). + // should have been used to pre-filter the set of target build + // configurations). // void match (const strings&, @@ -634,12 +971,7 @@ namespace bpkg bool& result) const; bool - match (const strings& cs, const build_class_inheritance_map& bs) const - { - bool r (false); - match (cs, bs, r); - return r; - } + match (const strings&, const build_class_inheritance_map&) const; }; inline std::ostream& @@ -648,27 +980,131 @@ namespace bpkg return os << bce.string (); } - enum class text_type + // Build auxiliary configuration name-matching wildcard. Includes optional + // environment name (specified as a suffix in the [*-]build-auxiliary[-*] + // value name) and comment. + // + class LIBBPKG_EXPORT build_auxiliary { - plain, - common_mark, - github_mark - }; + public: + std::string environment_name; - LIBBPKG_EXPORT std::string - to_string (text_type); + // Filesystem wildcard pattern for the build auxiliary configuration name. + // + std::string config; - // Throw std::invalid_argument if the argument is not a well-formed text - // type. Otherwise, return nullopt for an unknown text variant. - // - LIBBPKG_EXPORT butl::optional<text_type> - to_text_type (const std::string&); // May throw std::invalid_argument. + std::string comment; - inline std::ostream& - operator<< (std::ostream& os, text_type t) + build_auxiliary () = default; + build_auxiliary (std::string en, + std::string cf, + std::string cm) + : environment_name (std::move (en)), + config (std::move (cf)), + comment (std::move (cm)) {} + + // Parse a package manifest value name in the [*-]build-auxiliary[-*] form + // into the pair of the build package configuration name (first) and the + // build auxiliary environment name (second), with an unspecified name + // represented as an empty string. Return nullopt if the value name + // doesn't match this form. + // + static butl::optional<std::pair<std::string, std::string>> + parse_value_name (const std::string&); + }; + + // Package build configuration. Includes comment and optional overrides for + // target build configuration class expressions/constraints and build + // notification emails. + // + class build_package_config { - return os << to_string (t); - } + public: + using email_type = bpkg::email; + + std::string name; + + // Whitespace separated list of potentially double/single-quoted package + // configuration arguments for bpkg-pkg-build command executed by + // automated build bots. + // + std::string arguments; + + std::string comment; + + butl::small_vector<build_class_expr, 1> builds; + std::vector<build_constraint> constraints; + + // Note that all entries in this list must have distinct environment names + // (with empty name being one of the possibilities). + // + std::vector<build_auxiliary> auxiliaries; + + butl::optional<email_type> email; + butl::optional<email_type> warning_email; + butl::optional<email_type> error_email; + + build_package_config () = default; + + // Built incrementally. + // + explicit + build_package_config (std::string n): name (move (n)) {} + + // Return the configuration's build class expressions/constraints if they + // override the specified common expressions/constraints and return the + // latter otherwise (see package_manifest::override() for the override + // semantics details). + // + const butl::small_vector<build_class_expr, 1>& + effective_builds (const butl::small_vector<build_class_expr, 1>& common) + const noexcept + { + return !builds.empty () ? builds : common; + } + + const std::vector<build_constraint>& + effective_constraints (const std::vector<build_constraint>& common) const + noexcept + { + return !builds.empty () || !constraints.empty () ? constraints : common; + } + + // Return the configuration's auxiliaries, if specified, and the common + // ones otherwise. + // + const std::vector<build_auxiliary>& + effective_auxiliaries (const std::vector<build_auxiliary>& common) const + noexcept + { + return !auxiliaries.empty () ? auxiliaries : common; + } + + // Return the configuration's build notification emails if they override + // the specified common build notification emails and return the latter + // otherwise (see package_manifest::override() for the override semantics + // details). + // + const butl::optional<email_type>& + effective_email (const butl::optional<email_type>& common) const noexcept + { + return email || warning_email || error_email ? email : common; + } + + const butl::optional<email_type>& + effective_warning_email (const butl::optional<email_type>& common) const + noexcept + { + return email || warning_email || error_email ? warning_email : common; + } + + const butl::optional<email_type>& + effective_error_email (const butl::optional<email_type>& common) const + noexcept + { + return email || warning_email || error_email ? error_email : common; + } + }; enum class test_dependency_type { @@ -691,15 +1127,95 @@ namespace bpkg return os << to_string (t); } - struct test_dependency: dependency + struct LIBBPKG_EXPORT test_dependency: dependency { test_dependency_type type; + bool buildtime; + butl::optional<std::string> enable; + butl::optional<std::string> reflect; test_dependency () = default; test_dependency (package_name n, test_dependency_type t, - butl::optional<version_constraint> c) - : dependency {std::move (n), std::move (c)}, type (t) {} + bool b, + butl::optional<version_constraint> c, + butl::optional<std::string> e, + butl::optional<std::string> r) + : dependency {std::move (n), std::move (c)}, + type (t), + buildtime (b), + enable (std::move (e)), + reflect (std::move (r)) {} + + // Parse the test dependency string representation in the + // `[*] <name> [<version-constraint>] ['?' <enable-condition>] [<reflect-config>]` + // form. Throw std::invalid_argument if the value is invalid. + // + // Verify that the reflect clause, if present, refers to the test + // dependency package configuration variable. Note that such variable + // value normally signals the dependent package being tested. + // + test_dependency (std::string, test_dependency_type); + + std::string + string () const; + }; + + // Package's buildfile path and content. + // + struct buildfile + { + // The path is relative to the package's build/ subdirectory with the + // extension stripped. + // + // For example, for the build/config/common.build file the path will be + // config/common. + // + // Note that the actual file path depends on the project's buildfile + // naming scheme and for the config/common example above the actual path + // can also be build2/config/common.build2. + // + butl::path path; + std::string content; + + buildfile () = default; + buildfile (butl::path p, std::string c) + : path (std::move (p)), + content (std::move (c)) {} + }; + + // Binary distribution package information. + // + // The name is prefixed with the <distribution> id, typically name/version + // pair in the <name>[_<version>] form. For example: + // + // debian-name + // debian_10-name + // ubuntu_20.04-name + // + // Currently recognized names: + // + // <distribution>-name + // <distribution>-version + // <distribution>-to-downstream-version + // + // Note that the value format/semantics can be distribution-specific. + // + struct distribution_name_value + { + std::string name; + std::string value; + + distribution_name_value () = default; + distribution_name_value (std::string n, std::string v) + : name (std::move (n)), + value (std::move (v)) {} + + // Return the name's <distribution> component if the name has the + // specified suffix, which is assumed to be valid (-name, etc). + // + butl::optional<std::string> + distribution (const std::string& suffix) const; }; class LIBBPKG_EXPORT package_manifest @@ -712,21 +1228,18 @@ namespace bpkg package_name name; version_type version; butl::optional<std::string> upstream_version; + butl::optional<std::string> type; // <name>[, ...] + butl::small_vector<language, 1> languages; // <name>[=impl][, ...] butl::optional<package_name> project; butl::optional<priority_type> priority; std::string summary; - - // @@ Replace with small_vector<licenses, 1>. Note that currently it is - // unsupported by the odb::nested_*() functions that are - // std::vector-specific. - // - std::vector<licenses> license_alternatives; + butl::small_vector<licenses, 1> license_alternatives; butl::small_vector<std::string, 5> topics; butl::small_vector<std::string, 5> keywords; - butl::optional<text_file> description; - butl::optional<std::string> description_type; - butl::small_vector<text_file, 1> changes; + butl::optional<typed_text_file> description; + butl::optional<typed_text_file> package_description; + butl::small_vector<typed_text_file, 1> changes; butl::optional<manifest_url> url; butl::optional<manifest_url> doc_url; butl::optional<manifest_url> src_url; @@ -740,8 +1253,39 @@ namespace bpkg std::vector<requirement_alternatives> requirements; butl::small_vector<test_dependency, 1> tests; + // Common build classes, constraints, and auxiliaries that apply to all + // configurations unless overridden. + // + // Note that all entries in build_auxiliaries must have distinct + // environment names (with empty name being one of the possibilities). + // butl::small_vector<build_class_expr, 1> builds; std::vector<build_constraint> build_constraints; + std::vector<build_auxiliary> build_auxiliaries; + + // Note that the parsing constructor adds the implied (empty) default + // configuration at the beginning of the list. Also note that serialize() + // writes no values for such a configuration. + // + butl::small_vector<build_package_config, 1> build_configs; // 1 for default. + + // If true, then this package use the alternative buildfile naming scheme + // (build2/, .build2). In the manifest serialization this is encoded as + // either *-build or *-build2 value names. + // + butl::optional<bool> alt_naming; + + butl::optional<std::string> bootstrap_build; + butl::optional<std::string> root_build; + + // Additional buildfiles which are potentially included by root.build. + // + std::vector<buildfile> buildfiles; // Buildfiles content. + std::vector<butl::path> buildfile_paths; + + // The binary distributions package information. + // + std::vector<distribution_name_value> distribution_values; // The following values are only valid in the manifest list (and only for // certain repository types). @@ -750,19 +1294,45 @@ namespace bpkg butl::optional<std::string> sha256sum; butl::optional<std::string> fragment; - const package_name& - effective_project () const noexcept {return project ? *project : name;} + // Extract the name from optional type, returning either `exe`, `lib`, or + // `other`. + // + // Specifically, if type is present but the name is not recognized, then + // return `other`. If type is absent and the package name starts with the + // `lib` prefix, then return `lib`. Otherwise, return `exe`. + // + std::string + effective_type () const; - // Return the description type value if present, text_type::github_mark if - // the description refers to a file with the .md or .markdown extension - // and text_type::plain if it refers to a file with the .txt extension or - // no extension or the description does not come from a file. Depending on - // the ignore_unknown value either throw std::invalid_argument or return - // nullopt if the description value or the file extension is unknown. - // Throw std::logic_error if the description value is nullopt. + static std::string + effective_type (const butl::optional<std::string>&, const package_name&); + + // Extract sub-options from optional type. // - butl::optional<text_type> - effective_description_type (bool ignore_unknown = false) const; + strings + effective_type_sub_options () const; + + static strings + effective_type_sub_options (const butl::optional<std::string>&); + + // Translate the potentially empty list of languages to a non-empty one. + // + // Specifically, if the list of languages is not empty, then return it as + // is. Otherwise, if the package name has an extension (as in, say, + // libbutl.bash), then return it as the language. Otherwise, return `cc` + // (unspecified c-common language). + // + butl::small_vector<language, 1> + effective_languages () const; + + static butl::small_vector<language, 1> + effective_languages (const butl::small_vector<language, 1>&, + const package_name&); + + // Return effective project name. + // + const package_name& + effective_project () const noexcept {return project ? *project : name;} public: package_manifest () = default; @@ -774,7 +1344,7 @@ namespace bpkg // package_manifest (butl::manifest_parser&, bool ignore_unknown = false, - bool complete_dependencies = true, + bool complete_values = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | @@ -786,7 +1356,7 @@ namespace bpkg // release, etc). // // In particular, the translation function may "patch" the version with - // the snapshot information (see <libbutl/standard-version.mxx> for + // the snapshot information (see <libbutl/standard-version.hxx> for // details). This translation is normally required for manifests of // packages that are accessed as directories (as opposed to package // archives that should have their version already patched). @@ -796,7 +1366,32 @@ namespace bpkg package_manifest (butl::manifest_parser&, const std::function<translate_function>&, bool ignore_unknown = false, - bool complete_depends = true, + bool complete_values = true, + package_manifest_flags = + package_manifest_flags::forbid_location | + package_manifest_flags::forbid_sha256sum | + package_manifest_flags::forbid_fragment); + + // As above but construct the package manifest from the pre-parsed + // manifest values list. + // + // Note that the list is expected not to contain the format version nor + // the end-of-manifest/stream pairs. + // + package_manifest (const std::string& name, + std::vector<butl::manifest_name_value>&&, + bool ignore_unknown = false, + bool complete_values = true, + package_manifest_flags = + package_manifest_flags::forbid_location | + package_manifest_flags::forbid_sha256sum | + package_manifest_flags::forbid_fragment); + + package_manifest (const std::string& name, + std::vector<butl::manifest_name_value>&&, + const std::function<translate_function>&, + bool ignore_unknown = false, + bool complete_values = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | @@ -807,27 +1402,63 @@ namespace bpkg package_manifest (butl::manifest_parser&, butl::manifest_name_value start, bool ignore_unknown, - bool complete_depends, + bool complete_values, package_manifest_flags); // Override manifest values with the specified. Throw manifest_parsing if // any value is invalid, cannot be overridden, or its name is not // recognized. // - // The specified values override the whole groups they belong to, - // resetting all the group values prior to being applied. Currently, only - // the following value groups can be overridden: {build-*email} and - // {builds, build-{include,exclude}}. - // - // Note that the build constraints group values are overridden - // hierarchically so that the build-{include,exclude} overrides don't - // affect the builds values. + // The specified values other than [*-]build-auxiliary[-*] override the + // whole groups they belong to, resetting all the group values prior to + // being applied. The [*-]build-auxiliary[-*] values only override the + // matching values, which are expected to already be present in the + // manifest. Currently, only the following value groups/values can be + // overridden: + // + // {build-*email} + // {builds, build-{include,exclude}} + // {*-builds, *-build-{include,exclude}} + // {*-build-config} + // {*-build-*email} + // + // [*-]build-auxiliary[-*] + // + // Throw manifest_parsing if the configuration specified by the build + // package configuration-specific build constraint, email, or auxiliary + // value override doesn't exists. In contrast, for the build config + // override add a new configuration if it doesn't exist and update the + // arguments of the existing configuration otherwise. In the former case, + // all the potential build constraint, email, and auxiliary overrides for + // such a newly added configuration must follow the respective + // *-build-config override. + // + // Note that the build constraints group values (both common and build + // config-specific) are overridden hierarchically so that the + // [*-]build-{include,exclude} overrides don't affect the respective + // [*-]builds values. + // + // Also note that the common and build config-specific build constraints + // group value overrides are mutually exclusive. If the common build + // constraints are overridden, then all the build config-specific + // constraints are removed. Otherwise, if some build config-specific + // constraints are overridden, then for the remaining configs the build + // constraints are reset to `builds: none`. + // + // Similar to the build constraints groups, the common and build + // config-specific build emails group value overrides are mutually + // exclusive. If the common build emails are overridden, then all the + // build config-specific emails are reset to nullopt. Otherwise, if some + // build config-specific emails are overridden, then for the remaining + // configs the email is reset to the empty value and the warning and error + // emails are reset to nullopt (which effectively disables email + // notifications for such configurations). // // If a non-empty source name is specified, then the specified values are // assumed to also include the line/column information and the possibly - // thrown manifest_parsing exception will contain the invalid value + // thrown manifest_parsing exception will contain the invalid value's // location information. Otherwise, the exception description will refer - // to the invalid value name instead. + // to the invalid value instead. // void override (const std::vector<butl::manifest_name_value>&, @@ -835,12 +1466,30 @@ namespace bpkg // Validate the overrides without applying them to any manifest. // + // Specifically, validate that the override values can be parsed according + // to their name semantics and that the value sequence makes sense (no + // mutually exclusive values, etc). Note, however, that the subsequent + // applying of the successfully validated overrides to a specific package + // manifest may still fail (no build config exists for specified *-builds, + // etc). + // static void validate_overrides (const std::vector<butl::manifest_name_value>&, const std::string& source_name); + // If the minimum libbpkg version is specified, then also apply the + // required backward compatibility workarounds to the serialized manifest + // so that clients of all libbpkg versions greater or equal to the + // specified version can parse it, ignoring unknown values. + // + // Note that clients of the latest major libbpkg version can fully + // recognize the produced manifest and thus can parse it without ignoring + // unknown values. + // void - serialize (butl::manifest_serializer&) const; + serialize ( + butl::manifest_serializer&, + const butl::optional<butl::standard_version>& = butl::nullopt) const; // Serialize only package manifest header values. // @@ -849,17 +1498,20 @@ namespace bpkg // Load the *-file manifest values using the specified load function that // returns the file contents passing through any exception it may throw. - // Set the potentially absent description type value to the effective - // description type. If the effective type is nullopt then assign a - // synthetic unknown type. + // If nullopt is returned, then the respective *-file value is left + // unexpanded. Set the potentially absent project description, package + // description, and changes type values to their effective types. If an + // effective type is nullopt then assign a synthetic unknown type if the + // ignore_unknown argument is true and throw manifest_parsing otherwise. // // Note that if the returned file contents is empty, load_files() makes // sure that this is allowed by the value's semantics throwing // manifest_parsing otherwise. However, the load function may want to // recognize such cases itself in order to issue more precise diagnostics. // - using load_function = std::string (const std::string& name, - const butl::path& value); + using load_function = + butl::optional<std::string> (const std::string& name, + const butl::path& value); void load_files (const std::function<load_function>&, @@ -868,13 +1520,10 @@ namespace bpkg // Create individual package manifest. // - inline package_manifest + package_manifest pkg_package_manifest (butl::manifest_parser& p, bool ignore_unknown = false, - bool complete_depends = true) - { - return package_manifest (p, ignore_unknown, complete_depends); - } + bool complete_values = true); LIBBPKG_EXPORT package_manifest dir_package_manifest (butl::manifest_parser&, bool ignore_unknown = false); @@ -902,10 +1551,12 @@ namespace bpkg // Serialize. // inline void - pkg_package_manifest (butl::manifest_serializer& s, - const package_manifest& m) + pkg_package_manifest ( + butl::manifest_serializer& s, + const package_manifest& m, + const butl::optional<butl::standard_version>& min_ver = butl::nullopt) { - m.serialize (s); + m.serialize (s, min_ver); } // Normally there is no need to serialize dir and git package manifests, @@ -934,8 +1585,14 @@ namespace bpkg pkg_package_manifests (butl::manifest_parser&, bool ignore_unknown = false); + // If the minimum libbpkg version is specified, then also apply the + // required backward compatibility workarounds to the serialized package + // manifests list (see package_manifest::serialize() for details). + // void - serialize (butl::manifest_serializer&) const; + serialize ( + butl::manifest_serializer&, + const butl::optional<butl::standard_version>& = butl::nullopt) const; }; class LIBBPKG_EXPORT dir_package_manifests: @@ -1172,9 +1829,8 @@ namespace bpkg repository_type, const repository_location& base); - repository_location (const repository_location& l, - const repository_location& base) - : repository_location (l.url (), l.type (), base) {} + repository_location (const repository_location&, + const repository_location& base); // Note that relative locations have no canonical name. Canonical name of // an empty location is the empty name. @@ -1192,59 +1848,22 @@ namespace bpkg empty () const noexcept {return url_.empty ();} bool - local () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return url_.scheme == repository_protocol::file; - } + local () const; bool - remote () const - { - return !local (); - } + remote () const; bool - absolute () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - // Note that in remote locations path is always relative. - // - return url_.path->absolute (); - } + absolute () const; bool - relative () const - { - return local () && url_.path->relative (); - } + relative () const; repository_type - type () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return type_; - } + type () const; repository_basis - basis () const - { - switch (type ()) - { - case repository_type::pkg: return repository_basis::archive; - case repository_type::dir: return repository_basis::directory; - case repository_type::git: return repository_basis::version_control; - } - - assert (false); // Can't be here. - return repository_basis::archive; - } + basis () const; // Note that the URL of an empty location is empty. // @@ -1258,69 +1877,30 @@ namespace bpkg // "directories" it always contains the trailing slash. // const butl::path& - path () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return *url_.path; - } + path () const; const std::string& - host () const - { - if (local ()) - throw std::logic_error ("local location"); - - return url_.authority->host; - } + host () const; // Value 0 indicated that no port was specified explicitly. // std::uint16_t - port () const - { - if (local ()) - throw std::logic_error ("local location"); - - return url_.authority->port; - } + port () const; repository_protocol - proto () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return url_.scheme; - } + proto () const; const butl::optional<std::string>& - fragment () const - { - if (relative ()) - throw std::logic_error ("relative filesystem path"); - - return url_.fragment; - } + fragment () const; bool - archive_based () const - { - return basis () == repository_basis::archive; - } + archive_based () const; bool - directory_based () const - { - return basis () == repository_basis::directory; - } + directory_based () const; bool - version_control_based () const - { - return basis () == repository_basis::version_control; - } + version_control_based () const; // Return an untyped URL if the correct type can be guessed just from // the URL. Otherwise, return the typed URL. @@ -1481,6 +2061,13 @@ namespace bpkg butl::manifest_name_value start, bool ignore_unknown = false); + struct repositories_manifest_header + { + public: + butl::optional<butl::standard_version> min_bpkg_version; + butl::optional<std::string> compression; + }; + class LIBBPKG_EXPORT pkg_repository_manifests: public std::vector<repository_manifest> { @@ -1489,6 +2076,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: pkg_repository_manifests () = default; pkg_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1505,6 +2095,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: dir_repository_manifests () = default; dir_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1521,6 +2114,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: git_repository_manifests () = default; git_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1568,6 +2164,39 @@ namespace bpkg butl::manifest_name_value start, bool ignore_unknown); }; + + // Extract the package name component from <name>[/<version>] or + // <name><version-constraint>. Throw invalid_argument on parsing error. + // + // Note: the version and version constraint are not verified. + // + LIBBPKG_EXPORT package_name + extract_package_name (const char*, bool allow_version = true); + + inline package_name + extract_package_name (const std::string& s, bool allow_version = true) + { + return extract_package_name (s.c_str (), allow_version); + } + + // Extract the package version component from <name>[/<version>]. Return + // empty version if none is specified. Throw invalid_argument on parsing + // error and for the earliest and stub versions. + // + // Note: the package name is not verified. + // + LIBBPKG_EXPORT version + extract_package_version (const char*, + version::flags fl = version::fold_zero_revision); + + inline version + extract_package_version (const std::string& s, + version::flags fl = version::fold_zero_revision) + { + return extract_package_version (s.c_str (), fl); + } } +#include <libbpkg/manifest.ixx> + #endif // LIBBPKG_MANIFEST_HXX diff --git a/libbpkg/manifest.ixx b/libbpkg/manifest.ixx new file mode 100644 index 0000000..589d00f --- /dev/null +++ b/libbpkg/manifest.ixx @@ -0,0 +1,412 @@ +// file : libbpkg/manifest.ixx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <stdexcept> // logic_error + +namespace bpkg +{ + // version + // + inline int version:: + compare (const version& v, bool ir, bool ii) const noexcept + { + if (epoch != v.epoch) + return epoch < v.epoch ? -1 : 1; + + if (int c = canonical_upstream.compare (v.canonical_upstream)) + return c; + + if (int c = canonical_release.compare (v.canonical_release)) + return c; + + if (!ir) + { + if (revision != v.revision) + return revision < v.revision ? -1 : 1; + + if (!ii && iteration != v.iteration) + return iteration < v.iteration ? -1 : 1; + } + + return 0; + } + + inline bool version:: + operator< (const version& v) const noexcept + { + return compare (v) < 0; + } + + inline bool version:: + operator> (const version& v) const noexcept + { + return compare (v) > 0; + } + + inline bool version:: + operator== (const version& v) const noexcept + { + return compare (v) == 0; + } + + inline bool version:: + operator<= (const version& v) const noexcept + { + return compare (v) <= 0; + } + + inline bool version:: + operator>= (const version& v) const noexcept + { + return compare (v) >= 0; + } + + inline bool version:: + operator!= (const version& v) const noexcept + { + return compare (v) != 0; + } + + inline version::flags + operator&= (version::flags& x, version::flags y) + { + return x = static_cast<version::flags> ( + static_cast<std::uint16_t> (x) & + static_cast<std::uint16_t> (y)); + } + + inline version::flags + operator|= (version::flags& x, version::flags y) + { + return x = static_cast<version::flags> ( + static_cast<std::uint16_t> (x) | + static_cast<std::uint16_t> (y)); + } + + inline version::flags + operator& (version::flags x, version::flags y) + { + return x &= y; + } + + inline version::flags + operator| (version::flags x, version::flags y) + { + return x |= y; + } + + // version_constraint + // + inline bool + operator== (const version_constraint& x, const version_constraint& y) + { + return x.min_version == y.min_version && x.max_version == y.max_version && + x.min_open == y.min_open && x.max_open == y.max_open; + } + + inline bool + operator!= (const version_constraint& x, const version_constraint& y) + { + return !(x == y); + } + + // dependency + // + inline std::string dependency:: + string () const + { + std::string r (name.string ()); + + if (constraint) + { + r += ' '; + r += constraint->string (); + } + + return r; + } + + inline std::ostream& + operator<< (std::ostream& os, const dependency& d) + { + return os << d.string (); + } + + // dependency_alternatives + // + inline bool dependency_alternatives:: + conditional () const + { + for (const dependency_alternative& da: *this) + { + if (da.enable) + return true; + } + + return false; + } + + // requirement_alternatives + // + inline bool requirement_alternatives:: + conditional () const + { + for (const requirement_alternative& ra: *this) + { + if (ra.enable) + return true; + } + + return false; + } + + // distribution_name_value + // + inline butl::optional<std::string> distribution_name_value:: + distribution (const std::string& s) const + { + using namespace std; + + size_t sn (s.size ()); + size_t nn (name.size ()); + + if (nn > sn && name.compare (nn - sn, sn, s) == 0) + { + size_t p (name.find ('-')); + + if (p == nn - sn) + return string (name, 0, p); + } + + return butl::nullopt; + } + + // package_manifest_flags + // + inline package_manifest_flags + operator&= (package_manifest_flags& x, package_manifest_flags y) + { + return x = static_cast<package_manifest_flags> ( + static_cast<std::uint16_t> (x) & + static_cast<std::uint16_t> (y)); + } + + inline package_manifest_flags + operator|= (package_manifest_flags& x, package_manifest_flags y) + { + return x = static_cast<package_manifest_flags> ( + static_cast<std::uint16_t> (x) | + static_cast<std::uint16_t> (y)); + } + + inline package_manifest_flags + operator& (package_manifest_flags x, package_manifest_flags y) + { + return x &= y; + } + + inline package_manifest_flags + operator| (package_manifest_flags x, package_manifest_flags y) + { + return x |= y; + } + + // build_class_expr + // + inline bool build_class_expr:: + match (const strings& cs, const build_class_inheritance_map& bs) const + { + bool r (false); + match (cs, bs, r); + return r; + } + + // package_manifest + // + inline package_manifest:: + package_manifest (butl::manifest_parser& p, + bool iu, + bool cv, + package_manifest_flags fl) + : package_manifest (p, std::function<translate_function> (), iu, cv, fl) + { + } + + inline package_manifest + pkg_package_manifest (butl::manifest_parser& p, bool iu, bool cvs) + { + return package_manifest (p, iu, cvs); + } + + inline std::string package_manifest:: + effective_type (const butl::optional<std::string>& t, const package_name& n) + { + if (t) + { + std::string tp (*t, 0, t->find (',')); + butl::trim (tp); + return tp == "exe" || tp == "lib" ? tp : "other"; + } + + const std::string& s (n.string ()); + return s.size () > 3 && s.compare (0, 3, "lib") == 0 ? "lib" : "exe"; + } + + inline std::string package_manifest:: + effective_type () const + { + return effective_type (type, name); + } + + inline strings package_manifest:: + effective_type_sub_options () const + { + return effective_type_sub_options (type); + } + + inline butl::small_vector<language, 1> package_manifest:: + effective_languages (const butl::small_vector<language, 1>& ls, + const package_name& n) + { + if (!ls.empty ()) + return ls; + + std::string ext (n.extension ()); + return butl::small_vector<language, 1> ( + 1, + language (!ext.empty () ? move (ext) : "cc", false /* impl */)); + } + + inline butl::small_vector<language, 1> package_manifest:: + effective_languages () const + { + return effective_languages (languages, name); + } + + // repository_location + // + inline repository_type repository_location:: + type () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + return type_; + } + + inline repository_location:: + repository_location (const repository_location& l, + const repository_location& base) + : repository_location (l.url (), l.type (), base) + { + } + + inline bool repository_location:: + local () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + return url_.scheme == repository_protocol::file; + } + + inline bool repository_location:: + remote () const + { + return !local (); + } + + inline bool repository_location:: + absolute () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + // Note that in remote locations path is always relative. + // + return url_.path->absolute (); + } + + inline bool repository_location:: + relative () const + { + return local () && url_.path->relative (); + } + + inline repository_basis repository_location:: + basis () const + { + switch (type ()) + { + case repository_type::pkg: return repository_basis::archive; + case repository_type::dir: return repository_basis::directory; + case repository_type::git: return repository_basis::version_control; + } + + assert (false); // Can't be here. + return repository_basis::archive; + } + + inline bool repository_location:: + archive_based () const + { + return basis () == repository_basis::archive; + } + + inline bool repository_location:: + directory_based () const + { + return basis () == repository_basis::directory; + } + + inline bool repository_location:: + version_control_based () const + { + return basis () == repository_basis::version_control; + } + + inline const butl::path& repository_location:: + path () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + return *url_.path; + } + + inline const std::string& repository_location:: + host () const + { + if (local ()) + throw std::logic_error ("local location"); + + return url_.authority->host; + } + + inline std::uint16_t repository_location:: + port () const + { + if (local ()) + throw std::logic_error ("local location"); + + return url_.authority->port; + } + + inline repository_protocol repository_location:: + proto () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + return url_.scheme; + } + + inline const butl::optional<std::string>& repository_location:: + fragment () const + { + if (relative ()) + throw std::logic_error ("relative filesystem path"); + + return url_.fragment; + } +} diff --git a/libbpkg/package-name.hxx b/libbpkg/package-name.hxx index 1828fa2..a47dc9a 100644 --- a/libbpkg/package-name.hxx +++ b/libbpkg/package-name.hxx @@ -4,7 +4,7 @@ #ifndef LIBBPKG_PACKAGE_NAME_HXX #define LIBBPKG_PACKAGE_NAME_HXX -#include <libbutl/project-name.mxx> +#include <libbutl/project-name.hxx> #include <libbpkg/export.hxx> #include <libbpkg/version.hxx> @@ -1,6 +1,6 @@ : 1 name: libbpkg -version: 0.14.0-a.0.z +version: 0.17.0-a.0.z project: build2 summary: build2 package dependency manager utility library license: MIT @@ -12,8 +12,8 @@ doc-url: https://build2.org/doc.xhtml src-url: https://git.build2.org/cgit/libbpkg/tree/ email: users@build2.org build-warning-email: builds@build2.org -builds: all +builds: all : &host requires: c++14 -depends: * build2 >= 0.13.0 -depends: * bpkg >= 0.13.0 -depends: libbutl [0.14.0-a.0.1 0.14.0-a.1) +depends: * build2 >= 0.16.0- +depends: * bpkg >= 0.16.0- +depends: libbutl [0.17.0-a.0.1 0.17.0-a.1) diff --git a/tests/build-class-expr/driver.cxx b/tests/build-class-expr/driver.cxx index 2ca123a..b2ee895 100644 --- a/tests/build-class-expr/driver.cxx +++ b/tests/build-class-expr/driver.cxx @@ -5,11 +5,14 @@ #include <string> #include <iostream> -#include <libbutl/utility.mxx> // eof(), operator<<(ostream, exception) -#include <libbutl/optional.mxx> +#include <libbutl/utility.hxx> // eof(), operator<<(ostream, exception) +#include <libbutl/optional.hxx> #include <libbpkg/manifest.hxx> +#undef NDEBUG +#include <cassert> + // Usages: // // argv[0] -p diff --git a/tests/build/root.build b/tests/build/root.build index bb274d3..f97c101 100644 --- a/tests/build/root.build +++ b/tests/build/root.build @@ -13,6 +13,15 @@ if ($cxx.target.system == 'win32-msvc') if ($cxx.class == 'msvc') cxx.coptions += /wd4251 /wd4275 /wd4800 +elif ($cxx.id == 'gcc') +{ + cxx.coptions += -Wno-maybe-uninitialized -Wno-free-nonheap-object # libbutl + + if ($cxx.version.major >= 13) + cxx.coptions += -Wno-dangling-reference +} +elif ($cxx.id.type == 'clang' && $cxx.version.major >= 15) + cxx.coptions += -Wno-unqualified-std-cast-call # Every exe{} in this subproject is by default a test. # 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 7ea77e3..56c886d 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -3,24 +3,28 @@ #include <ios> // ios_base::failbit, ios_base::badbit #include <string> -#include <cassert> #include <iostream> -#include <libbutl/manifest-parser.mxx> -#include <libbutl/manifest-serializer.mxx> -#include <libbutl/standard-version.mxx> +#include <libbutl/manifest-parser.hxx> +#include <libbutl/manifest-serializer.hxx> +#include <libbutl/standard-version.hxx> #include <libbpkg/manifest.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; 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] -et <type> <name> +// argv[0] -v // // In the first form read and parse manifest list from stdin and serialize it // to stdout. The following options specify the manifest type. @@ -32,19 +36,28 @@ using namespace bpkg; // -dr parse dir repository manifest list // -gr parse git repository manifest list // -s parse signature manifest +// -v print the libbpkg version // // In the second form read and parse the package manifest from stdin and // serialize it to stdout. // -// -c complete the dependency constraints +// -c complete the incomplete values (depends, <distribution>-version, etc) // -i ignore unknown // // 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. // +// In the forth form print the effective type and the type sub-options to +// stdout (one per line) and exit. +// +// In the fifth form print the libbpkg version to stdout and exit. +// int main (int argc, char* argv[]) { @@ -53,28 +66,38 @@ main (int argc, char* argv[]) cout.exceptions (ios_base::failbit | ios_base::badbit); - manifest_parser p (cin, "stdin"); - manifest_serializer s (cout, "stdout"); + if (mode == "-v") + { + cout << standard_version (LIBBPKG_VERSION_STR) << endl; + return 0; + } + + manifest_parser p (cin, "stdin"); try { if (mode == "-p") { - bool complete_dependencies (false); + bool complete_values (false); bool ignore_unknown (false); + bool long_lines (false); for (int i (2); i != argc; ++i) { string o (argv[i]); if (o == "-c") - complete_dependencies = true; + complete_values = 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 ( @@ -95,7 +118,7 @@ main (int argc, char* argv[]) } }, ignore_unknown, - complete_dependencies).serialize (s); + complete_values).serialize (s); } else if (mode == "-ec") { @@ -116,9 +139,36 @@ main (int argc, char* argv[]) cout << c << " " << ec << endl; } } + else if (mode == "-et") + { + assert (argc == 4); + + optional<string> t (*argv[2] != '\0' + ? string (argv[2]) + : optional<string> ()); + + package_name n (argv[3]); + + cout << package_manifest::effective_type (t, n) << endl; + + for (const string& so: package_manifest::effective_type_sub_options (t)) + cout << so << endl; + } 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 3d6b060..488668c 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -102,6 +102,176 @@ EOE } + : type + : + { + : valid + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + type: lib + summary: Modern C++ parser + license: LGPLv2 + EOF + + : extras + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + type: bash, something extra + summary: Modern C++ parser + license: LGPLv2 + EOI + : 1 + name: foo + version: 2.0.0 + type: bash, something extra + summary: Modern C++ parser + license: LGPLv2 + EOO + + : duplicate + : + $* <<EOI 2>'stdin:5:1: error: package type redefinition' != 0 + : 1 + name: libfoo + version: 2.0.0 + type: lib + type: exe + summary: Modern C++ parser + license: LGPLv2 + EOI + + : empty + : + $* <<EOI 2>'stdin:4:6: error: empty package type' != 0 + : 1 + name: libfoo + version: 2.0.0 + type: + summary: Modern C++ parser + license: LGPLv2 + EOI + + : empty-extras + : + $* <<EOI 2>'stdin:4:7: error: empty package type' != 0 + : 1 + name: libfoo + version: 2.0.0 + type: , extras + summary: Modern C++ parser + license: LGPLv2 + EOI + } + + : language + : + { + : valid + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + language: c++ + language: c=impl + summary: Modern C++ parser + license: LGPLv2 + EOF + + : extras + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + language: c++, something extra + language: c=impl, something extra + summary: Modern C++ parser + license: LGPLv2 + EOI + : 1 + name: foo + version: 2.0.0 + language: c++ + language: c=impl + summary: Modern C++ parser + license: LGPLv2 + EOO + + : empty + : + $* <<EOI 2>'stdin:4:10: error: empty package language' != 0 + : 1 + name: libfoo + version: 2.0.0 + language: + summary: Modern C++ parser + license: LGPLv2 + EOI + + : empty-extras + : + $* <<EOI 2>'stdin:4:11: error: empty package language' != 0 + : 1 + name: libfoo + version: 2.0.0 + language: , extras + summary: Modern C++ parser + license: LGPLv2 + EOI + + : empty-impl + : + $* <<EOI 2>'stdin:4:11: error: empty package language' != 0 + : 1 + name: libfoo + version: 2.0.0 + language: =impl + summary: Modern C++ parser + license: LGPLv2 + EOI + + : invalid-value + : + $* <<EOI 2>"stdin:4:11: error: unexpected 'imp' value after '='" != 0 + : 1 + name: libfoo + version: 2.0.0 + language: c++=imp + summary: Modern C++ parser + license: LGPLv2 + EOI + + : empty-value + : + $* <<EOI 2>"stdin:4:11: error: expected 'impl' after '='" != 0 + : 1 + name: libfoo + version: 2.0.0 + language: c++= + summary: Modern C++ parser + license: LGPLv2 + EOI + + : duplicate + : + $* <<EOI 2>"stdin:5:11: error: duplicate package language" != 0 + : 1 + name: libfoo + version: 2.0.0 + language: c++=impl + language: c++ + summary: Modern C++ parser + license: LGPLv2 + EOI + } + : license : { @@ -257,9 +427,9 @@ description-file: /README EOI %( - stdin:6:19: error: package description-file path is absolute + stdin:6:19: error: project description file path is absolute %| - stdin:6:19: error: invalid package description file: invalid filesystem path + stdin:6:19: error: invalid project description file: invalid filesystem path %) EOE } @@ -289,7 +459,20 @@ description: libfoo is a very modern C++ XML parser. description-type: image/gif EOI - stdin:7:19: error: invalid package description type: text type expected + stdin:7:19: error: invalid project description type: text type expected + EOE + + : no-description + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + description-type: image/gif + EOI + stdin:6:1: error: no project description for specified type EOE : deducing @@ -305,7 +488,7 @@ license: LGPLv2 description-file: README.rtf EOI - stdin:6:19: error: invalid package description file: unknown text type + stdin:6:19: error: invalid project description file: unknown text type (use description-type manifest value to specify explicitly) EOE : ignore-unknown @@ -334,7 +517,7 @@ description: libfoo is a very modern C++ XML parser. description-type: text/markdowns EOI - stdin:7:19: error: invalid package description type: unknown text type + stdin:7:19: error: invalid project description type: unknown text type EOE : ignore @@ -376,7 +559,7 @@ description: libfoo is a very modern C++ XML parser. description-type: text/plain; EOI - stdin:7:19: error: invalid package description type: missing '=' + stdin:7:19: error: invalid project description type: missing '=' EOE } @@ -433,7 +616,7 @@ description: libfoo is a very modern C++ XML parser. description-type: text/markdown; variant=Original EOI - stdin:7:19: error: invalid package description type: unknown text type + stdin:7:19: error: invalid project description type: unknown text type EOE : ignore @@ -463,7 +646,7 @@ description: libfoo is a very modern C++ XML parser. description-type: text/markdown; variants=GFM EOI - stdin:7:19: error: invalid package description type: unknown text type + stdin:7:19: error: invalid project description type: unknown text type EOE : ignore @@ -481,6 +664,330 @@ } } + : package-description-file + : + { + : absolute-path + : + $* <<EOI 2>>~%EOE% != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description-file: /README + EOI + %( + stdin:6:27: error: package description file path is absolute + %| + stdin:6:27: error: invalid package description file: invalid filesystem path + %) + EOE + } + + : package-description-type + : + { + : absent + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + EOF + + : not-text + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: image/gif + EOI + stdin:7:27: error: invalid package description type: text type expected + EOE + + : no-description + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description-type: image/gif + EOI + stdin:6:1: error: no package description for specified type + EOE + + : deducing + : + { + : fail + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description-file: README.rtf + EOI + stdin:6:27: error: invalid package description file: unknown text type (use package-description-type manifest value to specify explicitly) + EOE + + : ignore-unknown + : + $* -i <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description-file: README.rtf + EOF + } + + : unknown + : + { + : fail + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdowns + EOI + stdin:7:27: error: invalid package description type: unknown text type + EOE + + : ignore + : + $* -i <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdowns + EOF + } + + : plain + : + { + : valid + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/plain + EOF + + : invalid + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/plain; + EOI + stdin:7:27: error: invalid package description type: missing '=' + EOE + } + + : markdown + : + { + : default + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown + EOF + + : gfm + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variant=GFM + EOF + + : common-mark + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variant=CommonMark + EOF + + : invalid-variant + : + { + : fail + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variant=Original + EOI + stdin:7:27: error: invalid package description type: unknown text type + EOE + + : ignore + : + $* -i <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variant=Original + EOF + } + + : invalid-parameter + : + { + : fail + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variants=GFM + EOI + stdin:7:27: error: invalid package description type: unknown text type + EOE + + : ignore + : + $* -i <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-description: libfoo is a very modern C++ XML parser. + package-description-type: text/markdown; variants=GFM + EOF + } + } + } + + : changes-file + : + { + : absolute-path + : + $* <<EOI 2>>~%EOE% != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + changes-file: /CHANGES + EOI + %( + stdin:6:15: error: changes file path is absolute + %| + stdin:6:15: error: invalid changes file: invalid filesystem path + %) + EOE + + : unknown-text-type + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + changes-file: CHANGES.0 + EOI + stdin:6:15: error: invalid changes file: unknown text type (use changes-type manifest value to specify explicitly) + EOE + + : different-type + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + changes-file: CHANGES1 + changes-file: CHANGES2.md + EOI + stdin:7:15: error: changes type 'text/markdown;variant=GFM' differs from previous type 'text/plain' + EOE + + : same-type + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + changes-file: CHANGES1.markdown + changes-file: CHANGES2.md + EOF + + : explicit-type + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + changes-file: CHANGES1 + changes-file: CHANGES2.md + changes-type: text/plain + EOF + } + : src-url : { @@ -541,12 +1048,468 @@ EOI } + : build-auxiliary + : + { + : named + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + build-auxiliary-mysql: *-mysql_* + EOF + } + + : unnamed + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql** + EOF + } + + : empty-config-pattern + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: + EOI + stdin:6:17: error: empty build auxiliary configuration name pattern + EOE + } + + : mixed + : + { + : named-unnamed + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql** + build-auxiliary: *-mysql** + EOF + } + + : unnamed-named + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-mysql** + build-auxiliary-pgsql: *-postgresql** + EOF + } + + : unnamed-unnamed + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-mysql** + build-auxiliary: *-postgresql** + EOI + stdin:7:1: error: build auxiliary environment redefinition + EOE + } + + : redefinition + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql** + build-auxiliary-pgsql: *-postgresql** + EOI + stdin:7:1: error: build auxiliary environment redefinition + EOE + } + } + } + + : build-config + : + { + : multiple + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-builds: all + bar-build-config: config.foo.bar = true; Bar. + baz-build-config: config.foo.baz = true; Baz. + EOF + } + + : empty + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: ; None. + EOF + + : undefined + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-builds: default + baz-build-config: config.foo.bar = true + EOF + } + + : redefinition + : + { + $* <<EOI 2>"stdin:3:1: error: build configuration redefinition" != 0 + : 1 + bar-build-config: config.foo.bar = true + bar-build-config: config.foo.bar = true + EOI + } + + : unexpected-underlying-class-set + : + { + $* <<EOI 2>"stdin:4:13: error: invalid package builds: unexpected underlying class set" != 0 + : 1 + bar-build-config: config.foo.bar = true + bar-builds: all + bar-builds: all + EOI + } + + : auxiliary + { + : named + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary-pgsql: *-postgresql_* + baz-build-auxiliary-mysql: *-mysql_* + EOF + } + + : unnamed + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary: *-postgresql** + EOF + } + + : empty-config-pattern + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary: + EOI + stdin:6:21: error: empty build auxiliary configuration name pattern + EOE + } + + : mixed + : + { + : named-unnamed + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary-pgsql: *-postgresql** + bar-build-auxiliary: *-mysql** + EOF + } + + : unnamed-named + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary: *-mysql** + bar-build-auxiliary-pgsql: *-postgresql** + EOF + } + + : unnamed-unnamed + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary: *-mysql** + bar-build-auxiliary: *-postgresql** + EOI + stdin:7:1: error: build auxiliary environment redefinition + EOE + } + + : redefinition + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bar-build-auxiliary-pgsql: *-postgresql** + bar-build-auxiliary-pgsql: *-postgresql** + EOI + stdin:7:1: error: build auxiliary environment redefinition + EOE + } + } + } + + : email + : + { + : override + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-email: package@example.com + build-email: build@example.com + build-warning-email: build-warning@example.com + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-email: bar-build@example.com + bar-build-warning-email: bar-build-warning@example.com + bar-build-error-email: bar-build-error@example.com + EOF + } + + : disabled + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + package-email: package@example.com + build-email: build@example.com + build-warning-email: build-warning@example.com + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-email: + EOF + } + + : unrecognized + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-error-email: build-error@example.com + bar-build-email: bar-build@example.com + EOI + stdin:7:1: error: stray build notification email: no build package configuration 'bar' + EOE + } + + : empty + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-error-email: build-error@example.com + bar-build-config: config.foo.bar = true; Bar. + bar-build-warning-email: ; Empty + EOI + stdin:8:26: error: empty build configuration warning email + EOE + } + } + } + + : distribution + : + { + : incomplete + : + { + $* <<EOF >>EOF + : 1 + name: libcrypto + version: 1.1.1+18 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + debian-name: libssl1.1 libssl-dev + debian-version: 1.1.1n + debian-to-downstream-version: /([^.])\.([^.])\.([^.])n/\1.\2.\3+18/ + debian-to-downstream-version: /([^.])\.([^.])\.([^.])o/\1.\2.\3+19/ + debian-to-downstream-version: /([^.])\.([^.])\.([^.])p/\1.\2.\3+20/ + fedora-name: openssl-libs openssl-devel + fedora-version: $ + EOF + } + + : complete + : + { + $* -c <<EOI >>EOO + : 1 + name: libcrypto + version: +2-1.1.1-a.1+2 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + fedora-name: openssl-libs openssl-devel + fedora-version: $ + fedora-to-downstream-version: $ + EOI + : 1 + name: libcrypto + version: +2-1.1.1-a.1+2 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + fedora-name: openssl-libs openssl-devel + fedora-version: 1.1.1 + fedora-to-downstream-version: $ + EOO + } + + : multiple-names + : + { + $* <<EOO >>EOO + : 1 + name: libcrypto + version: 1.1.1+18 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + debian-name: libcurl4 libcurl4-doc libcurl4-openssl-dev + debian-name: libcurl3-gnutls libcurl4-gnutls-dev + EOO + } + + : dash-in-name + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: libcrypto + version: 1.1.1+18 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + de-bian-name: libssl1.1 libssl-dev + EOI + stdin:7:1: error: distribution name 'de-bian' contains '-' + EOE + } + + : empty-value + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: libcrypto + version: 1.1.1+18 + upstream-version: 1.1.1n + summary: C library providing general cryptography and X.509 support + license: OpenSSL + debian-name: + EOI + stdin:7:13: error: empty package distribution value + EOE + } + } + : depends : { : short-name : - $* <<EOI 2>'stdin:6:10: error: invalid prerequisite package name: length is less than two characters' != 0 + $* <<EOI 2>'stdin:6:10: error: invalid package name: length is less than two characters' != 0 : 1 name: foo version: 2.0.0 @@ -557,7 +1520,7 @@ : invalid-version-range : - $* -c <<EOI 2>'stdin:6:10: error: invalid prerequisite 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 +1591,7 @@ license: LGPLv2 depends: bar ~$ EOI - stdin:6:10: error: invalid prerequisite package constraint: dependent version is not standard + stdin:6:10: error: invalid package constraint '~$': dependent version is not standard EOE : latest-snapshot @@ -661,6 +1624,2480 @@ 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 + } + + : comments + : + $* <<EOI 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 + } + } | + {bar # Error. + \ + EOI + stdin:15:6: error: invalid package name: length is less than two characters + 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 + + + : empty + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + prefer + { + } + + accept (true) + } + \ + 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 + + : 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 + ; + } + } + \ + 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 + { + # @@ 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. + # + # @@ 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. + # + # @@ 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. + # + # @@ 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. + # + # @@ 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? + # + # @@ 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 + + # @@ 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 + } + + # @@ Not sure if/when this is necessary. + # + # cxx.libs += -latomic + + # libexecinfo is required for backtrace(3) on BSD (see global/qlogging.cpp). + # + # @@ 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. + # + # @@ 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 + } + + : comments + : + { + : single-line-separate + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + # Configure bar. + # + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOO + + : single-line-trailing + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require # Configure bar. + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + } + \ + EOO + + : multi-line + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + #\\ + # Configure bar. + # + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + #\\ + + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + } + \ + EOI + : 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) + } + \ + EOO + + : multi-line-unterm + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends:\ + bar + { + #\\ + # Configure bar. + # + require + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + prefer + { + config.bar.frame=4016 + config.bar.timeout=10 + } + + accept ($config.bar.frame >= 1024 && config.bar.timeout < 20) + } + \ + EOI + stdin:25:2: error: unterminated multi-line comment + 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 + + # @@ TMP Drop this test and uncomment the next one when toolchain 0.15.0 + # is released (see dependency_alternatives_parser::parse_alternative() + # for details). + # + : old-fashioned + : + $* <<EOI >>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: ? vc15; VC 15 or later if targeting Windows. + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + requires: vc15 ? ; VC 15 or later if targeting Windows. + EOO + + #\ + : 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 @@ -668,7 +4105,7 @@ { : short-name : - $* <<EOI 2>'stdin:6:8: error: invalid tests package name: length is less than two characters' != 0 + $* <<EOI 2>'stdin:6:8: error: invalid package name: length is less than two characters' != 0 : 1 name: foo version: 2.0.0 @@ -679,7 +4116,7 @@ : invalid-version-range-incomplete : - $* -c <<EOI 2>'stdin:6:8: error: invalid tests 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 @@ -690,7 +4127,7 @@ : invalid-version-range : - $* -c <<EOI 2>'stdin:6:8: error: invalid tests package constraint: min version is greater than max version' != 0 + $* -c <<EOI 2>'stdin:6:8: error: invalid package constraint: min version is greater than max version' != 0 : 1 name: foo version: 2.0.0 @@ -701,7 +4138,7 @@ : invalid-version : - $* -c <<EOI 2>'stdin:6:8: error: invalid tests package constraint: invalid version: equal version endpoints are earliest' != 0 + $* -c <<EOI 2>'stdin:6:8: error: invalid package constraint: invalid version: equal version endpoints are earliest' != 0 : 1 name: foo version: 2.0.0 @@ -710,6 +4147,28 @@ tests: bar == 2.0.0- EOI + : no-name + : + $* <<EOI 2>'stdin:6:8: error: no package name specified' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: * + EOI + + : buildtime + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: * foo-tests + EOF + : complete : { @@ -743,7 +4202,7 @@ license: LGPLv2 tests: bar ~$ EOI - stdin:6:8: error: invalid tests package constraint: dependent version is not standard + stdin:6:8: error: invalid package constraint '~$': dependent version is not standard EOE } @@ -757,6 +4216,506 @@ license: LGPLv2 tests: bar == $ EOF + + : reflect + : + { + : after-version-constraint + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == 1.0.0 config.bar.test = foo + EOF + + : no-version-constraint + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar config.bar.test = foo + EOF + + : enable + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == 1.0.0 ? ($windows) config.bar.test = foo + EOF + + : invalid-variable + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar config.foo.test = bar + EOI + stdin:6:8: error: config.bar.* variable assignment expected instead of <buildfile fragment> + EOE + } + + : enable + : + { + : after-version-constraint + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == 1.0.0 ? ($windows) + EOF + + : no-version-constraint + : + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar ? ($windows) + EOF + + : unterminated + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar ? ($windows + EOI + stdin:6:8: error: unterminated evaluation context + EOE + } + + : newline + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests:\ + * + bar + \ + EOI + stdin:7:1: error: unexpected <newline> + EOE + + : no-package + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: * + EOI + stdin:6:8: error: no package name specified + EOE + + : multiple-alternatives + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar | baz + EOI + stdin:6:8: error: unexpected '|' + EOE + + : multiple-dependencies + : + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: {bar baz} + EOI + stdin:6:8: error: only single package allowed + EOE + } + + : buildfile + : + { + : standard-naming + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bootstrap-build:\ + project = libfoo + + using version + using config + using dist + using test + using install + \ + root-build:\ + include config/common.build + + cxx.std = latest + + using cxx + \ + config/common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOF + + : alt-naming + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bootstrap-build2:\ + project = libfoo + + using version + using config + using dist + using test + using install + \ + root-build2:\ + include config/common.build2 + + cxx.std = latest + + using cxx + \ + config/common-build2:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOF + + : mixed-naming + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bootstrap-build:\ + project = libfoo + + using version + using config + using dist + using test + using install + \ + root-build:\ + include config/common.build + + cxx.std = latest + + using cxx + \ + config/common-build2:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:22:1: error: standard buildfile naming scheme is already used + EOE + + : backslash + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + config\common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:6:1: error: backslash in package buildfile path + EOE + + : unknown + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + common.build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:6:1: error: unknown name 'common.build' in package manifest + EOE + + : empty-name + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + config/-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:6:1: error: empty package buildfile name + EOE + + : absolute-invalid + : + $* <<EOI 2>>~%EOE% != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + /config/common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + %stdin:6:1: error: (absolute|invalid) package buildfile path% + EOE + + : outside + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + common/../../common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:6:1: error: package buildfile path refers outside build/ subdirectory + EOE + + : redefinition + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + EOI + stdin:11:1: error: package buildfile redefinition + EOE + } + + : buildfile-path + : + { + : standard-naming + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bootstrap-build:\ + project = libfoo + + using version + using config + using dist + using test + using install + \ + root-build:\ + include config/common.build + include config/extra.build + + cxx.std = latest + + using cxx + \ + config/common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + build-file: config/extra.build + EOF + + : alt-naming + : + $* <<EOF >>EOF + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + bootstrap-build2:\ + project = libfoo + + using version + using config + using dist + using test + using install + \ + root-build2:\ + include config/common.build2 + + cxx.std = latest + + using cxx + \ + build-file: config/common.build2 + EOF + + : mixed-naming + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-file: config/common.build + build-file: config/extra.build2 + \ + EOI + stdin:7:13: error: standard buildfile naming scheme is already used + EOE + + : empty-path + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-file: + EOI + stdin:6:12: error: path with build or build2 extension expected + EOE + + : invalid-extension + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-file: common.buildfile + EOI + stdin:6:13: error: path with build or build2 extension expected + EOE + + : redefinition1 + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + common-build:\ + { + config [bool] config.libfoo.extras ?= false + } + \ + build-file: common.build + EOI + stdin:11:13: error: package buildfile redefinition + EOE + + : redefinition2 + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-file: config/common.build + build-file: config/common.build + EOI + stdin:7:13: error: package buildfile redefinition + EOE + + : bootstrap-build + : + $* <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-file: bootstrap.build + EOI + stdin:6:13: error: bootstrap not allowed + EOE } } @@ -776,6 +4735,8 @@ : name: libfoo version: 1.2.3+2 + type: lib + language: c++ project: foo priority: high; Due to critical bug fix. summary: Modern XML parser @@ -784,8 +4745,11 @@ keywords: c++ xml parser serializer pull description: libfoo is a very modern C++ XML parser. description-type: text/plain + package-description: packaged for build2. + package-description-type: text/plain changes: 1.2.3+2: applied upstream patch for critical bug bar changes: 1.2.3+1: applied upstream patch for critical bug foo + changes-type: text/plain url: http://www.example.org/projects/libfoo/; libfoo project page url doc-url: http://www.example.org/projects/libfoo/man.xhtml; documentation page src-url: http://scm.example.com/?p=odb/libodb.git\;a=tree; source tree @@ -798,9 +4762,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. @@ -814,6 +4778,20 @@ build-include: linux* build-include: freebsd* build-exclude: *; Only supports Linux and FreeBSD. + network-builds: default + network-build-include: linux* + network-build-exclude: *; Only supports Linux. + network-build-config: config.libfoo.network=true; Enable networking API. + bootstrap-build:\ + project = libfoo + + \ + root-build:\ + cxx.std = latest + + using cxx + + \ location: libfoo-1.2.3+2.tar.bz2 sha256sum: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 : @@ -829,6 +4807,10 @@ builds: default legacy; Default and legacy. builds: -windows; Not on Windows. build-exclude: *-msvc_14*/i?86-*; Linker crash. + bootstrap-build:\ + project = libbar + + \ location: bar/libbar-3.4A.5+6.tbz sha256sum: d4b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 : @@ -840,6 +4822,10 @@ email: libbaz-users@example.org build-error-email: libbaz-issues@example.org; Email for libbaz issues. builds: default experimental + bootstrap-build:\ + project = libbaz + + \ location: libbaz/libbaz-+2-3.4A.5+3.tar.gz sha256sum: b5b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EOF @@ -849,7 +4835,7 @@ { : incomplete : - $* <<EOI 2>'stdin:8:10: error: $ not allowed' != 0 + $* <<EOI 2>"stdin:8:10: error: invalid package constraint '== \$': \$ not allowed" != 0 : 1 sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 : @@ -860,6 +4846,28 @@ depends: bar == $ EOI } + + : buildfiles + : + { + # @@ TMP Uncomment when the missing bootstrap-build value related + # workaround is removed (see manifest.cxx for details). + #\ + : no-bootstrap + : + $* <<EOI 2>"stdin:10:1: error: no package bootstrap build specified" != 0 + : 1 + sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + : + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + location: foo/foo-2.0.0.tar.gz + sha256sum: b5b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + EOI + #\ + } } : dir @@ -913,6 +4921,149 @@ : repository-list : { + : header + : + { + +$* -v | set v + + test.options += -pr + + : version + : + { + $* <<"EOF" >>"EOF" + : 1 + min-bpkg-version: $v + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + url: http://cppget.org + email: repoman@cppget.org; General mailing list. + EOF + } + + : invalid-version + : + { + $* <<EOI 2>'stdin:2:19: error: invalid minimum bpkg version: invalid major version' != 0 + : 1 + min-bpkg-version: foo + EOI + } + + : too-new + : + { + $* <<EOI 2>'stdin:2:19: error: incompatible repositories manifest: minimum bpkg version is 1000.0.0' != 0 + : 1 + min-bpkg-version: 1000.0.0 + EOI + } + + : non-version + : + { + $* <<EOF >>EOF + : 1 + compression: none + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + url: http://cppget.org + email: repoman@cppget.org; General mailing list. + EOF + } + + : version-non-first + : + { + $* <<"EOI" 2>"stdin:3:1: error: minimum bpkg version must be first in repositories manifest header" != 0 + : 1 + compression: none + min-bpkg-version: $v + EOI + } + + : unknown-header-value + : + { + $* <<EOI 2>"stdin:3:1: error: unknown name 'unknown' in repositories manifest header" != 0 + : 1 + compression: none + unknown: foo + EOI + } + + : unknown-manifest-value + : + { + $* <<EOI 2>"stdin:2:1: error: unknown name 'unknown' in repository manifest" != 0 + : 1 + unknown: foo + EOI + } + + : empty-repository-manifest + : + { + $* <<EOF >>EOF + : 1 + compression: none + : + EOF + } + + : base-redefinition + : + { + $* <<EOI 2>'stdin:4:1: error: base repository manifest redefinition' != 0 + : 1 + compression: none + : + : + EOI + } + + : empty-base + : + { + $* <<"EOF" >>"EOF" + : 1 + compression: none + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + EOF + } + + : no-repository-manifest + : + { + $* <<EOF >>EOF + : 1 + compression: none + EOF + } + + : only-empty-base + : + { + $* <<EOF >>EOF + : 1 + EOF + } + + : empty-manifest-list + : + $* <'' 2>'stdin:2:1: error: start of repository manifest expected' != 0 + } + : pkg : { @@ -935,7 +5086,7 @@ summary: General C++ package repository description: This is the awesome C++ package repository full of exciting\ stuff. - certificate: \ + certificate:\ -----BEGIN CERTIFICATE----- MIIFLzCCAxegAwIBAgIJAJ71rMp8mDy1MA0GCSqGSIb3DQEBCwUAMDMxFzAVBgNV BAoMDkNvZGUgU3ludGhlc2lzMRgwFgYDVQQDDA9uYW1lOmNwcGdldC5vcmcwHhcN @@ -1182,7 +5333,7 @@ $* -s <<EOF >>EOF : 1 sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - signature: \ + signature:\ geWdw7Gm+Rt+CLDMBby5Y796E8rxwImb0bmcZwGWar9D3vkFm9Kjh00Buuo1PuU7tP1dV6yvRbH8 NzC0IryEoUJHx9909AJ449ET9Zb+C3ykEeBlKH2wonj7cAVK9ZEDpPEGAtp56XWZQEawl50mwq6t XkZAABxtOswXiicdh3HK7kaPHp38/9CBMc0rva6wDnkbTigUYA2ULqLtP5a5mLovVc48zI9A/hmb @@ -1277,3 +5428,21 @@ } } } + +: effective-type +: +{ + test.options += -et + + $* '' libfoo >'lib' : lib-prefix + $* '' foo >'exe' : no-lib-prefix + $* 'mixed' foo >'other' : other + + : lib-binless + : + $* 'lib,binless,extras' libfoo >>EOO + lib + binless + extras + EOO +} diff --git a/tests/overrides/driver.cxx b/tests/overrides/driver.cxx index 4302bb1..c4a09ef 100644 --- a/tests/overrides/driver.cxx +++ b/tests/overrides/driver.cxx @@ -6,15 +6,17 @@ #include <vector> #include <cstddef> // size_t #include <cstdint> // uint64_t -#include <cassert> #include <iostream> -#include <libbutl/utility.mxx> // trim() -#include <libbutl/manifest-parser.mxx> -#include <libbutl/manifest-serializer.mxx> +#include <libbutl/utility.hxx> // trim() +#include <libbutl/manifest-parser.hxx> +#include <libbutl/manifest-serializer.hxx> #include <libbpkg/manifest.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; using namespace bpkg; @@ -31,7 +33,7 @@ main (int argc, char* argv[]) { vector<manifest_name_value> overrides; - bool name (false); + string name; uint64_t l (1); for (int i (1); i != argc; ++i) @@ -40,7 +42,7 @@ main (int argc, char* argv[]) if (a == "-n") { - name = true; + name = "args"; } else { @@ -76,7 +78,19 @@ main (int argc, char* argv[]) try { package_manifest m (p); - m.override (overrides, name ? "args" : string ()); + m.override (overrides, name); + + // While at it, test validate_overrides(). + // + try + { + package_manifest::validate_overrides (overrides, name); + } + catch (const manifest_parsing&) + { + assert (false); // Validation must never fail if override succeeds. + } + m.serialize (s); } catch (const manifest_parsing& e) diff --git a/tests/overrides/testscript b/tests/overrides/testscript index babe57d..60168a4 100644 --- a/tests/overrides/testscript +++ b/tests/overrides/testscript @@ -15,6 +15,10 @@ build-email: foo@example.com build-error-email: error@example.com build-warning-email: warning@example.com + network-build-config: config.libfoo.network=true + network-build-email: network-foo@example.com + network-build-error-email: network-error@example.com + network-build-warning-email: network-warning@example.com EOI : 1 name: libfoo @@ -22,6 +26,7 @@ summary: Modern C++ parser license: LGPLv2 build-email: bar@example.com + network-build-config: config.libfoo.network=true EOO : builds @@ -35,6 +40,10 @@ builds: default build-include: linux* build-exclude: *; Only supports Linux. + network-build-config: config.libfoo.network=true + network-builds: default + network-build-include: linux* + network-build-exclude: * EOI : 1 name: libfoo @@ -42,6 +51,7 @@ summary: Modern C++ parser license: LGPLv2 builds: gcc + network-build-config: config.libfoo.network=true EOO : build-include-exclude @@ -54,6 +64,10 @@ license: LGPLv2 builds: default build-exclude: freebsd* + network-build-config: config.libfoo.network=true + network-builds: default + network-build-include: linux* + network-build-exclude: * EOI : 1 name: libfoo @@ -63,6 +77,7 @@ builds: default build-include: linux* build-exclude: *; Only supports Linux. + network-build-config: config.libfoo.network=true EOO : builds-build-include-exclude @@ -86,6 +101,142 @@ build-exclude: *; Only supports Linux. EOO + : build-configs + : + $* 'network-builds: all' 'network-build-include: windows*' 'network-build-exclude: *' \ + 'network-build-warning-email: network-warning@example.com' 'sys-build-email:' \ + 'cache-build-error-email: cache-error@example.com' \ + 'cache-build-include: freebsd*' 'cache-build-exclude: *' 'cache-builds: legacy' \ + 'cache-build-config: config.libfoo.cache=true config.libfoo.buffer=1028' \ + 'deprecated-api-build-config: config.libfoo.deprecated_api=true' 'deprecated-api-builds: windows' \ + 'experimental-api-build-config: config.libfoo.experimental_api=true' \ + 'sys-build-include: linux*' 'sys-build-exclude: *' \ + 'fancy-builds: gcc' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: foo@example.com + build-error-email: error@example.com + build-warning-email: warning@example.com + builds: all + build-include: linux* + build-include: macos* + build-include: freebsd* + build-exclude: * + network-builds: default + network-build-include: linux* + network-build-exclude: * + network-build-config: config.libfoo.network=true + network-build-error-email: network-error@example.com + cache-builds: default + cache-build-include: macos* + cache-build-exclude: * + cache-build-config: config.libfoo.cache=true + cache-build-email: cache@example.com + sys-builds: default + sys-build-include: freebsd* + sys-build-exclude: * + sys-build-config: ?sys:libcrypto + sys-build-email: sys@example.com + older-builds: default + older-build-include: windows* + older-build-exclude: * + older-build-config: ?libbar/1.0.0 + fancy-builds: default + fancy-build-include: windows* + fancy-build-exclude: * + fancy-build-config: config.libfoo.fancy=true + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-email: foo@example.com + build-warning-email: warning@example.com + build-error-email: error@example.com + builds: all + build-include: linux* + build-include: macos* + build-include: freebsd* + build-exclude: * + default-builds: none + default-build-email: + network-builds: all + network-build-include: windows* + network-build-exclude: * + network-build-config: config.libfoo.network=true + network-build-warning-email: network-warning@example.com + cache-builds: legacy + cache-build-include: freebsd* + cache-build-exclude: * + cache-build-config: config.libfoo.cache=true config.libfoo.buffer=1028 + cache-build-error-email: cache-error@example.com + sys-builds: default + sys-build-include: linux* + sys-build-exclude: * + sys-build-config: ?sys:libcrypto + sys-build-email: + older-builds: none + older-build-config: ?libbar/1.0.0 + older-build-email: + fancy-builds: gcc + fancy-build-config: config.libfoo.fancy=true + fancy-build-email: + deprecated-api-builds: windows + deprecated-api-build-config: config.libfoo.deprecated_api=true + deprecated-api-build-email: + experimental-api-builds: none + experimental-api-build-config: config.libfoo.experimental_api=true + experimental-api-build-email: + EOO + + : build-config-default + : + $* 'default-builds: all' 'default-build-include: windows*' 'default-build-exclude: *' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-builds: all + network-build-config: config.libfoo.network=true + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + default-builds: all + default-build-include: windows* + default-build-exclude: * + network-builds: none + network-build-config: config.libfoo.network=true + EOO + + : add-build-config + : + $* 'experimental-api-build-config: config.libfoo.experimental_api=true' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + network-builds: all + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-builds: all + network-build-config: config.libfoo.network=true + experimental-api-build-config: config.libfoo.experimental_api=true + EOO + : none : $* <<EOI >>EOO @@ -103,6 +254,84 @@ license: LGPLv2 build-email: foo@example.com EOO + + : build-auxiliary + : + { + : named + : + $* 'build-auxiliary-pgsql: *-postgresql**' \ + 'foo-build-auxiliary-oracle: *-oracle**' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + build-auxiliary-mysql: *-mysql_* + foo-build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-oracle: *-oracle_* + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql** + build-auxiliary-mysql: *-mysql_* + foo-build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-oracle: *-oracle** + EOO + + : unnamed + : + $* 'build-auxiliary: *-postgresql**' \ + 'foo-build-auxiliary: *-oracle**' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql_* + foo-build-auxiliary: *-oracle_* + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql** + foo-build-auxiliary: *-oracle** + EOO + + : new-config + : + $* 'bar-build-config:' \ + 'bar-build-auxiliary-mysql: *-mysql_8' \ + 'bar-build-auxiliary-pgsql: *-postgresql_16' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-mysql: *-mysql_* + foo-build-auxiliary-oracle: *-oracle_* + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-mysql: *-mysql_* + foo-build-auxiliary-oracle: *-oracle_* + bar-build-auxiliary-mysql: *-mysql_8 + bar-build-auxiliary-pgsql: *-postgresql_16 + EOO + } } : invalid @@ -141,4 +370,198 @@ EOI args:2:8: error: invalid package builds: unexpected underlying class set EOE + + + : no-build-config + : + $* 'network-builds: default' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + cannot override 'network-builds' value: no build package configuration 'network' + EOE + + : config-builds-after-builds + : + $* 'builds: all' 'network-builds: default' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'network-builds' override specified together with 'builds' override + EOE + + : config-builds-after-build-exclude + : + $* 'build-exclude: *' 'network-builds: default' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'network-builds' override specified together with 'build-exclude' override + EOE + + : builds-after-config-builds + : + $* 'network-builds: default' 'builds: all' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'builds' override specified together with 'network-builds' override + EOE + + : build-exclude-after-config-builds + : + $* 'network-builds: default' 'build-exclude: *' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'build-exclude' override specified together with 'network-builds' override + EOE + + : build-config-after-config-builds + : + $* 'deprecated-api-builds: windows' 'deprecated-api-build-config: config.libfoo.deprecated-api=true' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + cannot override 'deprecated-api-builds' value: no build package configuration 'deprecated-api' + EOE + + : config-email-after-email + : + $* 'build-email: foo@example.com' 'network-build-warning-email: warning@example.com' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'network-build-warning-email' override specified together with 'build-email' override + EOE + + : email-after-config-email + : + $* 'network-build-warning-email: warning@example.com' 'build-email: foo@example.com' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + network-build-config: config.libfoo.network=true + EOI + 'build-email' override specified together with 'network-build-warning-email' override + EOE + + : build-auxiliary + : + { + : named-common + : + $* 'build-auxiliary-mysql: *-mysql_*' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + foo-build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-oracle: *-oracle_* + EOI + no match for 'build-auxiliary-mysql' value override + EOE + + : named-config1 + : + $* 'foo-build-auxiliary-mysql: *-mysql_*' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + foo-build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-oracle: *-oracle_* + EOI + no match for 'foo-build-auxiliary-mysql' value override + EOE + + : named-config2 + : + $* 'bar-build-auxiliary-oracle: *-oracle**' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary-pgsql: *-postgresql_* + foo-build-auxiliary-mssql: *-mssql_* + foo-build-auxiliary-oracle: *-oracle_* + EOI + cannot override 'bar-build-auxiliary-oracle' value: no build package configuration 'bar' + EOE + + : unnamed-common + : + $* 'build-auxiliary-mysql: *-mysql_*' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql_* + foo-build-auxiliary: *-oracle_* + EOI + no match for 'build-auxiliary-mysql' value override + EOE + + : unnamed-config1 + : + $* 'foo-build-auxiliary-mysql: *-mysql_*' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql_* + foo-build-auxiliary: *-oracle_* + EOI + no match for 'foo-build-auxiliary-mysql' value override + EOE + + : unnamed-config2 + : + $* 'bar-build-auxiliary: *-mysql_*' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-auxiliary: *-postgresql_* + foo-build-auxiliary: *-oracle_* + EOI + cannot override 'bar-build-auxiliary' value: no build package configuration 'bar' + EOE + } } diff --git a/tests/package-version/driver.cxx b/tests/package-version/driver.cxx index 57c3f04..0a5ff43 100644 --- a/tests/package-version/driver.cxx +++ b/tests/package-version/driver.cxx @@ -2,17 +2,19 @@ // license : MIT; see accompanying LICENSE file #include <string> -#include <cassert> #include <cstdint> // uint16 #include <iostream> #include <exception> #include <stdexcept> // invalid_argument -#include <libbutl/utility.mxx> // operator<<(ostream, exception) -#include <libbutl/optional.mxx> +#include <libbutl/utility.hxx> // operator<<(ostream, exception) +#include <libbutl/optional.hxx> #include <libbpkg/manifest.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; @@ -22,11 +24,12 @@ namespace bpkg using butl::nullopt; static bool - bad_version (const string& v) + bad_version (const string& v, + version::flags fl = version::fold_zero_revision) { try { - version bv (v); + version bv (v, fl); return false; } catch (const invalid_argument&) @@ -136,6 +139,16 @@ namespace bpkg assert (bad_version (0, "", "", 0)); // Same. assert (bad_version (0, "", "", 0, 1)); // Unexpected iteration. + assert (bad_version ("1.0.0#1")); // Iteration disallowed. + + // Bad iteration. + // + assert (bad_version ("1.0.0#a", version::allow_iteration)); + assert (bad_version ("1.0.0#1a", version::allow_iteration)); + assert (bad_version ("1.0.0#", version::allow_iteration)); + assert (bad_version ("1.0.0#5000000000", version::allow_iteration)); + assert (bad_version ("1.0.0#+1", version::allow_iteration)); + { version v1; assert (v1.empty ()); @@ -256,7 +269,7 @@ namespace bpkg assert (test_constructor (v)); } { - version v ("+10-B+0", false /* fold_zero_revision */); + version v ("+10-B+0", version::none); assert (v.string () == "+10-B+0"); assert (v.canonical_upstream == "b"); assert (test_constructor (v)); @@ -405,6 +418,9 @@ namespace bpkg assert (version (1, "2.0", nullopt, 3, 4).compare ( version (1, "2.0", nullopt, 5, 6), true) == 0); + + assert (version ("1.1.1-a.0.1+2#34", version::flags::allow_iteration) == + version (1, "1.1.1", string ("a.0.1"), 2, 34)); } catch (const exception& e) { diff --git a/tests/repository-location/driver.cxx b/tests/repository-location/driver.cxx index 32f5b8e..4a4bbe4 100644 --- a/tests/repository-location/driver.cxx +++ b/tests/repository-location/driver.cxx @@ -2,16 +2,18 @@ // license : MIT; see accompanying LICENSE file #include <string> -#include <cassert> #include <sstream> #include <iostream> #include <stdexcept> // invalid_argument, logic_error -#include <libbutl/optional.mxx> -#include <libbutl/manifest-parser.mxx> +#include <libbutl/optional.hxx> +#include <libbutl/manifest-parser.hxx> #include <libbpkg/manifest.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; @@ -765,6 +767,18 @@ namespace bpkg assert (l1.string () == l2.string ()); assert (l1.canonical_name () == l2.canonical_name ()); } + { + repository_location l1 (loc ("c:/var/pkg/1/misc")); + repository_location l2 (loc ("c:/var/Pkg/1/Misc")); + assert (l1.canonical_name () == "pkg:misc"); + assert (l2.canonical_name () == l1.canonical_name ()); + } + { + repository_location l1 (loc ("c:\\repo.git", repository_type::git)); + repository_location l2 (loc ("C:/Repo.Git", repository_type::git)); + assert (l1.canonical_name () == "git:c:\\repo"); + assert (l2.canonical_name () == l1.canonical_name ()); + } #endif { repository_location l1 (loc ("http://www.cppget.org/1/stable")); @@ -888,10 +902,10 @@ namespace bpkg assert (git_ref_filter (n) == git_ref_filter (n, nullopt, false)); assert (git_ref_filter ('+' + n) == git_ref_filter (n, nullopt, false)); assert (git_ref_filter ('-' + n) == git_ref_filter (n, nullopt, true)); - assert (git_ref_filter (c + "@") == git_ref_filter (c, nullopt, false)); + assert (git_ref_filter (c + '@') == git_ref_filter (c, nullopt, false)); assert (git_ref_filter (c) == git_ref_filter (nullopt, c, false)); - assert (git_ref_filter ("@" + c) == git_ref_filter (nullopt, c, false)); - assert (git_ref_filter (n + "@" + c) == git_ref_filter (n, c, false)); + assert (git_ref_filter ('@' + c) == git_ref_filter (nullopt, c, false)); + assert (git_ref_filter (n + '@' + c) == git_ref_filter (n, c, false)); assert (parse_git_ref_filters (nullopt) == git_ref_filters {git_ref_filter ()}); |