Channel initialization (#2953)

* Set default_channels at config level

* Resolve default multi channel at the config level

* Fix Yaml source printers

* Resolve local multi channel at the config level

* Remove ChannelContext::make_simple_channel

* Remove Channel::name usage

* Factorize split_once functions

* Try looking for name match in URL path

* Refactor ending_splits_in

* Code improvement

* Replace get_common_parts with concat_dedup

* Remove Channel::name

* Remove Channel::location

* Use URL for priority in channel loader

* Make Channel value-based

* Add range hashing

* Add Channel comparison and hash

* Make channel map/list return by value

* Resolve 1

* Resolve 2

* Resolve 3

* Remove unknown channels

* Hide get_known_platforms

* Add unknown ChannelSpec

* Remove unused function

* Rename Channel::canonical_name > display_name

* Remove Channel friend class

* Add Channel::resolve

* Remove ChannelContext::from_value
This commit is contained in:
Antoine Prouvost 2023-11-15 15:52:59 +01:00 committed by GitHub
parent ddea95e16c
commit cd744e765c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 857 additions and 690 deletions

View File

@ -55,7 +55,7 @@ tasks:
Mamba. Many tasks are automatically run inside this environment.
The environment is located at "{{.DEV_ENV_DIR}}" and can also be activated with micromamba to
benefit from the executables and LSP tools.
cmds: [{task: '_create-env', vars: {prefix: '{{.DEV_ENV_DIR}}'}}]
cmds: [{task: '_create-env', vars: {prefix: '{{.PWD}}/{{.DEV_ENV_DIR}}'}}]
create-test-env:
desc: Create a local test environment with as a copy of the dev environment.

View File

@ -7,9 +7,9 @@
#ifndef MAMBA_CORE_CHANNEL_HPP
#define MAMBA_CORE_CHANNEL_HPP
#include <map>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include "mamba/specs/conda_url.hpp"
@ -26,27 +26,43 @@ namespace mamba
namespace specs
{
class ChannelSpec;
class AuthenticationDataBase;
}
std::vector<std::string> get_known_platforms();
// Note: Channels can only be created using ChannelContext.
class Channel
{
public:
Channel(const Channel&) = delete;
Channel& operator=(const Channel&) = delete;
Channel(Channel&&) noexcept = default;
Channel& operator=(Channel&&) noexcept = default;
struct ResolveParams
{
using platform_list = util::flat_set<std::string>;
using channel_list = std::vector<Channel>;
using channel_map = std::map<std::string, Channel>;
using multichannel_map = std::unordered_map<std::string, channel_list>;
~Channel();
const platform_list& platforms;
const specs::CondaURL& channel_alias;
const channel_map& custom_channels;
const specs::AuthenticationDataBase& auth_db;
const std::string& location() const;
const std::string& name() const;
const std::string& canonical_name() const;
const util::flat_set<std::string>& platforms() const;
const specs::CondaURL& url() const;
// TODO add CWD and home
};
using platform_list = util::flat_set<std::string>;
[[nodiscard]] static auto resolve(specs::ChannelSpec spec, ResolveParams params) -> Channel;
Channel(specs::CondaURL url, std::string display_name, util::flat_set<std::string> platforms = {});
[[nodiscard]] auto url() const -> const specs::CondaURL&;
void set_url(specs::CondaURL url);
[[nodiscard]] auto platforms() const -> const platform_list&;
void set_platforms(platform_list platforms);
[[nodiscard]] auto display_name() const -> const std::string&;
void set_display_name(std::string display_name);
std::string base_url() const;
std::string platform_url(std::string_view platform, bool with_credential = true) const;
@ -57,38 +73,33 @@ namespace mamba
private:
Channel(
specs::CondaURL url,
std::string location,
std::string name,
std::string canonical_name,
util::flat_set<std::string> platforms = {}
);
specs::CondaURL m_url;
std::string m_location;
std::string m_name;
std::string m_canonical_name;
std::string m_display_name;
util::flat_set<std::string> m_platforms;
// Note: as long as Channel is not a regular value-type and we want each
// instance only possible to create through ChannelContext, we need
// to have Channel's constructor only available to ChannelContext,
// therefore enabling it's use through this `friend` statement.
// However, all this should be removed as soon as Channel is changed to
// be a regular value-type (regular as in the regular concept).
friend class ChannelContext;
};
using ChannelCache = std::map<std::string, Channel>;
/** Tuple-like equality of all observable members */
auto operator==(const Channel& a, const Channel& b) -> bool;
auto operator!=(const Channel& a, const Channel& b) -> bool;
}
template <>
struct std::hash<mamba::Channel>
{
auto operator()(const mamba::Channel& c) const -> std::size_t;
};
namespace mamba
{
class ChannelContext
{
public:
using channel_list = std::vector<std::string>;
using channel_map = std::map<std::string, Channel>;
using multichannel_map = std::map<std::string, std::vector<std::string>>;
using channel_list = std::vector<Channel>;
using multichannel_map = std::unordered_map<std::string, channel_list>;
ChannelContext(Context& context);
~ChannelContext();
@ -99,7 +110,7 @@ namespace mamba
ChannelContext& operator=(ChannelContext&&) = delete;
const Channel& make_channel(const std::string& value);
std::vector<const Channel*> get_channels(const std::vector<std::string>& channel_names);
auto get_channels(const std::vector<std::string>& channel_names) -> channel_list;
const specs::CondaURL& get_channel_alias() const;
const channel_map& get_custom_channels() const;
@ -112,6 +123,8 @@ namespace mamba
private:
using ChannelCache = std::map<std::string, Channel>;
Context& m_context;
ChannelCache m_channel_cache;
specs::CondaURL m_channel_alias;
@ -119,22 +132,6 @@ namespace mamba
multichannel_map m_custom_multichannels;
void init_custom_channels();
Channel make_simple_channel(
const specs::CondaURL& channel_alias,
const std::string& channel_url,
const std::string& channel_name,
const std::string& channel_canonical_name
);
Channel from_any_path(specs::ChannelSpec&& spec);
Channel from_package_path(specs::ChannelSpec&& spec);
Channel from_path(specs::ChannelSpec&& spec);
Channel from_any_url(specs::ChannelSpec&& spec);
Channel from_package_url(specs::ChannelSpec&& spec);
Channel from_url(specs::ChannelSpec&& spec);
Channel from_name(specs::ChannelSpec&& spec);
Channel from_value(const std::string& value);
};
} // namespace mamba

View File

@ -7,6 +7,7 @@
#ifndef MAMBA_SPECS_CHANNEL_SPEC_HPP
#define MAMBA_SPECS_CHANNEL_SPEC_HPP
#include <array>
#include <string>
#include <string_view>
@ -63,10 +64,22 @@ namespace mamba::specs
* Example "conda-forge", "locals", "my-channel/my-label".
*/
Name,
/**
* An unknown channel source.
*
* It is currently unclear why it is needed.
*/
Unknown,
};
static constexpr std::string_view default_name = "defaults";
static constexpr std::string_view platform_separators = "|,;";
static constexpr std::string_view unknown_channel = "<unknown>";
static constexpr std::array<std::string_view, 4> invalid_channels_lower = {
"<unknown>",
"none:///<unknown>",
"none",
":///<unknown>",
};
using dynamic_platform_set = util::flat_set<std::string>;
@ -87,9 +100,9 @@ namespace mamba::specs
private:
std::string m_location = std::string(default_name);
std::string m_location = std::string(unknown_channel);
dynamic_platform_set m_platform_filters = {};
Type m_type = {};
Type m_type = Type::Unknown;
};
}
#endif

View File

@ -12,9 +12,11 @@
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
@ -192,6 +194,16 @@ namespace mamba::util
template <typename UnaryFunc>
std::array<std::wstring_view, 3> strip_if_parts(std::wstring_view input, UnaryFunc should_strip);
[[nodiscard]] auto split_once(std::string_view str, char sep)
-> std::tuple<std::string_view, std::optional<std::string_view>>;
[[nodiscard]] auto split_once(std::string_view str, std::string_view sep)
-> std::tuple<std::string_view, std::optional<std::string_view>>;
[[nodiscard]] auto rsplit_once(std::string_view str, char sep)
-> std::tuple<std::optional<std::string_view>, std::string_view>;
[[nodiscard]] auto rsplit_once(std::string_view str, std::string_view sep)
-> std::tuple<std::optional<std::string_view>, std::string_view>;
std::vector<std::string>
split(std::string_view input, std::string_view sep, std::size_t max_split = SIZE_MAX);
std::vector<std::wstring>
@ -202,6 +214,18 @@ namespace mamba::util
std::vector<std::wstring>
rsplit(std::wstring_view input, std::wstring_view sep, std::size_t max_split = SIZE_MAX);
/**
* Concatenate string while removing the suffix of the first that may be prefix of second.
*
* Comparison are done as if comparing elements in a split given by @p sep.
* For instance "private/channel" and "channel/label/foo" with separator "/"
* would return "private/channel/label/foo", but "private/chan" and "channel/label/foo"
* would return the "private/chan/channel/label/foo".
*/
std::string concat_dedup_splits(std::string_view str1, std::string_view str2, char sep);
std::string
concat_dedup_splits(std::string_view str1, std::string_view str2, std::string_view sep);
void replace_all(std::string& data, std::string_view search, std::string_view replace);
void replace_all(std::wstring& data, std::wstring_view search, std::wstring_view replace);
@ -643,14 +667,6 @@ namespace mamba::util
return hex_string(buffer, buffer.size());
}
/**
* Return the common parts of two strings by blocks located between the given sep,
* and considering that these common parts would be located at the end of str1 (search from
* left to right).
* str1 is considered smaller than (or equal to) str2.
* cf. Channels use case.
*/
std::string get_common_parts(std::string_view str1, std::string_view str2, std::string_view sep);
}
#endif

View File

