mirror of https://github.com/mamba-org/mamba.git
fix: Adaptive level for compatible Version formatting (#3818)
Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
This commit is contained in:
parent
89abba3df0
commit
402b2d474b
|
@ -545,45 +545,6 @@ 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(
|
||||
|
|
|
@ -264,11 +264,14 @@ fmt::formatter<mamba::specs::VersionPredicate>::format(
|
|||
}
|
||||
if constexpr (std::is_same_v<Op, VersionPredicate::compatible_with>)
|
||||
{
|
||||
// Make sure to print the version without loosing information.
|
||||
auto version_level = pred.m_version.version().size();
|
||||
auto format_level = std::max(op.level, version_level);
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}{}",
|
||||
VersionSpec::compatible_str,
|
||||
pred.m_version.str(op.level)
|
||||
pred.m_version.str(format_level)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -128,6 +128,20 @@ namespace
|
|||
REQUIRE(ms.str() == "abc>3");
|
||||
}
|
||||
|
||||
SECTION("numpy~=1.26.0")
|
||||
{
|
||||
auto ms = MatchSpec::parse("numpy~=1.26.0").value();
|
||||
REQUIRE(ms.name().str() == "numpy");
|
||||
REQUIRE(ms.version().str() == "~=1.26.0");
|
||||
REQUIRE(ms.build_string().is_explicitly_free());
|
||||
REQUIRE(ms.build_number().is_explicitly_free());
|
||||
REQUIRE(ms.str() == "numpy~=1.26.0");
|
||||
|
||||
// TODO: test this assumption for many more cases
|
||||
auto ms2 = MatchSpec::parse(ms.str()).value();
|
||||
REQUIRE(ms2 == ms);
|
||||
}
|
||||
|
||||
// Invalid case from `inform2w64-sysroot_win-64-v12.0.0.r2.ggc561118da-h707e725_0.conda`
|
||||
// which is currently supported but which must not.
|
||||
SECTION("mingw-w64-ucrt-x86_64-crt-git v12.0.0.r2.ggc561118da h707e725_0")
|
||||
|
@ -475,11 +489,10 @@ namespace
|
|||
{
|
||||
auto ms = MatchSpec::parse(R"(numpy >1.8,<2|==1.7,!=1.9,~=1.7.1 py34_0)").value();
|
||||
REQUIRE(ms.name().str() == "numpy");
|
||||
REQUIRE(ms.version().str() == ">1.8,((<2|==1.7),(!=1.9,(>=1.7.1,=1.7)))");
|
||||
REQUIRE(ms.version().str() == ">1.8,((<2|==1.7),(!=1.9,~=1.7.1))");
|
||||
REQUIRE(ms.build_string().str() == "py34_0");
|
||||
REQUIRE(
|
||||
ms.str()
|
||||
== R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,(>=1.7.1,=1.7)))",build="py34_0"])ms"
|
||||
ms.str() == R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,~=1.7.1))",build="py34_0"])ms"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -487,16 +500,25 @@ namespace
|
|||
{
|
||||
auto ms = MatchSpec::parse("python-graphviz~=0.20").value();
|
||||
REQUIRE(ms.name().str() == "python-graphviz");
|
||||
REQUIRE(ms.version().str() == ">=0.20,=0");
|
||||
REQUIRE(ms.str() == R"ms(python-graphviz[version=">=0.20,=0"])ms");
|
||||
REQUIRE(ms.version().str() == "~=0.20");
|
||||
REQUIRE(ms.str() == R"ms(python-graphviz~=0.20)ms");
|
||||
}
|
||||
|
||||
SECTION("python-graphviz ~= 0.20")
|
||||
{
|
||||
auto ms = MatchSpec::parse("python-graphviz ~= 0.20").value();
|
||||
REQUIRE(ms.name().str() == "python-graphviz");
|
||||
REQUIRE(ms.version().str() == ">=0.20,=0");
|
||||
REQUIRE(ms.str() == R"ms(python-graphviz[version=">=0.20,=0"])ms");
|
||||
REQUIRE(ms.version().str() == "~=0.20");
|
||||
REQUIRE(ms.str() == R"ms(python-graphviz~=0.20)ms");
|
||||
}
|
||||
|
||||
SECTION("python[version='~=3.11.0',build=*_cpython]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("python[version='~=3.11.0',build=*_cpython]").value();
|
||||
REQUIRE(ms.name().str() == "python");
|
||||
REQUIRE(ms.version().str() == "~=3.11.0");
|
||||
REQUIRE(ms.build_string().str() == "*_cpython");
|
||||
REQUIRE(ms.str() == R"ms(python[version="~=3.11.0",build="*_cpython"])ms");
|
||||
}
|
||||
|
||||
SECTION("*[md5=fewjaflknd]")
|
||||
|
@ -1000,6 +1022,189 @@ namespace
|
|||
/* .track_features =*/{},
|
||||
}));
|
||||
}
|
||||
|
||||
SECTION("pytorch~=2.3.1=py3.10_cuda11.8*")
|
||||
{
|
||||
const auto ms = "pytorch~=2.3.1=py3.10_cuda11.8*"_ms;
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "pytorch",
|
||||
/* .version= */ "2.3.1"_v,
|
||||
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "pytorch",
|
||||
/* .version= */ "2.3.2"_v,
|
||||
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "pytorch",
|
||||
/* .version= */ "2.4.0"_v,
|
||||
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "pytorch",
|
||||
/* .version= */ "3.0"_v,
|
||||
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "pytorch",
|
||||
/* .version= */ "2.3.0"_v,
|
||||
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
}
|
||||
|
||||
SECTION("numpy~=1.26.0")
|
||||
{
|
||||
const auto ms = "numpy~=1.26.0"_ms;
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.26.0"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.26.1"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.27"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "2.0.0"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_1",
|
||||
/* .build_number= */ 1,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.25.0"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
}
|
||||
|
||||
SECTION("numpy~=1.26")
|
||||
{
|
||||
const auto ms = "numpy~=1.26"_ms;
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.26.0"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.26.1"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "1.27"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_0",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
|
||||
REQUIRE_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "numpy",
|
||||
/* .version= */ "2.0.0"_v,
|
||||
/* .build_string= */ "py310h1d0b8b9_1",
|
||||
/* .build_number= */ 1,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("MatchSpec comparability and hashability")
|
||||
|
|
|
@ -422,6 +422,27 @@ namespace
|
|||
REQUIRE(vs.str() == "=2.3,<3.0");
|
||||
REQUIRE(vs.str_conda_build() == "2.3.*,<3.0");
|
||||
}
|
||||
|
||||
SECTION("~=1")
|
||||
{
|
||||
auto vs = VersionSpec::parse("~=1").value();
|
||||
REQUIRE(vs.str() == "~=1");
|
||||
REQUIRE(vs.str_conda_build() == "~=1");
|
||||
}
|
||||
|
||||
SECTION("~=1.8")
|
||||
{
|
||||
auto vs = VersionSpec::parse("~=1.8").value();
|
||||
REQUIRE(vs.str() == "~=1.8");
|
||||
REQUIRE(vs.str_conda_build() == "~=1.8");
|
||||
}
|
||||
|
||||
SECTION("~=1.8.0")
|
||||
{
|
||||
auto vs = VersionSpec::parse("~=1.8.0").value();
|
||||
REQUIRE(vs.str() == "~=1.8.0");
|
||||
REQUIRE(vs.str_conda_build() == "~=1.8.0");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("VersionSpec::is_explicitly_free")
|
||||
|
|
|
@ -3,6 +3,7 @@ import platform
|
|||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from packaging.version import Version
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
@ -1693,3 +1694,13 @@ def test_non_url_encoding(tmp_path):
|
|||
non_encoded_url_start = "https://conda.anaconda.org/conda-forge/linux-64/x264-1!"
|
||||
out = helpers.run_env("export", "-p", env_prefix, "--explicit")
|
||||
assert non_encoded_url_start in out
|
||||
|
||||
|
||||
def test_compatible_release(tmp_path):
|
||||
# Non-regression test for: https://github.com/mamba-org/mamba/issues/3472
|
||||
env_prefix = tmp_path / "env-compatible-release"
|
||||
|
||||
out = helpers.create("--json", "jupyterlab~=4.3", "-p", env_prefix, "--dry-run")
|
||||
|
||||
jupyterlab_package = next(pkg for pkg in out["actions"]["LINK"] if pkg["name"] == "jupyterlab")
|
||||
assert Version(jupyterlab_package["version"]) >= Version("4.3.0")
|
||||
|
|
Loading…
Reference in New Issue