Skip to content

Commit

Permalink
pip packages support with list
Browse files Browse the repository at this point in the history
  • Loading branch information
Hind-M committed Oct 24, 2024
1 parent bd8c1d2 commit dfb6da4
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 36 deletions.
2 changes: 0 additions & 2 deletions libmamba/include/mamba/core/activation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,6 @@ namespace mamba
fs::u8path hook_source_path() override;
};

std::vector<fs::u8path> get_path_dirs(const fs::u8path& prefix);

} // namespace mamba

#endif
2 changes: 2 additions & 0 deletions libmamba/include/mamba/core/prefix_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ namespace mamba

PrefixData(const fs::u8path& prefix_path, ChannelContext& channel_context);

void load_site_packages();

History m_history;
package_map m_package_records;
fs::u8path m_prefix_path;
Expand Down
1 change: 1 addition & 0 deletions libmamba/include/mamba/specs/package_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ namespace mamba::specs
PackageInfo() = default;
explicit PackageInfo(std::string name);
PackageInfo(std::string name, std::string version, std::string build_string, std::size_t build_number);
PackageInfo(std::string name, std::string version, std::string build_string, std::string channel);

[[nodiscard]] auto json_signable() const -> nlohmann::json;
[[nodiscard]] auto str() const -> std::string;
Expand Down
6 changes: 6 additions & 0 deletions libmamba/include/mamba/util/environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "mamba/fs/filesystem.hpp"
#include "mamba/util/build.hpp"
Expand Down Expand Up @@ -93,6 +94,11 @@ namespace mamba::util
*/
[[nodiscard]] constexpr auto pathsep() -> char;

/**
* Return directories of the given prefix path.
*/
[[nodiscard]] auto get_path_dirs(const fs::u8path& prefix) -> std::vector<fs::u8path>;

/**
* Return the full path of a program from its name.
*/
Expand Down
2 changes: 1 addition & 1 deletion libmamba/src/api/install.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include "mamba/api/channel_loader.hpp"
#include "mamba/api/configuration.hpp"
#include "mamba/api/install.hpp"
#include "mamba/core/activation.hpp"
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/env_lockfile.hpp"
Expand All @@ -24,6 +23,7 @@
#include "mamba/download/downloader.hpp"
#include "mamba/fs/filesystem.hpp"
#include "mamba/solver/libsolv/solver.hpp"
#include "mamba/util/environment.hpp"
#include "mamba/util/path_manip.hpp"
#include "mamba/util/string.hpp"