@ -9,26 +9,41 @@
#include <functional>
#include <tuple>
#include <type_traits>
namespace mamba::util
{
constexpr void hash_combine(std::size_t& seed, std::size_t other)
constexpr auto hash_combine(std::size_t seed, std::size_t other) -> std::size_t
{
const auto boost_magic_num = 0x9e3779b9;
seed ^= other + boost_magic_num + (seed << 6) + (seed >> 2);
return seed;
}
template <class T, typename Hasher = std::hash<T>>
constexpr void hash_combine_val(std::size_t& seed, const T& val, const Hasher& hasher = {})
constexpr auto hash_combine_val(std::size_t seed, const T& val, const Hasher& hasher = {})
-> std::size_t
{
hash_combine(seed, hasher(val));
return hash_combine(seed, hasher(val));
}
template <typename Iter, typename Hasher = std::hash<std::decay_t<decltype(*std::declval<Iter>())>>>
auto hash_combine_val_range(std::size_t seed, Iter first, Iter last, const Hasher& hasher = {})
-> std::size_t
{
for (; first != last; ++first)
{
seed = hash_combine_val(seed, hasher(*first));
}
return seed;
}
template <typename... T>
constexpr auto hash_vals(const T&... vals) -> std::size_t
{
std::size_t seed = 0;
(hash_combine_val(seed, vals), ...);
auto combine = [&seed](const auto& val) { seed = hash_combine_val(seed, val); };
(combine(vals), ...);
return seed;
}
@ -46,5 +61,11 @@ namespace mamba::util
return hash_tuple(t);
}
};
template <typename Range>
constexpr auto hash_range(const Range& rng) -> std::size_t
{
return hash_combine_val_range(0, rng.begin(), rng.end());
}
}
#endif

View File

