Change MatchSpec::parse to named constructor (#3048)

This commit is contained in:
Antoine Prouvost 2023-12-07 16:06:02 +01:00 committed by GitHub
parent 96e9be052b
commit f36c3f222e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 220 additions and 282 deletions

View File

@ -14,6 +14,7 @@
#include <solv/solver.h>
#include <yaml-cpp/yaml.h>
#include "mamba/core/match_spec.hpp"
#include "mamba/core/package_cache.hpp"
#include "mamba/core/package_info.hpp"
#include "mamba/core/pool.hpp"
@ -87,11 +88,8 @@ namespace mamba
yaml_file_contents read_yaml_file(fs::u8path yaml_file, const std::string platform);
std::tuple<std::vector<PackageInfo>, std::vector<MatchSpec>> parse_urls_to_package_info(
const std::vector<std::string>& urls,
Context& ctx,
ChannelContext& channel_context
);
std::tuple<std::vector<PackageInfo>, std::vector<MatchSpec>>
parse_urls_to_package_info(const std::vector<std::string>& urls);
inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&)
{

View File

@ -113,11 +113,8 @@ namespace mamba
/// Read an environment lock YAML file and returns it's structured content or an error if
/// failed.
tl::expected<EnvironmentLockFile, mamba_error> read_environment_lockfile(
const Context& ctx,
ChannelContext& channel_context,
const mamba::fs::u8path& lockfile_location
);
tl::expected<EnvironmentLockFile, mamba_error>
read_environment_lockfile(const mamba::fs::u8path& lockfile_location);
/// Returns `true` if the filename matches names of files which should be interpreted as conda

View File

@ -52,7 +52,7 @@ namespace mamba
std::vector<ParseResult> parse();
bool parse_comment_line(const std::string& line, UserRequest& req);
std::vector<UserRequest> get_user_requests();
std::unordered_map<std::string, MatchSpec> get_requested_specs_map(const Context& ctx);
std::unordered_map<std::string, MatchSpec> get_requested_specs_map();
void add_entry(const History::UserRequest& entry);
fs::u8path m_prefix;

View File

@ -17,25 +17,18 @@
namespace mamba
{
class Context;
class ChannelContext;
class MatchSpec
{
public:
MatchSpec() = default;
[[nodiscard]] static auto parse_version_and_build(std::string_view s)
-> std::tuple<std::string, std::string>;
[[nodiscard]] static auto parse(std::string_view spec) -> MatchSpec;
MatchSpec(std::string_view i_spec);
[[nodiscard]] auto conda_build_form() const -> std::string;
[[nodiscard]] auto str() const -> std::string;
void parse();
std::string conda_build_form() const;
std::string str() const;
bool is_simple() const;
static std::tuple<std::string, std::string> parse_version_and_build(std::string_view s);
std::string spec;
[[nodiscard]] auto is_simple() const -> bool;
std::optional<specs::ChannelSpec> channel;
std::string name;
@ -51,6 +44,5 @@ namespace mamba
std::unordered_map<std::string, std::string> brackets;
std::unordered_map<std::string, std::string> parens;
};
} // namespace mamba
}
#endif

View File

@ -20,12 +20,7 @@ namespace mamba
class u8path;
}
std::string python_pin(
const Context& ctx,
ChannelContext& chc,
PrefixData& prefix_data,
const std::vector<std::string>& specs
);
std::string python_pin(PrefixData& prefix_data, const std::vector<std::string>& specs);
std::vector<std::string> file_pins(const fs::u8path& file);
}

View File

