aboutsummaryrefslogtreecommitdiff
path: root/libbpkg/manifest.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbpkg/manifest.cxx')
-rw-r--r--libbpkg/manifest.cxx2394
1 files changed, 2007 insertions, 387 deletions
diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx
index 6a5ce25..bd69b85 100644
--- a/libbpkg/manifest.cxx
+++ b/libbpkg/manifest.cxx
@@ -9,10 +9,10 @@
#include <sstream>
#include <cassert>
#include <cstdlib> // strtoull()
-#include <cstring> // strncmp(), strcmp(), strchr()
+#include <cstring> // strncmp(), strcmp(), strchr(), strcspn()
#include <utility> // move()
#include <cstdint> // uint*_t
-#include <algorithm> // find(), find_if_not(), find_first_of(), replace()
+#include <algorithm> // find(), find_if(), find_first_of(), replace()
#include <stdexcept> // invalid_argument
#include <type_traits> // remove_reference
@@ -566,7 +566,7 @@ namespace bpkg
}
version& version::
- operator= (version&& v)
+ operator= (version&& v) noexcept
{
if (this != &v)
{
@@ -624,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));
@@ -642,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;
}
@@ -660,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::
@@ -1111,20 +1238,6 @@ namespace bpkg
}
}
- std::string dependency::
- string () const
- {
- std::string r (name.string ());
-
- if (constraint)
- {
- r += ' ';
- r += constraint->string ();
- }
-
- return r;
- }
-
// dependency_alternative
//
string dependency_alternative::
@@ -1225,14 +1338,6 @@ namespace bpkg
return r;
}
- bool dependency_alternative::
- single_line () const
- {
- return !prefer &&
- !require &&
- (!reflect || reflect->find ('\n') == string::npos);
- }
-
// dependency_alternatives
//
class dependency_alternatives_lexer: public char_scanner<utf8_validator>
@@ -1257,7 +1362,6 @@ namespace bpkg
rsbrace, // ]
equal, // ==
- not_equal, // !=
less, // <
greater, // >
less_equal, // <=
@@ -1281,6 +1385,11 @@ namespace bpkg
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.
//
@@ -1406,12 +1515,11 @@ namespace bpkg
case ']': return make_token (type::rsbrace);
case '=':
- case '!':
{
- if ((peek ()) == '=')
+ if (peek () == '=')
{
get ();
- return make_token (c == '=' ? type::equal : type::not_equal);
+ return make_token (type::equal);
}
break;
}
@@ -1453,7 +1561,7 @@ namespace bpkg
// Add subsequent characters until eos or separator is encountered.
//
- const char* s (" \n\t?(){}[]=!<>~^|");
+ const char* s (" \n\t?(){}[]=<>~^|");
for (c = peek (); !eos (c) && strchr (s, c) == nullptr; c = peek ())
{
r += c;
@@ -1551,6 +1659,60 @@ namespace bpkg
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.
@@ -1579,22 +1741,21 @@ namespace bpkg
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::question: return q + '?' + q;
+ case token_type::lparen: return q + '(' + q;
+ case token_type::rparen: return q + ')' + q;
+ case token_type::lcbrace: return q + '{' + q;
+ case token_type::rcbrace: return q + '}' + q;
+ case token_type::lsbrace: return q + '[' + q;
+ case token_type::rsbrace: return q + ']' + q;
case token_type::equal: return q + "==" + q;
- case token_type::not_equal: return q + "!=" + q;
- case token_type::less: return q + "<" + q;
- case token_type::greater: return q + ">" + q;
+ case token_type::less: 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;
+ 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.
@@ -1778,7 +1939,7 @@ namespace bpkg
dependency_alternative r;
string what (requirements_ ? "requirement" : "dependency");
- string config ("config." + dependent_->variable () + ".");
+ string config ("config." + dependent_->variable () + '.');
auto bad_token = [&t, this] (string&& what)
{
@@ -1855,7 +2016,6 @@ namespace bpkg
}
case type::equal:
- case type::not_equal:
case type::less:
case type::greater:
case type::less_equal:
@@ -1989,9 +2149,26 @@ namespace bpkg
if (requirements_ && first && tt == type::question)
{
r.emplace_back (dependency ());
- r.enable = lexer_->peek_char () == '(' ? parse_eval () : string ();
+
+ 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;
}
@@ -2099,6 +2276,10 @@ namespace bpkg
t.column,
"multi-line " + what + " form with inline reflect clause");
+ // Allow comments.
+ //
+ lexer_->comments = true;
+
next (t, tt);
expect_token (type::newline);
@@ -2142,7 +2323,9 @@ namespace bpkg
// curly braces (expected to be on the separate lines) and return
// the enclosed fragment.
//
- auto parse_block = [&t, &tt, &expect_token, &bad_token, this] ()
+ // Note that an empty buildfile fragment is allowed.
+ //
+ auto parse_block = [&t, &tt, &expect_token, this] ()
{
next (t, tt);
expect_token (type::newline);
@@ -2155,14 +2338,7 @@ namespace bpkg
next_block (t, tt);
- string r (move (t.value));
-
- // Fail if the buildfile fragment is empty.
- //
- if (r.find_first_not_of (" \t\n") == string::npos)
- bad_token ("buildfile fragment");
-
- return r;
+ return move (t.value);
};
const string& v (t.value);
@@ -2252,6 +2428,11 @@ namespace bpkg
}
expect_token (type::rcbrace);
+
+ // Disallow comments.
+ //
+ lexer_->comments = false;
+
next (t, tt);
}
}
@@ -2342,18 +2523,6 @@ namespace bpkg
return serializer::merge_comment (r, comment);
}
- bool dependency_alternatives::
- conditional () const
- {
- for (const dependency_alternative& da: *this)
- {
- if (da.enable)
- return true;
- }
-
- return false;
- }
-
// requirement_alternative
//
string requirement_alternative::
@@ -2438,12 +2607,6 @@ namespace bpkg
return r;
}
- bool requirement_alternative::
- single_line () const
- {
- return !reflect || reflect->find ('\n') == string::npos;
- }
-
// requirement_alternatives
//
requirement_alternatives::
@@ -2543,18 +2706,6 @@ namespace bpkg
return serializer::merge_comment (r, comment);
}
- bool requirement_alternatives::
- conditional () const
- {
- for (const requirement_alternative& ra: *this)
- {
- if (ra.enable)
- return true;
- }
-
- return false;
- }
-
// build_class_term
//
build_class_term::
@@ -2567,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)
@@ -2591,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));
}
@@ -2623,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] == '_';
@@ -2918,102 +3069,42 @@ namespace bpkg
match_classes (cs, im, expr, r);
}
- // text_type
+ // build_auxiliary
//
- 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)
+ optional<pair<string, string>> build_auxiliary::
+ parse_value_name (const string& n)
{
- 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.
- //
- // Note that type, subtype, and parameter names are matched
- // case-insensitively.
+ // Check if this is a build-auxiliary-* name.
//
- 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
@@ -3038,7 +3129,7 @@ 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 + '\'');
}
@@ -3050,14 +3141,123 @@ namespace bpkg
{
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");
- static_cast<dependency&> (*this) =
- dependency (p == 0 ? move (v) : string (v, p));
+ 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
@@ -3114,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 ('/'));
@@ -3156,6 +3356,62 @@ 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));
+ }
+
+ // Parse the [*-]build-bot manifest value and append it to the specified
+ // custom bot public keys list. Make sure the specified key is not empty and
+ // is not a duplicate and throw parsing if that's not the case.
+ //
+ // Note: value name is not used by this function (and so can be moved out,
+ // etc before the call).
+ //
+ static void
+ parse_build_bot (const name_value& nv, const string& source_name, strings& r)
+ {
+ const string& v (nv.value);
+
+ auto bad_value = [&nv, &source_name, &v] (const string& d,
+ bool add_key = true)
+ {
+ throw !source_name.empty ()
+ ? parsing (source_name, nv.value_line, nv.value_column, d)
+ : parsing (!add_key ? d : (d + ":\n" + v));
+ };
+
+ if (v.empty ())
+ bad_value ("empty custom build bot public key", false /* add_key */);
+
+ if (find (r.begin (), r.end (), v) != r.end ())
+ bad_value ("duplicate custom build bot public key");
+
+ r.push_back (v);
+ }
+
const version stub_version (0, "0", nullopt, nullopt, 0);
// Parse until next() returns end-of-manifest value.
@@ -3166,7 +3422,7 @@ namespace bpkg
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)
{
@@ -3178,16 +3434,37 @@ namespace bpkg
auto bad_value ([&name, &nv](const string& d) {
throw parsing (name, nv.value_line, nv.value_column, d);});
- 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,
@@ -3251,11 +3528,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.
//
@@ -3270,11 +3726,27 @@ namespace bpkg
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;
+
+ m.build_configs.emplace_back ("default");
for (nv = next (); !nv.empty (); nv = next ())
{
@@ -3343,6 +3815,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)
@@ -3399,28 +3920,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");
}
@@ -3429,32 +3950,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")
{
@@ -3465,7 +4023,7 @@ namespace bpkg
}
else if (n == "email")
{
- parse_email (nv, m.email, "project", name);
+ parse_email (nv, m.email, "project");
}
else if (n == "doc-url")
{
@@ -3490,19 +4048,19 @@ namespace bpkg
}
else if (n == "package-email")
{
- parse_email (nv, m.package_email, "package", name);
+ parse_email (nv, m.package_email, "package");
}
else if (n == "build-email")
{
- parse_email (nv, m.build_email, "build", 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", 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", name);
+ parse_email (nv, m.build_error_email, "build error");
}
else if (n == "priority")
{
@@ -3576,6 +4134,93 @@ namespace bpkg
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 == "build-bot")
+ {
+ parse_build_bot (nv, name, m.build_bot_keys);
+ }
+ else if (n.size () > 13 &&
+ n.compare (n.size () - 13, 13, "-build-config") == 0)
+ {
+ auto vc (parser::split_comment (v));
+
+ n.resize (n.size () - 13);
+
+ build_package_config& bc (build_conf (move (n)));
+
+ if (!bc.arguments.empty () || !bc.comment.empty ())
+ bad_name ("build configuration redefinition");
+
+ 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);
+
+ build_package_config& bc (build_conf (move (n)));
+
+ 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)
+ {
+ 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.size () > 14 &&
+ n.compare (n.size () - 14, 14, "-build-exclude") == 0)
+ {
+ 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.size () > 10 &&
+ n.compare (n.size () - 10, 10, "-build-bot") == 0)
+ {
+ n.resize (n.size () - 10);
+
+ build_package_config& bc (build_conf (move (n)));
+ parse_build_bot (nv, name, bc.bot_keys);
+ }
+ else if (n.size () > 12 &&
+ n.compare (n.size () - 12, 12, "-build-email") == 0)
+ {
+ n.resize (n.size () - 12);
+ build_config_emails.push_back (move (nv));
+ }
+ else if (n.size () > 20 &&
+ n.compare (n.size () - 20, 20, "-build-warning-email") == 0)
+ {
+ n.resize (n.size () - 20);
+ build_config_warning_emails.push_back (move (nv));
+ }
+ 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" ||
@@ -3590,6 +4235,98 @@ namespace bpkg
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))
@@ -3670,23 +4407,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));
@@ -3697,51 +4435,201 @@ 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)");
+ }
}
+
+ 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_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>
+ {
+ // Verify that the text/file value is specified if the type value is
+ // specified.
+ //
+ if (!text_file)
+ {
+ if (type)
+ {
+ // Restore as bad_name() uses its line/column.
+ //
+ nv = move (*type);
+
+ bad_name (string ("no ") + what + " for specified type");
+ }
+
+ return nullopt;
+ }
+
+ return parse_text_file (move (*text_file), move (type), what);
+ };
+
+ // 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)
+ {
+ 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) + "'");
+ }
+ }
+ }
+ }
+
+ // 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)
+ {
+ // 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 */);
+ }
+ };
+
+ 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));
+
// Now, when the version manifest value is parsed, we can parse the
// dependencies and complete their constraints, if requested.
//
- auto complete_constraint = [&m, cd, &flag] (auto&& dep)
+ auto complete_constraint = [&m, cv, &flag] (auto&& dep)
{
if (dep.constraint)
try
@@ -3749,12 +4637,12 @@ namespace bpkg
version_constraint& vc (*dep.constraint);
if (!vc.complete () &&
- flag (package_manifest_flags::forbid_incomplete_dependencies))
+ flag (package_manifest_flags::forbid_incomplete_values))
throw invalid_argument ("$ not allowed");
// Complete the constraint.
//
- if (cd)
+ if (cv)
vc = vc.effective (m.version);
}
catch (const invalid_argument& e)
@@ -3827,16 +4715,82 @@ namespace bpkg
}
}
- 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
@@ -3845,7 +4799,7 @@ namespace bpkg
name_value nv,
const function<package_manifest::translate_function>& tf,
bool iu,
- bool cd,
+ bool cv,
package_manifest_flags fl,
package_manifest& m)
{
@@ -3865,7 +4819,7 @@ namespace bpkg
[&p] () {return p.next ();},
tf,
iu,
- cd,
+ cv,
fl,
m);
}
@@ -3877,12 +4831,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
@@ -3891,10 +4846,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.
//
@@ -3905,20 +4860,11 @@ namespace bpkg
}
package_manifest::
- package_manifest (manifest_parser& p,
- bool iu,
- bool cd,
- package_manifest_flags fl)
- : package_manifest (p, function<translate_function> (), iu, cd, fl)
- {
- }
-
- package_manifest::
package_manifest (const string& name,
vector<name_value>&& vs,
const function<translate_function>& tf,
bool iu,
- bool cd,
+ bool cv,
package_manifest_flags fl)
{
auto i (vs.begin ());
@@ -3933,7 +4879,7 @@ namespace bpkg
},
tf,
iu,
- cd,
+ cv,
fl,
*this);
}
@@ -3942,13 +4888,13 @@ namespace bpkg
package_manifest (const string& name,
vector<name_value>&& vs,
bool iu,
- bool cd,
+ bool cv,
package_manifest_flags fl)
: package_manifest (name,
move (vs),
function<translate_function> (),
iu,
- cd,
+ cv,
fl)
{
}
@@ -3957,190 +4903,739 @@ 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");
-
- optional<text_type> r;
+ strings 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-bot} override value.
+ //
+ const manifest_name_value* cbb (nullptr);
+
+ // The first {*-build-bot} override value.
+ //
+ const manifest_name_value* pbb (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);
+
+ // 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.
+ //
+ vector<pair<size_t, bool>> obcs;
+
+ // List of indexes of the build configurations with the overridden bots.
+ //
+ vector<size_t> obbs;
+
+ // List of indexes of the build configurations with the overridden emails.
+ //
+ vector<size_t> obes;
- // Reset the build emails value group on the first call.
+ // Return true if the specified package build configuration is newly
+ // created by the *-build-config override.
//
- bool rbe (true);
- auto reset_build_emails = [&rbe, this] ()
+ 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}, *-build-bot, 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. Also verify that no common
+ // build constraints group value overrides are applied yet and throw if
+ // that's not the case.
+ //
+ 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-bot} value group on the first call but throw if any
+ // of the {*-build-bot} override values are already encountered.
+ //
+ auto reset_build_bots = [&cbb, &pbb, &nv, &bad_name, &m] ()
+ {
+ if (cbb == nullptr)
+ {
+ if (pbb != nullptr)
+ bad_name ('\'' + nv.name + "' override specified together with '" +
+ pbb->name + "' override");
+
+ m.build_bot_keys.clear ();
+ cbb = &nv;
+ }
+ };
+
+ // Return the reference to the package build configuration which matches
+ // the build config-specific build bot 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. Also verify that no common
+ // build bot value overrides are applied yet and throw if that's not the
+ // case.
+ //
+ auto build_conf_bot =
+ [&pbb, &cbb, &nv, &obbs, &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 bot value overrides
+ // are applied yet and throw if that's not the case.
+ //
+ if (pbb == nullptr)
+ {
+ if (cbb != nullptr)
+ bad_name ('\'' + nm + "' override specified together with '" +
+ cbb->name + "' override");
+
+ pbb = &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-bot} override for this
+ // build config, then clear this config' bot_keys members and add an
+ // entry to the overridden configs list.
+ //
+ if (find (obbs.begin (), obbs.end (), ci) == obbs.end ())
+ {
+ r.bot_keys.clear ();
+
+ obbs.push_back (ci);
+ }
+
+ 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. Also verify that no common
+ // build emails group value overrides are applied yet and throw if
+ // that's not the case.
+ //
+ 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 == "build-bot")
+ {
+ reset_build_bots ();
+
+ parse_build_bot (nv, name, m.build_bot_keys);
+ }
+ 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.size () > 10 &&
+ n.compare (n.size () - 10, 10, "-build-bot") == 0)
+ {
+ build_package_config& bc (build_conf_bot (n.size () - 10));
+ parse_build_bot (nv, name, bc.bot_keys);
+ }
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, bot keys, and email overrides are applied, perform the
+ // final adjustments to the build config constraints, bot keys, and
+ // emails.
+ //
+ if (!validate_only)
+ {
+ if (cbc != nullptr) // Common build constraints are overridden?
+ {
+ for (build_package_config& c: m.build_configs)
+ {
+ c.builds.clear ();
+ c.constraints.clear ();
+ }
+ }
+ else if (pbc != nullptr) // Build config constraints are overridden?
{
- string d ("cannot override '" + n + "' value");
+ 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 (cbb != nullptr) // Common build bots are overridden?
+ {
+ for (build_package_config& c: m.build_configs)
+ c.bot_keys.clear ();
+ }
+
+ 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 ();
+
+ if (optional<string> fc = loader (file_value_name, text.path))
+ {
+ if (fc->empty ())
+ throw parsing ("package manifest value " + file_value_name +
+ " references empty file");
- description = text_file (load (description_file, description->path));
+ text = typed_text_file (move (*fc), move (text.type));
+ }
}
- }
+ };
- // Load the changes-file manifest values.
+ // Load the descriptions and changes, if present.
//
- for (text_file& c: changes)
+ 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 build-file manifest values.
+ //
+ 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;
+ }
}
}
@@ -4173,6 +5668,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 ());
@@ -4200,26 +5701,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)
@@ -4297,9 +5818,97 @@ 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 string& k: m.build_bot_keys)
+ s.next ("build-bot", k);
+
+ 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.bot_keys.empty ())
+ {
+ string n (bc.name + "-build-bot");
+
+ for (const string& k: bc.bot_keys)
+ s.next (n, k);
+ }
+
+ 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 ());
@@ -4548,21 +6157,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");
@@ -4916,7 +6536,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