@ -57,7 +57,7 @@ namespace mamba
std::vector<std::pair<int, int>> priorities;
int max_prio = static_cast<int>(channel_urls.size());
std::string prev_channel_name;
auto prev_channel_url = specs::CondaURL();
Console::instance().init_progress_bar_manager(ProgressBarMode::multi);
@ -65,11 +65,11 @@ namespace mamba
for (auto channel : pool.channel_context().get_channels(channel_urls))
{
for (auto& [platform, url] : channel->platform_urls(true))
for (auto& [platform, url] : channel.platform_urls(true))
{
auto sdires = MSubdirData::create(
pool.channel_context(),
*channel,
channel,
platform,
url,
package_caches,
@ -89,10 +89,10 @@ namespace mamba
else
{
// Consider 'flexible' and 'strict' the same way
if (channel->canonical_name() != prev_channel_name)
if (channel.url() != prev_channel_url)
{
max_prio--;
prev_channel_name = channel->canonical_name();
prev_channel_url = channel.url();
}
priorities.push_back(std::make_pair(max_prio, 0));
}

View File

@ -879,6 +879,43 @@ namespace mamba
return paths;
}
void custom_channels_hook(std::map<std::string, std::string>& custom_channels)
{
// Hard coded Anaconda channels names.
// This will not redefine them if the user has already defined these keys.
custom_channels.emplace("pkgs/main", "https://repo.anaconda.com/pkgs/main");
custom_channels.emplace("pkgs/r", "https://repo.anaconda.com/pkgs/r");
custom_channels.emplace("pkgs/pro", "https://repo.anaconda.com/pkgs/pro");
if (util::on_win)
{
custom_channels.emplace("pkgs/msys2", "https://repo.anaconda.com/pkgs/msys2");
}
}
void custom_multichannels_hook(
const Context& context,
std::map<std::string, std::vector<std::string>>& custom_multichannels
)
{
custom_multichannels.emplace("defaults", context.default_channels);
auto local_channels = std::vector<std::string>();
local_channels.reserve(3);
for (auto p : {
context.prefix_params.target_prefix / "conda-bld",
context.prefix_params.root_prefix / "conda-bld",
env::home_directory() / "conda-bld",
})
{
if (fs::exists(p))
{
local_channels.push_back(std::move(p));
}
}
custom_multichannels.emplace("local", std::move(local_channels));
}
void pkgs_dirs_hook(std::vector<fs::u8path>& dirs)
{
for (auto& d : dirs)
@ -999,7 +1036,14 @@ namespace mamba
out << YAML::BeginSeq;
for (std::size_t n = 0; n < value.size(); ++n)
{
print_node(out, value[n], source[n], show_source);
if (source.IsSequence() && (source.size() == value.size()))
{
print_node(out, value[n], source[n], show_source);
}
else
{
print_node(out, value[n], source, show_source);
}
}
out << YAML::EndSeq;
}
@ -1264,14 +1308,28 @@ namespace mamba
.set_rc_configurable()
.set_env_var_names()
.description("Custom channels")
.long_description("A dictionary with name: url to use for custom channels."));
.long_description( //
"A dictionary with name: url to use for custom channels.\n"
"If not defined, the Conda special names "
R"("pkgs/main", "pkgs/r", "pkgs/pro", and "pkgs/msys2" (Windows only) )"
"will be added."
)
.set_post_merge_hook(detail::custom_channels_hook));
insert(Configurable("custom_multichannels", &m_context.custom_multichannels)
.group("Channels")
.set_rc_configurable()
.description("Custom multichannels")
.long_description(
"A dictionary with name: list of names/urls to use for custom multichannels."
.long_description( //
"A dictionary where keys are multi channels names, and values are a list "
"of correspinding names / urls / file paths to use.\n"
R"(If not defined, the Conda special mutli channels "defaults" is added )"
R"(with values from the "default_channels" option, and "local" is added )"
R"(with "~/conda-bld" and target and root prefix "conda-bld" subfolders/)"
)
.needs({ "default_channels", "target_prefix", "root_prefix" })
.set_post_merge_hook<std::map<std::string, std::vector<std::string>>>(
[this](auto& val) { detail::custom_multichannels_hook(m_context, val); }
));
insert(Configurable("override_channels_enabled", &m_context.override_channels_enabled)

View File

@ -164,7 +164,7 @@ namespace mamba
std::vector<std::string> channel_urls;
for (auto channel : channel_context.get_channels(channels))
{
for (auto url : channel->urls(true))
for (auto url : channel.urls(true))
{
channel_urls.push_back(url);
}

View File

@ -77,7 +77,7 @@ namespace mamba
obj["base_url"] = channel.base_url();
obj["build_number"] = pkg_info.build_number;
obj["build_string"] = pkg_info.build_string;
obj["channel"] = channel.canonical_name();
obj["channel"] = channel.display_name();
obj["dist_name"] = pkg_info.str();
obj["name"] = pkg_info.name;
obj["platform"] = pkg_info.subdir;
@ -112,7 +112,7 @@ namespace mamba
else
{
const Channel& channel = channel_context.make_channel(package.second.url);
formatted_pkgs.channel = channel.canonical_name();
formatted_pkgs.channel = channel.display_name();
}
packages.push_back(formatted_pkgs);
}

View File

@ -5,91 +5,62 @@
// The full license is in the file LICENSE, distributed with this software.
#include <cassert>
#include <set>
#include <tuple>
#include <unordered_set>
#include <utility>
#include "mamba/core/channel.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/package_cache.hpp"
#include "mamba/core/validate.hpp"
#include "mamba/specs/channel_spec.hpp"
#include "mamba/specs/conda_url.hpp"
#include "mamba/util/path_manip.hpp"
#include "mamba/util/string.hpp"
#include "mamba/util/tuple_hash.hpp"
#include "mamba/util/url.hpp"
#include "mamba/util/url_manip.hpp"
namespace mamba
{
namespace
{
const std::map<std::string, std::string> DEFAULT_CUSTOM_CHANNELS = {
{ "pkgs/pro", "https://repo.anaconda.com" }
};
const char UNKNOWN_CHANNEL[] = "<unknown>";
const std::set<std::string> INVALID_CHANNELS = { "<unknown>",
"None:///<unknown>",
"None",
"",
":///<unknown>" };
const char LOCAL_CHANNELS_NAME[] = "local";
const char DEFAULT_CHANNELS_NAME[] = "defaults";
}
std::vector<std::string> get_known_platforms()
{
auto plats = specs::known_platform_names();
return { plats.begin(), plats.end() };
}
/**************************
* Channel implementation *
**************************/
Channel::Channel(
specs::CondaURL url,
std::string location,
std::string name,
std::string canonical_name,
util::flat_set<std::string> platforms
)
Channel::Channel(specs::CondaURL url, std::string display_name, util::flat_set<std::string> platforms)
: m_url(std::move(url))
, m_location(std::move(location))
, m_name(std::move(name))
, m_canonical_name(std::move(canonical_name))
, m_display_name(std::move(display_name))
, m_platforms(std::move(platforms))
{
}
Channel::~Channel() = default;
const specs::CondaURL& Channel::url() const
auto Channel::url() const -> const specs::CondaURL&
{
return m_url;
}
const std::string& Channel::location() const
void Channel::set_url(specs::CondaURL url)
{
return m_location;
m_url = std::move(url);
}
const std::string& Channel::name() const
{
return m_name;
}
const util::flat_set<std::string>& Channel::platforms() const
auto Channel::platforms() const -> const platform_list&
{
return m_platforms;
}
const std::string& Channel::canonical_name() const
void Channel::set_platforms(platform_list platforms)
{
return m_canonical_name;
m_platforms = std::move(platforms);
}
auto Channel::display_name() const -> const std::string&
{
return m_display_name;
}
void Channel::set_display_name(std::string display_name)
{
m_display_name = std::move(display_name);
}
std::string Channel::base_url() const
@ -143,79 +114,42 @@ namespace mamba
return (url() / platform).str(cred);
}
namespace
{
auto attrs(const Channel& chan)
{
return std::tie(chan.url(), chan.platforms(), chan.display_name());
}
}
/** Tuple-like equality of all observable members */
auto operator==(const Channel& a, const Channel& b) -> bool
{
return attrs(a) == attrs(b);
}
auto operator!=(const Channel& a, const Channel& b) -> bool
{
return !(a == b);
}
}
auto
std::hash<mamba::Channel>::operator()(const mamba::Channel& chan) const -> std::size_t
{
return mamba::util::hash_combine(
mamba::util::hash_vals(chan.url(), chan.display_name()),
mamba::util::hash_range(chan.platforms())
);
}
namespace mamba
{
/*********************************
* ChannelContext implementation *
*********************************/
Channel ChannelContext::make_simple_channel(
const specs::CondaURL& channel_alias,
const std::string& channel_url,
const std::string& channel_name,
const std::string& channel_canonical_name
)
{
if (!util::url_has_scheme(channel_url))
{
const auto& alias = get_channel_alias();
auto url = alias;
auto name = std::string(util::strip(channel_name.empty() ? channel_url : channel_name, '/')
);
url.append_path(channel_url);
return Channel(
/* url= */ std::move(url),
/* location= */ alias.pretty_str(specs::CondaURL::StripScheme::yes, '/', specs::CondaURL::Credentials::Remove),
/* name= */ std::move(name),
/* canonical_name= */ channel_canonical_name
);
}
auto url = specs::CondaURL::parse(channel_url);
std::string location = url.pretty_str(
specs::CondaURL::StripScheme::yes,
'/',
specs::CondaURL::Credentials::Remove
);
std::string name(channel_name);
if (channel_name.empty())
{
auto ca_location = channel_alias.pretty_str(
specs::CondaURL::StripScheme::yes,
'/',
specs::CondaURL::Credentials::Remove
);
if (util::starts_with(location, ca_location))
{
name = std::string(util::strip(util::remove_prefix(location, ca_location), '/'));
location = std::move(ca_location);
}
else if (url.scheme() == "file")
{
const auto pos = location.rfind('/');
name = location.substr(pos + 1);
location = location.substr(0, pos);
}
else
{
location = url.authority(specs::CondaURL::Credentials::Remove);
name = url.path_without_token();
}
}
else
{
url.append_path(channel_name);
}
name = util::strip(name.empty() ? channel_url : name, '/');
return Channel(
/* url = */ std::move(url),
/* location= */ std::move(location),
/* name= */ std::move(name),
/* canonical_name= */ channel_canonical_name
);
}
namespace
{
auto url_match(const specs::CondaURL& registered, const specs::CondaURL& candidate) -> bool
@ -281,264 +215,158 @@ namespace mamba
}
}
auto rsplit_once(std::string_view str, char sep)
auto
make_platforms(util::flat_set<std::string> filters, const util::flat_set<std::string>& defaults)
{
auto [head, tail] = util::rstrip_if_parts(str, [sep](char c) { return c != sep; });
if (head.empty())
{
return std::array{ head, tail };
}
return std::array{ head.substr(0, head.size() - 1), tail };
return filters.empty() ? defaults : filters;
}
auto
make_platforms(util::flat_set<std::string> filters, const std::vector<std::string>& defaults)
auto resolve_path_name(const specs::CondaURL& uri, Channel::ResolveParams params)
-> std::string
{
if (filters.empty())
for (const auto& [display_name, chan] : params.custom_channels)
{
for (const auto& plat : defaults)
if (url_match(chan.url(), uri))
{
filters.insert(plat);
return std::string(display_name);
}
}
return filters;
};
}
Channel ChannelContext::from_any_path(specs::ChannelSpec&& spec)
{
auto uri = specs::CondaURL::parse(util::path_or_url_to_url(spec.location()));
auto path = uri.pretty_path();
auto [parent, current] = rsplit_once(path, '/');
for (const auto& [canonical_name, chan] : get_custom_channels())
{
if (url_match(chan.url(), uri))
if (const auto& ca = params.channel_alias; url_match(ca, uri))
{
return Channel(
/* url= */ std::move(uri),
/* location= */ chan.url().pretty_str(specs::CondaURL::StripScheme::yes),
/* name= */ std::string(util::rstrip(parent, '/')),
/* canonical_name= */ std::string(canonical_name)
);
return std::string(util::strip(util::remove_prefix(uri.path(), ca.path()), '/'));
}
return uri.pretty_str();
}
if (const auto& ca = get_channel_alias(); url_match(ca, uri))
auto resolve_path(specs::ChannelSpec&& spec, Channel::ResolveParams params) -> Channel
{
auto name = util::strip(util::remove_prefix(uri.path(), ca.path()), '/');
return Channel(
/* url= */ std::move(uri),
/* location= */ ca.pretty_str(specs::CondaURL::StripScheme::yes),
/* name= */ std::string(name),
/* canonical_name= */ std::string(name)
);
}
auto canonical_name = uri.pretty_str();
return Channel(
/* url= */ std::move(uri),
/* location= */ std::string(util::rstrip(parent, '/')),
/* name= */ std::string(util::rstrip(current, '/')),
/* canonical_name= */ std::move(canonical_name)
);
}
Channel ChannelContext::from_package_path(specs::ChannelSpec&& spec)
{
assert(spec.type() == specs::ChannelSpec::Type::PackagePath);
return from_any_path(std::move(spec));
}
Channel ChannelContext::from_path(specs::ChannelSpec&& spec)
{
assert(spec.type() == specs::ChannelSpec::Type::Path);
auto platforms = make_platforms(spec.clear_platform_filters(), m_context.platforms());
auto chan = from_any_path(std::move(spec));
chan.m_platforms = std::move(platforms);
return chan;
}
Channel ChannelContext::from_any_url(specs::ChannelSpec&& spec)
{
assert(util::url_has_scheme(spec.location()));
auto url = specs::CondaURL::parse(spec.location());
assert(url.scheme() != "file");
using StripScheme = typename specs::CondaURL::StripScheme;
using Credentials = typename specs::CondaURL::Credentials;
std::string default_location = url.pretty_str(StripScheme::yes, '/', Credentials::Remove);
for (const auto& [canonical_name, chan] : get_custom_channels())
{
if (url_match(chan.url(), url))
auto uri = specs::CondaURL::parse(util::path_or_url_to_url(spec.location()));
auto display_name = resolve_path_name(uri, params);
auto platforms = Channel::ResolveParams::platform_list{};
if (spec.type() == specs::ChannelSpec::Type::Path)
{
std::string location = chan.location();
// TODO cannot change all the locations at once since they are used in from_name
// std::string location = chan.url().pretty_str(StripScheme::yes, '/',
// Credentials::Remove);
std::string name = std::string(
util::strip(util::remove_prefix(default_location, location), '/')
platforms = make_platforms(spec.clear_platform_filters(), params.platforms);
}
return Channel(std::move(uri), std::move(display_name), std::move(platforms));
}
auto resolve_url_name(const specs::CondaURL& url, Channel::ResolveParams params) -> std::string
{
using StripScheme = typename specs::CondaURL::StripScheme;
using Credentials = typename specs::CondaURL::Credentials;
std::string url_str = url.pretty_str(StripScheme::yes, '/', Credentials::Remove);
for (const auto& [display_name, chan] : params.custom_channels)
{
if (url_match(chan.url(), url))
{
return std::string(display_name);
}
}
if (const auto& ca = params.channel_alias; url_match(ca, url))
{
auto ca_str = ca.pretty_str(StripScheme::yes, '/', Credentials::Remove);
return std::string(util::strip(util::remove_prefix(url_str, ca_str), '/'));
}
return url.pretty_str(StripScheme::no, '/', Credentials::Remove);
}
auto resolve_url(specs::ChannelSpec&& spec, Channel::ResolveParams params) -> Channel
{
assert(util::url_has_scheme(spec.location()));
assert(util::url_get_scheme(spec.location()) != "file");
auto url = specs::CondaURL::parse(spec.location());
auto display_name = resolve_url_name(url, params);
set_fallback_credential_from_db(url, params.auth_db);
auto platforms = Channel::ResolveParams::platform_list{};
if (spec.type() == specs::ChannelSpec::Type::URL)
{
platforms = make_platforms(spec.clear_platform_filters(), params.platforms);
}
return Channel(std::move(url), std::move(display_name), std::move(platforms));
}
auto resolve_name(specs::ChannelSpec&& spec, Channel::ResolveParams params) -> Channel
{
std::string name = spec.clear_location();
const auto it_end = params.custom_channels.end();
auto it = it_end;
{
auto considered_name = std::optional<std::string_view>(name);
while ((it == it_end))
{
if (!considered_name.has_value())
{
break;
}
it = params.custom_channels.find(std::string(considered_name.value()));
considered_name = std::get<0>(util::rsplit_once(considered_name.value(), '/'));
}
}
if (it != it_end)
{
auto url = it->second.url();
// we can have a channel like
// testchannel: https://server.com/private/testchannel
// where `name == private/testchannel` and we need to join the remaining label part
// of the channel (e.g. -c testchannel/mylabel/xyz)
// needs to result in `name = private/testchannel/mylabel/xyz`
std::string combined_name = util::concat_dedup_splits(
util::rstrip(url.path(), '/'),
util::lstrip(name, '/'),
'/'
);
url.set_path(combined_name);
set_fallback_credential_from_db(url, params.auth_db);
return Channel(
/* url= */ std::move(url),
/* location= */ std::move(location),
/* name= */ std::move(name),
/* canonical_name= */ std::string(canonical_name)
/* display_name= */ std::move(name),
/* platforms= */ make_platforms(spec.clear_platform_filters(), params.platforms)
);
}
}
if (const auto& ca = get_channel_alias(); url_match(ca, url))
{
auto location = ca.pretty_str(StripScheme::yes, '/', Credentials::Remove);
// Overridding url scheme since chan_url could have been defaulted
auto name = std::string(util::strip(util::remove_prefix(default_location, location), '/'));
return Channel(
/*..url= */ std::move(url),
/* location= */ std::move(location),
/* name= */ name,
/* canonical_name= */ name
);
}
auto name = std::string(util::strip(url.path_without_token(), '/'));
auto location = url.authority(Credentials::Remove);
auto canonical_name = url.pretty_str(StripScheme::no, '/', Credentials::Remove);
return Channel(
/* url= */ std::move(url),
/* location= */ std::move(location),
/* name= */ std::move(name),
/* canonical_name= */ std::move(canonical_name)
);
}
Channel ChannelContext::from_package_url(specs::ChannelSpec&& spec)
{
auto chan = from_any_url(std ::move(spec));
set_fallback_credential_from_db(chan.m_url, m_context.authentication_info());
return chan;
}
Channel ChannelContext::from_url(specs::ChannelSpec&& spec)
{
auto platforms = make_platforms(spec.clear_platform_filters(), m_context.platforms());
auto chan = from_any_url(std::move(spec));
chan.m_platforms = std::move(platforms);
set_fallback_credential_from_db(chan.m_url, m_context.authentication_info());
return chan;
}
Channel ChannelContext::from_name(specs::ChannelSpec&& spec)
{
std::string name = spec.clear_location();
const auto& custom_channels = get_custom_channels();
const auto it_end = custom_channels.end();
auto it = custom_channels.find(name);
{
std::string considered_name = name;
while (it == it_end)
{
if (const auto pos = considered_name.rfind("/"); pos != std::string::npos)
{
considered_name = considered_name.substr(0, pos);
it = custom_channels.find(considered_name);
}
else
{
break;
}
}
}
if (it != it_end)
{
auto url = it->second.url();
// we can have a channel like
// testchannel: https://server.com/private/testchannel
// where `name == private/testchannel` and we need to join the remaining label part
// of the channel (e.g. -c testchannel/mylabel/xyz)
// needs to result in `name = private/testchannel/mylabel/xyz`
std::string combined_name = it->second.name();
if (combined_name != name)
{
// Find common string between `name` and `combined_name`
auto common_str = util::get_common_parts(combined_name, name, "/");
// Combine names properly
if (common_str.empty())
{
url.append_path(name);
combined_name += "/" + name;
}
else
{
// NOTE We assume that the `common_str`, if not empty, is necessarily at the
// beginning of `name` and at the end of `combined_name` (I don't know about
// other use cases for now)
combined_name += name.substr(common_str.size());
url.append_path(name.substr(common_str.size()));
}
}
set_fallback_credential_from_db(url, m_context.authentication_info());
auto url = params.channel_alias;
url.append_path(name);
set_fallback_credential_from_db(url, params.auth_db);
return Channel(
/* url= */ std::move(url),
/* location= */ it->second.location(),
/* name= */ std::move(combined_name),
/* canonical_name= */ std::move(name),
/* platforms= */ make_platforms(spec.clear_platform_filters(), m_context.platforms())
/* display_name= */ name,
/* platforms= */ make_platforms(spec.clear_platform_filters(), params.platforms)
);
}
const auto& alias = get_channel_alias();
auto url = alias;
url.append_path(name);
set_fallback_credential_from_db(url, m_context.authentication_info());
return Channel(
/* url= */ std::move(url),
/* location= */ alias.pretty_str(specs::CondaURL::StripScheme::yes, '/', specs::CondaURL::Credentials::Remove),
/* name= */ name,
/* canonical_name= */ name,
/* platforms= */ make_platforms(spec.clear_platform_filters(), m_context.platforms())
);
}
Channel ChannelContext::from_value(const std::string& in_value)
auto Channel::resolve(specs::ChannelSpec spec, ResolveParams params) -> Channel
{
if (INVALID_CHANNELS.count(in_value) > 0)
{
return Channel(
/* url= */ specs::CondaURL{},
/* location= */ "",
/* name= */ UNKNOWN_CHANNEL,
/* canonical_name= */ UNKNOWN_CHANNEL
);
}
auto spec = specs::ChannelSpec::parse(in_value);
switch (spec.type())
{
case specs::ChannelSpec::Type::PackagePath:
{
return from_package_path(std::move(spec));
}
case specs::ChannelSpec::Type::Path:
{
return from_path(std::move(spec));
return resolve_path(std::move(spec), params);
}
case specs::ChannelSpec::Type::PackageURL:
{
return from_package_url(std::move(spec));
}
case specs::ChannelSpec::Type::URL:
{
return from_url(std::move(spec));
return resolve_url(std::move(spec), params);
}
case specs::ChannelSpec::Type::Name:
{
return from_name(std::move(spec));
return resolve_name(std::move(spec), params);
}
case specs::ChannelSpec::Type::Unknown:
{
return Channel(specs::CondaURL{}, spec.clear_location());
}
}
throw std::invalid_argument("Invalid ChannelSpec::Type");
@ -551,45 +379,53 @@ namespace mamba
return it->second;
}
auto [it, inserted] = m_channel_cache.emplace(value, from_value(value));
auto spec = specs::ChannelSpec::parse(value);
const auto platforms = [](const auto& plats) {
return Channel::ResolveParams::platform_list(plats.cbegin(), plats.cend());
}(m_context.platforms());
auto params = Channel::ResolveParams{
/* .platforms */ platforms,
/* .channel_alias */ m_channel_alias,
/* .custom_channels */ m_custom_channels,
/* .auth_db */ m_context.authentication_info(),
};
auto [it, inserted] = m_channel_cache.emplace(value, Channel::resolve(std::move(spec), params));
assert(inserted);
return it->second;
}
std::vector<const Channel*>
ChannelContext::get_channels(const std::vector<std::string>& channel_names)
auto ChannelContext::get_channels(const std::vector<std::string>& channel_names) -> channel_list
{
std::set<const Channel*> added;
std::vector<const Channel*> result;
auto added = std::unordered_set<Channel>();
auto result = channel_list();
for (auto name : channel_names)
{
std::string platform_spec;
auto platform_spec_ind = name.find("[");
if (platform_spec_ind != std::string::npos)
{
platform_spec = name.substr(platform_spec_ind);
name = name.substr(0, platform_spec_ind);
}
auto spec = specs::ChannelSpec::parse(name);
auto add_channel = [&](const std::string& lname)
const auto& multi_chan = get_custom_multichannels();
if (auto iter = multi_chan.find(spec.location()); iter != multi_chan.end())
{
auto* channel = &make_channel(lname + platform_spec);
if (added.insert(channel).second)
for (const auto& chan : iter->second)
{
result.push_back(channel);
}
};
auto multi_iter = get_custom_multichannels().find(name);
if (multi_iter != get_custom_multichannels().end())
{
for (const auto& n : multi_iter->second)
{
add_channel(n);
auto channel = chan;
if (!spec.platform_filters().empty())
{
channel.set_platforms(spec.platform_filters());
}
if (added.insert(channel).second)
{
result.push_back(std::move(channel));
}
}
}
else
{
add_channel(name);
auto channel = make_channel(name);
if (added.insert(channel).second)
{
result.push_back(std::move(channel));
}
}
}
return result;
@ -621,89 +457,22 @@ namespace mamba
void ChannelContext::init_custom_channels()
{
/******************
* MULTI CHANNELS *
******************/
// Default channels
auto& default_channels = m_context.default_channels;
std::vector<std::string> default_names(default_channels.size());
auto default_name_iter = default_names.begin();
for (auto& url : default_channels)
for (const auto& [name, location] : m_context.custom_channels)
{
auto channel = make_simple_channel(m_channel_alias, url, "", DEFAULT_CHANNELS_NAME);
std::string name = channel.name();
auto res = m_custom_channels.emplace(std::move(name), std::move(channel));
*default_name_iter++ = res.first->first;
}
m_custom_multichannels.emplace(DEFAULT_CHANNELS_NAME, std::move(default_names));
// Local channels
std::vector<std::string> local_channels = { m_context.prefix_params.target_prefix
/ "conda-bld",
m_context.prefix_params.root_prefix / "conda-bld",
env::home_directory() / "conda-bld" };
bool at_least_one_local_dir = false;
std::vector<std::string> local_names;
local_names.reserve(local_channels.size());
for (const auto& p : local_channels)
{
if (fs::is_directory(p))
{
at_least_one_local_dir = true;
std::string url = util::path_or_url_to_url(p);
auto channel = make_simple_channel(m_channel_alias, url, "", LOCAL_CHANNELS_NAME);
std::string name = channel.name();
auto res = m_custom_channels.emplace(std::move(name), std::move(channel));
local_names.push_back(res.first->first);
}
}
// Throw if `-c local` is given but none of the specified `local_channels` are found
if (!at_least_one_local_dir
&& std::find(m_context.channels.begin(), m_context.channels.end(), LOCAL_CHANNELS_NAME)
!= m_context.channels.end())
{
throw std::runtime_error(
"No 'conda-bld' directory found in target prefix, root prefix or home directories!"
);
}
m_custom_multichannels.emplace(LOCAL_CHANNELS_NAME, std::move(local_names));
const auto& context_custom_channels = m_context.custom_channels;
for (const auto& [name, location] : context_custom_channels)
{
auto channel = from_value(location);
channel.m_canonical_name = name;
auto channel = make_channel(location);
channel.set_display_name(name);
m_custom_channels.emplace(name, std::move(channel));
}
for (const auto& [multi_name, location_list] : m_context.custom_multichannels)
{
std::vector<std::string> names = {};
names.reserve(location_list.size());
auto channels = channel_list();
channels.reserve(location_list.size());
for (auto& location : location_list)
{
auto channel = from_value(location);
// No cannonical name give to mulit_channels
names.push_back(location);
channels.push_back(make_channel(location));
}
m_custom_multichannels.emplace(multi_name, std::move(names));
}
/*******************
* SIMPLE CHANNELS *
*******************/
// Default local channel
for (auto& ch : DEFAULT_CUSTOM_CHANNELS)
{
m_custom_channels.emplace(
ch.first,
make_simple_channel(m_channel_alias, ch.second, ch.first, ch.first)
);
m_custom_multichannels.emplace(multi_name, std::move(channels));
}
}
} // namespace mamba