Expand Down
37 changes: 27 additions & 10 deletions libmamba/src/api/list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,31 @@ namespace mamba
{
auto channels = channel_context.make_channel(pkg_info.package_url);
assert(channels.size() == 1); // A URL can only resolve to one channel
obj["base_url"] = strip_from_filename_and_platform(
channels.front().url().str(specs::CondaURL::Credentials::Remove),
pkg_info.filename,
pkg_info.platform
);

if (pkg_info.package_url.empty() && (pkg_info.channel == "pypi"))
{
// TODO Need to correctly set `platform`, which is empty in PyPI case
// Note that this is only a problem when using `--json`
// (otherwise, the missing info is not needed/used)
// cf. `formatted_pkgs` below
obj["base_url"] = "https://pypi.org/";
obj["channel"] = pkg_info.channel;
}
else
{
obj["base_url"] = strip_from_filename_and_platform(
channels.front().url().str(specs::CondaURL::Credentials::Remove),
pkg_info.filename,
pkg_info.platform
);
obj["channel"] = strip_from_filename_and_platform(
channels.front().display_name(),
pkg_info.filename,
pkg_info.platform
);
}
obj["build_number"] = pkg_info.build_number;
obj["build_string"] = pkg_info.build_string;
obj["channel"] = strip_from_filename_and_platform(
channels.front().display_name(),
pkg_info.filename,
pkg_info.platform
);
obj["dist_name"] = pkg_info.str();
obj["name"] = pkg_info.name;
obj["platform"] = pkg_info.platform;
Expand Down Expand Up @@ -132,6 +145,10 @@ namespace mamba
{
formatted_pkgs.channel = "";
}
else if (package.second.channel == "pypi")
{
formatted_pkgs.channel = package.second.channel;
}
else
{
auto channels = channel_context.make_channel(package.second.channel);
Expand Down
3 changes: 1 addition & 2 deletions libmamba/src/api/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

// TODO includes to be removed after moving some functions/structs around
#include "mamba/api/install.hpp" // other_pkg_mgr_spec
#include "mamba/core/activation.hpp" // get_path_dirs
#include "mamba/core/context.hpp"
#include "mamba/core/util.hpp"
#include "mamba/fs/filesystem.hpp"
Expand All @@ -33,7 +32,7 @@ namespace mamba
)
{
const auto get_python_path = [&]
{ return util::which_in("python", get_path_dirs(target_prefix)).string(); };
{ 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" };
Expand Down
23 changes: 3 additions & 20 deletions libmamba/src/core/activation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,23 +208,6 @@ namespace mamba
}
}

std::vector<fs::u8path> get_path_dirs(const fs::u8path& prefix)
{
if (util::on_win)
{
return { prefix,
prefix / "Library" / "mingw-w64" / "bin",
prefix / "Library" / "usr" / "bin",
prefix / "Library" / "bin",
prefix / "Scripts",
prefix / "bin" };
}
else
{
return { prefix / "bin" };
}
}

std::vector<fs::u8path> Activator::get_PATH()
{
std::vector<fs::u8path> path;
Expand Down Expand Up @@ -271,7 +254,7 @@ namespace mamba

// TODO check if path_conversion does something useful here.
// path_list[0:0] = list(self.path_conversion(self._get_path_dirs(prefix)))
std::vector<fs::u8path> final_path = get_path_dirs(prefix);
std::vector<fs::u8path> final_path = util::get_path_dirs(prefix);
final_path.insert(final_path.end(), path_list.begin(), path_list.end());
final_path.erase(std::unique(final_path.begin(), final_path.end()), final_path.end());
std::string result = util::join(util::pathsep(), final_path).string();
Expand All @@ -285,7 +268,7 @@ namespace mamba
std::vector<fs::u8path> current_path = get_PATH();
assert(!old_prefix.empty());

std::vector<fs::u8path> old_prefix_dirs = get_path_dirs(old_prefix);
std::vector<fs::u8path> old_prefix_dirs = util::get_path_dirs(old_prefix);

// remove all old paths
std::vector<fs::u8path> cleaned_path;
Expand All @@ -312,7 +295,7 @@ namespace mamba
std::vector<fs::u8path> final_path;
if (!new_prefix.empty())
{
final_path = get_path_dirs(new_prefix);
final_path = util::get_path_dirs(new_prefix);
final_path.insert(final_path.end(), current_path.begin(), current_path.end());

// remove duplicates
Expand Down
61 changes: 60 additions & 1 deletion libmamba/src/core/prefix_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
#include <unordered_map>
#include <utility>

#include <reproc++/run.hpp>

#include "mamba/core/channel_context.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/prefix_data.hpp"
#include "mamba/core/util.hpp"
#include "mamba/specs/conda_url.hpp"
#include "mamba/util/environment.hpp"
#include "mamba/util/graph.hpp"
#include "mamba/util/string.hpp"

Expand Down Expand Up @@ -56,6 +59,8 @@ namespace mamba
}
}
}
// Load packages installed with pip
load_site_packages();
}

void PrefixData::add_packages(const std::vector<specs::PackageInfo>& packages)
Expand Down Expand Up @@ -166,10 +171,64 @@ namespace mamba

auto channels = m_channel_context.make_channel(prec.channel);
// If someone wrote multichannel names in repodata_record, we don't know which one is the
// correct URL. This is must never happen!
// correct URL. This must never happen!
assert(channels.size() == 1);
using Credentials = specs::CondaURL::Credentials;
prec.channel = channels.front().platform_url(prec.platform).str(Credentials::Remove);
m_package_records.insert({ prec.name, std::move(prec) });
}

