Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Version::starts_with and Version::compatible_with #2645

Merged
merged 6 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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); }
AntoinePrv marked this conversation as resolved.
Show resolved Hide resolved
).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); }
AntoinePrv marked this conversation as resolved.
Show resolved Hide resolved
).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;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an ambiguity if someone calls starts_with_three_way(AlwaysEqual(), AlwaysEqual()); I guess this combination does not make sense, but it might be worth precising it in a comment to avoid the future definition of a useless overload.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding

[[maybe_unused]] auto starts_with_three_way(const AlwaysEqual&, const AlwaysEqual&) -> strong_ordering {
    static_assert(false, "This comparison should not happen with the current implementation.");
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work since this is not a template, I made a regular assert.


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