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

fix: Support for PEP 440 "Compatible Releases" (operator ~= for MatchSpec) #3483

Merged
merged 5 commits into from
Sep 30, 2024
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
45 changes: 44 additions & 1 deletion libmamba/src/specs/match_spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ namespace mamba::specs
std::string raw_match_spec_str = std::string(str);
raw_match_spec_str = util::strip(raw_match_spec_str);

// Those are temporary adaptations to handle some instances of `MatchSpec` which is not
// yet formally specified.
// For a tentative formulation of the MatchSpec see: https://github.com/conda/ceps/pull/82

// Remove any with space after binary operators, such as:
// - `openmpi-4.1.4-ha1ae619_102`'s improperly encoded `constrains`: "cudatoolkit >= 10.2"
// - `pytorch-1.13.0-cpu_py310h02c325b_0.conda`'s improperly encoded
Expand All @@ -516,7 +520,7 @@ namespace mamba::specs
// TODO: this solution reallocates memory several times potentially, but the
// number of operators is small and the strings are short, so it must be fine.
// If needed it can be optimized so that the string is only copied once.
for (const std::string& op : { ">=", "<=", "==", ">", "<", "!=", "=", "==", "," })
for (const std::string& op : { ">=", "<=", "==", ">", "<", "!=", "=", "==", "~=", "," })
{
const std::string& bad_op = op + " ";
while (raw_match_spec_str.find(bad_op) != std::string::npos)
Expand All @@ -528,6 +532,45 @@ namespace mamba::specs
}
}

// Handle PEP 440 "Compatible release" specification
// See: https://peps.python.org/pep-0440/#compatible-release
//
// Find a general replacement of the encoding of `~=` with `>=,.*` to be able to parse it
// properly.
//
// For instance:
//
// "~=x.y" must be replaced to ">=x.y,x.*" where `x` and `y` are positive integers.
//
// This solution must handle the case where the version is encoded with `~=` within the
// specification for instance:
//
// ">1.8,<2|==1.7,!=1.9,~=1.7.1 py34_0"
//
// must be replaced with:
//
// ">1.8,<2|==1.7,!=1.9,>=1.7.1,1.7.* py34_0"
//
while (raw_match_spec_str.find("~=") != std::string::npos)
{
// Extract the string before the `~=` operator (">1.8,<2|==1.7,!=1.9," for the above
// example)
const auto before = raw_match_spec_str.substr(0, str.find("~="));
// Extract the string after the `~=` operator (include `~=` in it) and the next operator
// space or end of the string ("~=1.7.1 py34_0" for the above example)
const auto after = raw_match_spec_str.substr(str.find("~="));
// Extract the version part after the `~=` operator ("1.7.1" for the above example)
const auto version = after.substr(2, after.find_first_of(" ,") - 2);
// Extract the version part without the last segment ("1.7" for the above example)
const auto version_without_last_segment = version.substr(0, version.find_last_of('.'));
// Extract the build part after the version part (" py34_0" for the above example) if
// present
const auto build = after.find(" ") != std::string::npos ? after.substr(after.find(" "))
: "";
raw_match_spec_str = before + ">=" + version + "," + version_without_last_segment + ".*"
+ build;
}

auto parse_error = [&raw_match_spec_str](std::string_view err) -> tl::unexpected<ParseError>
{
return tl::make_unexpected(ParseError(
Expand Down
23 changes: 21 additions & 2 deletions libmamba/tests/src/specs/test_match_spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,28 @@ TEST_SUITE("specs::match_spec")
{
auto ms = MatchSpec::parse(R"(numpy >1.8,<2|==1.7,!=1.9,~=1.7.1 py34_0)").value();
jjerphan marked this conversation as resolved.
Show resolved Hide resolved
CHECK_EQ(ms.name().str(), "numpy");
CHECK_EQ(ms.version().str(), ">1.8,((<2|==1.7),(!=1.9,~=1.7))");
CHECK_EQ(ms.version().str(), ">1.8,((<2|==1.7),(!=1.9,(>=1.7.1,=1.7)))");
CHECK_EQ(ms.build_string().str(), "py34_0");
CHECK_EQ(ms.str(), R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,~=1.7))",build="py34_0"])ms");
CHECK_EQ(
ms.str(),
R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,(>=1.7.1,=1.7)))",build="py34_0"])ms"
);
}

SUBCASE("python-graphviz~=0.20")
{
auto ms = MatchSpec::parse("python-graphviz~=0.20").value();
CHECK_EQ(ms.name().str(), "python-graphviz");
CHECK_EQ(ms.version().str(), ">=0.20,=0");
CHECK_EQ(ms.str(), R"ms(python-graphviz[version=">=0.20,=0"])ms");
}

SUBCASE("python-graphviz ~= 0.20")
{
auto ms = MatchSpec::parse("python-graphviz ~= 0.20").value();
CHECK_EQ(ms.name().str(), "python-graphviz");
CHECK_EQ(ms.version().str(), ">=0.20,=0");
CHECK_EQ(ms.str(), R"ms(python-graphviz[version=">=0.20,=0"])ms");
}

SUBCASE("*[md5=fewjaflknd]")
Expand Down
9 changes: 9 additions & 0 deletions micromamba/tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys
from pathlib import Path
from packaging.version import Version

import pytest

Expand Down Expand Up @@ -633,6 +634,14 @@ def test_force_reinstall_not_installed(self, existing_cache):
reinstall_res = helpers.install("xtensor", "--force-reinstall", "--json")
assert "xtensor" in {pkg["name"] for pkg in reinstall_res["actions"]["LINK"]}

def test_install_compatible_release(self, existing_cache):
"""Install compatible release."""
res = helpers.install("numpy~=1.26.0", "--force-reinstall", "--json")
assert "numpy" in {pkg["name"] for pkg in res["actions"]["LINK"]}

numpy = [pkg for pkg in res["actions"]["LINK"] if pkg["name"] == "numpy"][0]
assert Version(numpy["version"]) >= Version("1.26.0")


def test_install_check_dirs(tmp_home, tmp_root_prefix):
env_name = "myenv"
Expand Down
Loading