From 8e1998d8ebdb9ead5e432201998cb4db70918f95 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 23 Dec 2015 17:48:21 +0200 Subject: Support version release --- bpkg/manifest | 32 +++-- bpkg/manifest.cxx | 266 +++++++++++++++++++++++++++++---------- tests/manifest/packages | 20 +-- tests/package-version/driver.cxx | 165 ++++++++++++++++++------ 4 files changed, 358 insertions(+), 125 deletions(-) diff --git a/bpkg/manifest b/bpkg/manifest index 1d554d4..e53bc69 100644 --- a/bpkg/manifest +++ b/bpkg/manifest @@ -7,6 +7,7 @@ #include #include +#include #include // uint16_t #include #include // move() @@ -31,12 +32,17 @@ namespace bpkg // const std::uint16_t epoch; const std::string upstream; + const std::string release; const std::uint16_t revision; // Upstream part canonical representation. // const std::string canonical_upstream; + // Release part canonical representation. + // + const std::string canonical_release; + // Create a special empty version. // version (): epoch (0), revision (0) {} @@ -48,13 +54,14 @@ namespace bpkg version (const std::string& v): version (v.c_str ()) {} explicit - version (const char* v): version (data_type (v, false)) {} + version (const char* v): version (data_type (v, data_type::parse::full)) {} - // Create the version object from separate epoch, upstream, and + // Create the version object from separate epoch, upstream, release, and // revision parts. // version (std::uint16_t epoch, std::string upstream, + std::string release, std::uint16_t revision); version (version&&) = default; @@ -92,6 +99,9 @@ namespace bpkg 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 && revision != v.revision) return revision < v.revision ? -1 : 1; @@ -101,30 +111,34 @@ namespace bpkg bool empty () const noexcept { - // No sense to test epoch and revision for 0 as properly constructed - // version object can not have them different from 0 if upstream is - // empty. - // - return upstream.empty (); + bool e (upstream.empty ()); + assert (!e || (epoch == 0 && release.empty () && revision == 0)); + return e; } private: struct data_type { - data_type (const char*, bool upstream_only); + enum class parse {full, upstream, release}; + + data_type (const char*, parse); std::uint16_t epoch; std::string upstream; + std::string release; std::uint16_t revision; std::string canonical_upstream; + std::string canonical_release; }; explicit version (data_type&& d) : epoch (d.epoch), upstream (std::move (d.upstream)), + release (std::move (d.release)), revision (d.revision), - canonical_upstream (std::move (d.canonical_upstream)) {} + canonical_upstream (std::move (d.canonical_upstream)), + canonical_release (std::move (d.canonical_release)) {} }; inline std::ostream& diff --git a/bpkg/manifest.cxx b/bpkg/manifest.cxx index 20ded6d..cca4e9d 100644 --- a/bpkg/manifest.cxx +++ b/bpkg/manifest.cxx @@ -10,7 +10,7 @@ #include #include #include -#include // strncmp() +#include // strncmp(), strcmp() #include // move() #include // uint64_t, uint16_t, UINT16_MAX #include // back_insert_iterator @@ -178,17 +178,68 @@ namespace bpkg // version // version:: - version (uint16_t e, std::string u, uint16_t r) + version (uint16_t e, std::string u, std::string l, uint16_t r) : epoch (e), upstream (move (u)), + release (move (l)), revision (r), canonical_upstream ( - data_type (upstream.c_str (), true).canonical_upstream) + data_type (upstream.c_str (), data_type::parse::upstream). + canonical_upstream), + canonical_release ( + data_type (release.c_str (), data_type::parse::release). + canonical_release) { + if (release.empty () && r != 0) + // Empty release signifies the earliest possible release. Revision is + // meaningless in such a context. + // + throw invalid_argument ("revision for earliest possible release"); } + // Builder of the upstream or release version part canonical representation. + // + struct canonical_part: string + { + string + final () const {return substr (0, len_);} + + void + add (const char* begin, const char* end, bool numeric) + { + if (!empty ()) + append (1, '.'); + + bool zo (false); // Digit-only zero component. + if (numeric) + { + if (end - begin > 8) + throw invalid_argument ("8 digits maximum allowed in a component"); + + append (8 - (end - begin), '0'); // Add padding spaces. + + string c (begin, end); + append (c); + zo = stoul (c) == 0; + } + else + { + for (const char* i (begin); i != end; ++i) + append (1, lowercase (*i)); + } + + if (!zo) + len_ = size (); + } + + private: + // Length without trailing digit-only zero components. + // + size_t len_{0}; + }; + version::data_type:: - data_type (const char* v, bool upstream_only): epoch (0), revision (0) + data_type (const char* v, parse pr): epoch (0), revision (0) { // Otherwise compiler gets confused with string() member. // @@ -196,6 +247,14 @@ namespace bpkg assert (v != nullptr); + if (pr == parse::release && strcmp (v, "~") == 0) + { + // Special case: composing final version release part. + // + canonical_release = v; + return; + } + auto bad_arg ([](const string& d) {throw invalid_argument (d);}); auto uint16 ( @@ -209,58 +268,40 @@ namespace bpkg return static_cast (v); }); - auto add_canonical_component ( - [this, &bad_arg](const char* b, const char* e, bool numeric) -> bool - { - auto& cu (canonical_upstream); - - if (!cu.empty ()) - cu.append (1, '.'); - - if (numeric) - { - if (e - b > 8) - bad_arg ("8 digits maximum allowed in a component"); + enum class mode {epoch, upstream, release, revision}; + mode m (pr == parse::full + ? mode::epoch + : pr == parse::upstream + ? mode::upstream + : mode::release); - cu.append (8 - (e - b), '0'); // Add padding spaces. + canonical_part canon_upstream; + canonical_part canon_release; - string c (b, e); - cu.append (c); - return stoul (c) != 0; - } - else - { - for (const char* i (b); i != e; ++i) - cu.append (1, lowercase (*i)); - - return true; - } - }); - - enum class mode {epoch, upstream, revision} m (mode::epoch); + canonical_part* canon_part ( + pr == parse::release ? &canon_release : &canon_upstream); const char* cb (v); // Begin of a component. - const char* ub (v); // Begin of upstream component. - const char* ue (v); // End of upstream component. + const char* ub (v); // Begin of upstream part. + const char* ue (v); // End of upstream part. + const char* rb (v); // Begin of release part. + const char* re (v); // End of release part. const char* lnn (v - 1); // Last non numeric char. - // Length of upstream version canonical representation without trailing - // digit-only zero components. - // - size_t cl (0); - const char* p (v); for (char c; (c = *p) != '\0'; ++p) { switch (c) { - case '+': + case '~': { - if (upstream_only) - bad_arg ("unexpected '+' character"); + if (pr != parse::full) + bad_arg ("unexpected '~' character"); + // Process the epoch part. + // if (m != mode::epoch || p == v) - bad_arg ("unexpected '+' character position"); + bad_arg ("unexpected '~' character position"); if (lnn >= cb) // Contains non-digits. bad_arg ("epoch should be 2-byte unsigned integer"); @@ -272,25 +313,52 @@ namespace bpkg break; } + case '+': case '-': + case '.': { - if (upstream_only) - bad_arg ("unexpected '-' character"); + // Process the upsteam or release part component. + // - // No break, go to the next case. - } + // Characters '+', '-' are only valid for the full version parsing. + // + if (c != '.' && pr != parse::full) + bad_arg (string ("unexpected '") + c + "' character"); - case '.': - { - if ((m != mode::epoch && m != mode::upstream) || p == cb) + // Check if the component ending is valid for the current parsing + // state. + // + if (m == mode::revision || (c == '-' && m == mode::release) || + p == cb) bad_arg (string ("unexpected '") + c + "' character position"); - if (add_canonical_component (cb, p, lnn < cb)) - cl = canonical_upstream.size (); + // Append the component to the current canonical part. + // + canon_part->add (cb, p, lnn < cb); - ue = p; - m = c == '-' ? mode::revision : mode::upstream; + // Update the parsing state. + // cb = p + 1; + + if (m == mode::upstream || m == mode::epoch) + ue = p; + else if (m == mode::release) + re = p; + else + assert (false); + + if (c == '+') + m = mode::revision; + else if (c == '-') + { + m = mode::release; + canon_part = &canon_release; + rb = cb; + re = cb; + } + else if (m == mode::epoch) + m = mode::upstream; + break; } default: @@ -304,9 +372,15 @@ namespace bpkg lnn = p; } - if (p == cb) + assert (p >= cb); // 'p' denotes the end of the last component. + + // An empty component is valid for the release part only. + // + if (p == cb && m != mode::release) bad_arg ("unexpected end"); + // Parse the last component. + // if (m == mode::revision) { if (lnn >= cb) // Contains non-digits. @@ -314,20 +388,64 @@ namespace bpkg revision = uint16 (cb, "revision"); } - else + else if (cb != p) { - if (add_canonical_component (cb, p, lnn < cb)) - cl = canonical_upstream.size (); + canon_part->add (cb, p, lnn < cb); - ue = p; + if (m == mode::epoch || m == mode::upstream) + ue = p; + else if (m == mode::release) + re = p; } - assert (ub != ue); // Can't happen if through all previous checks. + // Upstream and release pointer ranges are valid at the and of the day. + // + assert (ub <= ue && rb <= re); - if (!upstream_only) - upstream.assign (ub, ue); + if (pr != parse::release) + { + // Fill upstream original and canonical parts. + // + assert (ub != ue); // Can't happen if through all previous checks. + canonical_upstream = canon_upstream.final (); - canonical_upstream.resize (cl); + if (pr == parse::full) + upstream.assign (ub, ue); + } + + if (pr != parse::upstream) + { + // Fill release original and canonical parts. + // + if (!canon_release.empty ()) + { + assert (rb != re); // Can't happen if through all previous checks. + canonical_release = canon_release.final (); + + if (pr == parse::full) + release.assign (rb, re); + } + else + { + if (m == mode::release) + { + // Empty release part signifies the earliest possible version + // release. Do nothing, keep original and canonical representations + // empty. + // + } + else + { + // Absent release part signifies the final (max) version release. + // Assign the special value to canonical and original representations. + // + canonical_release = "~"; + + if (pr == parse::full) + release = "~"; + } + } + } } version& version:: @@ -352,11 +470,19 @@ namespace bpkg string version:: string (bool ignore_revision) const { - std::string v (epoch != 0 ? to_string (epoch) + "+" + upstream : upstream); + std::string v (epoch != 0 ? to_string (epoch) + "~" + upstream : upstream); - if (!ignore_revision && revision != 0) + // The empty version represented as an empty string. + // + if (!empty () && release != "~") { v += '-'; + v += release; + } + + if (!ignore_revision && revision != 0) + { + v += '+'; v += to_string (revision); } @@ -476,6 +602,12 @@ namespace bpkg { bad_value (string ("invalid package version: ") + e.what ()); } + + // Versions like 1.2.3- are forbidden in manifest as intended to be + // used for version constrains rather than actual releases. + // + if (version.release.empty ()) + bad_name ("invalid package version release"); } else if (n == "summary") { @@ -806,6 +938,7 @@ namespace bpkg serialize (serializer& s) const { // @@ Should we check that all non-optional values are specified ? + // @@ Should we check the version release is not empty ? // s.next ("", "1"); // Start of manifest. @@ -1382,8 +1515,9 @@ namespace bpkg return *role; } else - return location.empty () ? - repository_role::base : repository_role::prerequisite; + return location.empty () + ? repository_role::base + : repository_role::prerequisite; } // repository_manifests diff --git a/tests/manifest/packages b/tests/manifest/packages index 9ae65bc..d2058e3 100644 --- a/tests/manifest/packages +++ b/tests/manifest/packages @@ -1,20 +1,20 @@ : 1 name: libfoo -version: 1.2.3-2 +version: 1.2.3+2 priority: high; Due to critical bug fix. summary: Modern XML parser license: LGPLv2, MIT; Both required. license: BSD tags: c++, xml, parser, serializer, pull, streaming, modern description: libfoo is a very modern C++ XML parser. -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: 1.2.3+2: applied upstream patch for critical bug bar +changes: 1.2.3+1: applied upstream patch for critical bug foo changes-file: NEWS url: http://www.example.org/projects/libfoo/; libfoo project page url -package-url: http://www.example.org/projects/libbar/1.2.3-2; package url +package-url: http://www.example.org/projects/libbar/1.2.3+2; package url email: libfoo-users@example.org; Public mailing list, posts by non-members\ are allowed but moderated. -package-email: libfoo-1.2.3-2@example.org; Bug reports are welcome. +package-email: libfoo-1.2.3+2@example.org; Bug reports are welcome. depends: libz depends: libgnutls <= 1.2.3 | libopenssl >= 2.3.4 depends: ? libboost-regex >= 1.52.0; Only if C++ compiler doesn't support\ @@ -26,22 +26,22 @@ requires: ? ; VC++ 12.0 or later if targeting Windows. 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. -location: libfoo-1.2.3-2.tar.bz2 +location: libfoo-1.2.3+2.tar.bz2 : name: libbar -version: 3.4A.5-6 +version: 3.4A.5+6 summary: Modern bar management framework license: LGPLv2 tags: c++, xml, modern description-file: README; Comprehensive description url: http://www.example.org/projects/libbar/ email: libbar-users@example.org -location: bar/libbar-3.4A.5-6.tbz +location: bar/libbar-3.4A.5+6.tbz : name: libbaz -version: 2+3.4A.5-3 +version: 2~3.4A.5+3 summary: Modern baz system license: LGPLv2 url: http://www.example.org/projects/libbar/ email: libbaz-users@example.org -location: libbaz/libbaz-2+3.4A.5-3.tar.gz +location: libbaz/libbaz-2~3.4A.5+3.tar.gz diff --git a/tests/package-version/driver.cxx b/tests/package-version/driver.cxx index df4d73b..d28fabf 100644 --- a/tests/package-version/driver.cxx +++ b/tests/package-version/driver.cxx @@ -29,11 +29,11 @@ bad_version (const string& v) } static bool -bad_version (uint16_t e, const string& u, uint16_t r) +bad_version (uint16_t e, const string& u, const string& l, uint16_t r) { try { - version bv (e, u, r); + version bv (e, u, l, r); return false; } catch (const invalid_argument&) @@ -45,7 +45,7 @@ bad_version (uint16_t e, const string& u, uint16_t r) static bool test_constructor (const version& v) { - return v == version (v.epoch, v.upstream, v.revision); + return v == version (v.epoch, v.upstream, v.release, v.revision); } int @@ -60,31 +60,58 @@ main (int argc, char* argv[]) try { assert (bad_version ("")); // Empty upstream. - assert (bad_version ("1+")); // Same. - assert (bad_version ("1+-3")); // Same. - assert (bad_version ("-3")); // Same. - assert (bad_version ("+3.5")); // Empty epoch. - assert (bad_version ("a-")); // Empty revision. - assert (bad_version ("1+2+4.1-3")); // Extra epoch. - assert (bad_version ("3.5-1-4")); // Extra revision. - assert (bad_version ("1++2-3")); // Duplicated epoch separator. - assert (bad_version ("1+2--3")); // Duplicated revision separator. + assert (bad_version ("1~")); // Same. + assert (bad_version ("1~+3")); // Same. + assert (bad_version ("+3")); // Same. + assert (bad_version ("1~-a")); // Same. + assert (bad_version ("1~-a+3")); // Same. + assert (bad_version ("-a+3")); // Same. + assert (bad_version ("~3.5")); // Empty epoch. + assert (bad_version ("a+")); // Empty revision. + assert (bad_version ("1~2~4.1+3")); // Extra epoch. + assert (bad_version ("3.5+1+4")); // Extra revision. + assert (bad_version ("1~~2+3")); // Duplicated epoch separator. + assert (bad_version ("1~2++3")); // Duplicated revision separator. assert (bad_version ("a.394857391.3")); // Too long numeric component. assert (bad_version ("a.000000000.3")); // Too long numeric zero component. - assert (bad_version ("65536+q.3")); // Too big epoch. - assert (bad_version ("1+q-65536")); // Too big revision. - assert (bad_version ("3.5+1.4")); // Components in epoch. - assert (bad_version ("3.5-1.4")); // Components in revision. - assert (bad_version ("3 5-1")); // Non alpha-numeric in upstream. - assert (bad_version ("1+ -3")); // Same. - assert (bad_version ("3 5+4-1")); // Non alpha-numeric in epoch. - assert (bad_version ("2b+a")); // Same. - assert (bad_version ("1+34.1-3 5")); // Non alpha-numeric in revision. - assert (bad_version ("a-3s")); // Same. + assert (bad_version ("1-a.000000000")); // Same. + assert (bad_version ("65536~q.3")); // Too big epoch. + assert (bad_version ("1+q+65536")); // Too big revision. + assert (bad_version ("3.5~1.4")); // Components in epoch. + assert (bad_version ("3.5+1.4")); // Components in revision. + assert (bad_version ("3 5+1")); // Non alpha-numeric in upstream. + assert (bad_version ("1~ +3")); // Same. + assert (bad_version ("1-3 5+1")); // Non alpha-numeric in release. + assert (bad_version ("1~1- +3")); // Same. + assert (bad_version ("3 5~4+1")); // Non alpha-numeric in epoch. + assert (bad_version ("2b~a")); // Same. + assert (bad_version ("1~34.1+3 5")); // Non numeric in revision. + assert (bad_version ("a+3s")); // Same. assert (bad_version ("a.")); // Not completed upstream. assert (bad_version ("a..b")); // Empty upstream component. - assert (bad_version (1, "1+1.1", 2)); // Epoch in upstream. - assert (bad_version (1, "1.1-1", 2)); // Revision in upstream. + assert (bad_version ("a.b-+1")); // Revision for empty release. + + assert (bad_version (0, "", "", 0)); // Empty upstream. + assert (bad_version (0, "1", "", 1)); // Revision for empty release. + assert (bad_version (1, "1~1.1", "~", 2)); // Epoch in upstream. + assert (bad_version (1, "1.1-1", "~", 2)); // Release in upstream. + assert (bad_version (1, "1.1+1", "~", 2)); // Revision in upstream. + assert (bad_version (1, "1", "1~1.1", 2)); // Epoch in release. + assert (bad_version (1, "1", "1.1-1", 2)); // Release in release. + assert (bad_version (1, "1", "1.1+1", 2)); // Revision in release. + + { + version v1; + assert (v1.empty ()); + assert (v1.string ().empty ()); + + version v2 ("0.0.0"); + assert (!v2.empty ()); + + // @@ It doesn't look nice. + // + assert (v1.canonical_upstream == v2.canonical_upstream); + } { version v ("a"); @@ -94,8 +121,8 @@ main (int argc, char* argv[]) } { - version v ("65535+ab-65535"); - assert (v.string () == "65535+ab-65535"); + version v ("65535~ab+65535"); + assert (v.string () == "65535~ab+65535"); assert (v.canonical_upstream == "ab"); assert (test_constructor (v)); } @@ -150,33 +177,81 @@ main (int argc, char* argv[]) } { - version v ("1+0"); - assert (v.string () == "1+0"); + version v ("1~0"); + assert (v.string () == "1~0"); assert (v.canonical_upstream.empty ()); assert (test_constructor (v)); } { - version v ("0+A-1"); - assert (v.string () == "A-1"); + version v ("0~A+1"); + assert (v.string () == "A+1"); assert (v.canonical_upstream == "a"); assert (test_constructor (v)); } { - version v ("10+B-0"); - assert (v.string () == "10+B"); + version v ("10~B+0"); + assert (v.string () == "10~B"); assert (v.canonical_upstream == "b"); assert (test_constructor (v)); } { - version v ("3+1A.31.0.4.0-7"); - assert (v.string () == "3+1A.31.0.4.0-7"); + version v ("3~1A.31.0.4.0+7"); + assert (v.string () == "3~1A.31.0.4.0+7"); assert (v.canonical_upstream == "1a.00000031.00000000.00000004"); assert (test_constructor (v)); } + { + version v ("1.2.3"); + assert (v.string () == "1.2.3"); + assert (v.release == "~"); + assert (v.canonical_release == "~"); + assert (test_constructor (v)); + } + + { + version v ("1.2.3+1"); + assert (v.string () == "1.2.3+1"); + assert (v.release == "~"); + assert (v.canonical_release == "~"); + assert (test_constructor (v)); + } + + { + version v ("1.2.3-"); + assert (v.string () == "1.2.3-"); + assert (v.release.empty ()); + assert (v.canonical_release.empty ()); + assert (test_constructor (v)); + } + + { + version v ("1~A-1.2.3B.00+0"); + assert (v.string () == "1~A-1.2.3B.00"); + assert (v.release == "1.2.3B.00"); + assert (v.canonical_release == "00000001.00000002.3b"); + assert (test_constructor (v)); + } + + { + version v (1, "1", "~", 2); + assert (v.string () == "1~1+2"); + assert (v.release == "~"); + assert (v.canonical_release == "~"); + assert (test_constructor (v)); + } + + { + version v (1, "1", "", 0); + assert (v.string () == "1~1-"); + assert (v.release.empty ()); + assert (v.canonical_release.empty ()); + assert (test_constructor (v)); + } + assert (version ("a") == version ("a")); assert (version ("a") < version ("b")); assert (version ("a") < version ("aa")); @@ -184,13 +259,13 @@ main (int argc, char* argv[]) assert (version ("a") < version ("a.a")); assert (version ("ab") == version ("ab")); assert (version ("ac") < version ("bc")); - assert (version ("ab-0") == version ("ab")); - assert (version ("a.1-1") > version ("a.1")); - assert (version ("0+ab") == version ("ab")); + assert (version ("ab+0") == version ("ab")); + assert (version ("a.1+1") > version ("a.1")); + assert (version ("0~ab") == version ("ab")); assert (version ("1.2") > version ("1.1")); - assert (version ("1+1.0") > version ("2.0")); - assert (version ("0+ab-1") == version ("ab-1")); - assert (version ("0+ab-1").compare (version ("0+ab-2"), true) == 0); + assert (version ("1~1.0") > version ("2.0")); + assert (version ("0~ab+1") == version ("ab+1")); + assert (version ("0~ab+1").compare (version ("0~ab+2"), true) == 0); assert (version ("12") > version ("2")); assert (version ("2") < version ("12")); assert (version ("1") == version ("01")); @@ -208,6 +283,16 @@ main (int argc, char* argv[]) assert (version ("1.0.0") == version ("01")); assert (version ("0.1.00") == version ("00.1")); assert (version ("0.0a.00") == version ("00.0a")); + assert (version ("1.0-alpha") < version ("1.0")); + assert (version ("1.0-") < version ("1.0")); + assert (version ("1.0-") < version ("1.0-alpha")); + assert (version ("1.0-alpha") < version ("1.1")); + assert (version ("1.0-alpha+1") < version ("1.0")); + assert (version ("1.0-alpha+1") < version ("1.1")); + assert (version ("1.0-alpha") > version ("1.0-1")); + assert (version ("1.0-alpha") == version ("1.0-alpha.0")); + assert (version (1, "2.0", "~", 3) == version ("1~2+3")); + assert (version (1, "2.0", "", 0) == version ("1~2-")); } catch (const exception& e) { -- cgit v1.1