aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--LICENSE2
-rw-r--r--build/root.build9
-rw-r--r--libbpkg/buildfile-scanner.cxx38
-rw-r--r--libbpkg/buildfile-scanner.hxx96
-rw-r--r--libbpkg/buildfile-scanner.txx272
-rw-r--r--libbpkg/manifest.cxx4240
-rw-r--r--libbpkg/manifest.hxx1221
-rw-r--r--libbpkg/manifest.ixx412
-rw-r--r--libbpkg/package-name.hxx2
-rw-r--r--manifest10
-rw-r--r--tests/build-class-expr/driver.cxx7
-rw-r--r--tests/build/root.build9
-rw-r--r--tests/buildfile-scanner/buildfile7
-rw-r--r--tests/buildfile-scanner/driver.cxx106
-rw-r--r--tests/buildfile-scanner/testscript342
-rw-r--r--tests/manifest/driver.cxx76
-rw-r--r--tests/manifest/testscript4213
-rw-r--r--tests/overrides/driver.cxx28
-rw-r--r--tests/overrides/testscript423
-rw-r--r--tests/package-version/driver.cxx28
-rw-r--r--tests/repository-location/driver.cxx26
22 files changed, 10693 insertions, 880 deletions
diff --git a/.gitignore b/.gitignore
index c3de2e7..5046596 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,10 +5,16 @@
*.d
*.t
*.i
+*.i.*
*.ii
+*.ii.*
*.o
*.obj
+*.gcm
+*.pcm
+*.ifc
*.so
+*.dylib
*.dll
*.a
*.lib
diff --git a/LICENSE b/LICENSE
index 2cf3738..e63bcec 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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>
diff --git a/manifest b/manifest
index 1732f32..454925e 100644
--- a/manifest
+++ b/manifest
@@ -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 ()});