diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | build/root.build | 7 | ||||
-rw-r--r-- | libbpkg/manifest.cxx | 1984 | ||||
-rw-r--r-- | libbpkg/manifest.hxx | 792 | ||||
-rw-r--r-- | libbpkg/manifest.ixx | 412 | ||||
-rw-r--r-- | manifest | 10 | ||||
-rw-r--r-- | tests/build/root.build | 7 | ||||
-rw-r--r-- | tests/manifest/driver.cxx | 29 | ||||
-rw-r--r-- | tests/manifest/testscript | 1178 | ||||
-rw-r--r-- | tests/overrides/driver.cxx | 18 | ||||
-rw-r--r-- | tests/overrides/testscript | 530 | ||||
-rw-r--r-- | tests/repository-location/driver.cxx | 6 |
13 files changed, 4254 insertions, 727 deletions
@@ -5,10 +5,16 @@ *.d *.t *.i +*.i.* *.ii +*.ii.* *.o *.obj +*.gcm +*.pcm +*.ifc *.so +*.dylib *.dll *.a *.lib @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2022 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 75ac4be..c9ab5dc 100644 --- a/build/root.build +++ b/build/root.build @@ -16,4 +16,11 @@ 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/manifest.cxx b/libbpkg/manifest.cxx index 50a86f3..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> @@ -1636,21 +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::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. @@ -1834,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) { @@ -2418,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:: @@ -2514,12 +2607,6 @@ namespace bpkg return r; } - bool requirement_alternative:: - single_line () const - { - return !reflect || reflect->find ('\n') == string::npos; - } - // requirement_alternatives // requirement_alternatives:: @@ -2619,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:: @@ -2643,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) @@ -2667,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)); } @@ -2699,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] == '_'; @@ -2994,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); + if (n == "build-auxiliary") + return make_pair (string (), string ()); - small_vector<pair<string, string>, 1> ps; - - 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 @@ -3114,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 + '\''); } @@ -3127,11 +3142,12 @@ namespace bpkg using std::string; // We will use the dependency alternatives parser to parse the - // `<name> [<version-constraint>] [<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. + // `<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. @@ -3210,16 +3226,14 @@ namespace bpkg // // 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. - // - if (da.enable) - throw invalid_argument ("unexpected enable clause"); + // is not the case. So there is nothing to verify here. - // Move the dependency and the reflect clause into the being created test - // dependency object. + // 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); } @@ -3230,6 +3244,13 @@ namespace bpkg ? "* " + dependency::string () : dependency::string ()); + if (enable) + { + r += " ? ("; + r += *enable; + r += ')'; + } + if (reflect) { r += ' '; @@ -3293,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 ('/')); @@ -3335,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. @@ -3345,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) { @@ -3357,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, @@ -3430,6 +3528,43 @@ 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; @@ -3542,6 +3677,41 @@ namespace bpkg } }; + // 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. // @@ -3556,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 ()) { @@ -3629,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) @@ -3685,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"); } @@ -3715,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") { @@ -3751,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") { @@ -3776,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") { @@ -3862,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" || @@ -3933,6 +4292,41 @@ namespace bpkg 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)) @@ -4013,23 +4407,24 @@ namespace bpkg m.upstream_version = move (nv.value); } - // Verify that description is specified if the description type is - // specified. + // Parse and validate a text/file manifest value and its respective type + // value, if present. Return a typed_text_file object. // - if (description_type && !description) - bad_value ("no package description for specified description type"); - - // Validate (and set) description and its type. - // - 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)); @@ -4040,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 @@ -4092,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) @@ -4170,16 +4715,61 @@ namespace bpkg } } + // 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 (m.description && - !m.description_type && - flag (package_manifest_flags::require_description_type)) - bad_name ("no package description type 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)) @@ -4209,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) { @@ -4229,7 +4819,7 @@ namespace bpkg [&p] () {return p.next ();}, tf, iu, - cd, + cv, fl, m); } @@ -4241,12 +4831,12 @@ namespace bpkg p, move (nv), iu, - false /* complete_depends */, - package_manifest_flags::forbid_file | - package_manifest_flags::forbid_fragment | - package_manifest_flags::forbid_incomplete_dependencies | - package_manifest_flags::require_location | - package_manifest_flags::require_description_type | + false /* complete_values */, + package_manifest_flags::forbid_file | + package_manifest_flags::forbid_fragment | + package_manifest_flags::forbid_incomplete_values | + package_manifest_flags::require_location | + package_manifest_flags::require_text_type | package_manifest_flags::require_bootstrap_build); } @@ -4256,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. // @@ -4270,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 ()); @@ -4298,7 +4879,7 @@ namespace bpkg }, tf, iu, - cd, + cv, fl, *this); } @@ -4307,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) { } @@ -4322,199 +4903,714 @@ 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); - // Reset the build emails value group on the first call. + // The first {*-build-bot} override value. // - bool rbe (true); - auto reset_build_emails = [&rbe, this] () + 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; + + // 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}, *-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? { - 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 (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 build_file ("build-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) { - // Load a file and verify that its content is not empty, if the loader - // returns the content. + // 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 = [&loader] (const string& n, const path& p) + auto load = [iu, &loader] (typed_text_file& text, + const string& file_value_name) { - optional<string> r (loader (n, p)); + // Make the type explicit. + // + optional<text_type> t; - if (r && 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 = load (description_file, description->path)) - description = text_file (move (*fc)); + 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 changes-file manifest values. + // Load the descriptions and changes, if present. // - for (text_file& c: changes) - { - if (c.file) - { - if (optional<string> fc = load (changes_file, c.path)) - c = text_file (move (*fc)); - } - } + 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. // @@ -4572,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 ()); @@ -4599,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) @@ -4696,9 +5818,78 @@ 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) @@ -4715,6 +5906,9 @@ namespace bpkg 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 ()); @@ -4963,23 +6157,29 @@ 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"); @@ -5336,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 diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index cad3c1e..feb3b96 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -10,8 +10,7 @@ #include <cassert> #include <cstdint> // uint*_t #include <ostream> -#include <utility> // move() -#include <stdexcept> // logic_error +#include <utility> // move(), pair #include <functional> #include <libbutl/url.hxx> @@ -102,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 @@ -111,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). @@ -135,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 @@ -207,33 +174,10 @@ namespace bpkg return os << (v.empty () ? "<empty-version>" : v.string ()); } - 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::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 // @@ -251,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: @@ -281,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> @@ -415,17 +431,10 @@ 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 { @@ -447,11 +456,8 @@ namespace bpkg string () const; }; - inline std::ostream& - operator<< (std::ostream& os, const dependency& d) - { - return os << d.string (); - } + std::ostream& + operator<< (std::ostream&, const dependency&); // depends // @@ -593,8 +599,13 @@ namespace bpkg // Return true if the string() function would return the single-line // representation. // - LIBBPKG_EXPORT bool - single_line () const; + bool + single_line () const + { + return !prefer && + !require && + (!reflect || reflect->find ('\n') == std::string::npos); + } }; inline std::ostream& @@ -650,7 +661,7 @@ namespace bpkg // Return true if there is a conditional alternative in the list. // - LIBBPKG_EXPORT bool + bool conditional () const; }; @@ -701,8 +712,11 @@ namespace bpkg // Return true if the string() function would return the single-line // representation. // - LIBBPKG_EXPORT bool - single_line () const; + 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. @@ -755,7 +769,7 @@ namespace bpkg // Return true if there is a conditional alternative in the list. // - LIBBPKG_EXPORT bool + bool conditional () const; // Return true if this is a single simple requirement alternative. @@ -776,7 +790,7 @@ namespace bpkg 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; @@ -811,49 +825,33 @@ namespace bpkg // enum class package_manifest_flags: std::uint16_t { - none = 0x000, - - forbid_file = 0x001, // Forbid *-file manifest values. - forbid_location = 0x002, - forbid_sha256sum = 0x004, - forbid_fragment = 0x008, - forbid_incomplete_dependencies = 0x010, - - require_location = 0x020, - require_sha256sum = 0x040, - require_description_type = 0x080, - require_bootstrap_build = 0x100 + 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 { @@ -882,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 (); @@ -903,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 { @@ -953,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: // @@ -964,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&, @@ -972,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& @@ -986,27 +980,173 @@ 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, auxiliaries, + // custom bot public keys, and notification emails. + // + // Note that in the package manifest the build bot keys list contains the + // public keys data (std::string type). However, for other use cases it may + // be convenient to store some other key representations (public key object + // pointers represented as key fingerprints, etc; see brep for such a use + // case). + // + template <typename K> + class build_package_config_template { - return os << to_string (t); - } + public: + using email_type = bpkg::email; + using key_type = K; + + 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; + + std::vector<key_type> bot_keys; + + butl::optional<email_type> email; + butl::optional<email_type> warning_email; + butl::optional<email_type> error_email; + + build_package_config_template () = default; + + build_package_config_template (std::string n, + std::string a, + std::string c, + butl::small_vector<build_class_expr, 1> bs, + std::vector<build_constraint> cs, + std::vector<build_auxiliary> as, + std::vector<key_type> bks, + butl::optional<email_type> e, + butl::optional<email_type> we, + butl::optional<email_type> ee) + : name (move (n)), + arguments (move (a)), + comment (move (c)), + builds (move (bs)), + constraints (move (cs)), + auxiliaries (move (as)), + bot_keys (move (bks)), + email (move (e)), + warning_email (move (we)), + error_email (move (ee)) {} + + // Built incrementally. + // + explicit + build_package_config_template (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 custom bot public keys, if specified, and + // the common ones otherwise. + // + const std::vector<key_type>& + effective_bot_keys (const std::vector<key_type>& common) const noexcept + { + return !bot_keys.empty () ? bot_keys : 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; + } + }; + + using build_package_config = build_package_config_template<std::string>; enum class test_dependency_type { @@ -1033,6 +1173,7 @@ namespace bpkg { test_dependency_type type; bool buildtime; + butl::optional<std::string> enable; butl::optional<std::string> reflect; test_dependency () = default; @@ -1040,15 +1181,17 @@ namespace bpkg test_dependency_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>] [<reflect-config>]` form. Throw - // std::invalid_argument if the value is invalid. + // `[*] <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 @@ -1083,6 +1226,40 @@ namespace bpkg 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 { public: @@ -1093,16 +1270,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; - 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; @@ -1116,8 +1295,22 @@ namespace bpkg std::vector<requirement_alternatives> requirements; butl::small_vector<test_dependency, 1> tests; + // Common build classes, constraints, auxiliaries, and custom bot public + // keys 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; + strings build_bot_keys; + + // 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 @@ -1133,6 +1326,10 @@ namespace bpkg 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). // @@ -1140,19 +1337,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; + + static std::string + effective_type (const butl::optional<std::string>&, const package_name&); - // 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. + // 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; @@ -1164,7 +1387,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 | @@ -1186,7 +1409,7 @@ 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 | @@ -1201,7 +1424,7 @@ namespace bpkg package_manifest (const std::string& name, std::vector<butl::manifest_name_value>&&, 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 | @@ -1211,7 +1434,7 @@ namespace bpkg std::vector<butl::manifest_name_value>&&, 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 | @@ -1222,27 +1445,72 @@ 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}} + // {build-bot} + // {*-builds, *-build-{include,exclude}} + // {*-build-bot} + // {*-build-config} + // {*-build-*email} + // + // [*-]build-auxiliary[-*] + // + // Throw manifest_parsing if the configuration specified by the build + // package configuration-specific build constraint, email, auxiliary, or + // custom bot public key 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, auxiliary, + // and bot key 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 custom bot key value overrides are mutually + // exclusive. If the common custom bot keys are overridden, then all the + // build config-specific custom bot keys are removed. Otherwise, if some + // build config-specific custom bot keys are overridden, then for the + // remaining configs the custom bot keys are left unchanged. + // + // Similar to the above, 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>&, @@ -1250,6 +1518,13 @@ 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); @@ -1276,9 +1551,10 @@ namespace bpkg // Load the *-file manifest values using the specified load function that // returns the file contents passing through any exception it may throw. // If nullopt is returned, then the respective *-file value is left - // unexpanded. Set the potentially absent description type value to the - // effective description type. If the effective type is nullopt then - // assign a synthetic unknown type. + // 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 @@ -1296,13 +1572,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); @@ -1608,9 +1881,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. @@ -1628,59 +1900,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. // @@ -1694,69 +1929,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. @@ -2053,4 +2249,6 @@ namespace bpkg } } +#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; + } +} @@ -1,6 +1,6 @@ : 1 name: libbpkg -version: 0.15.0 +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: host +builds: all : &host requires: c++14 -depends: * build2 >= 0.15.0- -depends: * bpkg >= 0.15.0- -depends: libbutl ^0.15.0 +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/root.build b/tests/build/root.build index 8a3f2e7..f97c101 100644 --- a/tests/build/root.build +++ b/tests/build/root.build @@ -14,8 +14,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. # exe{*}: test = true diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index c0d8693..56c886d 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -23,6 +23,7 @@ using namespace bpkg; // 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 @@ -40,7 +41,7 @@ using namespace bpkg; // 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. @@ -52,7 +53,10 @@ using namespace bpkg; // roundtrip them to stdout together with their effective constraints, // calculated using version passed as an argument. // -// In the forth form print the libbpkg version to stdout and exit. +// 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[]) @@ -74,7 +78,7 @@ main (int argc, char* argv[]) { if (mode == "-p") { - bool complete_dependencies (false); + bool complete_values (false); bool ignore_unknown (false); bool long_lines (false); @@ -83,7 +87,7 @@ main (int argc, char* argv[]) string o (argv[i]); if (o == "-c") - complete_dependencies = true; + complete_values = true; else if (o == "-i") ignore_unknown = true; else if (o == "-l") @@ -114,7 +118,7 @@ main (int argc, char* argv[]) } }, ignore_unknown, - complete_dependencies).serialize (s); + complete_values).serialize (s); } else if (mode == "-ec") { @@ -135,6 +139,21 @@ 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 { bool long_lines (false); diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 440e61f..d7ec37f 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,6 +1048,571 @@ 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-bot + : + { + : basics + : + { + $* <<EOF >>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot:\ + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw5liP5pyU9ebC/nD3djZ + 1H2dlKmUyiX0Z8POvKhLREd0B3rM59bPcnbRB4HMIhj0J0hUBvS8xb4u5udCPToa + x0A/LMWZ6claiivNtJ3CdLV98eklWdNUg5WXOuqq9QDKXw2ZpGbwDwCOh6aHSWVq + 98N9AQx0ZMmMWz3qhRyxPfh+GeJ05uj2ohU9FeUJxeqUcgJT/UcMZ3+7KYbwr+Uq + /HCoX1BmN6nvzhQGHvJIZ2IcjvOQ0AUrPmpSZN01Zr3ZEpkHM3hJWNLu3ntJLGBQ + 0aT5kG3iqFyr9q3M3c4J8c0AWrnDjvj0qnCyjNwqW+qIpatmCNT43DmgYr9fQLW0 + UHusburz53AbXs12zu3gZzkb0irlShatkMqqQaqaU0/+zw1LnoZ+rvmn2XV97UuK + LFKMKXCnyi2ZG65IZHGkjBVAPuvsX6RgLNyner/QtkDJTbfhktInbG08dCPqv1EF + 1OtcYKMTn8I5P2VmMO6SXXDLMSdU8b5DA5EY6Ca6JBB8g06S9sqGqXgQFysAnZs1 + VFgMopf8WZqj23x+DX+9KKT2pVnjbwRvBAntuCDoO75gWoETDnCQXEei/PbyamPq + 9+NjNsTDn67iJTGncZbII+eciY2YiFHm6GMzBPsUYlQcxiuO4X36jW6m2rwuw37K + oFDbGI3uY4LnhwmDFLbjtk8CAwEAAQ== + -----END PUBLIC KEY----- + \ + build-bot:\ + -----BEGIN PUBLIC KEY----- + AIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw5liP5pyU9ebC/nD3djZ + 2H2dlKmUyiX0Z8POvKhLREd0B3rM59bPcnbRB4HMIhj0J0hUBvS8xb4u5udCPToa + x0A/LMWZ6claiivNtJ3CdLV98eklWdNUg5WXOuqq9QDKXw2ZpGbwDwCOh6aHSWVq + 98N9AQx0ZMmMWz3qhRyxPfh+GeJ05uj2ohU9FeUJxeqUcgJT/UcMZ3+7KYbwr+Uq + /HCoX1BmN6nvzhQGHvJIZ2IcjvOQ0AUrPmpSZN01Zr3ZEpkHM3hJWNLu3ntJLGBQ + 0aT5kG3iqFyr9q3M3c4J8c0AWrnDjvj0qnCyjNwqW+qIpatmCNT43DmgYr9fQLW0 + UHusburz53AbXs12zu3gZzkb0irlShatkMqqQaqaU0/+zw1LnoZ+rvmn2XV97UuK + LFKMKXCnyi2ZG65IZHGkjBVAPuvsX6RgLNyner/QtkDJTbfhktInbG08dCPqv1EF + 1OtcYKMTn8I5P2VmMO6SXXDLMSdU8b5DA5EY6Ca6JBB8g06S9sqGqXgQFysAnZs1 + VFgMopf8WZqj23x+DX+9KKT2pVnjbwRvBAntuCDoO75gWoETDnCQXEei/PbyamPq + 9+NjNsTDn67iJTGncZbII+eciY2YiFHm6GMzBPsUYlQcxiuO4X36jW6m2rwuw37K + oFDbGI3uY4LnhwmDFLbjtk8CAwEAAQ== + -----END PUBLIC KEY----- + \ + EOF + } + + : empty + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: + EOI + stdin:6:11: error: empty custom build bot public key + EOE + } + + : duplicate + : + { + $* <<EOI 2>>EOE != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot:\ + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw5liP5pyU9ebC/nD3djZ + 1H2dlKmUyiX0Z8POvKhLREd0B3rM59bPcnbRB4HMIhj0J0hUBvS8xb4u5udCPToa + x0A/LMWZ6claiivNtJ3CdLV98eklWdNUg5WXOuqq9QDKXw2ZpGbwDwCOh6aHSWVq + 98N9AQx0ZMmMWz3qhRyxPfh+GeJ05uj2ohU9FeUJxeqUcgJT/UcMZ3+7KYbwr+Uq + /HCoX1BmN6nvzhQGHvJIZ2IcjvOQ0AUrPmpSZN01Zr3ZEpkHM3hJWNLu3ntJLGBQ + 0aT5kG3iqFyr9q3M3c4J8c0AWrnDjvj0qnCyjNwqW+qIpatmCNT43DmgYr9fQLW0 + UHusburz53AbXs12zu3gZzkb0irlShatkMqqQaqaU0/+zw1LnoZ+rvmn2XV97UuK + LFKMKXCnyi2ZG65IZHGkjBVAPuvsX6RgLNyner/QtkDJTbfhktInbG08dCPqv1EF + 1OtcYKMTn8I5P2VmMO6SXXDLMSdU8b5DA5EY6Ca6JBB8g06S9sqGqXgQFysAnZs1 + VFgMopf8WZqj23x+DX+9KKT2pVnjbwRvBAntuCDoO75gWoETDnCQXEei/PbyamPq + 9+NjNsTDn67iJTGncZbII+eciY2YiFHm6GMzBPsUYlQcxiuO4X36jW6m2rwuw37K + oFDbGI3uY4LnhwmDFLbjtk8CAwEAAQ== + -----END PUBLIC KEY----- + \ + build-bot:\ + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw5liP5pyU9ebC/nD3djZ + 1H2dlKmUyiX0Z8POvKhLREd0B3rM59bPcnbRB4HMIhj0J0hUBvS8xb4u5udCPToa + x0A/LMWZ6claiivNtJ3CdLV98eklWdNUg5WXOuqq9QDKXw2ZpGbwDwCOh6aHSWVq + 98N9AQx0ZMmMWz3qhRyxPfh+GeJ05uj2ohU9FeUJxeqUcgJT/UcMZ3+7KYbwr+Uq + /HCoX1BmN6nvzhQGHvJIZ2IcjvOQ0AUrPmpSZN01Zr3ZEpkHM3hJWNLu3ntJLGBQ + 0aT5kG3iqFyr9q3M3c4J8c0AWrnDjvj0qnCyjNwqW+qIpatmCNT43DmgYr9fQLW0 + UHusburz53AbXs12zu3gZzkb0irlShatkMqqQaqaU0/+zw1LnoZ+rvmn2XV97UuK + LFKMKXCnyi2ZG65IZHGkjBVAPuvsX6RgLNyner/QtkDJTbfhktInbG08dCPqv1EF + 1OtcYKMTn8I5P2VmMO6SXXDLMSdU8b5DA5EY6Ca6JBB8g06S9sqGqXgQFysAnZs1 + VFgMopf8WZqj23x+DX+9KKT2pVnjbwRvBAntuCDoO75gWoETDnCQXEei/PbyamPq + 9+NjNsTDn67iJTGncZbII+eciY2YiFHm6GMzBPsUYlQcxiuO4X36jW6m2rwuw37K + oFDbGI3uY4LnhwmDFLbjtk8CAwEAAQ== + -----END PUBLIC KEY----- + \ + EOI + stdin:23:1: error: duplicate custom build bot public key + 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 : { @@ -3279,6 +4351,17 @@ 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 @@ -3293,6 +4376,45 @@ 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 @@ -3347,19 +4469,6 @@ EOI stdin:6:8: error: only single package allowed EOE - - : enable - : - $* <<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: unexpected enable clause - EOE } : buildfile @@ -3735,6 +4844,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 @@ -3743,8 +4854,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 @@ -3773,6 +4887,10 @@ 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 @@ -4419,3 +5537,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 be3e0ff..c4a09ef 100644 --- a/tests/overrides/driver.cxx +++ b/tests/overrides/driver.cxx @@ -33,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) @@ -42,7 +42,7 @@ main (int argc, char* argv[]) if (a == "-n") { - name = true; + name = "args"; } else { @@ -78,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..a903d05 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,153 @@ 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 + } + + : build-bot + : + { + : common + : + $* 'build-bot: key3' 'build-bot: key4' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key1 + foo-build-bot: key2 + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key3 + build-bot: key4 + EOO + + : config + : + $* 'foo-build-bot: key3' 'foo-build-bot: key4' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key1 + foo-build-bot: key2 + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key1 + foo-build-bot: key3 + foo-build-bot: key4 + EOO + + : new-config + : + $* 'bar-build-config:' \ + 'bar-build-bot: key1' \ + 'bar-build-bot: key2' <<EOI >>EOO + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key1 + foo-build-bot: key2 + EOI + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + build-bot: key1 + foo-build-bot: key2 + bar-build-bot: key1 + bar-build-bot: key2 + EOO + } } : invalid @@ -141,4 +439,236 @@ 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-bot-after-built-bot + : + $* 'build-bot: key1' 'foo-build-bot: key2' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + foo-build-config: + EOI + 'foo-build-bot' override specified together with 'build-bot' override + EOE + + : built-bot-after-config-bot + : + $* 'foo-build-bot: key1' 'build-bot: key2' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + foo-build-config: + EOI + 'build-bot' override specified together with 'foo-build-bot' override + EOE + + : no-build-bot-config + : + $* 'foo-build-bot: key1' <<EOI 2>>EOE != 0 + : 1 + name: libfoo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + EOI + cannot override 'foo-build-bot' value: no build package configuration 'foo' + 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/repository-location/driver.cxx b/tests/repository-location/driver.cxx index c93b257..4a4bbe4 100644 --- a/tests/repository-location/driver.cxx +++ b/tests/repository-location/driver.cxx @@ -902,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 ()}); |