diff --git a/libmamba/src/api/install.cpp b/libmamba/src/api/install.cpp index 8b6e0ff20..75aa17c05 100644 --- a/libmamba/src/api/install.cpp +++ b/libmamba/src/api/install.cpp @@ -28,15 +28,17 @@ namespace mamba { namespace { - tl::expected get_other_pkg_mgr_install_instructions( - const std::string& name, const std::string& target_prefix) + using command_args = std::vector; + + tl::expected get_other_pkg_mgr_install_instructions( + const std::string& name, const std::string& target_prefix, fs::path spec_file) { const auto get_python_path = [&] { return env::which("python", get_path_dirs(target_prefix)).u8string(); }; - const std::unordered_map other_pkg_mgr_install_instructions{ + const std::unordered_map other_pkg_mgr_install_instructions{ { "pip", - fmt::format("{} {}", get_python_path(), "-m pip install -r {0} --no-input") } + { get_python_path(), "-m", "pip", "install", "-r", spec_file, "--no-input" } } }; auto found_it = other_pkg_mgr_install_instructions.find(name); @@ -90,26 +92,25 @@ namespace mamba const auto& ctx = Context::instance(); - std::string install_instructions = [&] + TemporaryFile specs; + { + std::ofstream specs_f = open_ofstream(specs.path()); + for (auto& d : deps) + specs_f << d.c_str() << '\n'; + } + + command_args install_instructions = [&] { const auto maybe_instructions - = get_other_pkg_mgr_install_instructions(pkg_mgr, ctx.target_prefix); + = get_other_pkg_mgr_install_instructions(pkg_mgr, ctx.target_prefix, specs.path()); if (maybe_instructions) return maybe_instructions.value(); else throw maybe_instructions.error(); }(); - TemporaryFile specs; - std::ofstream specs_f = open_ofstream(specs.path()); - for (auto& d : deps) - specs_f << d.c_str() << '\n'; - specs_f.close(); - - replace_all(install_instructions, "{0}", specs.path()); - - std::vector install_args = split(install_instructions, " "); - auto [wrapped_command, tmpfile] = prepare_wrapped_call(ctx.target_prefix, install_args); + auto [wrapped_command, tmpfile] + = prepare_wrapped_call(ctx.target_prefix, install_instructions); reproc::options options; options.redirect.parent = true; @@ -118,7 +119,7 @@ namespace mamba Console::stream() << "\n" << termcolor::cyan << "Installing " << pkg_mgr << " packages: " << join(", ", deps) << termcolor::reset; - LOG_INFO << "Calling: " << join(" ", install_args); + LOG_INFO << "Calling: " << join(" ", install_instructions); auto [status, ec] = reproc::run(wrapped_command, options); assert_reproc_success(options, status, ec); diff --git a/micromamba/tests/env-requires-pip-install-with-spaces.yaml b/micromamba/tests/env-requires-pip-install-with-spaces.yaml new file mode 100644 index 000000000..2a413e38a --- /dev/null +++ b/micromamba/tests/env-requires-pip-install-with-spaces.yaml @@ -0,0 +1,8 @@ +name: test env with spaces +channels: +- conda-forge +dependencies: +- python=3.9 +- pip +- pip: + - pydantic diff --git a/micromamba/tests/env-requires-pip-install.yaml b/micromamba/tests/env-requires-pip-install.yaml new file mode 100644 index 000000000..eafc353d0 --- /dev/null +++ b/micromamba/tests/env-requires-pip-install.yaml @@ -0,0 +1,8 @@ +name: test_env +channels: +- conda-forge +dependencies: +- python=3.9 +- pip +- pip: + - pydantic diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index db4c7840b..997c35d17 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -13,6 +13,21 @@ from .helpers import * source_dir_path = os.path.dirname(os.path.realpath(__file__)) +this_source_file_dir_path = Path(__file__).parent.resolve() + +test_env_requires_pip_install_path = os.path.join( + this_source_file_dir_path, "env-requires-pip-install.yaml" +) + +test_env_requires_pip_install_path_with_whitespaces = os.path.join( + this_source_file_dir_path, "env-requires-pip-install-with-spaces.yaml" +) + +test_envs = [ + test_env_requires_pip_install_path, + test_env_requires_pip_install_path_with_whitespaces, +] + class TestCreate: @@ -641,3 +656,15 @@ class TestCreate: create(*cmd) assert (site_packages / pyc_fn).exists() assert pyc_fn.name in six_meta + + @pytest.mark.parametrize("env_file", test_envs) + def test_requires_pip_install(self, env_file): + prefix = Path(TestCreate.prefix) + cmd = ["-p", f"{prefix}", "-f", env_file] + create(*cmd) + + @pytest.mark.parametrize("env_file", test_envs) + def test_requires_pip_install_prefix_spaces(self, env_file): + prefix = Path(f"{TestCreate.prefix} with space") + cmd = ["-p", f"{prefix}", "-f", env_file] + create(*cmd)