diff --git a/libmamba/include/mamba/core/context.hpp b/libmamba/include/mamba/core/context.hpp index 71160c2e1f..069321d5b3 100644 --- a/libmamba/include/mamba/core/context.hpp +++ b/libmamba/include/mamba/core/context.hpp @@ -259,6 +259,11 @@ namespace mamba std::vector pinned_packages = {}; + // Keys are virtual package names without the `__` prefix (e.g. "cuda", "glibc"). + // Names with a `__` prefix are also accepted and normalized on lookup. + // Environment variables `CONDA_OVERRIDE_` take precedence over this map. + std::map override_virtual_packages = {}; + bool use_only_tar_bz2 = false; bool repodata_use_zst = true; diff --git a/libmamba/include/mamba/core/virtual_packages.hpp b/libmamba/include/mamba/core/virtual_packages.hpp index 6111e6a347..79eafce016 100644 --- a/libmamba/include/mamba/core/virtual_packages.hpp +++ b/libmamba/include/mamba/core/virtual_packages.hpp @@ -7,20 +7,31 @@ #ifndef MAMBA_CORE_VIRTUAL_PACKAGES_HPP #define MAMBA_CORE_VIRTUAL_PACKAGES_HPP +#include +#include #include +#include #include #include "mamba/specs/package_info.hpp" namespace mamba { - class Context; - - std::vector get_virtual_packages(const std::string& platform); + std::vector get_virtual_packages( + const std::string& platform, + const std::map& override_virtual_packages = {} + ); namespace detail { - std::string cuda_version(); + /** Resolve a virtual-package override: `CONDA_OVERRIDE_` then config map. */ + [[nodiscard]] auto get_virtual_package_override( + std::string_view name, + const std::map& overrides + ) -> std::optional; + + std::string + cuda_version(const std::map& override_virtual_packages = {}); auto make_virtual_package( std::string name, @@ -29,7 +40,10 @@ namespace mamba std::string build_string = "" ) -> specs::PackageInfo; - std::vector dist_packages(const std::string& platform); + std::vector dist_packages( + const std::string& platform, + const std::map& override_virtual_packages = {} + ); } } diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index f1e2ae3251..70c8a1e6fd 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -1597,6 +1597,26 @@ namespace mamba // Solver + insert(Configurable("override_virtual_packages", &m_context.override_virtual_packages) + .group("Solver") + .set_rc_configurable() + .description("Override values for virtual packages") + .long_description(unindent(R"( + A dictionary of virtual package overrides. Keys are virtual package names + without the leading `__` (e.g. `cuda`, `glibc`, `archspec`), though names + with `__` are also accepted. Values override the detected version (or the + build string for `archspec`). + + Environment variables of the form `CONDA_OVERRIDE_` take precedence + over this setting. + + Example: + override_virtual_packages: + cuda: "12.8" + glibc: "2.17" + archspec: "x86_64_v3" + )"))); + insert(Configurable("channel_priority", &m_context.channel_priority) .group("Solver") .set_rc_configurable() diff --git a/libmamba/src/api/info.cpp b/libmamba/src/api/info.cpp index d542000d84..3baaa00af3 100644 --- a/libmamba/src/api/info.cpp +++ b/libmamba/src/api/info.cpp @@ -217,7 +217,7 @@ namespace mamba items.push_back({ "populated config files", sources }); std::vector virtual_pkgs; - for (auto pkg : get_virtual_packages(ctx.platform)) + for (auto pkg : get_virtual_packages(ctx.platform, ctx.override_virtual_packages)) { virtual_pkgs.push_back(util::concat(pkg.name, "=", pkg.version, "=", pkg.build_string)); } diff --git a/libmamba/src/core/package_database_loader.cpp b/libmamba/src/core/package_database_loader.cpp index edc3b334d3..372eb480ed 100644 --- a/libmamba/src/core/package_database_loader.cpp +++ b/libmamba/src/core/package_database_loader.cpp @@ -200,7 +200,10 @@ namespace mamba "installed", solver::libsolv::PipAsPythonDependency::No ); - database.add_virtual_packages(repo, get_virtual_packages(ctx.platform)); + database.add_virtual_packages( + repo, + get_virtual_packages(ctx.platform, ctx.override_virtual_packages) + ); database.internalize_repo(repo); database.set_installed_repo(repo); return repo; diff --git a/libmamba/src/core/virtual_packages.cpp b/libmamba/src/core/virtual_packages.cpp index b7ac78ee12..b0b9e02dbd 100644 --- a/libmamba/src/core/virtual_packages.cpp +++ b/libmamba/src/core/virtual_packages.cpp @@ -29,12 +29,35 @@ namespace mamba { namespace detail { - std::string glibc_version() + auto get_virtual_package_override( + std::string_view name, + const std::map& overrides + ) -> std::optional { - auto override_version = util::get_env("CONDA_OVERRIDE_GLIBC"); - if (override_version) + // Environment variables take precedence over config (conda-compatible). + if (auto env_override = util::get_env("CONDA_OVERRIDE_" + util::to_upper(name))) { - return override_version.value(); + return env_override; + } + + for (const auto& [key, value] : overrides) + { + const auto normalized = util::starts_with(key, "__") + ? std::string_view(key).substr(2) + : std::string_view(key); + if (normalized == name) + { + return value; + } + } + return std::nullopt; + } + + std::string glibc_version(const std::map& overrides) + { + if (auto override_version = get_virtual_package_override("glibc", overrides)) + { + return std::move(override_version).value(); } if (!util::on_linux) @@ -57,16 +80,14 @@ namespace mamba return std::string(util::strip(version, "glibc ")); } - std::string cuda_version() + std::string cuda_version(const std::map& overrides) { LOG_DEBUG << "Loading CUDA virtual package"; - auto override_version = util::get_env("CONDA_OVERRIDE_CUDA"); - if (override_version) + if (auto override_version = get_virtual_package_override("cuda", overrides)) { - LOG_DEBUG << "CUDA version set by `CONDA_OVERRIDE_CUDA`: " - << override_version.value(); - return override_version.value(); + LOG_DEBUG << "CUDA version set by override: " << override_version.value(); + return std::move(override_version).value(); } std::string cuda_version; @@ -305,12 +326,13 @@ namespace mamba return "x86_64"; } - std::string get_archspec(const std::string& arch) + std::string + get_archspec(const std::string& arch, const std::map& overrides) { - auto override_version = util::get_env("CONDA_OVERRIDE_ARCHSPEC"); - if (override_version) + // For archspec, the override applies to the build string (conda-compatible). + if (auto override_build = get_virtual_package_override("archspec", overrides)) { - return override_version.value(); + return std::move(override_build).value(); } if (arch == "64") @@ -327,34 +349,41 @@ namespace mamba } } - [[nodiscard]] auto overridable_linux_version() -> tl::expected + [[nodiscard]] auto + overridable_linux_version(const std::map& overrides) + -> tl::expected { - if (auto override_version = util::get_env("CONDA_OVERRIDE_LINUX")) + if (auto override_version = get_virtual_package_override("linux", overrides)) { return { std::move(override_version).value() }; } return util::linux_version(); } - [[nodiscard]] auto overridable_osx_version() -> tl::expected + [[nodiscard]] auto + overridable_osx_version(const std::map& overrides) + -> tl::expected { - if (auto override_version = util::get_env("CONDA_OVERRIDE_OSX")) + if (auto override_version = get_virtual_package_override("osx", overrides)) { return { std::move(override_version).value() }; } return util::osx_version(); } - [[nodiscard]] auto overridable_windows_version() -> tl::expected + [[nodiscard]] auto + overridable_windows_version(const std::map& overrides) + -> tl::expected { - if (auto override_version = util::get_env("CONDA_OVERRIDE_WIN")) + if (auto override_version = get_virtual_package_override("win", overrides)) { return { std::move(override_version).value() }; } return util::windows_version(); } - std::vector dist_packages(const std::string& platform) + std::vector + dist_packages(const std::string& platform, const std::map& overrides) { LOG_DEBUG << "Loading distribution virtual packages"; @@ -371,7 +400,7 @@ namespace mamba if (os == "win") { - auto result = overridable_windows_version(); + auto result = overridable_windows_version(overrides); if (result) { res.push_back(make_virtual_package("__win", platform, std::move(result).value())); @@ -390,7 +419,7 @@ namespace mamba { res.push_back(make_virtual_package("__unix", platform)); - auto result = overridable_linux_version(); + auto result = overridable_linux_version(overrides); if (result) { res.push_back(make_virtual_package("__linux", platform, std::move(result).value())); @@ -404,7 +433,7 @@ namespace mamba LOG_DEBUG << std::move(result).error().message; } - std::string libc_ver = detail::glibc_version(); + std::string libc_ver = detail::glibc_version(overrides); if (!libc_ver.empty()) { res.push_back(make_virtual_package("__glibc", platform, libc_ver)); @@ -419,7 +448,7 @@ namespace mamba { res.push_back(make_virtual_package("__unix", platform)); - auto result = overridable_osx_version(); + auto result = overridable_osx_version(overrides); if (result) { res.push_back(make_virtual_package("__osx", platform, std::move(result).value())); @@ -452,18 +481,23 @@ namespace mamba res.push_back(make_virtual_package("__unix", platform)); } - res.push_back(make_virtual_package("__archspec", platform, "1", get_archspec(arch))); + res.push_back( + make_virtual_package("__archspec", platform, "1", get_archspec(arch, overrides)) + ); return res; } } - std::vector get_virtual_packages(const std::string& platform) + std::vector get_virtual_packages( + const std::string& platform, + const std::map& override_virtual_packages + ) { LOG_DEBUG << "Loading virtual packages"; - auto res = detail::dist_packages(platform); + auto res = detail::dist_packages(platform, override_virtual_packages); - auto cuda_ver = detail::cuda_version(); + auto cuda_ver = detail::cuda_version(override_virtual_packages); if (!cuda_ver.empty()) { res.push_back(detail::make_virtual_package("__cuda", platform, cuda_ver)); diff --git a/libmamba/tests/src/core/test_configuration.cpp b/libmamba/tests/src/core/test_configuration.cpp index f061c59856..b5327036ba 100644 --- a/libmamba/tests/src/core/test_configuration.cpp +++ b/libmamba/tests/src/core/test_configuration.cpp @@ -861,6 +861,23 @@ namespace mamba REQUIRE(config.dump() == "proxy_servers:\n http: foo\n https: bar"); } + TEST_CASE_METHOD(Configuration, "override_virtual_packages") + { + std::string rc = unindent(R"( + override_virtual_packages: + cuda: "13.1" + glibc: "2.15" + archspec: "x86_64_v4")"); + load_test_config(rc); + auto& actual = config.at("override_virtual_packages") + .value>(); + std::map expected = { { "cuda", "13.1" }, + { "glibc", "2.15" }, + { "archspec", "x86_64_v4" } }; + REQUIRE(actual == expected); + REQUIRE(ctx.override_virtual_packages == expected); + } + TEST_CASE_METHOD(Configuration, "platform") { mambatests::ScopedContextChange context_change{ ctx }; diff --git a/libmamba/tests/src/core/test_virtual_packages.cpp b/libmamba/tests/src/core/test_virtual_packages.cpp index 9e82747126..4b94dd4cbc 100644 --- a/libmamba/tests/src/core/test_virtual_packages.cpp +++ b/libmamba/tests/src/core/test_virtual_packages.cpp @@ -39,6 +39,7 @@ namespace mamba TEST_CASE("dist_packages") { using Version = specs::Version; + mambatests::EnvironmentCleaner env_clean(mambatests::CleanMambaEnv{}); auto& ctx = mambatests::context(); mambatests::ScopedContextChange context_change{ ctx }; @@ -116,40 +117,98 @@ namespace mamba TEST_CASE("get_virtual_packages") { + mambatests::EnvironmentCleaner env_clean(mambatests::CleanMambaEnv{}); + const std::map no_overrides; + + // Use a fixed platform to isolate from shared context state. + // Use "linux-64" with explicit overrides so dist_packages returns + // a deterministic set regardless of host OS. + const std::string platform = "linux-64"; + const std::map overrides = { + { "linux", "5.15" }, + { "glibc", "2.35" }, + { "archspec", "x86_64_v3" }, + }; + + // Suppress any host CUDA detection so the baseline is predictable. + util::set_env("CONDA_OVERRIDE_CUDA", ""); + auto baseline = get_virtual_packages(platform, overrides); + const size_t baseline_count = baseline.size(); + REQUIRE(baseline_count == 4); + + // With an explicit CUDA env override, __cuda is appended. util::set_env("CONDA_OVERRIDE_CUDA", "9.0"); - const auto& context = mambatests::context(); - auto pkgs = get_virtual_packages(context.platform); - size_t pkgs_count; + auto pkgs = get_virtual_packages(platform, overrides); + REQUIRE(pkgs.size() == baseline_count + 1); + REQUIRE(pkgs.back().name == "__cuda"); + REQUIRE(pkgs.back().version == "9.0"); - if (util::on_win) - { - pkgs_count = 2; - } - if (util::on_linux) - { - pkgs_count = 4; - } - if (util::on_mac) - { - pkgs_count = 3; - } + // Suppressing CUDA again removes __cuda. + util::set_env("CONDA_OVERRIDE_CUDA", ""); + pkgs = get_virtual_packages(platform, overrides); + REQUIRE(pkgs.size() == baseline_count); + } + + TEST_CASE("override_virtual_packages_from_config") + { + mambatests::EnvironmentCleaner env_clean(mambatests::CleanMambaEnv{}); + + const std::map overrides = { + { "cuda", "13.1" }, { "glibc", "2.15" }, { "linux", "5.7" }, + { "osx", "12.1" }, { "archspec", "x86_64_v4" }, + }; + + auto pkgs = get_virtual_packages("linux-64", overrides); + REQUIRE(pkgs.size() == 5); + REQUIRE(pkgs[0].name == "__unix"); + REQUIRE(pkgs[1].name == "__linux"); + REQUIRE(pkgs[1].version == "5.7"); + REQUIRE(pkgs[2].name == "__glibc"); + REQUIRE(pkgs[2].version == "2.15"); + REQUIRE(pkgs[3].name == "__archspec"); + REQUIRE(pkgs[3].version == "1"); + REQUIRE(pkgs[3].build_string == "x86_64_v4"); + REQUIRE(pkgs[4].name == "__cuda"); + REQUIRE(pkgs[4].version == "13.1"); + + pkgs = get_virtual_packages("osx-64", overrides); + REQUIRE(pkgs.size() == 4); + REQUIRE(pkgs[0].name == "__unix"); + REQUIRE(pkgs[1].name == "__osx"); + REQUIRE(pkgs[1].version == "12.1"); + REQUIRE(pkgs[2].name == "__archspec"); + REQUIRE(pkgs[2].build_string == "x86_64_v4"); + REQUIRE(pkgs[3].name == "__cuda"); + REQUIRE(pkgs[3].version == "13.1"); + } + + TEST_CASE("override_virtual_packages_dunder_prefix") + { + mambatests::EnvironmentCleaner env_clean(mambatests::CleanMambaEnv{}); + + // Keys with `__` prefix are accepted and normalized (conda-compatible). + const std::map overrides = { + { "__cuda", "11.8" }, + { "__archspec", "x86_64_v2" }, + }; - ++pkgs_count; - REQUIRE(pkgs.size() == pkgs_count); + auto pkgs = get_virtual_packages("linux-64", overrides); REQUIRE(pkgs.back().name == "__cuda"); - REQUIRE(pkgs.back().version == "9.0"); + REQUIRE(pkgs.back().version == "11.8"); + REQUIRE(pkgs[pkgs.size() - 2].name == "__archspec"); + REQUIRE(pkgs[pkgs.size() - 2].build_string == "x86_64_v2"); + } - util::unset_env("CONDA_OVERRIDE_CUDA"); - pkgs = get_virtual_packages(context.platform); + TEST_CASE("override_virtual_packages_env_over_config") + { + mambatests::EnvironmentCleaner env_clean(mambatests::CleanMambaEnv{}); - if (!detail::cuda_version().empty()) - { - REQUIRE(pkgs.size() == pkgs_count); - } - else - { - REQUIRE(pkgs.size() == pkgs_count - 1); - } + util::set_env("CONDA_OVERRIDE_CUDA", "9.0"); + const std::map overrides = { { "cuda", "13.1" } }; + + auto pkgs = get_virtual_packages("linux-64", overrides); + REQUIRE(pkgs.back().name == "__cuda"); + REQUIRE(pkgs.back().version == "9.0"); } } } diff --git a/libmambapy/bindings/legacy.cpp b/libmambapy/bindings/legacy.cpp index 3280c33ade..bd3cf15baa 100644 --- a/libmambapy/bindings/legacy.cpp +++ b/libmambapy/bindings/legacy.cpp @@ -1747,7 +1747,8 @@ bind_submodule_impl(pybind11::module_ m) m.def( "get_virtual_packages", - [](Context& context) { return get_virtual_packages(context.platform); } + [](Context& context) + { return get_virtual_packages(context.platform, context.override_virtual_packages); } ); m.def("cancel_json_output", [](Context&) { mambapy::singletons().console().cancel_json_print(); }); diff --git a/micromamba/tests/test_virtual_pkgs.py b/micromamba/tests/test_virtual_pkgs.py index 06a26c4426..5712544343 100644 --- a/micromamba/tests/test_virtual_pkgs.py +++ b/micromamba/tests/test_virtual_pkgs.py @@ -29,3 +29,22 @@ def test_virtual_linux(self): else: infos = info(env={**os.environ, "CONDA_SUBDIR": "linux-64"}) assert "__linux=0=0" in infos + + def test_override_virtual_packages_from_rc(self, tmp_home, tmp_root_prefix, tmp_path): + rc_file = tmp_path / ".mambarc" + rc_file.write_text( + """\ +channels: + - conda-forge +override_virtual_packages: + cuda: "13.1" + glibc: "2.15" + archspec: "x86_64_v4" +""" + ) + infos = info("--rc-file", str(rc_file)) + + assert "__cuda=13.1=0" in infos + assert "__archspec=1=x86_64_v4" in infos + if platform.system() == "Linux": + assert "__glibc=2.15=0" in infos