View File

@ -13,6 +13,7 @@
#include "mamba/core/output.hpp"
#include "mamba/core/util.hpp"
#include "mamba/specs/archive.hpp"
#include "mamba/specs/platform.hpp"
#include "mamba/util/string.hpp"
#include "mamba/util/url_manip.hpp"
@ -101,7 +102,7 @@ namespace mamba
version = dist[1];
build_string = dist[2];
channel = parsed_channel.canonical_name();
channel = parsed_channel.display_name();
// TODO how to handle this with multiple platforms?
if (const auto& plats = parsed_channel.platforms(); !plats.empty())
{
@ -188,6 +189,12 @@ namespace mamba
throw std::runtime_error("Parsing of channel / namespace / subdir failed.");
}
auto get_known_platforms = []() -> std::vector<std::string>
{
auto plats = specs::known_platform_names();
return { plats.begin(), plats.end() };
};
std::string cleaned_url;
std::string platform;
util::split_platform(

View File

@ -149,7 +149,7 @@ namespace mamba
}
auto& custom_multichannels = channel_context.context().custom_multichannels;
auto x = custom_multichannels.find(needle.name());
auto x = custom_multichannels.find(needle.display_name());
if (x != custom_multichannels.end())
{
for (auto el : (x->second))
@ -209,7 +209,7 @@ namespace mamba
) -> solv::DependencyId
{
// Poor man's ms repr to match waht the user provided
std::string const repr = fmt::format("{}::{}", ms.channel, ms.conda_build_form());
const std::string repr = fmt::format("{}::{}", ms.channel, ms.conda_build_form());
// Already added, return that id
if (const auto maybe_id = pool.find_string(repr))
@ -218,25 +218,22 @@ namespace mamba
}
// conda_build_form does **NOT** contain the channel info
solv::DependencyId const match = pool_conda_matchspec(
const solv::DependencyId match = pool_conda_matchspec(
pool.raw(),
ms.conda_build_form().c_str()
);
auto multi_channels = channel_context.get_custom_multichannels();
std::vector<const Channel*> channels;
auto multi_channels_it = multi_channels.find(ms.channel);
if (multi_channels_it != multi_channels.end())
const auto& multi_chan = channel_context.get_custom_multichannels();
auto channels = std::vector<Channel>();
if (auto iter = multi_chan.find(ms.channel); iter != multi_chan.end())
{
for (auto& c : multi_channels_it->second)
{
channels.push_back(&channel_context.make_channel(c));
}
channels.insert(channels.end(), iter->second.cbegin(), iter->second.cend());
}
else
{
channels.push_back(&channel_context.make_channel(ms.channel));
channels.push_back(channel_context.make_channel(ms.channel));
}
solv::ObjQueue selected_pkgs = {};
pool.for_each_whatprovides(
match,
@ -247,9 +244,9 @@ namespace mamba
auto repo = solv::ObjRepoView(*s.raw()->repo);
// TODO make_channel should disapear avoiding conflict here
auto const url = std::string(repo.url());
for (auto& c : channels)
for (auto const& c : channels)
{
if (channel_match(channel_context, channel_context.make_channel(url), *c))
if (channel_match(channel_context, channel_context.make_channel(url), c))
{
if (subdir_match(url, ms.spec))
{
@ -260,7 +257,7 @@ namespace mamba
}
);
solv::StringId const repr_id = pool.add_string(repr);
const solv::StringId repr_id = pool.add_string(repr);
// FRAGILE This get deleted when calling ``pool_createwhatprovides`` so care
// must be taken to do it before
// TODO investigate namespace providers

View File

@ -108,7 +108,7 @@ namespace mamba
MatchSpec modified_spec(ms);
const Channel& chan = m_pool.channel_context().make_channel(std::string(solvable->channel()));
modified_spec.channel = chan.canonical_name();
modified_spec.channel = chan.display_name();
modified_spec.version = solvable->version();
modified_spec.build_string = solvable->build_string();

View File

@ -547,7 +547,7 @@ namespace mamba
, m_expired_cache_path("")
, m_writable_pkgs_dir(caches.first_writable_path())
, m_repodata_url(util::concat(url, "/", repodata_fn))
, m_name(util::join_url(channel.canonical_name(), platform))
, m_name(util::join_url(channel.display_name(), platform))
, m_json_fn(cache_fn_url(m_repodata_url))
, m_solv_fn(m_json_fn.substr(0, m_json_fn.size() - 4) + "solv")
, m_is_noarch(platform == "noarch")

View File

@ -1232,7 +1232,7 @@ namespace mamba
else
{
const Channel& chan = m_pool.channel_context().make_channel(str);
chan_name = chan.canonical_name();
chan_name = chan.display_name();
}
}
else

View File

@ -4,7 +4,9 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <algorithm>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
@ -108,14 +110,24 @@ namespace mamba::specs
out = util::rstrip(out, '/');
return out;
}
auto is_unknown_channel(std::string_view str) -> bool
{
auto it = std::find(
ChannelSpec::invalid_channels_lower.cbegin(),
ChannelSpec::invalid_channels_lower.cend(),
util::to_lower(str)
);
return str.empty() || (it != ChannelSpec::invalid_channels_lower.cend());
}
}
auto ChannelSpec::parse(std::string_view str) -> ChannelSpec
{
str = util::strip(str);
if (str.empty())
if (is_unknown_channel(str))
{
return {};
return { std::string(unknown_channel), {}, Type::Unknown };
}
auto [location, filters] = split_location_platform(str);
@ -148,9 +160,17 @@ namespace mamba::specs
, m_platform_filters(std::move(filters))
, m_type(type)
{
if (m_type == Type::Unknown)
{
m_location = unknown_channel;
m_platform_filters = {};
}
if (m_location.empty())
{
m_location = std::string(default_name);
throw std::invalid_argument( //
"Cannot channel with empty location, "
"use unknown type instead."
);
}
}

View File

@ -4,6 +4,7 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <cassert>
#include <cctype>
#include <cwchar>
#include <cwctype>
@ -556,6 +557,65 @@ namespace mamba::util
return strip_parts_impl(input, chars);
}
/********************************************
* Implementation of split_once functions *
********************************************/
namespace
{
template <typename Char, typename CharOrStrView>
auto split_once_impl(std::basic_string_view<Char> str, CharOrStrView sep)
-> std::tuple<std::string_view, std::optional<std::string_view>>
{
static constexpr auto npos = std::basic_string_view<Char>::npos;
if (const auto pos = str.find(sep); pos != npos)
{
return { str.substr(0, pos), str.substr(pos + detail::length(sep)) };
}
return { str, std::nullopt };
}
}
auto split_once(std::string_view str, char sep)
-> std::tuple<std::string_view, std::optional<std::string_view>>
{
return split_once_impl(str, sep);
}
auto split_once(std::string_view str, std::string_view sep)
-> std::tuple<std::string_view, std::optional<std::string_view>>
{
return split_once_impl(str, sep);
}
namespace
{
template <typename Char, typename CharOrStrView>
auto rsplit_once_impl(std::basic_string_view<Char> str, CharOrStrView sep)
-> std::tuple<std::optional<std::string_view>, std::string_view>
{
static constexpr auto npos = std::basic_string_view<Char>::npos;
if (const auto pos = str.rfind(sep); pos != npos)
{
return { str.substr(0, pos), str.substr(pos + detail::length(sep)) };
}
return { std::nullopt, str };
}
}
auto rsplit_once(std::string_view str, char sep)
-> std::tuple<std::optional<std::string_view>, std::string_view>
{
return rsplit_once_impl(str, sep);
}
auto rsplit_once(std::string_view str, std::string_view sep)
-> std::tuple<std::optional<std::string_view>, std::string_view>
{
return rsplit_once_impl(str, sep);
}
/***************************************
* Implementation of split functions *
***************************************/
@ -675,6 +735,106 @@ namespace mamba::util
return rsplit<decltype(input)::value_type>(input, sep, max_split);
}
/*************************************
* Implementation of ending_splits *
*************************************/
namespace
{
template <typename Char, typename CharOrStrView>
auto starts_with_split(
std::basic_string_view<Char> str,
std::basic_string_view<Char> prefix,
CharOrStrView sep
) -> bool
{
auto end = prefix.size();
const auto sep_size = detail::length(sep);
const auto str_size = str.size();
return
// The substring is found
starts_with(str, prefix)
&& (
// Either it ends at the end
(end == str_size)
// Or it is found before a separator
|| ((end <= str_size) && ends_with(str.substr(0, end + sep_size), sep))
);
}
template <typename Char, typename CharOrStrView>
auto remove_suffix_splits(
std::basic_string_view<Char> str1,
std::basic_string_view<Char> str2,
CharOrStrView sep
) -> std::basic_string_view<Char>
{
static constexpr auto npos = std::basic_string_view<Char>::npos;
assert(!str1.empty());
assert(!str2.empty());
const auto sep_size = detail::length(sep);
assert(sep_size > 0);
auto get_common_candidate = [&](auto split)
{ return str1.substr((split == npos) ? 0 : split + sep_size); };
auto split1 = str1.rfind(sep);
// In the case we did not find a match, we try a bigger common part
while (!starts_with_split(str2, get_common_candidate(split1), sep))
{
if ((split1 == npos) || (split1 < sep_size))
{
// No further possibility to find a match, nothing to remove
return str1;
}
// Add the next split element
split1 = str1.rfind(sep, split1 - sep_size);
}
return str1.substr(0, (split1 == npos) ? 0 : split1);
}
template <typename Char, typename CharOrStrView>
auto concat_dedup_splits_impl(
std::basic_string_view<Char> str1,
std::basic_string_view<Char> str2,
CharOrStrView sep
) -> std::basic_string<Char>
{
if (str1.empty())
{
return std::string(str2);
}
if (str2.empty())
{
return std::string(str1);
}
if (detail::length(sep) < 1)
{
throw std::invalid_argument("Cannot split on empty separator");
}
auto str1_no_suffix = remove_suffix_splits(str1, str2, sep);
if (str1_no_suffix.empty())
{
return concat(str1_no_suffix, str2);
}
return concat(str1_no_suffix, sep, str2);
}
}
std::string concat_dedup_splits(std::string_view str1, std::string_view str2, char sep)
{
return concat_dedup_splits_impl(str1, str2, sep);
}
std::string
concat_dedup_splits(std::string_view str1, std::string_view str2, std::string_view sep)
{
return concat_dedup_splits_impl(str1, str2, sep);
}
/*****************************************
* Implementation of replace functions *
*****************************************/
@ -735,47 +895,4 @@ namespace mamba::util
}
}
/********************************************************
* Implementation of Channels use case util function *
*******************************************************/
std::string get_common_parts(std::string_view str1, std::string_view str2, std::string_view sep)
{
std::string common_str{ str1 };
while ((str2.find(common_str) == std::string::npos))
{
if (common_str.find(sep) != std::string::npos)
{
common_str = common_str.substr(common_str.find(sep) + 1);
}
else
{
return "";
}
}
// Case of non empty common_str
// Check that subparts of common_str are not substrings of elements between the sep
auto vec1 = split(common_str, sep);
auto vec2 = split(str2, sep);
std::vector<std::string> res_vec;
for (std::size_t idx = 0; idx < vec1.size(); ++idx)
{
auto it = std::find(vec2.begin(), vec2.end(), vec1.at(idx));
if (it != vec2.end())
{
res_vec.emplace_back(vec1.at(idx));
}
else
{
if (idx != 0)
{
return join(sep, res_vec);
}
}
}
return join(sep, res_vec);
}
}

