From 857d9566ffd4dfc572831fa3cc04e0394c0d7e92 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 22 Oct 2019 22:51:14 +0300 Subject: Add support for tests, examples, and benchmarks package manifest values Also rename dependency_constraint class to version_constraint. --- libbpkg/manifest.cxx | 370 +++++++++++++++++++++++++--------------------- libbpkg/manifest.hxx | 74 ++++++---- tests/manifest/driver.cxx | 10 +- tests/manifest/testscript | 106 ++++++++++++- 4 files changed, 352 insertions(+), 208 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index 06e0b40..ae4a70b 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -622,11 +622,13 @@ namespace bpkg throw invalid_argument ("no authority"); } - // depends + // version_constraint // - dependency_constraint:: - dependency_constraint (const string& s) + version_constraint:: + version_constraint (const std::string& s) { + using std::string; + auto bail = [] (const string& d) {throw invalid_argument (d);}; char c (s[0]); @@ -636,12 +638,11 @@ namespace bpkg size_t p (s.find_first_not_of (spaces, 1)); if (p == string::npos) - bail ("no prerequisite package min version specified"); + bail ("no min version specified"); size_t e (s.find_first_of (spaces, p)); - const char* no_max_version ( - "no prerequisite package max version specified"); + const char* no_max_version ("no max version specified"); if (e == string::npos) bail (no_max_version); @@ -659,8 +660,7 @@ namespace bpkg } catch (const invalid_argument& e) { - bail (string ("invalid prerequisite package min version: ") + - e.what ()); + bail (string ("invalid min version: ") + e.what ()); } p = s.find_first_not_of (spaces, e); @@ -669,7 +669,7 @@ namespace bpkg e = s.find_first_of (" \t])", p); - const char* invalid_range ("invalid prerequisite package version range"); + const char* invalid_range ("invalid version range"); if (e == string::npos) bail (invalid_range); @@ -687,8 +687,7 @@ namespace bpkg } catch (const invalid_argument& e) { - bail (string ("invalid prerequisite package max version: ") + - e.what ()); + bail (string ("invalid max version: ") + e.what ()); } e = s.find_first_of ("])", e); // Might be a space. @@ -696,19 +695,12 @@ namespace bpkg bail (invalid_range); if (e + 1 != s.size ()) - bail ("unexpected text after prerequisite package version range"); + bail ("unexpected text after version range"); - try - { - *this = dependency_constraint (move (min_version), - min_open, - move (max_version), - s[e] == ')'); - } - catch (const invalid_argument& e) - { - bail (string ("invalid dependency constraint: ") + e.what ()); - } + // Can throw invalid_argument that we don't need to intercept. + // + *this = version_constraint (move (min_version), min_open, + move (max_version), s[e] == ')'); } else if (c == '~' || c == '^') // The shortcut operator. { @@ -721,24 +713,17 @@ namespace bpkg if (p != string::npos && s[p] == '$' && p + 1 == s.size ()) { - *this = dependency_constraint (version (), c == '~', - version (), c == '^'); + *this = version_constraint (version (), c == '~', + version (), c == '^'); } else { // To be used in the shortcut operator the package version must be // standard. // - standard_version_constraint vc; - - try - { - vc = standard_version_constraint (s); - } - catch (const invalid_argument& e) - { - bail (string ("invalid dependency constraint: ") + e.what ()); - } + // Can throw invalid_argument that we don't need to intercept. + // + standard_version_constraint vc (s); try { @@ -749,11 +734,10 @@ namespace bpkg // translates into [X.Y.Z+0 X.Y+1.0-) which covers the same versions // set as [X.Y.Z X.Y+1.0-). // - *this = dependency_constraint ( - version (vc.min_version->string ()), - vc.min_open, - version (vc.max_version->string ()), - vc.max_open); + *this = version_constraint (version (vc.min_version->string ()), + vc.min_open, + version (vc.max_version->string ()), + vc.max_open); } catch (const invalid_argument&) { @@ -787,12 +771,12 @@ namespace bpkg p = 1; } else - bail ("invalid prerequisite package version comparison"); + bail ("invalid version comparison"); p = s.find_first_not_of (spaces, p); if (p == string::npos) - bail ("no prerequisite package version specified"); + bail ("no version specified"); try { @@ -808,33 +792,32 @@ namespace bpkg switch (operation) { case comparison::eq: - *this = dependency_constraint (v); + *this = version_constraint (v); break; case comparison::lt: - *this = dependency_constraint (nullopt, true, move (v), true); + *this = version_constraint (nullopt, true, move (v), true); break; case comparison::le: - *this = dependency_constraint (nullopt, true, move (v), false); + *this = version_constraint (nullopt, true, move (v), false); break; case comparison::gt: - *this = dependency_constraint (move (v), true, nullopt, true); + *this = version_constraint (move (v), true, nullopt, true); break; case comparison::ge: - *this = dependency_constraint (move (v), false, nullopt, true); + *this = version_constraint (move (v), false, nullopt, true); break; } } catch (const invalid_argument& e) { - bail (string ("invalid prerequisite package version: ") + e.what ()); + bail (string ("invalid version: ") + e.what ()); } - } } - dependency_constraint:: - dependency_constraint (optional mnv, bool mno, - optional mxv, bool mxo) + version_constraint:: + version_constraint (optional mnv, bool mno, + optional mxv, bool mxo) : min_version (move (mnv)), max_version (move (mxv)), min_open (mno), @@ -891,9 +874,11 @@ namespace bpkg } } - dependency_constraint dependency_constraint:: + version_constraint version_constraint:: effective (version v) const { + using std::string; + // The dependent package version can't be empty or earliest. // if (v.empty ()) @@ -940,15 +925,14 @@ namespace bpkg { assert (vc.min_version && vc.max_version); - return dependency_constraint ( - version (vc.min_version->string ()), - vc.min_open, - version (vc.max_version->string ()), - vc.max_open); + return version_constraint (version (vc.min_version->string ()), + vc.min_open, + version (vc.max_version->string ()), + vc.max_open); } catch (const invalid_argument&) { - // There shouldn't be a reason for dependency_constraint() to throw. + // There shouldn't be a reason for version_constraint() to throw. // assert (false); } @@ -956,67 +940,56 @@ namespace bpkg // Calculate effective constraint for a range. // - return dependency_constraint ( + return version_constraint ( min_version && min_version->empty () ? v : min_version, min_open, max_version && max_version->empty () ? v : max_version, max_open); } - ostream& - operator<< (ostream& o, const dependency_constraint& c) + std::string version_constraint:: + string () const { - assert (!c.empty ()); + assert (!empty ()); - auto print = [&o] (const version& v) -> ostream& - { - return v.empty () ? (o << '$') : (o << v); - }; + auto ver = [] (const version& v) {return v.empty () ? "$" : v.string ();}; - if (!c.min_version) - { - o << (c.max_open ? "< " : "<= "); - return print (*c.max_version); - } + if (!min_version) + return (max_open ? "< " : "<= ") + ver (*max_version); - if (!c.max_version) - { - o << (c.min_open ? "> " : ">= "); - return print (*c.min_version); - } + if (!max_version) + return (min_open ? "> " : ">= ") + ver (*min_version); - if (*c.min_version == *c.max_version) + if (*min_version == *max_version) { - const version& v (*c.min_version); + const version& v (*min_version); - if (!c.min_open && !c.max_open) - { - o << "== "; - return print (v); - } + if (!min_open && !max_open) + return "== " + ver (v); - assert (v.empty () && (!c.min_open || !c.max_open)); - return o << (c.min_open ? "~$" : "^$"); + assert (v.empty () && (!min_open || !max_open)); + return min_open ? "~$" : "^$"; } // If the range can potentially be represented as a range shortcut // operator (^ or ~), having the [ ) - // form, then print it using the standard version constraint code. + // form, then produce the resulting string using the standard version + // constraint code. // - if (!c.min_open && - c.max_open && - !c.min_version->empty () && - !c.max_version->empty ()) + if (!min_open && + max_open && + !min_version->empty () && + !max_version->empty ()) { if (optional mnv = - parse_standard_version (c.min_version->string (), + parse_standard_version (min_version->string (), standard_version::allow_earliest)) { if (optional mxv = - parse_standard_version (c.max_version->string (), + parse_standard_version (max_version->string (), standard_version::allow_earliest)) try { - return o << standard_version_constraint ( - move (*mnv), c.min_open, move (*mxv), c.max_open); + return standard_version_constraint ( + move (*mnv), min_open, move (*mxv), max_open).string (); } catch (const invalid_argument&) { @@ -1028,26 +1001,32 @@ namespace bpkg } } - // Print as a range. + // Represent as a range. // - o << (c.min_open ? '(' : '['); - print (*c.min_version); - o << ' '; - print (*c.max_version); - return o << (c.max_open ? ')' : ']'); + std::string r (min_open ? "(" : "["); + r += ver (*min_version); + r += ' '; + r += ver (*max_version); + r += max_open ? ')' : ']'; + return r; } - ostream& - operator<< (ostream& o, const dependency& d) + std::string dependency:: + string () const { - o << d.name; + std::string r (name.string ()); - if (d.constraint) - o << ' ' << *d.constraint; + if (constraint) + { + r += ' '; + r += constraint->string (); + } - return o; + return r; } + // dependency_alternatives + // ostream& operator<< (ostream& o, const dependency_alternatives& as) { @@ -1735,11 +1714,14 @@ namespace bpkg // optional upstream_version; - // We will cache the depends manifest values to parse and, if requested, - // complete the dependency constraints later, after the version value is - // parsed. + // We will cache the depends, tests, examples, and benchmarks manifest + // values to parse and, if requested, complete the version constraints + // later, after the version value is parsed. // vector dependencies; + small_vector tests; + small_vector examples; + small_vector benchmarks; // We will cache the description and its type values to validate them // later, after both are parsed. @@ -2056,6 +2038,18 @@ namespace bpkg { dependencies.push_back (move (nv)); } + else if (n == "tests") + { + tests.push_back (move (nv)); + } + else if (n == "examples") + { + examples.push_back (move (nv)); + } + else if (n == "benchmarks") + { + benchmarks.push_back (move (nv)); + } else if (n == "location") { if (flag (package_manifest_flags::forbid_location)) @@ -2205,8 +2199,73 @@ namespace bpkg } // Now, when the version manifest value is parsed, we can parse the - // dependencies and complete their constrains, if requested. + // dependencies, tests, examples, and benchmarks and complete their + // constraints, if requested. // + auto parse_dependency = [&m, cd, &flag, &bad_value] (string&& d, + const char* what) + { + using iterator = string::const_iterator; + + iterator b (d.begin ()); + iterator i (b); + iterator ne (b); // End of name. + iterator e (d.end ()); + + // Find end of name (ne). + // + // Grep for '=<>([~^' in the bpkg source code and update, if changed. + // + const string cb ("=<>([~^"); + for (char c; i != e && cb.find (c = *i) == string::npos; ++i) + { + if (!space (c)) + ne = i + 1; + } + + package_name nm; + + try + { + nm = package_name (i == e ? move (d) : string (b, ne)); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid ") + what + " package name: " + + e.what ()); + } + + dependency r; + + if (i == e) + r = dependency {move (nm), nullopt}; + else + { + try + { + version_constraint vc (string (i, e)); + + if (!vc.complete () && + flag (package_manifest_flags::forbid_incomplete_dependencies)) + bad_value ("$ not allowed"); + + // Complete the constraint. + // + if (cd) + vc = vc.effective (m.version); + + r = dependency {move (nm), move (vc)}; + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid ") + what + " package constraint: " + + e.what ()); + } + } + + return r; + }; + for (name_value& d: dependencies) { nv = move (d); // Restore as bad_value() uses its line/column. @@ -2235,61 +2294,7 @@ namespace bpkg list_parser lp (b, e, '|'); for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - { - using iterator = string::const_iterator; - - iterator b (lv.begin ()); - iterator i (b); - iterator ne (b); // End of name. - iterator e (lv.end ()); - - // Find end of name (ne). - // - const string cb ("=<>([~^"); - for (char c; i != e && cb.find (c = *i) == string::npos; ++i) - { - if (!space (c)) - ne = i + 1; - } - - package_name nm; - - try - { - nm = package_name (i == e ? move (lv) : string (b, ne)); - } - catch (const invalid_argument& e) - { - bad_value ( - string ("invalid prerequisite package name: ") + e.what ()); - } - - if (i == e) - da.push_back (dependency {move (nm), nullopt}); - else - { - try - { - dependency_constraint dc (string (i, e)); - - if (!dc.complete () && - flag (package_manifest_flags::forbid_incomplete_depends)) - bad_value ("$ not allowed"); - - // Complete the constraint. - // - if (cd) - dc = dc.effective (m.version); - - da.push_back (dependency {move (nm), move (dc)}); - } - catch (const invalid_argument& e) - { - bad_value ( - string ("invalid dependency constraint: ") + e.what ()); - } - } - } + da.push_back (parse_dependency (move (lv), "prerequisite")); if (da.empty ()) bad_value ("empty package dependency specification"); @@ -2297,6 +2302,22 @@ namespace bpkg m.dependencies.push_back (da); } + auto parse_deps = [&nv, &parse_dependency] + (small_vector&& nvs, const char* what) + { + small_vector r; + for (name_value& v: nvs) + { + nv = move (v); // Restore as bad_value() uses its line/column. + r.push_back (parse_dependency (move (nv.value), what)); + } + return r; + }; + + m.tests = parse_deps (move (tests), "tests"); + m.examples = parse_deps (move (examples), "examples"); + m.benchmarks = parse_deps (move (benchmarks), "benchmarks"); + if (m.description && !m.description_type && flag (package_manifest_flags::require_description_type)) @@ -2321,7 +2342,7 @@ namespace bpkg package_manifest_flags::require_description_type | package_manifest_flags::require_location | package_manifest_flags::forbid_fragment | - package_manifest_flags::forbid_incomplete_depends); + package_manifest_flags::forbid_incomplete_dependencies); } // package_manifest @@ -2650,24 +2671,33 @@ namespace bpkg serializer::merge_comment (*m.build_error_email, m.build_error_email->comment)); - for (const auto& d: m.dependencies) + for (const dependency_alternatives& d: m.dependencies) s.next ("depends", (d.conditional ? (d.buildtime ? "?* " : "? ") : (d.buildtime ? "* " : "")) + serializer::merge_comment (concatenate (d, " | "), d.comment)); - for (const auto& r: m.requirements) + for (const requirement_alternatives& r: m.requirements) s.next ("requires", (r.conditional ? (r.buildtime ? "?* " : "? ") : (r.buildtime ? "* " : "")) + serializer::merge_comment (concatenate (r, " | "), r.comment)); + for (const dependency& t: m.tests) + s.next ("tests", t.string ()); + + for (const dependency& t: m.examples) + s.next ("examples", t.string ()); + + for (const dependency& t: m.benchmarks) + s.next ("benchmarks", t.string ()); + for (const build_class_expr& e: m.builds) s.next ("builds", serializer::merge_comment (e.string (), e.comment)); - for (const auto& c: m.build_constraints) + for (const build_constraint& c: m.build_constraints) s.next (c.exclusion ? "build-exclude" : "build-include", serializer::merge_comment (!c.target ? c.config diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index 3d02e50..f65f4db 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -306,8 +306,6 @@ namespace bpkg : std::string (std::move (e)), comment (std::move (c)) {} }; - // depends - // // Represented as a version range. Note that the versions may refer to the // dependent package version and can be completed with the actual versions // using the effective() function. Such versions are represented as an empty @@ -326,7 +324,7 @@ namespace bpkg // [X Y) == [X+0 Y+0) // (X Y] == (X+max Y+max] // - class LIBBPKG_EXPORT dependency_constraint + class LIBBPKG_EXPORT version_constraint { public: butl::optional min_version; @@ -337,16 +335,16 @@ namespace bpkg // Preserve the zero endpoint version revisions (see above for details). // explicit - dependency_constraint (const std::string&); + version_constraint (const std::string&); - dependency_constraint (butl::optional min_version, bool min_open, - butl::optional max_version, bool max_open); + version_constraint (butl::optional min_version, bool min_open, + butl::optional max_version, bool max_open); explicit - dependency_constraint (const version& v) - : dependency_constraint (v, false, v, false) {} + version_constraint (const version& v) + : version_constraint (v, false, v, false) {} - dependency_constraint () = default; + version_constraint () = default; bool empty () const noexcept {return !min_version && !max_version;} @@ -364,35 +362,49 @@ namespace bpkg // version in range, non-standard or latest snapshot version for a // shortcut operator, etc.). // - dependency_constraint + version_constraint effective (version) const; + + std::string + string () const; }; - LIBBPKG_EXPORT std::ostream& - operator<< (std::ostream&, const dependency_constraint&); + inline std::ostream& + operator<< (std::ostream& os, const version_constraint& vc) + { + return os << vc.string (); + } inline bool - operator== (const dependency_constraint& x, const dependency_constraint& y) + 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; + x.min_open == y.min_open && x.max_open == y.max_open; } inline bool - operator!= (const dependency_constraint& x, const dependency_constraint& y) + operator!= (const version_constraint& x, const version_constraint& y) { return !(x == y); } - struct dependency + struct LIBBPKG_EXPORT dependency { package_name name; - butl::optional constraint; + butl::optional constraint; + + std::string + string () const; }; - LIBBPKG_EXPORT std::ostream& - operator<< (std::ostream&, const dependency&); + inline std::ostream& + operator<< (std::ostream& os, const dependency& d) + { + return os << d.string (); + } + // depends + // class dependency_alternatives: public butl::small_vector { public: @@ -460,17 +472,17 @@ namespace bpkg // enum class package_manifest_flags: std::uint16_t { - none = 0x00, + none = 0x00, - forbid_file = 0x01, // Forbid *-file manifest values. - forbid_location = 0x02, - forbid_sha256sum = 0x04, - forbid_fragment = 0x08, - forbid_incomplete_depends = 0x10, + forbid_file = 0x01, // Forbid *-file manifest values. + forbid_location = 0x02, + forbid_sha256sum = 0x04, + forbid_fragment = 0x08, + forbid_incomplete_dependencies = 0x10, - require_location = 0x20, - require_sha256sum = 0x40, - require_description_type = 0x80 + require_location = 0x20, + require_sha256sum = 0x40, + require_description_type = 0x80 }; inline package_manifest_flags @@ -693,6 +705,10 @@ namespace bpkg butl::optional build_error_email; std::vector dependencies; std::vector requirements; + butl::small_vector tests; + butl::small_vector examples; + butl::small_vector benchmarks; + butl::small_vector builds; std::vector build_constraints; @@ -727,7 +743,7 @@ namespace bpkg // package_manifest (butl::manifest_parser&, bool ignore_unknown = false, - bool complete_depends = true, + bool complete_dependencies = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index 6ef6193..85097be 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -61,7 +61,7 @@ main (int argc, char* argv[]) { if (mode == "-p") { - bool complete_depends (false); + bool complete_dependencies (false); bool ignore_unknown (false); for (int i (2); i != argc; ++i) @@ -69,7 +69,7 @@ main (int argc, char* argv[]) string o (argv[i]); if (o == "-c") - complete_depends = true; + complete_dependencies = true; else if (o == "-i") ignore_unknown = true; else @@ -96,7 +96,7 @@ main (int argc, char* argv[]) } }, ignore_unknown, - complete_depends).serialize (s); + complete_dependencies).serialize (s); } else if (mode == "-ec") { @@ -109,8 +109,8 @@ main (int argc, char* argv[]) string s; while (!eof (getline (cin, s))) { - dependency_constraint c (s); - dependency_constraint ec (c.effective (v)); + version_constraint c (s); + version_constraint ec (c.effective (v)); assert (c.complete () == (c == ec)); diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 2af195d..ee47889 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -499,7 +499,7 @@ EOI } - : dependency + : depends : { : short-name @@ -515,7 +515,7 @@ : invalid-version-range : - $* -c <'stdin:6:10: error: invalid dependency constraint: min version is greater than max version' != 0 + $* -c <'stdin:6:10: error: invalid prerequisite package constraint: min version is greater than max version' != 0 : 1 name: foo version: 2.0.0 @@ -586,7 +586,7 @@ license: LGPLv2 depends: bar ~$ EOI - stdin:6:10: error: invalid dependency constraint: dependent version is not standard + stdin:6:10: error: invalid prerequisite package constraint: dependent version is not standard EOE : latest-snapshot @@ -607,7 +607,6 @@ depends: bar == 2.0.0-a.0.123 | libbaz [2.0.0-a.0.1 2.0.0-a.1) | libbox\ [2.0.0-a.0.1 2.0.0-a.1) | libfox [1.0 2.0.0-a.0.123) EOO - } : incomplete @@ -621,6 +620,102 @@ depends: bar == $ | libbaz ~$ | libbox ^$ | libfox [1.0 $) EOF } + + : tests + : + { + : short-name + : + $* <'stdin:6:8: error: invalid tests package name: length is less than two characters' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: b + EOI + + : invalid-version-range-incomplete + : + $* -c <'stdin:6:8: error: invalid tests package constraint: min version is greater than max version' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar [$ 1.0.0] + EOI + + : invalid-version-range + : + $* -c <'stdin:6:8: error: invalid tests package constraint: min version is greater than max version' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar [2.0.0 1.0.0] + EOI + + : invalid-version + : + $* -c <'stdin:6:8: error: invalid tests package constraint: invalid version: equal version endpoints are earliest' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == 2.0.0- + EOI + + : complete + : + { + test.options += -c + + : final + : + $* <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == $ + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == 2.0.0 + EOO + + : non-standard-shortcut + : + $* <>EOE != 0 + : 1 + name: foo + version: 2.0.0-x + summary: Modern C++ parser + license: LGPLv2 + tests: bar ~$ + EOI + stdin:6:8: error: invalid tests package constraint: dependent version is not standard + EOE + } + + : incomplete + : + $* <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + tests: bar == $ + EOF + } } : package-list @@ -670,6 +765,9 @@ requires: ? ; libc++ standard library if using Clang on Mac OS X. requires: zlib; Most Linux/UNIX systems already have one; or get it at\ www.zlib.net. + tests: libfoo-tests + examples: libfoo-examples == 1.2.3 + benchmarks: libfoo-benchmarks ~1.2.0 builds: +!windows &( +gcc +clang ) +( +windows &msvc ) build-include: linux* build-include: freebsd* -- cgit v1.1