support installing pip dependencies with uv (#3918)

This commit is contained in:
Iisakki Rotko 2025-05-14 12:26:12 +02:00 committed by GitHub
parent c3681db47f
commit 3111778478
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 138 additions and 18 deletions

View File

@ -105,8 +105,12 @@ namespace mamba
bool eval_selector(const std::string& selector, const std::string& platform);
yaml_file_contents
read_yaml_file(const Context& ctx, const std::string& yaml_file, const std::string& platform);
yaml_file_contents read_yaml_file(
const Context& ctx,
const std::string& yaml_file,
const std::string& platform,
bool use_uv
);
inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&)
{

View File

@ -131,6 +131,7 @@ namespace mamba
bool experimental_repodata_parsing = true;
bool experimental_matchspec_parsing = false;
bool debug = false;
bool use_uv = false;
// TODO check writable and add other potential dirs
std::vector<fs::u8path> envs_dirs;

View File

@ -1857,6 +1857,13 @@ namespace mamba
.set_env_var_names()
.description("Defines if PYC files will be compiled or not"));
insert(Configurable("use_uv", &m_context.use_uv)
.group("Extract, Link & Install")
.set_rc_configurable()
.set_env_var_names()
.description("Whether to use uv for installing pip dependencies. Defaults to false."
));
// Output, Prompt and Flow
insert(Configurable("always_yes", &m_context.always_yes)
.group("Output, Prompt and Flow Control")

View File

@ -117,8 +117,12 @@ namespace mamba
return nullptr;
}
yaml_file_contents
read_yaml_file(const Context& ctx, const std::string& yaml_file, const std::string& platform)
yaml_file_contents read_yaml_file(
const Context& ctx,
const std::string& yaml_file,
const std::string& platform,
bool use_uv
)
{
// Download content of environment yaml file
auto tmp_yaml_file = downloaded_file_from_url(ctx, yaml_file);
@ -205,7 +209,7 @@ namespace mamba
yaml_parent_path = fs::absolute(yaml_file).parent_path().string();
}
result.others_pkg_mgrs_specs.push_back({
"pip",
use_uv ? "uv" : "pip",
map_el.second.as<std::vector<std::string>>(),
yaml_parent_path,
});
@ -233,7 +237,21 @@ namespace mamba
throw e;
}
if (has_pip_deps && !std::count(dependencies.begin(), dependencies.end(), "pip"))
if (has_pip_deps && use_uv && !std::count(dependencies.begin(), dependencies.end(), "uv"))
{
dependencies.push_back("uv");
}
else if (has_pip_deps && std::count(dependencies.begin(), dependencies.end(), "uv"))
{
for (auto& spec : result.others_pkg_mgrs_specs)
{
if (spec.pkg_mgr == "pip")
{
spec.pkg_mgr = "uv";
}
}
}
else if (has_pip_deps && !std::count(dependencies.begin(), dependencies.end(), "pip"))
{
dependencies.push_back("pip");
}
@ -890,7 +908,12 @@ namespace mamba
}
else if (is_yaml_file_name(file))
{
const auto parse_result = read_yaml_file(context, file, context.platform);
const auto parse_result = read_yaml_file(
context,
file,
context.platform,
context.use_uv
);
if (parse_result.channels.size() != 0)
{

View File

@ -24,7 +24,7 @@ namespace mamba
{
namespace
{
tl::expected<command_args, std::runtime_error> get_pip_install_command(
tl::expected<command_args, std::runtime_error> get_pkg_mgr_install_command(
const std::string& name,
const std::string& target_prefix,
const fs::u8path& spec_file,
@ -34,8 +34,28 @@ namespace mamba
const auto get_python_path = [&]
{ return util::which_in("python", util::get_path_dirs(target_prefix)).string(); };
command_args cmd = { get_python_path(), "-m", "pip", "install" };
command_args cmd_extension = { "-r", spec_file, "--no-input", "--quiet" };
const auto get_uv_path = [&]
{ return util::which_in("uv", util::get_path_dirs(target_prefix)).string(); };
command_args cmd = [&]
{
if (name == "uv")
{
return command_args{ get_uv_path() };
}
else
{
return command_args{ get_python_path(), "-m" };
}
}();
cmd.insert(cmd.end(), { "pip", "install" });
command_args cmd_extension = { "-r", spec_file, "--quiet" };
if (name != "uv")
{
cmd_extension.push_back("--no-input");
}
if (update == pip::Update::Yes)
{
@ -50,7 +70,8 @@ namespace mamba
cmd.insert(cmd.end(), cmd_extension.begin(), cmd_extension.end());
const std::unordered_map<std::string, command_args> pip_install_command{
{ "pip", cmd },
{ "pip --no-deps", cmd }
{ "pip --no-deps", cmd },
{ "uv", cmd },
};
auto found_it = pip_install_command.find(name);
@ -135,7 +156,7 @@ namespace mamba
command_args command = [&]
{
const auto maybe_command = get_pip_install_command(
const auto maybe_command = get_pkg_mgr_install_command(
pkg_mgr,
ctx.prefix_params.target_prefix.string(),
specs.path(),

View File

@ -0,0 +1,9 @@
name: env_4
channels: [conda-forge, bioconda]
dependencies:
- test1
- test2
- uv
- pip:
- pytest
- numpy

View File

@ -50,7 +50,8 @@ namespace mamba
auto res = detail::read_yaml_file(
context,
mambatests::test_data_dir / "env_file/env_1.yaml",
context.platform
context.platform,
false
);
REQUIRE(res.name == "env_1");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
@ -60,7 +61,8 @@ namespace mamba
auto res2 = detail::read_yaml_file(
context,
mambatests::test_data_dir / "env_file/env_2.yaml",
context.platform
context.platform,
false
);
REQUIRE(res2.name == "env_2");
REQUIRE(res2.channels == V({ "conda-forge", "bioconda" }));
@ -81,7 +83,8 @@ namespace mamba
auto res = detail::read_yaml_file(
context,
mambatests::test_data_dir / "env_file/env_3.yaml",
context.platform
context.platform,
false
);
REQUIRE(res.name == "env_3");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
@ -103,7 +106,8 @@ namespace mamba
auto res = detail::read_yaml_file(
context,
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/micromamba/tests/env-create-export.yaml",
context.platform
context.platform,
false
);
REQUIRE(res.name == "");
REQUIRE(res.channels == V({ "https://conda.anaconda.org/conda-forge" }));
@ -117,7 +121,8 @@ namespace mamba
auto res = detail::read_yaml_file(
context,
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_3.yaml",
context.platform
context.platform,
false
);
REQUIRE(res.name == "env_3");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
@ -132,6 +137,52 @@ namespace mamba
);
}
SECTION("env_yaml_file_with_uv_override")
{
const auto& context = mambatests::context();
using V = std::vector<std::string>;
auto res = detail::read_yaml_file(
context,
"https://raw.githubusercontent.com/iisakkirotko/mamba/refs/heads/yaml-install-uv/libmamba/tests/data/env_file/env_4.yaml",
context.platform,
false
);
REQUIRE(res.name == "env_4");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
REQUIRE(res.dependencies == V({ "test1", "test2", "uv" }));
REQUIRE(res.others_pkg_mgrs_specs.size() == 1);
auto o = res.others_pkg_mgrs_specs[0];
REQUIRE(o.pkg_mgr == "uv");
REQUIRE(o.deps == V({ "pytest", "numpy" }));
REQUIRE(
o.cwd == "https://raw.githubusercontent.com/iisakkirotko/mamba/refs/heads/yaml-install-uv/libmamba/tests/data/env_file/env_4.yaml"
);
}
SECTION("env_yaml_file_with_uv")
{
const auto& context = mambatests::context();
using V = std::vector<std::string>;
auto res = detail::read_yaml_file(
context,
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_3.yaml",
context.platform,
true
);
REQUIRE(res.name == "env_3");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
REQUIRE(res.dependencies == V({ "test1", "test2", "test3", "uv" }));
REQUIRE(res.others_pkg_mgrs_specs.size() == 1);
auto o = res.others_pkg_mgrs_specs[0];
REQUIRE(o.pkg_mgr == "uv");
REQUIRE(o.deps == V({ "pytest", "numpy" }));
REQUIRE(
o.cwd == "https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_3.yaml"
);
}
SECTION("env_yaml_file_with_specs_selection")
{
const auto& context = mambatests::context();
@ -139,7 +190,8 @@ namespace mamba
auto res = detail::read_yaml_file(
context,
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_2.yaml",
context.platform
context.platform,
false
);
REQUIRE(res.name == "env_2");
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));

View File

@ -88,6 +88,9 @@ init_general_options(CLI::App* subcom, Configuration& config)
->add_flag("--experimental", experimental.get_cli_config<bool>(), experimental.description())
->group(cli_group);
auto& use_uv = config.at("use_uv");
subcom->add_flag("--use-uv", use_uv.get_cli_config<bool>(), use_uv.description())->group(cli_group);
auto& debug = config.at("debug");
subcom->add_flag("--debug", debug.get_cli_config<bool>(), "Debug mode")->group("");