// Load python packages installed with pip in the site-packages of the prefix.
void PrefixData::load_site_packages()
{
LOG_INFO << "Loading site packages";

// Look for `pip` package and return if it doesn't exist
auto python_pkg_record = m_package_records.find("pip");
if (python_pkg_record == m_package_records.end())
{
LOG_DEBUG << "`pip` not found";
return;
}

// Run `pip freeze`
std::string out, err;

const auto get_python_path = [&]
{ return util::which_in("python", util::get_path_dirs(m_prefix_path)).string(); };

const auto args = std::array<std::string, 5>{ get_python_path(),
"-m",
"pip",
"freeze",
"--local" };
auto [status, ec] = reproc::run(
args,
reproc::options{},
reproc::sink::string(out),
reproc::sink::string(err)
);
if (ec)
{
throw std::runtime_error(ec.message());
}

// Nothing installed with `pip`
if (out.empty())
{
LOG_DEBUG << "Nothing installed with `pip`";
return;
}

auto pkgs_info_list = mamba::util::split(mamba::util::strip(out), "\n");
for (auto& pkg_info_line : pkgs_info_list)
{
if (pkg_info_line.find("==") != std::string::npos)
{
auto pkg_info = mamba::util::split(mamba::util::strip(pkg_info_line), "==");
auto prec = specs::PackageInfo(pkg_info[0], pkg_info[1], "pypi_0", "pypi");
m_package_records.insert({ prec.name, std::move(prec) });
}
}
}
} // namespace mamba
8 changes: 8 additions & 0 deletions libmamba/src/specs/package_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ namespace mamba::specs
{
}

PackageInfo::PackageInfo(std::string n, std::string v, std::string b, std::string c)
: name(std::move(n))
, version(std::move(v))
, build_string(std::move(b))
, channel(std::move(c))
{
}

namespace
{
template <typename T, typename U>
Expand Down
17 changes: 17 additions & 0 deletions libmamba/src/util/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,23 @@ namespace mamba::util
}
}

auto get_path_dirs(const fs::u8path& prefix) -> std::vector<fs::u8path>
{
if (on_win)
{
return { prefix,
prefix / "Library" / "mingw-w64" / "bin",
prefix / "Library" / "usr" / "bin",
prefix / "Library" / "bin",
prefix / "Scripts",
prefix / "bin" };
}
else
{
return { prefix / "bin" };
}
}

auto which(std::string_view exe) -> fs::u8path
{
if (auto paths = get_env("PATH"))
Expand Down
33 changes: 33 additions & 0 deletions micromamba/tests/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ def test_list_name(tmp_home, tmp_root_prefix, tmp_xtensor_env, quiet_flag):
assert full_names == ["xtensor"]


env_yaml_content_to_install_numpy_with_pip = """
channels:
- conda-forge
dependencies:
- pip
- pip:
- numpy==1.26.4
"""


@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_list_with_pip(tmp_home, tmp_root_prefix, tmp_path):
env_name = "env-list_with_pip"
tmp_root_prefix / "envs" / env_name

env_file_yml = tmp_path / "test_env_yaml_content_to_install_numpy_with_pip.yaml"
env_file_yml.write_text(env_yaml_content_to_install_numpy_with_pip)

helpers.create("-n", env_name, "python=3.12", "--json", no_dry_run=True)
helpers.install("-n", env_name, "-f", env_file_yml, "--json", no_dry_run=True)

res = helpers.umamba_list("-n", env_name, "--json")
assert any(
package["name"] == "numpy"
and package["version"] == "1.26.4"
and package["base_url"] == "https://pypi.org/"
and package["build_string"] == "pypi_0"
and package["channel"] == "pypi"
and package["platform"] == ""
for package in res
)


@pytest.mark.parametrize("env_selector", ["name", "prefix"])
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_not_existing(tmp_home, tmp_root_prefix, tmp_xtensor_env, env_selector):
Expand Down

0 comments on commit dfb6da4

Please sign in to comment.