Skip to content

Commit

Permalink
Add Version::starts_with and Version::compatible_with (#2645)
Browse files Browse the repository at this point in the history
Add Version::starts_with and Version::compatible_with
  • Loading branch information
AntoinePrv authored Jul 3, 2023
1 parent a42baaf commit cbd8087
Show file tree
Hide file tree
Showing 3 changed files with 623 additions and 274 deletions.
46 changes: 34 additions & 12 deletions libmamba/include/mamba/specs/version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,42 @@ namespace mamba::specs

static auto parse(std::string_view str) -> Version;

/** Construct version ``0.0``. */
Version() noexcept = default;
Version(std::size_t epoch, CommonVersion&& version, CommonVersion&& local = {}) noexcept;

auto epoch() const noexcept -> std::size_t;
auto version() const noexcept -> const CommonVersion&;
auto local() const noexcept -> const CommonVersion&;

auto str() const -> std::string;

auto operator==(const Version& other) const -> bool;
auto operator!=(const Version& other) const -> bool;
auto operator<(const Version& other) const -> bool;
auto operator<=(const Version& other) const -> bool;
auto operator>(const Version& other) const -> bool;
auto operator>=(const Version& other) const -> bool;
[[nodiscard]] auto epoch() const noexcept -> std::size_t;
[[nodiscard]] auto version() const noexcept -> const CommonVersion&;
[[nodiscard]] auto local() const noexcept -> const CommonVersion&;

[[nodiscard]] auto str() const -> std::string;

[[nodiscard]] auto operator==(const Version& other) const -> bool;
[[nodiscard]] auto operator!=(const Version& other) const -> bool;
[[nodiscard]] auto operator<(const Version& other) const -> bool;
[[nodiscard]] auto operator<=(const Version& other) const -> bool;
[[nodiscard]] auto operator>(const Version& other) const -> bool;
[[nodiscard]] auto operator>=(const Version& other) const -> bool;

/**
* Return true if this version starts with the other prefix.
*
* For instance 1.2.3 starts with 1.2 but not the opposite.
* Because Conda versions can contain an arbitrary number of segments, some of which
* with alpha releases, this function cannot be written as a comparison.
* One would need to comoare with a version with infinitely pre-release segments.
*/
[[nodiscard]] auto starts_with(const Version& prefix) const -> bool;

/**
* Return true if this version is a compatible upgrade to the given one.
*
* For instance 1.3.1 is compatible with 1.2.1 at level 0 (first component `1 == 1``),
* at level 1 (second component `` 3 >= 2``), but not at level two (because the second
* component is stricly larger ``3 > 2``).
* Compatible versions are always smaller than the current version.
*/
[[nodiscard]] auto compatible_with(const Version& older, std::size_t level) const -> bool;

private:

Expand Down
196 changes: 166 additions & 30 deletions libmamba/src/specs/version.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ namespace mamba::specs
{
return compare_three_way(std::strcmp(a.c_str(), b.c_str()), 0);
}

}

/***************************************
Expand Down Expand Up @@ -217,78 +216,108 @@ namespace mamba::specs
* ``[1, 2, 0, 0]`` are considered equal, however ``[1, 2]`` and ``[1, 0, 2]`` are not.
* Similarily ``[1, 1] is less than ``[1, 2, 0]`` but more than ``[1, 1, -1]``
* because ``-1 < 0``.
*
* @return The comparison between the two sequence
* @return The first index where the two sequence diverge.
*/
template <typename Iter1, typename Iter2, typename T, typename Cmp>
template <typename Iter1, typename Iter2, typename Empty1, typename Empty2, typename Cmp>
constexpr auto lexicographical_compare_three_way_trailing(
Iter1 first1,
Iter1 last1,
Iter2 first2,
Iter2 last2,
T empty,
const Empty1& empty1,
const Empty2& empty2,
Cmp comp
) -> strong_ordering
) -> std::pair<strong_ordering, std::size_t>
{
for (; (first1 != last1) && (first2 != last2); ++first1, ++first2)
assert(std::distance(first1, last1) >= 0);
assert(std::distance(first2, last2) >= 0);

auto iter1 = first1;
auto iter2 = first2;
for (; (iter1 != last1) && (iter2 != last2); ++iter1, ++iter2)
{
if (auto c = comp(*first1, *first2); c != strong_ordering::equal)
if (auto c = comp(*iter1, *iter2); c != strong_ordering::equal)
{
return c;
return { c, static_cast<std::size_t>(std::distance(first1, iter1)) };
}
}

// They have the same leading elements but 1 has more elements
// We do a lexicographic compare with an infite sequence of empties
if ((first1 != last1))
if ((iter1 != last1))
{
for (; first1 != last1; ++first1)
for (; iter1 != last1; ++iter1)
{
if (auto c = comp(*first1, empty); c != strong_ordering::equal)
if (auto c = comp(*iter1, empty2); c != strong_ordering::equal)
{
return c;
return { c, static_cast<std::size_t>(std::distance(first1, iter1)) };
}
}
}
// first2 != last2
// They have the same leading elements but 2 has more elements
// We do a lexicographic compare with an infite sequence of empties
if ((first2 != last2))
if ((iter2 != last2))
{
for (; first2 != last2; ++first2)
for (; iter2 != last2; ++iter2)
{
if (auto c = comp(empty, *first2); c != strong_ordering::equal)
if (auto c = comp(empty1, *iter2); c != strong_ordering::equal)
{
return c;
return { c, static_cast<std::size_t>(std::distance(first2, iter2)) };
}
}
}
// They have the same elements
return strong_ordering::equal;
return { strong_ordering::equal, static_cast<std::size_t>(std::distance(first1, iter1)) };
}

