From e47ed2a264364cace0519ee16f152fe882f2e6f8 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 29 Jan 2019 22:34:05 +0300 Subject: Add support for $ in standard version constraint --- libbutl/standard-version.cxx | 199 ++++++++++++++++++++++++++++++++------ libbutl/standard-version.mxx | 8 ++ tests/standard-version/driver.cxx | 23 ++++- tests/standard-version/testscript | 137 ++++++++++++++++++++++++-- 4 files changed, 327 insertions(+), 40 deletions(-) diff --git a/libbutl/standard-version.cxx b/libbutl/standard-version.cxx index 74991ad..41b3611 100644 --- a/libbutl/standard-version.cxx +++ b/libbutl/standard-version.cxx @@ -591,12 +591,52 @@ namespace butl standard_version::allow_earliest); } - standard_version_constraint:: - standard_version_constraint (const std::string& s) + // Parse the version constraint, optionally completing it using the + // specified dependent package version. + // + static standard_version_constraint + parse_constraint (const string& s, const standard_version* v) { using std::string; // Not to confuse with string(). auto bail = [] (const string& m) {throw invalid_argument (m);}; + + // The dependent package version can't be empty or earliest. It, however, + // can be a stub (think of build-time dependencies). + // + if (v != nullptr) + { + if (v->empty ()) + bail ("dependent version is empty"); + + if (v->earliest ()) + bail ("dependent version is earliest"); + } + + // Strip the dependent version revision. Fail for stubs and latest + // snapshots, which are meaningless to refer to from the constraint. + // Cache the result on the first call. + // + standard_version dv; + auto dependent_version = [v, &dv, &bail] () -> const standard_version& + { + if (dv.empty ()) + { + assert (v != nullptr); + + if (v->latest_snapshot ()) + bail ("dependent version is latest snapshot"); + + if (v->stub ()) + bail ("dependent version is stub"); + + dv = *v; + dv.revision = 0; + } + + return dv; + }; + const char* spaces (" \t"); size_t p (0); @@ -616,8 +656,11 @@ namespace butl try { - min_version = standard_version (s.substr (p, e - p), - standard_version::allow_earliest); + string mnv (s, p, e - p); + + min_version = v != nullptr && mnv == "$" + ? dependent_version () + : standard_version (mnv, standard_version::allow_earliest); } catch (const invalid_argument& e) { @@ -634,8 +677,11 @@ namespace butl try { - max_version = standard_version (s.substr (p, e - p), - standard_version::allow_earliest); + string mxv (s, p, e - p); + + max_version = v != nullptr && mxv == "$" + ? dependent_version () + : standard_version (mxv, standard_version::allow_earliest); } catch (const invalid_argument& e) { @@ -651,10 +697,10 @@ namespace butl if (++p != s.size ()) bail ("junk after constraint"); - // Verify and copy the constraint. + // Can throw. // - *this = standard_version_constraint (move (min_version), min_open, - move (max_version), max_open); + return standard_version_constraint (move (min_version), min_open, + move (max_version), max_open); } else if (c == '~' || c == '^') { @@ -662,19 +708,95 @@ namespace butl if (p == string::npos) bail ("no version"); - // Can throw. - // - standard_version min_version ( - standard_version (s.substr (p), standard_version::allow_earliest)); + standard_version min_version; + standard_version max_version; - // Can throw. - // - standard_version max_version ( - shortcut_max_version (c, min_version, false)); + try + { + string cv (s, p); + if (v != nullptr && cv == "$") // Dependent version reference. + { + const standard_version& dv (dependent_version ()); + + // For a release set the min version endpoint patch to zero. For ^ + // also set the minor version to zero, unless the major version is + // zero (reduced to ~). + // + if (dv.release ()) + { + min_version = standard_version ( + dv.epoch, + dv.major (), + c == '^' && dv.major () != 0 ? 0 : dv.minor (), + 0 /* patch */); + } + // + // For a final pre-release or a patch snapshot we check if there has + // been a compatible final release (patch is not zero for ~ and + // minor/patch are not zero for ^). If that's the case, then + // fallback to the release case and start the range from the first + // alpha otherwise. + // + else if (dv.final () || (dv.snapshot () && dv.patch () != 0)) + { + min_version = standard_version ( + dv.epoch, + dv.major (), + c == '^' && dv.major () != 0 ? 0 : dv.minor (), + 0 /* patch */, + dv.patch () != 0 || (c == '^' && dv.minor () != 0) + ? 0 + : 1 /* pre-release */); + } + // + // For a major/minor snapshot we assume that all the packages are + // developed in the lockstep and convert the constraint range to + // represent this "snapshot series". + // + else + { + assert (dv.snapshot () && dv.patch () == 0); + + uint16_t pr (*dv.pre_release ()); + + min_version = standard_version (dv.epoch, + dv.major (), + dv.minor (), + 0 /* patch */, + pr, + 1 /* snapshot_sn */, + "" /* snapshot_id */); + + max_version = standard_version (dv.epoch, + dv.major (), + dv.minor (), + 0 /* patch */, + pr + 1); + } + } + else // Version is specified literally. + { + // Can throw. + // + min_version = standard_version (cv, + standard_version::allow_earliest); + } + + // If the max version is not set for the lockstep (see above), then we + // set it normally. + // + if (max_version.empty ()) + max_version = shortcut_max_version ( + c, min_version, false /* ignore_overflow */); // Can throw. + } + catch (const invalid_argument& e) + { + bail (string ("invalid version: ") + e.what ()); + } try { - *this = standard_version_constraint ( + return standard_version_constraint ( move (min_version), false /* min_open */, move (max_version), true /* max_open */); } @@ -709,11 +831,15 @@ namespace butl if (p == string::npos) bail ("no version"); - standard_version v; + standard_version cv; try { - v = standard_version (s.substr (p), + string cver (s, p); + + cv = v != nullptr && cver == "$" + ? dependent_version () + : standard_version (cver, operation != comparison::eq ? standard_version::allow_earliest : standard_version::none); @@ -728,22 +854,35 @@ namespace butl switch (operation) { case comparison::eq: - *this = standard_version_constraint (v); - break; + return standard_version_constraint (cv); case comparison::lt: - *this = standard_version_constraint (nullopt, true, move (v), true); - break; + return standard_version_constraint (nullopt, true, move (cv), true); case comparison::le: - *this = standard_version_constraint (nullopt, true, move (v), false); - break; + return standard_version_constraint (nullopt, true, move (cv), false); case comparison::gt: - *this = standard_version_constraint (move (v), true, nullopt, true); - break; + return standard_version_constraint (move (cv), true, nullopt, true); case comparison::ge: - *this = standard_version_constraint (move (v), false, nullopt, true); - break; + return standard_version_constraint (move (cv), false, nullopt, true); } } + + // Can't be here. + // + assert (false); + return standard_version_constraint (); + } + + standard_version_constraint:: + standard_version_constraint (const std::string& s) + { + *this = parse_constraint (s, nullptr /* dependent_version */); + } + + standard_version_constraint:: + standard_version_constraint (const std::string& s, + const standard_version& dependent_version) + { + *this = parse_constraint (s, &dependent_version); } standard_version_constraint:: diff --git a/libbutl/standard-version.mxx b/libbutl/standard-version.mxx index 6688237..d9e46c7 100644 --- a/libbutl/standard-version.mxx +++ b/libbutl/standard-version.mxx @@ -287,6 +287,8 @@ LIBBUTL_MODEXPORT namespace butl // ('^' | '~') // ('(' | '[') (')' | ']') // + // The version may be `$` which refers to the dependent package version. + // struct LIBBUTL_SYMEXPORT standard_version_constraint { butl::optional min_version; @@ -299,6 +301,12 @@ LIBBUTL_MODEXPORT namespace butl explicit standard_version_constraint (const std::string&); + // As above but also completes the special `$` version using the specified + // dependent package version. + // + standard_version_constraint (const std::string&, + const standard_version& dependent_version); + // Throw std::invalid_argument if the specified version range is invalid. // standard_version_constraint ( diff --git a/tests/standard-version/driver.cxx b/tests/standard-version/driver.cxx index 353cc24..72dbb4a 100644 --- a/tests/standard-version/driver.cxx +++ b/tests/standard-version/driver.cxx @@ -147,7 +147,7 @@ version (const string& s, // // argv[0] (-rl|-pr|-al|-bt|-st|-el|-sn|-fn) // argv[0] -cm -// argv[0] -cr +// argv[0] -cr [] // argv[0] -sf // argv[0] // @@ -161,7 +161,8 @@ version (const string& s, // -fn output 'y' for final, 'n' otherwise // // -cm output 0 if versions are equal, -1 if the first one is less, 1 otherwise -// -cr create version constraints from stdin lines, and print them to stdout +// -cr create version constraints from stdin lines, optionally using the +// dependent version, and print them to stdout // -sf output 'y' if version satisfies constraint, 'n' otherwise // // If no options are specified, then create versions from stdin lines, and @@ -241,11 +242,25 @@ try } else if (o == "-cr") { - assert (argc == 2); + assert (argc <= 3); + + optional dv; + if (argc == 3) + { + string s (argv[2]); + + dv = s.empty () + ? standard_version () + : standard_version (s, + standard_version::allow_stub | + standard_version::allow_earliest); + } string s; while (getline (cin, s)) - cout << standard_version_constraint (s) << endl; + cout << (dv + ? standard_version_constraint (s, *dv) + : standard_version_constraint (s)) << endl; } else if (o == "-sf") { diff --git a/tests/standard-version/testscript b/tests/standard-version/testscript index cfe3b90..8a520a7 100644 --- a/tests/standard-version/testscript +++ b/tests/standard-version/testscript @@ -456,16 +456,141 @@ : invalid : { - $* <'-1.2.3' 2>'invalid constraint' == 1 : bad-char - $* <'~' 2>'no version' == 1 : no-version - $* <'~1.2' 2>"'.' expected after minor version" == 1 : bad-ver - $* <'~1.999.0' 2>"invalid minor version" == 1 : bad-min-tilde - $* <'^0.999.0' 2>"invalid minor version" == 1 : bad-min-caret - $* <'^999.0.0' 2>"invalid major version" == 1 : bad-maj-caret + $* <'-1.2.3' 2>'invalid constraint' == 1 : bad-char + $* <'~' 2>'no version' == 1 : no-version + $* <'~1.2' 2>"invalid version: '.' expected after minor version" == 1 : bad-ver + $* <'~1.999.0' 2>"invalid version: invalid minor version" == 1 : bad-min-tilde + $* <'^0.999.0' 2>"invalid version: invalid minor version" == 1 : bad-min-caret + $* <'^999.0.0' 2>"invalid version: invalid major version" == 1 : bad-maj-caret } } } +: constraints-dependent +: +{ + test.options += -cr + + : range + : + { + : valid + : + $* '1.2.3+1' <>EOE + [1.2.2 $] + (1.2.2 $) + [$ 1.2.4] + ($ 1.2.4] + EOI + [1.2.2 1.2.3] + (1.2.2 1.2.3) + [1.2.3 1.2.4] + (1.2.3 1.2.4] + EOE + } + + : comparison + : + { + : valid + : + $* '1.2.3+1' <>EOE + == $ + >= $ + <= $ + > $ + < $ + >= $ + <= $ + > $ + < $ + EOI + == 1.2.3 + >= 1.2.3 + <= 1.2.3 + > 1.2.3 + < 1.2.3 + >= 1.2.3 + <= 1.2.3 + > 1.2.3 + < 1.2.3 + EOE + + : invalid + : + { + $* '' <'== $' 2>'dependent version is empty' == 1 : empty-version + $* '1.2.3-' <'== $' 2>'dependent version is earliest' == 1 : earliest-version + $* '1.2.3-a.0.z' <'== $' 2>'invalid version: dependent version is latest snapshot' == 1 : latest-version + $* '0+1' <'== $' 2>'invalid version: dependent version is stub' == 1 : stub-version + } + } + + : shortcut + : + { + : final + : + { + $* '1.2.3+1' <>EOE + ~$ + ^$ + EOI + ~1.2.0 + ^1.0.0 + EOE + } + + : pre-release + : + { + : tilda + : + { + $* '1.2.0-b.2' <'~$' >'~1.2.0-a.1' : no-final + $* '1.2.1-a.1' <'~$' >'~1.2.0' : final-patch + } + + : carrot + : + { + $* '1.0.0-b.2' <'^$' >'^1.0.0-a.1' : no-final + $* '1.0.1-a.1' <'^$' >'^1.0.0' : final-patch + $* '1.1.0-b.2' <'^$' >'^1.0.0' : final-minor + } + } + + : snapshot + : + { + : tilda + : + { + $* '1.2.1-a.2.345' <'~$' >'~1.2.0' : patch + $* '1.2.0-a.0.345' <'~$' >'[1.2.0-a.0.1 1.2.0-a.1)' : minor + $* '1.0.0-a.0.345' <'~$' >'[1.0.0-a.0.1 1.0.0-a.1)' : major + } + + : carrot + : + { + $* '1.2.1-a.2.345' <'^$' >'^1.0.0' : patch + $* '1.2.0-a.0.345' <'^$' >'[1.2.0-a.0.1 1.2.0-a.1)' : minor + $* '1.0.0-a.0.345' <'^$' >'[1.0.0-a.0.1 1.0.0-a.1)' : major + } + } + } + + : invalid + : + { + $* <'[1.2.2 $]' 2>'invalid max version: invalid major version' != 0 : max + $* <'[$ 1.2.2]' 2>'invalid min version: invalid major version' != 0 : min + $* <'== $' 2>'invalid version: invalid major version' != 0 : eq + $* <'~$' 2>'invalid version: invalid major version' != 0 : shortcut + } +} + : satisfaction : { -- cgit v1.1