@ -342,11 +342,8 @@ namespace mamba
return result;
}
std::tuple<std::vector<PackageInfo>, std::vector<MatchSpec>> parse_urls_to_package_info(
const std::vector<std::string>& urls,
Context& ctx,
ChannelContext& channel_context
)
std::tuple<std::vector<PackageInfo>, std::vector<MatchSpec>>
parse_urls_to_package_info(const std::vector<std::string>& urls)
{
std::vector<PackageInfo> pi_result;
std::vector<MatchSpec> ms_result;
@ -357,7 +354,7 @@ namespace mamba
continue;
}
std::size_t hash = u.find_first_of('#');
MatchSpec ms(u.substr(0, hash));
auto ms = MatchSpec::parse(u.substr(0, hash));
PackageInfo p(ms.name);
p.url = ms.url;
p.build_string = ms.build_string;
@ -479,9 +476,9 @@ namespace mamba
// add channels from specs
for (const auto& s : specs)
{
if (auto m = MatchSpec{ s }; m.channel.has_value())
if (auto ms = MatchSpec::parse(s); ms.channel.has_value())
{
ctx.channels.push_back(m.channel->str());
ctx.channels.push_back(ms.channel->str());
}
}
@ -553,7 +550,7 @@ namespace mamba
if (!no_py_pin)
{
auto py_pin = python_pin(ctx, channel_context, prefix_data, specs);
auto py_pin = python_pin(prefix_data, specs);
if (!py_pin.empty())
{
solver.add_pin(py_pin);

View File

@ -95,7 +95,7 @@ namespace mamba
formatted_pkg formatted_pkgs;
std::vector<formatted_pkg> packages;
auto requested_specs = prefix_data.history().get_requested_specs_map(ctx);
auto requested_specs = prefix_data.history().get_requested_specs_map();
// order list of packages from prefix_data by alphabetical order
for (const auto& package : prefix_data.records())

View File

@ -113,7 +113,7 @@ namespace mamba
specs.begin(),
specs.end(),
std::back_inserter(mspecs),
[&](const auto& spec_str) { return MatchSpec{ spec_str }; }
[&](const auto& spec_str) { return MatchSpec::parse(spec_str); }
);
auto transaction = MTransaction(pool, mspecs, {}, package_caches);
execute_transaction(transaction);
@ -131,7 +131,7 @@ namespace mamba
);
History history(ctx.prefix_params.target_prefix, channel_context);
auto hist_map = history.get_requested_specs_map(ctx);
auto hist_map = history.get_requested_specs_map();
std::vector<std::string> keep_specs;
for (auto& it : hist_map)
{

View File

@ -38,7 +38,7 @@ namespace mamba
// add channels from specs
for (const auto& s : update_specs)
{
if (auto m = MatchSpec{ s }; m.channel.has_value())
if (auto m = MatchSpec::parse(s); m.channel.has_value())
{
ctx.channels.push_back(m.channel->str());
}
@ -93,7 +93,7 @@ namespace mamba
if (!no_py_pin)
{
auto py_pin = python_pin(ctx, channel_context, prefix_data, update_specs);
auto py_pin = python_pin(prefix_data, update_specs);
if (!py_pin.empty())
{
solver.add_pin(py_pin);
@ -114,7 +114,7 @@ namespace mamba
if (update_all)
{
auto hist_map = prefix_data.history().get_requested_specs_map(ctx);
auto hist_map = prefix_data.history().get_requested_specs_map();
std::vector<std::string> keep_specs;
for (auto& it : hist_map)
{
@ -132,7 +132,7 @@ namespace mamba
{
if (remove_not_specified)
{
auto hist_map = prefix_data.history().get_requested_specs_map(ctx);
auto hist_map = prefix_data.history().get_requested_specs_map();
std::vector<std::string> remove_specs;
for (auto& it : hist_map)
{

View File

@ -18,8 +18,7 @@ namespace mamba
{
using Package = EnvironmentLockFile::Package;
tl::expected<Package, mamba_error>
read_package_info(const Context& ctx, ChannelContext& channel_context, const YAML::Node& package_node)
tl::expected<Package, mamba_error> read_package_info(const YAML::Node& package_node)
{
Package package{
/* .info = */ mamba::PackageInfo{ package_node["name"].as<std::string>() },
@ -57,7 +56,7 @@ namespace mamba
}
package.info.url = package_node["url"].as<std::string>();
const MatchSpec spec{ package.info.url };
const auto spec = MatchSpec::parse(package.info.url);
package.info.fn = spec.fn;
package.info.build_string = spec.build_string;
if (spec.channel.has_value())
@ -152,11 +151,8 @@ namespace mamba
return metadata;
}
tl::expected<EnvironmentLockFile, mamba_error> read_environment_lockfile(
const Context& ctx,
ChannelContext& channel_context,
const YAML::Node& lockfile_yaml
)
tl::expected<EnvironmentLockFile, mamba_error>
read_environment_lockfile(const YAML::Node& lockfile_yaml)
{
const auto& maybe_metadata = read_metadata(lockfile_yaml["metadata"]);
if (!maybe_metadata)
@ -169,7 +165,7 @@ namespace mamba
std::vector<Package> packages;
for (const auto& package_node : lockfile_yaml["package"])
{
if (auto maybe_package = read_package_info(ctx, channel_context, package_node))
if (auto maybe_package = read_package_info(package_node))
{
packages.push_back(maybe_package.value());
}
@ -183,11 +179,8 @@ namespace mamba
}
}
tl::expected<EnvironmentLockFile, mamba_error> read_environment_lockfile(
const Context& ctx,
ChannelContext& channel_context,
const fs::u8path& lockfile_location
)
tl::expected<EnvironmentLockFile, mamba_error>
read_environment_lockfile(const fs::u8path& lockfile_location)
{
const auto file_path = fs::absolute(lockfile_location); // Having the complete path helps
// with logging and error reports.
@ -199,11 +192,7 @@ namespace mamba
switch (lockfile_version)
{
case 1:
return env_lockfile_v1::read_environment_lockfile(
ctx,
channel_context,
lockfile_content
);
return env_lockfile_v1::read_environment_lockfile(lockfile_content);
default:
{

View File

@ -6,6 +6,7 @@
#include <regex>
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/fsutil.hpp"
#include "mamba/core/history.hpp"
@ -190,7 +191,7 @@ namespace mamba
return res;
}
std::unordered_map<std::string, MatchSpec> History::get_requested_specs_map(const Context& ctx)
std::unordered_map<std::string, MatchSpec> History::get_requested_specs_map()
{
std::unordered_map<std::string, MatchSpec> map;
@ -200,7 +201,7 @@ namespace mamba
v.reserve(sv.size());
for (const auto& el : sv)
{
v.emplace_back(el);
v.emplace_back(MatchSpec::parse(el));
}
return v;
};

View File

@ -10,8 +10,6 @@
#include <fmt/format.h>
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/match_spec.hpp"
#include "mamba/core/output.hpp"
#include "mamba/specs/archive.hpp"
@ -23,7 +21,7 @@ namespace mamba
{
namespace
{
std::vector<std::string> parse_legacy_dist(std::string_view dist)
auto parse_legacy_dist(std::string_view dist) -> std::vector<std::string>
{
auto dist_str = std::string(specs::strip_archive_extension(dist));
auto split_str = util::rsplit(dist_str, "-", 2);
@ -36,13 +34,8 @@ namespace mamba
}
}
MatchSpec::MatchSpec(std::string_view i_spec)
: spec(i_spec)
{
parse();
}
std::tuple<std::string, std::string> MatchSpec::parse_version_and_build(std::string_view s)
auto MatchSpec::parse_version_and_build(std::string_view s)
-> std::tuple<std::string, std::string>
{
const std::size_t pos = s.find_last_of(" =");
if (pos == s.npos || pos == 0)
@ -75,16 +68,16 @@ namespace mamba
}
}
void MatchSpec::parse()
auto MatchSpec::parse(std::string_view spec) -> MatchSpec
{
std::string spec_str = spec;
auto spec_str = std::string(spec);
auto out = MatchSpec();
if (spec_str.empty())
{
return;
return out;
}
LOG_INFO << "Parsing MatchSpec " << spec;
std::size_t idx = spec_str.find('#');
if (idx != std::string::npos)
if (std::size_t idx = spec_str.find('#'); idx != std::string::npos)
{
spec_str = spec_str.substr(0, idx);
}
@ -92,16 +85,16 @@ namespace mamba
if (specs::has_archive_extension(spec_str))
{
channel = specs::ChannelSpec::parse(spec_str);
auto [path, pkg] = util::rsplit_once(channel->location(), '/');
out.channel = specs::ChannelSpec::parse(spec_str);
auto [path, pkg] = util::rsplit_once(out.channel->location(), '/');
auto dist = parse_legacy_dist(pkg);
name = dist[0];
version = dist[1];
build_string = dist[2];
fn = std::string(pkg);
url = util::path_or_url_to_url(spec_str);
is_file = true;
return;
out.name = dist[0];
out.version = dist[1];
out.build_string = dist[2];
out.fn = std::string(pkg);
out.url = util::path_or_url_to_url(spec_str);
out.is_file = true;
return out;
}
auto extract_kv = [&spec_str](const std::string& kv_string, auto& map)
@ -116,7 +109,9 @@ namespace mamba
auto value = kv_match[3].str();
if (key.size() == 0 || value.size() == 0)
{
throw std::runtime_error("key-value mismatch in brackets " + spec_str);
throw std::runtime_error(
util::concat(R"(key-value mismatch in brackets ")", spec_str, '"')
);
}
text_iter += kv_match.position() + kv_match.length();
map[key] = value;
@ -131,7 +126,7 @@ namespace mamba
{
auto brackets_str = match[1].str();
brackets_str = brackets_str.substr(1, brackets_str.size() - 2);
extract_kv(brackets_str, brackets);
extract_kv(brackets_str, out.brackets);
spec_str.erase(
static_cast<std::size_t>(match.position(1)),
static_cast<std::size_t>(match.length(1))
@ -144,10 +139,10 @@ namespace mamba
{
auto parens_str = match[1].str();
parens_str = parens_str.substr(1, parens_str.size() - 2);
extract_kv(parens_str, this->parens);
extract_kv(parens_str, out.parens);
if (parens_str.find("optional") != parens_str.npos)
{
optional = true;
out.optional = true;
}
spec_str.erase(
static_cast<std::size_t>(match.position(1)),
@ -160,13 +155,13 @@ namespace mamba
std::string channel_str;
if (m5_len == 3)
{
channel = specs::ChannelSpec::parse(m5[0]);
ns = m5[1];
out.channel = specs::ChannelSpec::parse(m5[0]);
out.ns = m5[1];
spec_str = m5[2];
}
else if (m5_len == 2)
{
ns = m5[0];
out.ns = m5[0];
spec_str = m5[1];
}
else if (m5_len == 1)
@ -195,9 +190,9 @@ namespace mamba
std::smatch vb_match;
if (std::regex_match(spec_str, vb_match, version_build_re))
{
name = vb_match[1].str();
version = util::strip(vb_match[2].str());
if (name.size() == 0)
out.name = vb_match[1].str();
out.version = util::strip(vb_match[2].str());
if (out.name.size() == 0)
{
throw std::runtime_error("Invalid spec, no package name found: " + spec_str);
}
@ -209,113 +204,120 @@ namespace mamba
// # Step 7. otherwise sort out version + build
// spec_str = spec_str and spec_str.strip()
if (!version.empty())
if (!out.version.empty())
{
if (version.find("[") != version.npos)
if (out.version.find('[') != out.version.npos)
{
throw std::runtime_error(
"Invalid match spec: multiple bracket sections not allowed " + spec
);
throw std::runtime_error(util::concat(
R"(Invalid match spec: multiple bracket sections not allowed ")",
spec,
'"'
));
}
version = std::string(util::strip(version));
auto [pv, pb] = parse_version_and_build(std::string(util::strip(version)));
out.version = std::string(util::strip(out.version));
auto [pv, pb] = parse_version_and_build(std::string(util::strip(out.version)));
version = pv;
build_string = pb;
out.version = pv;
out.build_string = pb;
// translate version '=1.2.3' to '1.2.3*'
// is it a simple version starting with '='? i.e. '=1.2.3'
if (version.size() >= 2 && version[0] == '=')
if (out.version.size() >= 2 && out.version[0] == '=')
{
auto rest = version.substr(1);
if (version[1] == '=' && build_string.empty())
auto rest = out.version.substr(1);
if (out.version[1] == '=' && out.build_string.empty())
{
version = version.substr(2);
out.version = out.version.substr(2);
}
else if (rest.find_first_of("=,|") == rest.npos)
{
if (build_string.empty() && version.back() != '*')
if (out.build_string.empty() && out.version.back() != '*')
{
version = util::concat(version, "*");
out.version = util::concat(out.version, "*");
}
else
{
version = rest;
out.version = rest;
}
}
}
}
else
{
version = "";
build_string = "";
out.version = "";
out.build_string = "";
}
// TODO think about using a hash function here, (and elsewhere), like:
// https://hbfs.wordpress.com/2017/01/10/strings-in-c-switchcase-statements/
for (auto& [k, v] : brackets)
for (auto& [k, v] : out.brackets)
{
if (k == "build_number")
{
build_number = v;
out.build_number = v;
}
else if (k == "build")
{
build_string = v;
out.build_string = v;
}
else if (k == "version")
{
version = v;
out.version = v;
}
else if (k == "channel")
{
if (!channel.has_value())
if (!out.channel.has_value())
{
channel = specs::ChannelSpec::parse(v);
out.channel = specs::ChannelSpec::parse(v);
}
else
{
// Subdirs might have been set with a previous subdir key
auto subdirs = channel->clear_platform_filters();
channel = specs::ChannelSpec::parse(v);
auto subdirs = out.channel->clear_platform_filters();
out.channel = specs::ChannelSpec::parse(v);
if (!subdirs.empty())
{
channel = specs::ChannelSpec(
channel->clear_location(),
out.channel = specs::ChannelSpec(
out.channel->clear_location(),
std::move(subdirs),
channel->type()
out.channel->type()
);
}
}
}
else if (k == "subdir")
{
if (!channel.has_value())
if (!out.channel.has_value())
{
channel = specs::ChannelSpec("", { v }, specs::ChannelSpec::Type::Unknown);
out.channel = specs::ChannelSpec("", { v }, specs::ChannelSpec::Type::Unknown);
}
// Subdirs specified in the channel part have higher precedence
else if (channel->platform_filters().empty())
else if (out.channel->platform_filters().empty())
{
channel = specs::ChannelSpec(channel->clear_location(), { v }, channel->type());
out.channel = specs::ChannelSpec(
out.channel->clear_location(),
{ v },
out.channel->type()
);
}
}
else if (k == "url")
{
is_file = true;
url = v;
out.is_file = true;
out.url = v;
}
else if (k == "fn")
{
is_file = true;
fn = v;
out.is_file = true;
out.fn = v;
}
}
return out;
}
std::string MatchSpec::conda_build_form() const
auto MatchSpec::conda_build_form() const -> std::string
{
std::stringstream res;
res << name;
@ -331,7 +333,7 @@ namespace mamba
return res.str();
}
std::string MatchSpec::str() const
auto MatchSpec::str() const -> std::string
{
std::stringstream res;
// builder = []
@ -478,7 +480,7 @@ namespace mamba
return res.str();
}
bool MatchSpec::is_simple() const
auto MatchSpec::is_simple() const -> bool
{
return version.empty() && build_string.empty() && build_number.empty();
}

View File

@ -14,12 +14,7 @@
namespace mamba
{
std::string python_pin(
const Context& ctx,
ChannelContext& channel_context,
PrefixData& prefix_data,
const std::vector<std::string>& specs
)
std::string python_pin(PrefixData& prefix_data, const std::vector<std::string>& specs)
{
std::string pin = "";
std::string py_version;
@ -36,8 +31,7 @@ namespace mamba
for (const auto& spec : specs)
{
MatchSpec ms{ spec };
if (ms.name == "python")
if (MatchSpec::parse(spec).name == "python")
{
return "";
}

View File

@ -241,7 +241,7 @@ namespace mamba
throw std::runtime_error(fmt::format(
R"(The package "{}" is not available for the specified platform{} ({}))"
R"( but is available on {}.)",
ms.spec,
ms.str(),
filters.size() > 1 ? "s" : "",
fmt::join(filters, ", "),
other_subdir_match
@ -252,7 +252,7 @@ namespace mamba
throw std::runtime_error(fmt::format(
R"(The package "{}" is not found in any loaded channels.)"
R"( Try adding more channels or subdirs.)",
ms.spec
ms.str()
));
}
}

View File

@ -101,7 +101,7 @@ namespace mamba
for (const auto& dep : record->depends)
{
// Creating a matchspec to parse the name (there may be a channel)
auto ms = MatchSpec{ dep };
auto ms = MatchSpec::parse(dep);
// Ignoring unmatched dependencies, the environment could be broken
// or it could be a matchspec
const auto from_iter = name_to_node_id.find(ms.name);

View File

@ -123,7 +123,7 @@ namespace mamba
{
for (const auto& job : jobs)
{
MatchSpec ms{ job };
auto ms = MatchSpec::parse(job);
int job_type = job_flag & SOLVER_JOBMASK;
if (ms.conda_build_form().empty())
@ -133,15 +133,15 @@ namespace mamba
if (job_type & SOLVER_INSTALL)
{
m_install_specs.emplace_back(job);
m_install_specs.emplace_back(MatchSpec::parse(job));
}
else if (job_type == SOLVER_ERASE)
{
m_remove_specs.emplace_back(job);
m_remove_specs.emplace_back(MatchSpec::parse(job));
}
else if (job_type == SOLVER_LOCK)
{
m_neuter_specs.emplace_back(job); // not used for the moment
m_neuter_specs.emplace_back(MatchSpec::parse(job)); // not used for the moment
}
const ::Id job_id = m_pool.matchspec2id(ms);
@ -171,7 +171,10 @@ namespace mamba
void MSolver::add_constraint(const std::string& job)
{
m_jobs->push_back(SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES, m_pool.matchspec2id({ job }));
m_jobs->push_back(
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
m_pool.matchspec2id(MatchSpec::parse(job))
);
}
void MSolver::add_pin(const std::string& pin)
@ -202,7 +205,7 @@ namespace mamba
// as one of its constrains.
// Then we lock this solvable and force the re-checking of its dependencies.
const auto pin_ms = MatchSpec{ pin };
const auto pin_ms = MatchSpec::parse(pin);
m_pinned_specs.push_back(pin_ms);
auto& pool = m_pool.pool();
@ -613,9 +616,9 @@ namespace mamba
);
node_id cons_id = add_solvable(
problem.dep_id,
ConstraintNode{ { dep.value() } }
ConstraintNode{ MatchSpec::parse(dep.value()) }
);
MatchSpec edge(dep.value());
MatchSpec edge(MatchSpec::parse(dep.value()));
m_graph.add_edge(src_id, cons_id, std::move(edge));
add_conflict(cons_id, tgt_id);
break;
@ -635,7 +638,7 @@ namespace mamba
problem.source_id,
PackageNode{ std::move(source).value() }
);
MatchSpec edge(dep.value());
MatchSpec edge(MatchSpec::parse(dep.value()));
bool added = add_expanded_deps_edges(src_id, problem.dep_id, edge);
if (!added)
{
@ -654,7 +657,7 @@ namespace mamba
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value());
MatchSpec edge(MatchSpec::parse(dep.value()));
bool added = add_expanded_deps_edges(m_root_node, problem.dep_id, edge);
if (!added)
{
@ -673,10 +676,10 @@ namespace mamba
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value());
MatchSpec edge(MatchSpec::parse(dep.value()));
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{ { std::move(dep).value() } }
UnresolvedDependencyNode{ MatchSpec::parse(dep.value()) }
);
m_graph.add_edge(m_root_node, dep_id, std::move(edge));
break;
@ -692,14 +695,14 @@ namespace mamba
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value());
MatchSpec edge(MatchSpec::parse(dep.value()));
node_id src_id = add_solvable(
problem.source_id,
PackageNode{ std::move(source).value() }
);
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{ { std::move(dep).value() } }
UnresolvedDependencyNode{ MatchSpec::parse(dep.value()) }
);
m_graph.add_edge(src_id, dep_id, std::move(edge));
break;
@ -742,7 +745,7 @@ namespace mamba
// how the solver is handling this package, as this is resolved in term of
// installed packages and solver flags (allow downgrade...) rather than a
// dependency.
MatchSpec edge(source.value().name);
MatchSpec edge(MatchSpec::parse(source.value().name));
// The package cannot exist without its name in the pool
assert(m_pool.pool().find_string(edge.name).has_value());
const auto dep_id = m_pool.pool().find_string(edge.name).value();

View File

@ -582,7 +582,7 @@ namespace mamba
std::vector<MatchSpec> specs_to_install;
for (const auto& pkginfo : packages)
{
specs_to_install.push_back(MatchSpec(
specs_to_install.push_back(MatchSpec::parse(
fmt::format("{}=={}={}", pkginfo.name, pkginfo.version, pkginfo.build_string)
));
}
@ -1380,7 +1380,7 @@ namespace mamba
}
const auto hash_idx = url.find_first_of('#');
specs_to_install.emplace_back(url.substr(0, hash_idx));
specs_to_install.emplace_back(MatchSpec::parse(url.substr(0, hash_idx)));
MatchSpec& ms = specs_to_install.back();
if (hash_idx != std::string::npos)
@ -1407,11 +1407,7 @@ namespace mamba
std::vector<detail::other_pkg_mgr_spec>& other_specs
)
{
const auto maybe_lockfile = read_environment_lockfile(
pool.context(),
pool.channel_context(),
env_lockfile_path
);
const auto maybe_lockfile = read_environment_lockfile(env_lockfile_path);
if (!maybe_lockfile)
{
throw maybe_lockfile.error(); // NOTE: we cannot return an `un/expected` because

View File

@ -20,13 +20,7 @@ namespace mamba
{
TEST_CASE("absent_file_fails")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const auto maybe_lockfile = read_environment_lockfile(
ctx,
channel_context,
"this/file/does/not/exists"
);
const auto maybe_lockfile = read_environment_lockfile("this/file/does/not/exists");
REQUIRE_FALSE(maybe_lockfile);
const auto error = maybe_lockfile.error();
REQUIRE_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code());
@ -45,15 +39,9 @@ namespace mamba
TEST_CASE("invalid_version_fails")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path invalid_version_lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/bad_version-lock.yaml" };
const auto maybe_lockfile = read_environment_lockfile(
ctx,
channel_context,
invalid_version_lockfile_path
);
const auto maybe_lockfile = read_environment_lockfile(invalid_version_lockfile_path);
REQUIRE_FALSE(maybe_lockfile);
const auto error = maybe_lockfile.error();
REQUIRE_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code());
@ -63,11 +51,9 @@ namespace mamba
TEST_CASE("valid_no_package_succeed")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_no_package-lock.yaml" };
const auto maybe_lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path);
const auto maybe_lockfile = read_environment_lockfile(lockfile_path);
REQUIRE_MESSAGE(maybe_lockfile, maybe_lockfile.error().what());
const auto lockfile = maybe_lockfile.value();
CHECK(lockfile.get_all_packages().empty());
@ -75,11 +61,9 @@ namespace mamba
TEST_CASE("invalid_package_fails")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/bad_package-lock.yaml" };
const auto maybe_lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path);
const auto maybe_lockfile = read_environment_lockfile(lockfile_path);
REQUIRE_FALSE(maybe_lockfile);
const auto error = maybe_lockfile.error();
REQUIRE_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code());
@ -89,11 +73,9 @@ namespace mamba
TEST_CASE("valid_one_package_succeed")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_one_package-lock.yaml" };
const auto maybe_lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path);
const auto maybe_lockfile = read_environment_lockfile(lockfile_path);
REQUIRE_MESSAGE(maybe_lockfile, maybe_lockfile.error().what());
const auto lockfile = maybe_lockfile.value();
CHECK_EQ(lockfile.get_all_packages().size(), 1);
@ -101,12 +83,10 @@ namespace mamba
TEST_CASE("valid_one_package_implicit_category")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{
mambatests::test_data_dir / "env_lockfile/good_one_package_missing_category-lock.yaml"
};
const auto maybe_lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path);
const auto maybe_lockfile = read_environment_lockfile(lockfile_path);
REQUIRE_MESSAGE(maybe_lockfile, maybe_lockfile.error().what());
const auto lockfile = maybe_lockfile.value();
CHECK_EQ(lockfile.get_all_packages().size(), 1);
@ -114,11 +94,9 @@ namespace mamba
TEST_CASE("valid_multiple_packages_succeed")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_multiple_packages-lock.yaml" };
const auto maybe_lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path);
const auto maybe_lockfile = read_environment_lockfile(lockfile_path);
REQUIRE_MESSAGE(maybe_lockfile, maybe_lockfile.error().what());
const auto lockfile = maybe_lockfile.value();
CHECK_GT(lockfile.get_all_packages().size(), 1);
@ -126,11 +104,9 @@ namespace mamba
TEST_CASE("get_specific_packages")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_multiple_packages-lock.yaml" };
const auto lockfile = read_environment_lockfile(ctx, channel_context, lockfile_path).value();
const auto lockfile = read_environment_lockfile(lockfile_path).value();
CHECK(lockfile.get_packages_for("", "", "").empty());
{
const auto packages = lockfile.get_packages_for("main", "linux-64", "conda");

View File

@ -65,34 +65,34 @@ TEST_SUITE("MatchSpec")
TEST_CASE("parse")
{
{
MatchSpec ms("xtensor==0.12.3");
auto ms = MatchSpec::parse("xtensor==0.12.3");
CHECK_EQ(ms.version, "0.12.3");
CHECK_EQ(ms.name, "xtensor");
}
{
MatchSpec ms("");
auto ms = MatchSpec::parse("");
CHECK_EQ(ms.version, "");
CHECK_EQ(ms.name, "");
}
{
MatchSpec ms("ipykernel");
auto ms = MatchSpec::parse("ipykernel");
CHECK_EQ(ms.version, "");
CHECK_EQ(ms.name, "ipykernel");
}
{
MatchSpec ms("ipykernel ");
auto ms = MatchSpec::parse("ipykernel ");
CHECK_EQ(ms.version, "");
CHECK_EQ(ms.name, "ipykernel");
}
{
MatchSpec ms("numpy 1.7*");
auto ms = MatchSpec::parse("numpy 1.7*");
CHECK_EQ(ms.version, "1.7*");
CHECK_EQ(ms.name, "numpy");
CHECK_EQ(ms.conda_build_form(), "numpy 1.7*");
CHECK_EQ(ms.str(), "numpy=1.7");
}
{
MatchSpec ms("numpy[version='1.7|1.8']");
auto ms = MatchSpec::parse("numpy[version='1.7|1.8']");
// TODO!
// CHECK_EQ(ms.version, "1.7|1.8");
CHECK_EQ(ms.name, "numpy");
@ -100,7 +100,7 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.str(), "numpy[version='1.7|1.8']");
}
{
MatchSpec ms("conda-forge/linux-64::xtensor==0.12.3");
auto ms = MatchSpec::parse("conda-forge/linux-64::xtensor==0.12.3");
CHECK_EQ(ms.version, "0.12.3");
CHECK_EQ(ms.name, "xtensor");
REQUIRE(ms.channel.has_value());
@ -109,7 +109,7 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.optional, false);
}
{
MatchSpec ms("conda-forge::foo[build=3](target=blarg,optional)");
auto ms = MatchSpec::parse("conda-forge::foo[build=3](target=blarg,optional)");
CHECK_EQ(ms.version, "");
CHECK_EQ(ms.name, "foo");
REQUIRE(ms.channel.has_value());
@ -119,19 +119,19 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.optional, true);
}
{
MatchSpec ms("python[build_number=3]");
auto ms = MatchSpec::parse("python[build_number=3]");
CHECK_EQ(ms.name, "python");
CHECK_EQ(ms.brackets["build_number"], "3");
CHECK_EQ(ms.build_number, "3");
}
{
MatchSpec ms("python[build_number='<=3']");
auto ms = MatchSpec::parse("python[build_number='<=3']");
CHECK_EQ(ms.name, "python");
CHECK_EQ(ms.brackets["build_number"], "<=3");
CHECK_EQ(ms.build_number, "<=3");
}
{
MatchSpec ms(
auto ms = MatchSpec::parse(
"https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
);
CHECK_EQ(ms.name, "_libgcc_mutex");
@ -144,7 +144,9 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.fn, "_libgcc_mutex-0.1-conda_forge.tar.bz2");
}
{
MatchSpec ms("/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2");
auto ms = MatchSpec::parse(
"/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
);
CHECK_EQ(ms.name, "_libgcc_mutex");
CHECK_EQ(ms.version, "0.1");
CHECK_EQ(ms.build_string, "conda_forge");
@ -171,7 +173,8 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.fn, "_libgcc_mutex-0.1-conda_forge.tar.bz2");
}
{
MatchSpec ms("xtensor[url=file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2]"
auto ms = MatchSpec::parse(
"xtensor[url=file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2]"
);
CHECK_EQ(ms.name, "xtensor");
CHECK_EQ(
@ -181,62 +184,64 @@ TEST_SUITE("MatchSpec")
CHECK_EQ(ms.url, "file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2");
}
{
MatchSpec ms("foo=1.0=2");
auto ms = MatchSpec::parse("foo=1.0=2");
CHECK_EQ(ms.conda_build_form(), "foo 1.0 2");
CHECK_EQ(ms.str(), "foo==1.0=2");
}
{
MatchSpec ms("foo=1.0=2[md5=123123123, license=BSD-3, fn='test 123.tar.bz2']");
auto ms = MatchSpec::parse("foo=1.0=2[md5=123123123, license=BSD-3, fn='test 123.tar.bz2']"
);
CHECK_EQ(ms.conda_build_form(), "foo 1.0 2");
CHECK_EQ(ms.str(), "foo==1.0=2[md5=123123123,license=BSD-3,fn='test 123.tar.bz2']");
}
{
MatchSpec ms("foo=1.0=2[md5=123123123, license=BSD-3, fn='test 123.tar.bz2', url='abcdef']"
auto ms = MatchSpec::parse(
"foo=1.0=2[md5=123123123, license=BSD-3, fn='test 123.tar.bz2', url='abcdef']"
);
CHECK_EQ(ms.conda_build_form(), "foo 1.0 2");
CHECK_EQ(ms.str(), "foo==1.0=2[url=abcdef,md5=123123123,license=BSD-3]");
}
{
MatchSpec ms("libblas=*=*mkl");
auto ms = MatchSpec::parse("libblas=*=*mkl");
CHECK_EQ(ms.conda_build_form(), "libblas * *mkl");
// CHECK_EQ(ms.str(), "foo==1.0=2");
}
{
MatchSpec ms("libblas=0.15*");
auto ms = MatchSpec::parse("libblas=0.15*");
CHECK_EQ(ms.conda_build_form(), "libblas 0.15*");
}
{
MatchSpec ms("xtensor =0.15*");
auto ms = MatchSpec::parse("xtensor =0.15*");
CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*");
CHECK_EQ(ms.str(), "xtensor=0.15");
}
{
MatchSpec ms("numpy=1.20");
auto ms = MatchSpec::parse("numpy=1.20");
CHECK_EQ(ms.str(), "numpy=1.20");
}
{
MatchSpec ms("conda-forge::tzdata");
auto ms = MatchSpec::parse("conda-forge::tzdata");
CHECK_EQ(ms.str(), "conda-forge::tzdata");
}
{
MatchSpec ms("conda-forge::noarch/tzdata");
auto ms = MatchSpec::parse("conda-forge::noarch/tzdata");
CHECK_EQ(ms.str(), "conda-forge::noarch/tzdata");
}
{
MatchSpec ms("pkgs/main::tzdata");
auto ms = MatchSpec::parse("pkgs/main::tzdata");
CHECK_EQ(ms.str(), "pkgs/main::tzdata");
}
{
MatchSpec ms("pkgs/main/noarch::tzdata");
auto ms = MatchSpec::parse("pkgs/main/noarch::tzdata");
CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata");
}
{
MatchSpec ms("conda-forge[noarch]::tzdata[subdir=linux64]");
auto ms = MatchSpec::parse("conda-forge[noarch]::tzdata[subdir=linux64]");
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
}
{
MatchSpec ms("conda-forge::tzdata[subdir=mamba-37]");
auto ms = MatchSpec::parse("conda-forge::tzdata[subdir=mamba-37]");
CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata");
}
}
@ -244,23 +249,23 @@ TEST_SUITE("MatchSpec")
TEST_CASE("is_simple")
{
{
MatchSpec ms("libblas");
auto ms = MatchSpec::parse("libblas");
CHECK(ms.is_simple());
}
{
MatchSpec ms("libblas=12.9=abcdef");
auto ms = MatchSpec::parse("libblas=12.9=abcdef");
CHECK_FALSE(ms.is_simple());
}
{
MatchSpec ms("libblas=0.15*");
auto ms = MatchSpec::parse("libblas=0.15*");
CHECK_FALSE(ms.is_simple());
}
{
MatchSpec ms("libblas[version=12.2]");
auto ms = MatchSpec::parse("libblas[version=12.2]");
CHECK_FALSE(ms.is_simple());
}
{
MatchSpec ms("xtensor =0.15*");
auto ms = MatchSpec::parse("xtensor =0.15*");
CHECK_FALSE(ms.is_simple());
}
}

View File

@ -7,7 +7,6 @@
#include <doctest/doctest.h>
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/pinning.hpp"
#include "mamba/core/prefix_data.hpp"
#include "mamba/core/util.hpp"
@ -25,7 +24,6 @@ namespace mamba
std::vector<std::string> specs;
std::string pin;
const auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(mambatests::context());
auto sprefix_data = PrefixData::create("", channel_context);
if (!sprefix_data)
@ -37,27 +35,27 @@ namespace mamba
REQUIRE_EQ(prefix_data.records().size(), 0);
specs = { "python" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python-test" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python=3" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python==3.8" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python==3.8.3" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "numpy" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
PackageInfo pkg_info("python", "3.7.10", "abcde", 0);
@ -65,35 +63,35 @@ namespace mamba
REQUIRE_EQ(prefix_data.records().size(), 1);
specs = { "python" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "numpy" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "python 3.7.*");
specs = { "python-test" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "python 3.7.*");
specs = { "python==3" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python=3.*" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python=3.8" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "python=3.8.3" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
specs = { "numpy", "python" };
pin = python_pin(ctx, channel_context, prefix_data, specs);
pin = python_pin(prefix_data, specs);
CHECK_EQ(pin, "");
}

View File

@ -291,8 +291,19 @@ bind_submodule_impl(pybind11::module_ m)
py::add_ostream_redirect(m, "ostream_redirect");
py::class_<MatchSpec>(m, "MatchSpec")
.def_static("parse", &MatchSpec::parse)
.def(py::init<>())
.def(py::init<std::string_view>(), py::arg("spec"))
.def(
// Deprecating would lead to confusing error. Better to make sure people stop using it.
py::init(
[](std::string_view) -> MatchSpec {
throw std::invalid_argument(
"Use 'MatchSpec.parse' to create a new object from a string"
);
}
),
py::arg("spec")
)
.def("conda_build_form", &MatchSpec::conda_build_form);
py::class_<MPool>(m, "Pool")
@ -307,12 +318,7 @@ bind_submodule_impl(pybind11::module_ m)
.def("set_debuglevel", &MPool::set_debuglevel)
.def("create_whatprovides", &MPool::create_whatprovides)
.def("select_solvables", &MPool::select_solvables, py::arg("id"), py::arg("sorted") = false)
.def("matchspec2id", &MPool::matchspec2id, py::arg("ms"))
.def(
"matchspec2id",
[](MPool& self, std::string_view spec) { return self.matchspec2id({ spec }); },
py::arg("spec")
)
.def("matchspec2id", &MPool::matchspec2id, py::arg("spec"))
.def("id2pkginfo", &MPool::id2pkginfo, py::arg("id"));
py::class_<MultiPackageCache>(m, "MultiPackageCache")
@ -490,10 +496,7 @@ bind_submodule_impl(pybind11::module_ m)
py::arg("path"),
py::arg("channel_context")
)
.def(
"get_requested_specs_map",
[](History& self) { return self.get_requested_specs_map(mambapy::singletons.context()); }
);
.def("get_requested_specs_map", &History::get_requested_specs_map);
/*py::class_<Query>(m, "Query")
.def(py::init<MPool&>())

View File

@ -9,8 +9,6 @@
#include "constructor.hpp"
#include "mamba/api/configuration.hpp"
#include "mamba/api/install.hpp"
#include "mamba/core/channel_context.hpp"
#include "mamba/core/match_spec.hpp"
#include "mamba/core/package_handling.hpp"
#include "mamba/core/package_info.hpp"
#include "mamba/core/subdirdata.hpp"
@ -80,8 +78,6 @@ construct(Configuration& config, const fs::u8path& prefix, bool extract_conda_pk
std::map<std::string, nlohmann::json> repodatas;
auto channel_context = ChannelContext::make_conda_compatible(config.context());
if (extract_conda_pkgs)
{
auto find_package = [](nlohmann::json& j, const std::string& fn) -> nlohmann::json
@ -106,11 +102,7 @@ construct(Configuration& config, const fs::u8path& prefix, bool extract_conda_pk
fs::u8path pkgs_dir = prefix / "pkgs";
fs::u8path urls_file = pkgs_dir / "urls";
auto [package_details, _] = detail::parse_urls_to_package_info(
read_lines(urls_file),
config.context(),
channel_context
);
auto [package_details, _] = detail::parse_urls_to_package_info(read_lines(urls_file));
for (const auto& pkg_info : package_details)
{

View File

@ -163,7 +163,7 @@ set_env_command(CLI::App* com, Configuration& config)
std::cout << "name: " << get_env_name(ctx, ctx.prefix_params.target_prefix) << "\n";
std::cout << "channels:\n";
auto requested_specs_map = hist.get_requested_specs_map(ctx);
auto requested_specs_map = hist.get_requested_specs_map();
std::stringstream dependencies;
std::set<std::string> channels;
for (const auto& [k, v] : versions_map)

View File

@ -75,7 +75,7 @@ handle_solve_request(
for (const auto& s : specs)
{
if (auto m = MatchSpec{ s }; m.channel.has_value())
if (auto m = MatchSpec::parse(s); m.channel.has_value())
{
channels.push_back(m.channel->str());
}

View File

@ -51,11 +51,11 @@ update_self(Configuration& config, const std::optional<std::string>& version)
std::string matchspec = version ? fmt::format("micromamba={}", version.value())
: fmt::format("micromamba>{}", umamba::version());
auto solvable_ids = pool.select_solvables(pool.matchspec2id({ matchspec }), true);
auto solvable_ids = pool.select_solvables(pool.matchspec2id(MatchSpec::parse(matchspec)), true);
if (solvable_ids.empty())
{
if (pool.select_solvables(pool.matchspec2id({ "micromamba" })).empty())
if (pool.select_solvables(pool.matchspec2id(MatchSpec::parse("micromamba"))).empty())
{
throw mamba::mamba_error(
"No micromamba found in the loaded channels. Add 'conda-forge' to your config file.",

View File

@ -632,7 +632,7 @@ def test_spec_with_channel_and_subdir():
helpers.create("-n", env_name, "conda-forge/noarch::xtensor", "--dry-run")
except subprocess.CalledProcessError as e:
assert (
'critical libmamba The package "conda-forge/noarch::xtensor" is '
'critical libmamba The package "conda-forge[noarch]::xtensor" is '
"not available for the specified platform (noarch) but is available on"
) in e.stderr.decode()
@ -648,7 +648,7 @@ def test_spec_with_slash_in_channel(tmp_home, tmp_root_prefix):
helpers.create("-n", "env1", "pkgs/main/noarch::python", "--dry-run")
assert info.value.stderr.decode() == (
'critical libmamba The package "pkgs/main/noarch::python" is '
'critical libmamba The package "pkgs/main[noarch]::python" is '
"not found in any loaded channels. Try adding more channels or subdirs.\n"
)