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 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 223 insertions(+), 75 deletions(-) (limited to 'bpkg') 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 -- cgit v1.1