View File

@ -38,7 +38,16 @@ namespace mamba
{
// ChannelContext builds its custom channels with
// make_simple_channel
ChannelContext channel_context{ mambatests::context() };
auto& ctx = mambatests::context();
// Hard coded Anaconda channels names set in configuration after refactor
// Should be moved to test_config
// FIXME: this has side effect on all tests
ctx.custom_channels.emplace("pkgs/main", "https://repo.anaconda.com/pkgs/main");
ctx.custom_channels.emplace("pkgs/r", "https://repo.anaconda.com/pkgs/r");
ctx.custom_channels.emplace("pkgs/pro", "https://repo.anaconda.com/pkgs/pro");
ChannelContext channel_context{ ctx };
const auto& ch = channel_context.get_channel_alias();
CHECK_EQ(ch.str(), "https://conda.anaconda.org/");
@ -47,17 +56,17 @@ namespace mamba
auto it = custom.find("pkgs/main");
REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main"));
CHECK_EQ(it->second.canonical_name(), "defaults");
CHECK_EQ(it->second.display_name(), "pkgs/main");
it = custom.find("pkgs/pro");
REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/pro"));
CHECK_EQ(it->second.canonical_name(), "pkgs/pro");
CHECK_EQ(it->second.display_name(), "pkgs/pro");
it = custom.find("pkgs/r");
REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r"));
CHECK_EQ(it->second.canonical_name(), "defaults");
CHECK_EQ(it->second.display_name(), "pkgs/r");
}
TEST_CASE("channel_alias")
@ -77,12 +86,12 @@ namespace mamba
auto it = custom.find("pkgs/main");
REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main"));
CHECK_EQ(it->second.canonical_name(), "defaults");
CHECK_EQ(it->second.display_name(), "pkgs/main");
std::string value = "conda-forge";
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://mydomain.com/channels/conda-forge"));
CHECK_EQ(c.canonical_name(), "conda-forge");
CHECK_EQ(c.display_name(), "conda-forge");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
ctx.channel_alias = "https://conda.anaconda.org";
@ -126,7 +135,7 @@ namespace mamba
std::string value = "test_channel";
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("file:///tmp/test_channel"));
CHECK_EQ(c.canonical_name(), "test_channel");
CHECK_EQ(c.display_name(), "test_channel");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({
std::string("file:///tmp/test_channel/") + platform,
@ -139,7 +148,7 @@ namespace mamba
std::string value = "some_channel";
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://conda.mydomain.xyz/some_channel"));
CHECK_EQ(c.canonical_name(), "some_channel");
CHECK_EQ(c.display_name(), "some_channel");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({
std::string("https://conda.mydomain.xyz/some_channel/") + platform,
@ -173,14 +182,14 @@ namespace mamba
auto x = channel_context.get_channels({ "xtest" });
CHECK_EQ(x.size(), 3);
auto* c1 = x[0];
auto c1 = x[0];
const UrlSet exp_urls({
std::string("https://mydomain.com/conda-forge/") + platform,
"https://mydomain.com/conda-forge/noarch",
});
CHECK_EQ(c1->urls(), exp_urls);
CHECK_EQ(c1.urls(), exp_urls);
const UrlSet exp_urlsy3({
std::string("https://otherdomain.com/snakepit/") + platform,
@ -188,9 +197,9 @@ namespace mamba
});
auto y = channel_context.get_channels({ "ytest" });
auto* y3 = y[2];
auto y3 = y[2];
CHECK_EQ(y3->urls(), exp_urlsy3);
CHECK_EQ(y3.urls(), exp_urlsy3);
ctx.channel_alias = "https://conda.anaconda.org";
ctx.custom_multichannels.clear();
@ -217,30 +226,30 @@ namespace mamba
auto x = channel_context.get_channels({ "everything" });
CHECK_EQ(x.size(), 3);
auto* c1 = x[0];
auto* c2 = x[1];
auto* c3 = x[2];
auto c1 = x[0];
auto c2 = x[1];
auto c3 = x[2];
const UrlSet exp_urls({
std::string("https://condaforge.org/channels/conda-forge/") + platform,
"https://condaforge.org/channels/conda-forge/noarch",
});
CHECK_EQ(c1->urls(), exp_urls);
CHECK_EQ(c1.urls(), exp_urls);
const UrlSet exp_urls2({
std::string("https://mydomain.com/bioconda/") + platform,
"https://mydomain.com/bioconda/noarch",
});
CHECK_EQ(c2->urls(), exp_urls2);
CHECK_EQ(c2.urls(), exp_urls2);
const UrlSet exp_urls3({
std::string("https://mydomain.xyz/xyzchannel/xyz/") + platform,
"https://mydomain.xyz/xyzchannel/xyz/noarch",
});
CHECK_EQ(c3->urls(), exp_urls3);
CHECK_EQ(c3.urls(), exp_urls3);
ctx.channel_alias = "https://conda.anaconda.org";
ctx.custom_multichannels.clear();
@ -250,26 +259,31 @@ namespace mamba
TEST_CASE("default_channels")
{
auto& ctx = mambatests::context();
// Hard coded Anaconda multi channel names set in configuration after refactor
// Should be moved to test_config
// FIXME: this has side effect on all tests
ctx.custom_multichannels["defaults"] = ctx.default_channels;
ChannelContext channel_context{ ctx };
auto x = channel_context.get_channels({ "defaults" });
#if !defined(_WIN32)
const Channel* c1 = x[0];
const Channel* c2 = x[1];
const Channel c1 = x[0];
const Channel c2 = x[1];
CHECK_EQ(c1->url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main"));
CHECK_EQ(c1.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main"));
const UrlSet exp_urls({
std::string("https://repo.anaconda.com/pkgs/main/") + platform,
"https://repo.anaconda.com/pkgs/main/noarch",
});
CHECK_EQ(c1->urls(), exp_urls);
CHECK_EQ(c1.urls(), exp_urls);
CHECK_EQ(c2->url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r"));
CHECK_EQ(c2.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r"));
const UrlSet exp_urls2({
std::string("https://repo.anaconda.com/pkgs/r/") + platform,
"https://repo.anaconda.com/pkgs/r/noarch",
});
CHECK_EQ(c2->urls(), exp_urls2);
CHECK_EQ(c2.urls(), exp_urls2);
#endif
ctx.custom_channels.clear();
@ -282,25 +296,28 @@ namespace mamba
"https://mamba.com/test/channel",
"https://mamba.com/stable/channel",
};
// Hard coded Anaconda multi channel names set by configuration after refactor
// FIXME: this has side effect on all tests
ctx.custom_multichannels["defaults"] = ctx.default_channels;
ChannelContext channel_context{ ctx };
auto x = channel_context.get_channels({ "defaults" });
const Channel* c1 = x[0];
const Channel* c2 = x[1];
const Channel c1 = x[0];
const Channel c2 = x[1];
CHECK_EQ(c1->url(), CondaURL::parse("https://mamba.com/test/channel"));
CHECK_EQ(c1.url(), CondaURL::parse("https://mamba.com/test/channel"));
const UrlSet exp_urls({
std::string("https://mamba.com/test/channel/") + platform,
"https://mamba.com/test/channel/noarch",
});
CHECK_EQ(c1->urls(), exp_urls);
CHECK_EQ(c1.urls(), exp_urls);
CHECK_EQ(c2->url(), CondaURL::parse("https://mamba.com/stable/channel"));
CHECK_EQ(c2.url(), CondaURL::parse("https://mamba.com/stable/channel"));
const UrlSet exp_urls2({
std::string("https://mamba.com/stable/channel/") + platform,
"https://mamba.com/stable/channel/noarch",
});
CHECK_EQ(c2->urls(), exp_urls2);
CHECK_EQ(c2.urls(), exp_urls2);
ctx.custom_channels.clear();
}
@ -309,30 +326,20 @@ namespace mamba
{
auto& ctx = mambatests::context();
// Create conda-bld directory to enable testing
auto conda_bld_dir = env::home_directory() / "conda-bld";
bool to_be_removed = fs::create_directories(conda_bld_dir);
// Hard coded Anaconda multi channel names set in configuration after refactor
// Should be moved to test_config
// FIXME: this has side effect on all tests
ctx.custom_multichannels["local"] = std::vector<std::string>{
ctx.prefix_params.target_prefix / "conda-bld",
ctx.prefix_params.root_prefix / "conda-bld",
env::home_directory() / "conda-bld",
};
ChannelContext channel_context{ ctx };
const auto& custom = channel_context.get_custom_channels();
CHECK_EQ(custom.size(), 4);
auto it = custom.find("conda-bld");
REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse(util::path_to_url(conda_bld_dir.string())));
CHECK_EQ(it->second.canonical_name(), "local");
CHECK_EQ(channel_context.get_custom_multichannels().at("local").size(), 3);
auto local_channels = channel_context.get_channels({ "local" });
CHECK_EQ(local_channels.size(), 1);
// Cleaning
ctx.custom_channels.clear();
if (to_be_removed)
{
fs::remove_all(conda_bld_dir);
}
CHECK_EQ(local_channels.size(), 3);
}
TEST_CASE("custom_channels_with_labels")
@ -348,7 +355,7 @@ namespace mamba
std::string value = "test_channel";
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://server.com/private/channels/test_channel"));
CHECK_EQ(c.canonical_name(), "test_channel");
CHECK_EQ(c.display_name(), "test_channel");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({
std::string("https://server.com/private/channels/test_channel/") + platform,
@ -364,7 +371,7 @@ namespace mamba
c.url(),
CondaURL::parse("https://server.com/private/channels/test_channel/mylabel/xyz")
);
CHECK_EQ(c.canonical_name(), "test_channel/mylabel/xyz");
CHECK_EQ(c.display_name(), "test_channel/mylabel/xyz");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({
std::string("https://server.com/private/channels/test_channel/mylabel/xyz/")
@ -382,7 +389,7 @@ namespace mamba
c.url(),
CondaURL::parse("https://server.com/random/channels/random/test_channel/pkg")
);
CHECK_EQ(c.canonical_name(), "random/test_channel/pkg");
CHECK_EQ(c.display_name(), "random/test_channel/pkg");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({
std::string("https://server.com/random/channels/random/test_channel/pkg/")
@ -405,7 +412,7 @@ namespace mamba
ChannelContext channel_context{ mambatests::context() };
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://repo.mamba.pm/conda-forge"));
CHECK_EQ(c.canonical_name(), "https://repo.mamba.pm/conda-forge");
CHECK_EQ(c.display_name(), "https://repo.mamba.pm/conda-forge");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
}
@ -415,32 +422,32 @@ namespace mamba
ChannelContext channel_context{ mambatests::context() };
const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://conda.anaconda.org/conda-forge"));
CHECK_EQ(c.canonical_name(), "conda-forge");
CHECK_EQ(c.display_name(), "conda-forge");
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
std::string value2 = "https://repo.anaconda.com/pkgs/main[" + platform + "]";
const Channel& c2 = channel_context.make_channel(value2);
CHECK_EQ(c2.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main"));
CHECK_EQ(c2.canonical_name(), "https://repo.anaconda.com/pkgs/main");
CHECK_EQ(c2.display_name(), "https://repo.anaconda.com/pkgs/main");
CHECK_EQ(c2.platforms(), PlatformSet({ platform }));
std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]";
const Channel& c3 = channel_context.make_channel(value3);
CHECK_EQ(c3.url(), c.url());
CHECK_EQ(c3.canonical_name(), c.canonical_name());
CHECK_EQ(c3.display_name(), c.display_name());
CHECK_EQ(c3.platforms(), PlatformSet({ platform }));
std::string value4 = "/home/mamba/test/channel_b";
const Channel& c4 = channel_context.make_channel(value4);
CHECK_EQ(c4.url(), CondaURL::parse(util::path_to_url(value4)));
CHECK_EQ(c4.canonical_name(), c4.url().pretty_str());
CHECK_EQ(c4.display_name(), c4.url().pretty_str());
CHECK_EQ(c4.platforms(), PlatformSet({ platform, "noarch" }));
std::string path5 = "/home/mamba/test/channel_b";
std::string value5 = util::concat(path5, '[', platform, ']');
const Channel& c5 = channel_context.make_channel(value5);
CHECK_EQ(c5.url(), CondaURL::parse(util::path_to_url(path5)));
CHECK_EQ(c5.canonical_name(), c5.url().pretty_str());
CHECK_EQ(c5.display_name(), c5.url().pretty_str());
CHECK_EQ(c5.platforms(), PlatformSet({ platform }));
std::string value6a = "http://localhost:8000/conda-forge[noarch]";

View File

@ -330,12 +330,12 @@ namespace
auto load_channels(MPool& pool, MultiPackageCache& cache, std::vector<std::string>&& channels)
{
auto sub_dirs = std::vector<MSubdirData>();
for (const auto* chan : pool.channel_context().get_channels(channels))
for (const auto& chan : pool.channel_context().get_channels(channels))
{
for (auto& [platform, url] : chan->platform_urls(true))
for (auto& [platform, url] : chan.platform_urls(true))
{
auto sub_dir = expected_value_or_throw(
MSubdirData::create(pool.channel_context(), *chan, platform, url, cache)
MSubdirData::create(pool.channel_context(), chan, platform, url, cache)
);
sub_dirs.push_back(std::move(sub_dir));
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2023, QuantStack and Mamba Contributors
//
// Distributed under the terms of the BSD 3-Clause License.
//
// The full license is in the file LICENSE, distributed with this software.
#include <optional>
#include <doctest/doctest.h>
#include <fmt/format.h>
namespace doctest
{
template <typename T>
struct StringMaker<std::optional<T>>
{
static auto convert(const std::optional<T>& value) -> String
{
if (value.has_value())
{
return { fmt::format("Some({})", value.value()).c_str() };
}
return "None";
}
};
}

View File

@ -6,22 +6,50 @@
#include <doctest/doctest.h>
#include "mamba/fs/filesystem.hpp"
#include "mamba/specs/channel_spec.hpp"
#include "mamba/util/build.hpp"
#include "mamba/util/path_manip.hpp"
#include "mamba/util/string.hpp"
using namespace mamba;
using namespace mamba::specs;
TEST_SUITE("specs::channel_spec")
{
TEST_CASE("Constructor")
{
SUBCASE("Default")
{
const auto spec = ChannelSpec();
CHECK_EQ(spec.type(), ChannelSpec::Type::Unknown);
CHECK_EQ(spec.location(), "<unknown>");
CHECK(spec.platform_filters().empty());
}
SUBCASE("Unknown")
{
const auto spec = ChannelSpec("hello", { "linux-78" }, ChannelSpec::Type::Unknown);
CHECK_EQ(spec.type(), ChannelSpec::Type::Unknown);
CHECK_EQ(spec.location(), "<unknown>");
CHECK(spec.platform_filters().empty());
}
}
TEST_CASE("Parsing")
{
using Type = typename ChannelSpec::Type;
using PlatformSet = typename util::flat_set<std::string>;
SUBCASE("Invalid channels")
{
for (std::string_view str : { "", "<unknown>", ":///<unknown>", "none" })
{
CAPTURE(str);
const auto spec = ChannelSpec::parse(str);
CHECK_EQ(spec.type(), Type::Unknown);
CHECK_EQ(spec.location(), "<unknown>");
CHECK_EQ(spec.platform_filters(), PlatformSet{});
}
}
SUBCASE("https://repo.anaconda.com/conda-forge")
{
const auto spec = ChannelSpec::parse("https://repo.anaconda.com/conda-forge");

View File

@ -4,7 +4,6 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
@ -14,7 +13,12 @@
#include "mamba/fs/filesystem.hpp"
#include "mamba/util/string.hpp"
namespace mamba::util
#include "doctest-printer/array.hpp"
#include "doctest-printer/optional.hpp"
using namespace mamba::util;
namespace
{
TEST_SUITE("util::string")
{
@ -364,6 +368,40 @@ namespace mamba::util
}
}
TEST_CASE("split_once")
{
using Out = std::tuple<std::string_view, std::optional<std::string_view>>;
CHECK_EQ(split_once("", '/'), Out{ "", std::nullopt });
CHECK_EQ(split_once("/", '/'), Out{ "", "" });
CHECK_EQ(split_once("hello/world", '/'), Out{ "hello", "world" });
CHECK_EQ(split_once("hello/my/world", '/'), Out{ "hello", "my/world" });
CHECK_EQ(split_once("/hello/world", '/'), Out{ "", "hello/world" });
CHECK_EQ(split_once("", "/"), Out{ "", std::nullopt });
CHECK_EQ(split_once("hello/world", "/"), Out{ "hello", "world" });
CHECK_EQ(split_once("hello//world", "//"), Out{ "hello", "world" });
CHECK_EQ(split_once("hello/my//world", "/"), Out{ "hello", "my//world" });
CHECK_EQ(split_once("hello/my//world", "//"), Out{ "hello/my", "world" });
}
TEST_CASE("rsplit_once")
{
using Out = std::tuple<std::optional<std::string_view>, std::string_view>;
CHECK_EQ(rsplit_once("", '/'), Out{ std::nullopt, "" });
CHECK_EQ(rsplit_once("/", '/'), Out{ "", "" });
CHECK_EQ(rsplit_once("hello/world", '/'), Out{ "hello", "world" });
CHECK_EQ(rsplit_once("hello/my/world", '/'), Out{ "hello/my", "world" });
CHECK_EQ(rsplit_once("hello/world/", '/'), Out{ "hello/world", "" });
CHECK_EQ(rsplit_once("", "/"), Out{ std::nullopt, "" });
CHECK_EQ(rsplit_once("hello/world", "/"), Out{ "hello", "world" });
CHECK_EQ(rsplit_once("hello//world", "//"), Out{ "hello", "world" });
CHECK_EQ(rsplit_once("hello//my/world", "/"), Out{ "hello//my", "world" });
CHECK_EQ(rsplit_once("hello//my/world", "//"), Out{ "hello", "my/world" });
}
TEST_CASE("split")
{
std::string a = "hello.again.it's.me.mario";
@ -406,7 +444,7 @@ namespace mamba::util
CHECK_EQ(joined, "a-bc-d");
}
{
std::vector<fs::u8path> to_join = { "/a", "bc", "d" };
std::vector<mamba::fs::u8path> to_join = { "/a", "bc", "d" };
auto joined = join("/", to_join);
static_assert(std::is_same<decltype(joined), decltype(to_join)::value_type>::value);
CHECK_EQ(joined, "/a/bc/d");
@ -464,37 +502,71 @@ namespace mamba::util
CHECK_EQ(concat("aa", std::string("bb"), std::string_view("cc"), 'd'), "aabbccd");
}
TEST_CASE("get_common_parts")
TEST_CASE("concat_dedup_splits")
{
CHECK_EQ(get_common_parts("", "", "/"), "");
CHECK_EQ(get_common_parts("", "test", "/"), "");
CHECK_EQ(get_common_parts("test", "test", "/"), "test");
CHECK_EQ(get_common_parts("test/chan", "test/chan", "/"), "test/chan");
CHECK_EQ(get_common_parts("st/ch", "test/chan", "/"), "");
CHECK_EQ(get_common_parts("st/chan", "test/chan", "/"), "chan");
CHECK_EQ(get_common_parts("st/chan/abc", "test/chan/abc", "/"), "chan/abc");
CHECK_EQ(get_common_parts("test/ch", "test/chan", "/"), "test");
CHECK_EQ(get_common_parts("test/an/abc", "test/chan/abc", "/"), "abc");
CHECK_EQ(get_common_parts("test/chan/label", "label/abcd/xyz", "/"), "label");
CHECK_EQ(get_common_parts("test/chan/label", "chan/label/abcd", "/"), "chan/label");
CHECK_EQ(get_common_parts("test/chan/label", "abcd/chan/label", "/"), "chan/label");
CHECK_EQ(get_common_parts("test", "abcd", "/"), "");
CHECK_EQ(get_common_parts("test", "abcd/xyz", "/"), "");
CHECK_EQ(get_common_parts("test/xyz", "abcd/xyz", "/"), "xyz");
CHECK_EQ(get_common_parts("test/xyz", "abcd/gef", "/"), "");
CHECK_EQ(get_common_parts("abcd/test", "abcd/xyz", "/"), "");
for (std::string_view sep : { "/", "//", "/////", "./", "./." })
{
CAPTURE(sep);
CHECK_EQ(get_common_parts("", "", "."), "");
CHECK_EQ(get_common_parts("", "test", "."), "");
CHECK_EQ(get_common_parts("test", "test", "."), "test");
CHECK_EQ(get_common_parts("test.chan", "test.chan", "."), "test.chan");
CHECK_EQ(get_common_parts("test.chan.label", "chan.label.abcd", "."), "chan.label");
CHECK_EQ(get_common_parts("test/chan/label", "chan/label/abcd", "."), "");
CHECK_EQ(get_common_parts("st/ch", "test/chan", "."), "");
CHECK_EQ(get_common_parts("st.ch", "test.chan", "."), "");
CHECK_EQ(concat_dedup_splits("", "", sep), "");
CHECK_EQ(get_common_parts("test..chan", "test..chan", ".."), "test..chan");
CHECK_EQ(
concat_dedup_splits(fmt::format("test{}chan", sep), "", sep),
fmt::format("test{}chan", sep)
);
CHECK_EQ(
concat_dedup_splits("", fmt::format("test{}chan", sep), sep),
fmt::format("test{}chan", sep)
);
CHECK_EQ(
concat_dedup_splits("test", fmt::format("test{}chan", sep), sep),
fmt::format("test{}chan", sep)
);
CHECK_EQ(concat_dedup_splits("test", "chan", sep), fmt::format("test{}chan", sep));
CHECK_EQ(
concat_dedup_splits(fmt::format("test{}chan", sep), "chan", sep),
fmt::format("test{}chan", sep)
);
CHECK_EQ(
concat_dedup_splits(fmt::format("test{}chan", sep), fmt::format("chan{}foo", sep), sep),
fmt::format("test{}chan{}foo", sep, sep)
);
CHECK_EQ(
concat_dedup_splits(
fmt::format("test{}chan-foo", sep),
fmt::format("foo{}bar", sep),
sep
),
fmt::format("test{}chan-foo{}foo{}bar", sep, sep, sep, sep)
);
CHECK_EQ(
concat_dedup_splits(
fmt::format("ab{}test{}chan", sep, sep),
fmt::format("chan{}foo{}ab", sep, sep),
sep
),
fmt::format("ab{}test{}chan{}foo{}ab", sep, sep, sep, sep)
);
CHECK_EQ(
concat_dedup_splits(
fmt::format("{}test{}chan", sep, sep),
fmt::format("chan{}foo{}", sep, sep),
sep
),
fmt::format("{}test{}chan{}foo{}", sep, sep, sep, sep)
);
CHECK_EQ(
concat_dedup_splits(
fmt::format("test{}chan", sep),
fmt::format("chan{}test", sep),
sep
),
fmt::format("test{}chan{}test", sep, sep)
);
}
CHECK_EQ(concat_dedup_splits("test/chan", "chan/foo", "//"), "test/chan//chan/foo");
CHECK_EQ(concat_dedup_splits("test/chan", "chan/foo", '/'), "test/chan/foo");
}
}
} // namespace mamba

View File

@ -28,4 +28,26 @@ TEST_SUITE("util::tuple_hash")
const auto t3 = std::tuple{ std::string("hello"), 33 };
CHECK_NE(hash_tuple(t1), hash_tuple(t3));
}
TEST_CASE("hash_combine_val_range")
{
const auto hello = std::string("hello");
// Hash colision are hard to predict, but this is so trivial it is likely a bug if it fails.
CHECK_NE(hash_combine_val_range(0, hello.cbegin(), hello.cend()), 0);
CHECK_NE(hash_combine_val_range(0, hello.crbegin(), hello.crend()), 0);
CHECK_NE(
hash_combine_val_range(0, hello.cbegin(), hello.cend()),
hash_combine_val_range(0, hello.crbegin(), hello.crend())
);
}
TEST_CASE("hash_range")
{
const auto hello = std::string("hello");
const auto world = std::string("world");
// Hash colision are hard to predict, but this is so trivial it is likely a bug if it fails.
CHECK_NE(hash_range(hello), 0);
CHECK_NE(hash_range(world), 0);
CHECK_NE(hash_range(hello), hash_range(world));
}
}

View File

@ -1154,10 +1154,8 @@ bind_submodule_impl(pybind11::module_ m)
);
}
))
.def_property_readonly("location", &Channel::location)
.def_property_readonly("name", &Channel::name)
.def_property_readonly("platforms", &Channel::platforms)
.def_property_readonly("canonical_name", &Channel::canonical_name)
.def_property_readonly("canonical_name", &Channel::display_name)
.def("urls", &Channel::urls, py::arg("with_credentials") = true)
.def("platform_urls", &Channel::platform_urls, py::arg("with_credentials") = true)
.def("platform_url", &Channel::platform_url, py::arg("platform"), py::arg("with_credentials") = true)
@ -1165,7 +1163,7 @@ bind_submodule_impl(pybind11::module_ m)
"__repr__",
[](const Channel& c)
{
auto s = c.name();
auto s = c.display_name();
s += "[";
bool first = true;
for (const auto& platform : c.platforms())

View File

@ -187,7 +187,7 @@ set_env_command(CLI::App* com, Configuration& config)
dependencies << "- ";
if (channel_subdir)
{
dependencies << channel.canonical_name() << "/" << v.subdir << "::";
dependencies << channel.display_name() << "/" << v.subdir << "::";
}
dependencies << v.name << "=" << v.version;
if (!no_build)
@ -201,7 +201,7 @@ set_env_command(CLI::App* com, Configuration& config)
dependencies << "\n";
}
channels.insert(channel.canonical_name());
channels.insert(channel.display_name());
}
for (const auto& c : channels)

View File

@ -226,10 +226,8 @@ class TestConfigList:
def test_list_map_with_sources(self, rc_file, source_flag):
home_folder = os.path.expanduser("~")
src = f" # '{str(rc_file).replace(home_folder, '~')}'"
assert (
config("list", "--no-env", "--rc-file", rc_file, source_flag).splitlines()
== f"custom_channels:\n key1: value1{src}\n".splitlines()
)
out = config("list", "--no-env", "--rc-file", rc_file, source_flag)
assert f"key1: value1{src}" in out
@pytest.mark.parametrize("desc_flag", ["--descriptions", "-d"])
@pytest.mark.parametrize("rc_file_args", ({"channels": ["channel1", "channel2"]},))
@ -850,6 +848,7 @@ class TestConfigExpandVars:
out["channel_alias"]
== "https://xxxxxxxxxxxxxxxxxxxx.com/t/kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk/get"
)
assert out["custom_channels"] == {
"yyyyyyyyyyyy": "https://uuuuuuuuu:pppppppppppppppppppp@xxxxxxxxxxxxxxx.com"
}
assert (
out["custom_channels"]["yyyyyyyyyyyy"]
== "https://uuuuuuuuu:pppppppppppppppppppp@xxxxxxxxxxxxxxx.com"
)