template <typename Iter1, typename Iter2, typename Empty, typename Cmp>
constexpr auto lexicographical_compare_three_way_trailing(
Iter1 first1,
Iter1 last1,
Iter2 first2,
Iter2 last2,
const Empty& empty,
Cmp comp
) -> std::pair<strong_ordering, std::size_t>
{
return lexicographical_compare_three_way_trailing(
first1,
last1,
first2,
last2,
empty,
empty,
comp
);
}

template <>
auto compare_three_way(const VersionPart& a, const VersionPart& b) -> strong_ordering
{
return lexicographical_compare_three_way_trailing(
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPartAtom{},
[](const auto& x, const auto& y) { return compare_three_way(x, y); }
);
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPartAtom{},
[](const auto& x, const auto& y) { return compare_three_way(x, y); }
).first;
}

template <>
auto compare_three_way(const CommonVersion& a, const CommonVersion& b) -> strong_ordering
{
return lexicographical_compare_three_way_trailing(
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPart{},
[](const auto& x, const auto& y) { return compare_three_way(x, y); }
);
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPart{},
[](const auto& x, const auto& y) { return compare_three_way(x, y); }
).first;
}

template <>
Expand Down Expand Up @@ -337,6 +366,113 @@ namespace mamba::specs
return compare_three_way(*this, other) != strong_ordering::less;
}

namespace
{
struct AlwaysEqual
{
};

[[maybe_unused]] auto starts_with_three_way(const AlwaysEqual&, const AlwaysEqual&)
-> strong_ordering
{
// This comparison should not happen with the current usage.
assert(false);
return strong_ordering::equal;
}

template <typename T>
auto starts_with_three_way(const AlwaysEqual&, const T&) -> strong_ordering
{
return strong_ordering::equal;
}

template <typename T>
auto starts_with_three_way(const T&, const AlwaysEqual&) -> strong_ordering
{
return strong_ordering::equal;
}

auto starts_with_three_way(const VersionPartAtom& a, const VersionPartAtom& b)
-> strong_ordering
{
if ((a.numeral() == b.numeral()) && b.literal().empty())
{
return strong_ordering::equal;
}
return compare_three_way(a, b);
}

auto starts_with_three_way(const VersionPart& a, const VersionPart& b) -> strong_ordering
{
return lexicographical_compare_three_way_trailing(
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPartAtom{},
AlwaysEqual{},
[](const auto& x, const auto& y) { return starts_with_three_way(x, y); }
).first;
}

auto starts_with_three_way(const CommonVersion& a, const CommonVersion& b) -> strong_ordering
{
return lexicographical_compare_three_way_trailing(
a.cbegin(),
a.cend(),
b.cbegin(),
b.cend(),
VersionPart{},
AlwaysEqual{},
[](const auto& x, const auto& y) { return starts_with_three_way(x, y); }
).first;
}

auto starts_with_three_way(const Version& a, const Version& b) -> strong_ordering
{
if (auto c = compare_three_way(a.epoch(), b.epoch()); c != strong_ordering::equal)
{
return c;
}
if (auto c = starts_with_three_way(a.version(), b.version()); c != strong_ordering::equal)
{
return c;
}
return compare_three_way(a.local(), b.local());
}
}

auto Version::starts_with(const Version& prefix) const -> bool
{
return starts_with_three_way(*this, prefix) == strong_ordering::equal;
}

namespace
{
auto
compatible_with_impl(const CommonVersion& newer, const CommonVersion& older, std::size_t level)
-> bool
{
auto [cmp, idx] = lexicographical_compare_three_way_trailing(
newer.cbegin(),
newer.cend(),
older.cbegin(),
older.cend(),
VersionPart{},
[](const auto& x, const auto& y) { return compare_three_way(x, y); }
);

return (cmp == strong_ordering::equal)
|| ((cmp == strong_ordering::greater) && (idx >= level));
}
}

auto Version::compatible_with(const Version& older, std::size_t level) const -> bool
{
return (epoch() == older.epoch()) && compatible_with_impl(version(), older.version(), level)
&& compatible_with_impl(local(), older.local(), level);
}

namespace
{
// TODO(C++20) This is a std::string_view constructor
Expand Down
Loading

0 comments on commit cbd8087

Please sign in to comment.