This commit is contained in:
Cornelius Roemer 2025-07-28 16:32:35 +02:00 committed by GitHub
commit e0f2162f9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 106 additions and 17 deletions

View File

@ -32,6 +32,7 @@ namespace mamba
bool canonical = false;
bool export_ = false;
bool revisions = false;
bool direct_deps_only = false;
};
struct formatted_pkg
@ -152,6 +153,7 @@ namespace mamba
{ return (regex.empty() || std::regex_search(pkg_info.name, spec_pat)); };
auto all_records = prefix_data.all_pkg_mgr_records();
auto requested_specs = prefix_data.history().get_requested_specs_map();
if (ctx.output_params.json)
{
@ -185,6 +187,12 @@ namespace mamba
auto obj = nlohmann::json();
const auto& pkg_info = all_records.find(key)->second;
if (options.direct_deps_only
&& requested_specs.find(pkg_info.name) == requested_specs.end())
{
continue;
}
if (accept_package(pkg_info))
{
auto channels = channel_context.make_channel(pkg_info.package_url);
@ -213,25 +221,29 @@ namespace mamba
<< "\n\n";
formatted_pkg formatted_pkgs;
std::vector<formatted_pkg> packages;
// order list of packages from prefix_data by alphabetical order
for (const auto& package : all_records)
for (const auto& [key, pkg_info] : all_records)
{
if (accept_package(package.second))
if (options.direct_deps_only
&& requested_specs.find(pkg_info.name) == requested_specs.end())
{
auto channels = channel_context.make_channel(package.second.channel);
assert(channels.size() == 1); // A URL can only resolve to one channel
formatted_pkgs.channel = get_formatted_channel(package.second, channels.front());
formatted_pkgs.name = package.second.name;
formatted_pkgs.version = package.second.version;
formatted_pkgs.build = package.second.build_string;
formatted_pkgs.url = package.second.package_url;
formatted_pkgs.md5 = package.second.md5;
formatted_pkgs.sha256 = package.second.sha256;
formatted_pkgs.build_string = package.second.build_string;
formatted_pkgs.platform = package.second.platform;
continue;
}
if (accept_package(pkg_info))
{
auto channels = channel_context.make_channel(pkg_info.channel);
assert(channels.size() == 1);
formatted_pkgs.channel = get_formatted_channel(pkg_info, channels.front());
formatted_pkgs.name = pkg_info.name;
formatted_pkgs.version = pkg_info.version;
formatted_pkgs.build = pkg_info.build_string;
formatted_pkgs.url = pkg_info.package_url;
formatted_pkgs.md5 = pkg_info.md5;
formatted_pkgs.sha256 = pkg_info.sha256;
formatted_pkgs.build_string = pkg_info.build_string;
formatted_pkgs.platform = pkg_info.platform;
packages.push_back(formatted_pkgs);
}
}
@ -240,7 +252,6 @@ namespace mamba
: compare_alphabetically;
std::sort(packages.begin(), packages.end(), comparator);
// format and print output
if (options.revisions)
{
if (options.explicit_)
@ -333,7 +344,6 @@ namespace mamba
}
else
{
auto requested_specs = prefix_data.history().get_requested_specs_map();
printers::Table t({ "Name", "Version", "Build", "Channel" });
t.set_alignment({ printers::alignment::left,
printers::alignment::left,
@ -379,6 +389,7 @@ namespace mamba
options.canonical = config.at("canonical").value<bool>();
options.export_ = config.at("export").value<bool>();
options.revisions = config.at("revisions").value<bool>();
options.direct_deps_only = config.at("direct_deps_only").value<bool>();
auto channel_context = ChannelContext::make_conda_compatible(config.context());
detail::list_packages(config.context(), regex, channel_context, std::move(options));

View File

@ -80,6 +80,17 @@ init_list_parser(CLI::App* subcom, Configuration& config)
Configurable("revisions", false).group("cli").description("List the revision history.")
);
subcom->add_flag("--revisions", revisions.get_cli_config<bool>(), revisions.description());
auto& direct_deps_only = config.insert(
Configurable("direct_deps_only", false)
.group("cli")
.description("Show only directly installed packages (user requested).")
);
subcom->add_flag(
"--direct-deps-only",
direct_deps_only.get_cli_config<bool>(),
direct_deps_only.description()
);
}
void

View File

@ -1,6 +1,7 @@
import platform
import subprocess
import sys
import json
import pytest
import re
@ -154,6 +155,72 @@ def test_list_name(tmp_home, tmp_root_prefix, tmp_xtensor_env, quiet_flag):
assert full_names == ["xtensor"]
@pytest.mark.parametrize("direct_deps_flag", ["", "--direct-deps-only"])
@pytest.mark.parametrize("env_selector", ["", "name", "prefix"])
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_list_direct_deps_only_json(
tmp_home, tmp_root_prefix, tmp_env_name, tmp_xtensor_env, env_selector, direct_deps_flag
):
"""Check filtering with --direct-deps-only and JSON output."""
if env_selector == "prefix":
res_str = helpers.umamba_list("-p", tmp_xtensor_env, "--json", direct_deps_flag)
elif env_selector == "name":
res_str = helpers.umamba_list("-n", tmp_env_name, "--json", direct_deps_flag)
else:
# Use current env
res_str = helpers.umamba_list("--json", direct_deps_flag)
res = json.loads(res_str)
names = [i["name"] for i in res]
if direct_deps_flag == "--direct-deps-only":
assert "xtensor" in names # Explicitly installed
assert "xtl" not in names # Dependency
assert len(names) == 1 # Only xtensor should be listed
else:
assert "xtensor" in names
assert "xtl" in names # Dependency should be included without the flag
assert len(names) > 1
assert all(
i["channel"] == "conda-forge" and i["base_url"] == "https://conda.anaconda.org/conda-forge"
for i in res
)
@pytest.mark.parametrize("direct_deps_flag", ["", "--direct-deps-only"])
@pytest.mark.parametrize("env_selector", ["", "name", "prefix"])
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_list_direct_deps_only_no_json(
tmp_home, tmp_root_prefix, tmp_env_name, tmp_xtensor_env, env_selector, direct_deps_flag
):
"""Check filtering with --direct-deps-only and table output."""
if env_selector == "prefix":
res = helpers.umamba_list("-p", tmp_xtensor_env, direct_deps_flag)
elif env_selector == "name":
res = helpers.umamba_list("-n", tmp_env_name, direct_deps_flag)
else:
# Use current env
res = helpers.umamba_list(direct_deps_flag)
# Split lines and ignore header/blank lines
lines = [line.strip() for line in res.strip().split("\n") if line]
package_lines = [
line
for line in lines
if not line.startswith("#") and "Name" not in line and "Version" not in line
]
if direct_deps_flag == "--direct-deps-only":
assert any("xtensor" in line for line in package_lines)
assert not any("xtl" in line for line in package_lines)
assert len(package_lines) == 1 # Only xtensor should be listed
else:
assert any("xtensor" in line for line in package_lines)
assert any("xtl" in line for line in package_lines)
assert len(package_lines) > 1
env_yaml_content_to_install_numpy_with_pip = """
channels:
- conda-forge