Skip to content

Commit 844e4bc

Browse files
committed
fix: Support globs in build strings
Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
1 parent 979162f commit 844e4bc

File tree

5 files changed

+103
-15
lines changed

5 files changed

+103
-15
lines changed

libmamba/include/mamba/specs/regex_spec.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace mamba::specs
3131
[[nodiscard]] static auto parse(std::string pattern) -> expected_parse_t<RegexSpec>;
3232

3333
RegexSpec();
34-
RegexSpec(std::regex pattern, std::string raw_pattern);
34+
RegexSpec(std::string raw_pattern);
3535

3636
[[nodiscard]] auto contains(std::string_view str) const -> bool;
3737

libmamba/src/specs/regex_spec.cpp

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include <algorithm>
88
#include <cassert>
9+
#include <iostream>
10+
#include <sstream>
911

1012
#include <fmt/format.h>
1113

@@ -25,12 +27,10 @@ namespace mamba::specs
2527

2628
auto RegexSpec::parse(std::string pattern) -> expected_parse_t<RegexSpec>
2729
{
28-
// No other mean of getting parse result with ``std::regex``, but parse error need
29-
// to be handled by ``tl::expected`` to be managed down the road.
30+
// Parse error need to be handled by ``tl::expected`` to be managed down the road.
3031
try
3132
{
32-
auto regex = std::regex(pattern);
33-
return { { std::move(regex), std::move(pattern) } };
33+
return { std::move(pattern) };
3434
}
3535
catch (const std::regex_error& e)
3636
{
@@ -39,25 +39,48 @@ namespace mamba::specs
3939
}
4040

4141
RegexSpec::RegexSpec()
42-
: RegexSpec(std::regex(free_pattern.data(), free_pattern.size()), std::string(free_pattern))
42+
: RegexSpec(std::string(free_pattern))
4343
{
4444
}
4545

46-
RegexSpec::RegexSpec(std::regex pattern, std::string raw_pattern)
47-
: m_pattern(std::move(pattern))
48-
, m_raw_pattern(std::move(raw_pattern))
46+
RegexSpec::RegexSpec(std::string raw_pattern)
4947
{
48+
// Construct ss from raw_pattern, in particular make sure to replace all `*` by `.*`
49+
// in the pattern if they are not preceded by a `.`.
5050
// We force regex to start with `^` and end with `$` to simplify the multiple
5151
// possible representations, and because this is the safest way we can make sure it is
5252
// not a glob when serializing it.
53-
if (!util::starts_with(m_raw_pattern, pattern_start))
54-
{
55-
m_raw_pattern.insert(m_raw_pattern.begin(), pattern_start);
56-
}
57-
if (!util::ends_with(m_raw_pattern, pattern_end))
53+
std::ostringstream ss;
54+
ss << pattern_start;
55+
56+
auto first_character_it = raw_pattern.cbegin();
57+
auto last_character_it = raw_pattern.cend() - 1;
58+
59+
for (auto it = first_character_it; it != raw_pattern.cend(); ++it)
5860
{
59-
m_raw_pattern.push_back(pattern_end);
61+
if (it == first_character_it && *it == pattern_start)
62+
{
63+
continue;
64+
}
65+
if (it == last_character_it && *it == pattern_end)
66+
{
67+
continue;
68+
}
69+
if (*it == '*' && (it == first_character_it || *(it - 1) != '.'))
70+
{
71+
ss << ".*";
72+
}
73+
else
74+
{
75+
ss << *it;
76+
}
6077
}
78+
79+
ss << pattern_end;
80+
81+
m_raw_pattern = ss.str();
82+
83+
m_pattern = std::regex(m_raw_pattern);
6184
}
6285

6386
auto RegexSpec::contains(std::string_view str) const -> bool

libmamba/tests/src/specs/test_match_spec.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,25 @@ namespace
981981
/* .track_features =*/{ "openssl", "mkl" },
982982
}));
983983
}
984+
985+
SECTION("pytorch=2.3.1=py3.10_cuda11.8*")
986+
{
987+
// Check that it contains `pytorch=2.3.1=py3.10_cuda11.8_cudnn8.7.0_0`
988+
989+
const auto ms = "pytorch=2.3.1=py3.10_cuda11.8*"_ms;
990+
991+
REQUIRE(ms.contains_except_channel(Pkg{
992+
/* .name= */ "pytorch",
993+
/* .version= */ "2.3.1"_v,
994+
/* .build_string= */ "py3.10_cuda11.8_cudnn8.7.0_0",
995+
/* .build_number= */ 0,
996+
/* .md5= */ "lemd5",
997+
/* .sha256= */ "somesha256",
998+
/* .license= */ "GPL",
999+
/* .platform= */ "linux-64",
1000+
/* .track_features =*/{},
1001+
}));
1002+
}
9841003
}
9851004

9861005
TEST_CASE("MatchSpec comparability and hashability")

libmamba/tests/src/specs/test_regex_spec.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,10 @@ namespace
8080
REQUIRE(hash_fn(spec1) == hash_fn(spec2));
8181
REQUIRE(hash_fn(spec1) != hash_fn(spec3));
8282
}
83+
84+
TEST_CASE("RegexSpec ^py3.10_cuda11.8*$")
85+
{
86+
auto spec = RegexSpec::parse("^py3.10_cuda11.8*$").value();
87+
REQUIRE(spec.contains("py3.10_cuda11.8_cudnn8.7.0_0"));
88+
}
8389
}

micromamba/tests/test_create.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,3 +1575,43 @@ def test_update_spec_list(tmp_path):
15751575
out = helpers.create("-p", env_prefix, "-f", env_spec_file, "--dry-run")
15761576

15771577
assert update_specs_list in out.replace("\r", "")
1578+
1579+
1580+
def test_glob_in_build_string(tmp_path):
1581+
# Non-regression test for https://github.com/mamba-org/mamba/issues/3699
1582+
env_prefix = tmp_path / "test_glob_in_build_string"
1583+
1584+
pytorch_match_spec = "pytorch=2.3.1=py3.10_cuda11.8*"
1585+
1586+
# Export CONDA_OVERRIDE_GLIBC=2.17 to force the solver to use the glibc 2.17 package
1587+
try:
1588+
os.environ["CONDA_OVERRIDE_GLIBC"] = "2.17"
1589+
1590+
# Should run without error
1591+
out = helpers.create(
1592+
"-p",
1593+
env_prefix,
1594+
pytorch_match_spec,
1595+
"-c",
1596+
"pytorch",
1597+
"-c",
1598+
"nvidia/label/cuda-11.8.0",
1599+
"-c",
1600+
"nvidia",
1601+
"-c",
1602+
"conda-forge",
1603+
"--platform",
1604+
"linux-64",
1605+
"--dry-run",
1606+
"--json",
1607+
)
1608+
finally:
1609+
os.environ.pop("CONDA_OVERRIDE_GLIBC", None)
1610+
1611+
# Check that a build of pytorch 2.3.1 with `py3.10_cuda11.8_cudnn8.7.0_0` as a build string is found
1612+
assert any(
1613+
package["name"] == "pytorch"
1614+
and package["version"] == "2.3.1"
1615+
and package["build_string"] == "py3.10_cuda11.8_cudnn8.7.0_0"
1616+
for package in out["actions"]["FETCH"]
1617+
)

0 commit comments

Comments
 (0)