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. 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 The environment is located at "{{.DEV_ENV_DIR}}" and can also be activated with micromamba to
benefit from the executables and LSP tools. 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: create-test-env:
desc: Create a local test environment with as a copy of the dev environment. 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 #ifndef MAMBA_CORE_CHANNEL_HPP
#define MAMBA_CORE_CHANNEL_HPP #define MAMBA_CORE_CHANNEL_HPP
#include <map>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_map>
#include <utility> #include <utility>
#include "mamba/specs/conda_url.hpp" #include "mamba/specs/conda_url.hpp"
@ -26,27 +26,43 @@ namespace mamba
namespace specs namespace specs
{ {
class ChannelSpec; class ChannelSpec;
class AuthenticationDataBase;
} }
std::vector<std::string> get_known_platforms();
// Note: Channels can only be created using ChannelContext. // Note: Channels can only be created using ChannelContext.
class Channel class Channel
{ {
public: public:
Channel(const Channel&) = delete; struct ResolveParams
Channel& operator=(const Channel&) = delete; {
Channel(Channel&&) noexcept = default; using platform_list = util::flat_set<std::string>;
Channel& operator=(Channel&&) noexcept = default; 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; // TODO add CWD and home
const std::string& name() const; };
const std::string& canonical_name() const;
const util::flat_set<std::string>& platforms() const; using platform_list = util::flat_set<std::string>;
const specs::CondaURL& url() const;
[[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 base_url() const;
std::string platform_url(std::string_view platform, bool with_credential = true) const; std::string platform_url(std::string_view platform, bool with_credential = true) const;
@ -57,38 +73,33 @@ namespace mamba
private: 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; specs::CondaURL m_url;
std::string m_location; std::string m_display_name;
std::string m_name;
std::string m_canonical_name;
util::flat_set<std::string> m_platforms; 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 class ChannelContext
{ {
public: public:
using channel_list = std::vector<std::string>;
using channel_map = std::map<std::string, Channel>; 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(Context& context);
~ChannelContext(); ~ChannelContext();
@ -99,7 +110,7 @@ namespace mamba
ChannelContext& operator=(ChannelContext&&) = delete; ChannelContext& operator=(ChannelContext&&) = delete;
const Channel& make_channel(const std::string& value); 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 specs::CondaURL& get_channel_alias() const;
const channel_map& get_custom_channels() const; const channel_map& get_custom_channels() const;
@ -112,6 +123,8 @@ namespace mamba
private: private:
using ChannelCache = std::map<std::string, Channel>;
Context& m_context; Context& m_context;
ChannelCache m_channel_cache; ChannelCache m_channel_cache;
specs::CondaURL m_channel_alias; specs::CondaURL m_channel_alias;
@ -119,22 +132,6 @@ namespace mamba
multichannel_map m_custom_multichannels; multichannel_map m_custom_multichannels;
void init_custom_channels(); 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 } // namespace mamba

View File

@ -7,6 +7,7 @@
#ifndef MAMBA_SPECS_CHANNEL_SPEC_HPP #ifndef MAMBA_SPECS_CHANNEL_SPEC_HPP
#define MAMBA_SPECS_CHANNEL_SPEC_HPP #define MAMBA_SPECS_CHANNEL_SPEC_HPP
#include <array>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -63,10 +64,22 @@ namespace mamba::specs
* Example "conda-forge", "locals", "my-channel/my-label". * Example "conda-forge", "locals", "my-channel/my-label".
*/ */
Name, 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 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>; using dynamic_platform_set = util::flat_set<std::string>;
@ -87,9 +100,9 @@ namespace mamba::specs
private: private:
std::string m_location = std::string(default_name); std::string m_location = std::string(unknown_channel);
dynamic_platform_set m_platform_filters = {}; dynamic_platform_set m_platform_filters = {};
Type m_type = {}; Type m_type = Type::Unknown;
}; };
} }
#endif #endif

View File

@ -12,9 +12,11 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iomanip> #include <iomanip>
#include <optional>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -192,6 +194,16 @@ namespace mamba::util
template <typename UnaryFunc> template <typename UnaryFunc>
std::array<std::wstring_view, 3> strip_if_parts(std::wstring_view input, UnaryFunc should_strip); 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> std::vector<std::string>
split(std::string_view input, std::string_view sep, std::size_t max_split = SIZE_MAX); split(std::string_view input, std::string_view sep, std::size_t max_split = SIZE_MAX);
std::vector<std::wstring> std::vector<std::wstring>
@ -202,6 +214,18 @@ namespace mamba::util
std::vector<std::wstring> std::vector<std::wstring>
rsplit(std::wstring_view input, std::wstring_view sep, std::size_t max_split = SIZE_MAX); 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::string& data, std::string_view search, std::string_view replace);
void replace_all(std::wstring& data, std::wstring_view search, std::wstring_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 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 #endif

View File

@ -9,26 +9,41 @@
#include <functional> #include <functional>
#include <tuple> #include <tuple>
#include <type_traits>
namespace mamba::util 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; const auto boost_magic_num = 0x9e3779b9;
seed ^= other + boost_magic_num + (seed << 6) + (seed >> 2); seed ^= other + boost_magic_num + (seed << 6) + (seed >> 2);
return seed;
} }
template <class T, typename Hasher = std::hash<T>> 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> template <typename... T>
constexpr auto hash_vals(const T&... vals) -> std::size_t constexpr auto hash_vals(const T&... vals) -> std::size_t
{ {
std::size_t seed = 0; 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; return seed;
} }
@ -46,5 +61,11 @@ namespace mamba::util
return hash_tuple(t); 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 #endif

View File

@ -57,7 +57,7 @@ namespace mamba
std::vector<std::pair<int, int>> priorities; std::vector<std::pair<int, int>> priorities;
int max_prio = static_cast<int>(channel_urls.size()); 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); 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 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( auto sdires = MSubdirData::create(
pool.channel_context(), pool.channel_context(),
*channel, channel,
platform, platform,
url, url,
package_caches, package_caches,
@ -89,10 +89,10 @@ namespace mamba
else else
{ {
// Consider 'flexible' and 'strict' the same way // Consider 'flexible' and 'strict' the same way
if (channel->canonical_name() != prev_channel_name) if (channel.url() != prev_channel_url)
{ {
max_prio--; max_prio--;
prev_channel_name = channel->canonical_name(); prev_channel_url = channel.url();
} }
priorities.push_back(std::make_pair(max_prio, 0)); priorities.push_back(std::make_pair(max_prio, 0));
} }

View File

@ -879,6 +879,43 @@ namespace mamba
return paths; 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) void pkgs_dirs_hook(std::vector<fs::u8path>& dirs)
{ {
for (auto& d : dirs) for (auto& d : dirs)
@ -999,7 +1036,14 @@ namespace mamba
out << YAML::BeginSeq; out << YAML::BeginSeq;
for (std::size_t n = 0; n < value.size(); ++n) 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; out << YAML::EndSeq;
} }
@ -1264,14 +1308,28 @@ namespace mamba
.set_rc_configurable() .set_rc_configurable()
.set_env_var_names() .set_env_var_names()
.description("Custom channels") .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) insert(Configurable("custom_multichannels", &m_context.custom_multichannels)
.group("Channels") .group("Channels")
.set_rc_configurable() .set_rc_configurable()
.description("Custom multichannels") .description("Custom multichannels")
.long_description( .long_description( //
"A dictionary with name: list of names/urls to use for custom multichannels." "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) insert(Configurable("override_channels_enabled", &m_context.override_channels_enabled)

View File

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

View File

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

View File

@ -5,91 +5,62 @@
// The full license is in the file LICENSE, distributed with this software. // The full license is in the file LICENSE, distributed with this software.
#include <cassert> #include <cassert>
#include <set> #include <tuple>
#include <unordered_set>
#include <utility> #include <utility>
#include "mamba/core/channel.hpp" #include "mamba/core/channel.hpp"
#include "mamba/core/context.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/channel_spec.hpp"
#include "mamba/specs/conda_url.hpp" #include "mamba/specs/conda_url.hpp"
#include "mamba/util/path_manip.hpp" #include "mamba/util/path_manip.hpp"
#include "mamba/util/string.hpp" #include "mamba/util/string.hpp"
#include "mamba/util/tuple_hash.hpp"
#include "mamba/util/url.hpp" #include "mamba/util/url.hpp"
#include "mamba/util/url_manip.hpp" #include "mamba/util/url_manip.hpp"
namespace mamba 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 implementation *
**************************/ **************************/
Channel::Channel( Channel::Channel(specs::CondaURL url, std::string display_name, util::flat_set<std::string> platforms)
specs::CondaURL url,
std::string location,
std::string name,
std::string canonical_name,
util::flat_set<std::string> platforms
)
: m_url(std::move(url)) : m_url(std::move(url))
, m_location(std::move(location)) , m_display_name(std::move(display_name))
, m_name(std::move(name))
, m_canonical_name(std::move(canonical_name))
, m_platforms(std::move(platforms)) , m_platforms(std::move(platforms))
{ {
} }
Channel::~Channel() = default; auto Channel::url() const -> const specs::CondaURL&
const specs::CondaURL& Channel::url() const
{ {
return m_url; 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 auto Channel::platforms() const -> const platform_list&
{
return m_name;
}
const util::flat_set<std::string>& Channel::platforms() const
{ {
return m_platforms; 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 std::string Channel::base_url() const
@ -143,79 +114,42 @@ namespace mamba
return (url() / platform).str(cred); 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 * * 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 namespace
{ {
auto url_match(const specs::CondaURL& registered, const specs::CondaURL& candidate) -> bool 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; }); return filters.empty() ? defaults : filters;
if (head.empty())
{
return std::array{ head, tail };
}
return std::array{ head.substr(0, head.size() - 1), tail };
} }
auto auto resolve_path_name(const specs::CondaURL& uri, Channel::ResolveParams params)
make_platforms(util::flat_set<std::string> filters, const std::vector<std::string>& defaults) -> 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) if (const auto& ca = params.channel_alias; url_match(ca, uri))
{
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))
{ {
return Channel( return std::string(util::strip(util::remove_prefix(uri.path(), ca.path()), '/'));
/* 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 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()), '/'); auto uri = specs::CondaURL::parse(util::path_or_url_to_url(spec.location()));
return Channel( auto display_name = resolve_path_name(uri, params);
/* url= */ std::move(uri), auto platforms = Channel::ResolveParams::platform_list{};
/* location= */ ca.pretty_str(specs::CondaURL::StripScheme::yes), if (spec.type() == specs::ChannelSpec::Type::Path)
/* 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))
{ {
std::string location = chan.location(); platforms = make_platforms(spec.clear_platform_filters(), params.platforms);
// 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); return Channel(std::move(uri), std::move(display_name), std::move(platforms));
std::string name = std::string( }
util::strip(util::remove_prefix(default_location, location), '/')
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( return Channel(
/* url= */ std::move(url), /* url= */ std::move(url),
/* location= */ std::move(location), /* display_name= */ std::move(name),
/* name= */ std::move(name), /* platforms= */ make_platforms(spec.clear_platform_filters(), params.platforms)
/* canonical_name= */ std::string(canonical_name)
); );
} }
}
if (const auto& ca = get_channel_alias(); url_match(ca, url)) auto url = params.channel_alias;
{ url.append_path(name);
auto location = ca.pretty_str(StripScheme::yes, '/', Credentials::Remove); set_fallback_credential_from_db(url, params.auth_db);
// 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());
return Channel( return Channel(
/* url= */ std::move(url), /* url= */ std::move(url),
/* location= */ it->second.location(), /* display_name= */ name,
/* name= */ std::move(combined_name), /* platforms= */ make_platforms(spec.clear_platform_filters(), params.platforms)
/* canonical_name= */ std::move(name),
/* platforms= */ make_platforms(spec.clear_platform_filters(), m_context.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()) switch (spec.type())
{ {
case specs::ChannelSpec::Type::PackagePath: case specs::ChannelSpec::Type::PackagePath:
{
return from_package_path(std::move(spec));
}
case specs::ChannelSpec::Type::Path: case specs::ChannelSpec::Type::Path:
{ {
return from_path(std::move(spec)); return resolve_path(std::move(spec), params);
} }
case specs::ChannelSpec::Type::PackageURL: case specs::ChannelSpec::Type::PackageURL:
{
return from_package_url(std::move(spec));
}
case specs::ChannelSpec::Type::URL: case specs::ChannelSpec::Type::URL:
{ {
return from_url(std::move(spec)); return resolve_url(std::move(spec), params);
} }
case specs::ChannelSpec::Type::Name: 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"); throw std::invalid_argument("Invalid ChannelSpec::Type");
@ -551,45 +379,53 @@ namespace mamba
return it->second; 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); assert(inserted);
return it->second; return it->second;
} }
std::vector<const Channel*> auto ChannelContext::get_channels(const std::vector<std::string>& channel_names) -> channel_list
ChannelContext::get_channels(const std::vector<std::string>& channel_names)
{ {
std::set<const Channel*> added; auto added = std::unordered_set<Channel>();
std::vector<const Channel*> result; auto result = channel_list();
for (auto name : channel_names) for (auto name : channel_names)
{ {
std::string platform_spec; auto spec = specs::ChannelSpec::parse(name);
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 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); for (const auto& chan : iter->second)
if (added.insert(channel).second)
{ {
result.push_back(channel); auto channel = chan;
} if (!spec.platform_filters().empty())
}; {
auto multi_iter = get_custom_multichannels().find(name); channel.set_platforms(spec.platform_filters());
if (multi_iter != get_custom_multichannels().end()) }
{ if (added.insert(channel).second)
for (const auto& n : multi_iter->second) {
{ result.push_back(std::move(channel));
add_channel(n); }
} }
} }
else else
{ {
add_channel(name); auto channel = make_channel(name);
if (added.insert(channel).second)
{
result.push_back(std::move(channel));
}
} }
} }
return result; return result;
@ -621,89 +457,22 @@ namespace mamba
void ChannelContext::init_custom_channels() void ChannelContext::init_custom_channels()
{ {
/****************** for (const auto& [name, location] : m_context.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)
{ {
auto channel = make_simple_channel(m_channel_alias, url, "", DEFAULT_CHANNELS_NAME); auto channel = make_channel(location);
std::string name = channel.name(); channel.set_display_name(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;
m_custom_channels.emplace(name, std::move(channel)); m_custom_channels.emplace(name, std::move(channel));
} }
for (const auto& [multi_name, location_list] : m_context.custom_multichannels) for (const auto& [multi_name, location_list] : m_context.custom_multichannels)
{ {
std::vector<std::string> names = {}; auto channels = channel_list();
names.reserve(location_list.size()); channels.reserve(location_list.size());
for (auto& location : location_list) for (auto& location : location_list)
{ {
auto channel = from_value(location); channels.push_back(make_channel(location));
// No cannonical name give to mulit_channels
names.push_back(location);
} }
m_custom_multichannels.emplace(multi_name, std::move(names)); m_custom_multichannels.emplace(multi_name, std::move(channels));
}
/*******************
* 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)
);
} }
} }
} // namespace mamba } // namespace mamba

View File

@ -13,6 +13,7 @@
#include "mamba/core/output.hpp" #include "mamba/core/output.hpp"
#include "mamba/core/util.hpp" #include "mamba/core/util.hpp"
#include "mamba/specs/archive.hpp" #include "mamba/specs/archive.hpp"
#include "mamba/specs/platform.hpp"
#include "mamba/util/string.hpp" #include "mamba/util/string.hpp"
#include "mamba/util/url_manip.hpp" #include "mamba/util/url_manip.hpp"
@ -101,7 +102,7 @@ namespace mamba
version = dist[1]; version = dist[1];
build_string = dist[2]; build_string = dist[2];
channel = parsed_channel.canonical_name(); channel = parsed_channel.display_name();
// TODO how to handle this with multiple platforms? // TODO how to handle this with multiple platforms?
if (const auto& plats = parsed_channel.platforms(); !plats.empty()) 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."); 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 cleaned_url;
std::string platform; std::string platform;
util::split_platform( util::split_platform(

View File

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

View File

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

View File

@ -547,7 +547,7 @@ namespace mamba
, m_expired_cache_path("") , m_expired_cache_path("")
, m_writable_pkgs_dir(caches.first_writable_path()) , m_writable_pkgs_dir(caches.first_writable_path())
, m_repodata_url(util::concat(url, "/", repodata_fn)) , 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_json_fn(cache_fn_url(m_repodata_url))
, m_solv_fn(m_json_fn.substr(0, m_json_fn.size() - 4) + "solv") , m_solv_fn(m_json_fn.substr(0, m_json_fn.size() - 4) + "solv")
, m_is_noarch(platform == "noarch") , m_is_noarch(platform == "noarch")

View File

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

View File

@ -4,7 +4,9 @@
// //
// The full license is in the file LICENSE, distributed with this software. // The full license is in the file LICENSE, distributed with this software.
#include <algorithm>
#include <optional> #include <optional>
#include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
@ -108,14 +110,24 @@ namespace mamba::specs
out = util::rstrip(out, '/'); out = util::rstrip(out, '/');
return 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 auto ChannelSpec::parse(std::string_view str) -> ChannelSpec
{ {
str = util::strip(str); 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); auto [location, filters] = split_location_platform(str);
@ -148,9 +160,17 @@ namespace mamba::specs
, m_platform_filters(std::move(filters)) , m_platform_filters(std::move(filters))
, m_type(type) , m_type(type)
{ {
if (m_type == Type::Unknown)
{
m_location = unknown_channel;
m_platform_filters = {};
}
if (m_location.empty()) 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. // The full license is in the file LICENSE, distributed with this software.
#include <cassert>
#include <cctype> #include <cctype>
#include <cwchar> #include <cwchar>
#include <cwctype> #include <cwctype>
@ -556,6 +557,65 @@ namespace mamba::util
return strip_parts_impl(input, chars); 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 * * Implementation of split functions *
***************************************/ ***************************************/
@ -675,6 +735,106 @@ namespace mamba::util
return rsplit<decltype(input)::value_type>(input, sep, max_split); 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 * * 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 // ChannelContext builds its custom channels with
// make_simple_channel // 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(); const auto& ch = channel_context.get_channel_alias();
CHECK_EQ(ch.str(), "https://conda.anaconda.org/"); CHECK_EQ(ch.str(), "https://conda.anaconda.org/");
@ -47,17 +56,17 @@ namespace mamba
auto it = custom.find("pkgs/main"); auto it = custom.find("pkgs/main");
REQUIRE_NE(it, custom.end()); REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); 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"); it = custom.find("pkgs/pro");
REQUIRE_NE(it, custom.end()); REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/pro")); 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"); it = custom.find("pkgs/r");
REQUIRE_NE(it, custom.end()); REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r")); 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") TEST_CASE("channel_alias")
@ -77,12 +86,12 @@ namespace mamba
auto it = custom.find("pkgs/main"); auto it = custom.find("pkgs/main");
REQUIRE_NE(it, custom.end()); REQUIRE_NE(it, custom.end());
CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); 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"; std::string value = "conda-forge";
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://mydomain.com/channels/conda-forge")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
ctx.channel_alias = "https://conda.anaconda.org"; ctx.channel_alias = "https://conda.anaconda.org";
@ -126,7 +135,7 @@ namespace mamba
std::string value = "test_channel"; std::string value = "test_channel";
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("file:///tmp/test_channel")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("file:///tmp/test_channel/") + platform, std::string("file:///tmp/test_channel/") + platform,
@ -139,7 +148,7 @@ namespace mamba
std::string value = "some_channel"; std::string value = "some_channel";
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://conda.mydomain.xyz/some_channel")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://conda.mydomain.xyz/some_channel/") + platform, std::string("https://conda.mydomain.xyz/some_channel/") + platform,
@ -173,14 +182,14 @@ namespace mamba
auto x = channel_context.get_channels({ "xtest" }); auto x = channel_context.get_channels({ "xtest" });
CHECK_EQ(x.size(), 3); CHECK_EQ(x.size(), 3);
auto* c1 = x[0]; auto c1 = x[0];
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://mydomain.com/conda-forge/") + platform, std::string("https://mydomain.com/conda-forge/") + platform,
"https://mydomain.com/conda-forge/noarch", "https://mydomain.com/conda-forge/noarch",
}); });
CHECK_EQ(c1->urls(), exp_urls); CHECK_EQ(c1.urls(), exp_urls);
const UrlSet exp_urlsy3({ const UrlSet exp_urlsy3({
std::string("https://otherdomain.com/snakepit/") + platform, std::string("https://otherdomain.com/snakepit/") + platform,
@ -188,9 +197,9 @@ namespace mamba
}); });
auto y = channel_context.get_channels({ "ytest" }); 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.channel_alias = "https://conda.anaconda.org";
ctx.custom_multichannels.clear(); ctx.custom_multichannels.clear();
@ -217,30 +226,30 @@ namespace mamba
auto x = channel_context.get_channels({ "everything" }); auto x = channel_context.get_channels({ "everything" });
CHECK_EQ(x.size(), 3); CHECK_EQ(x.size(), 3);
auto* c1 = x[0]; auto c1 = x[0];
auto* c2 = x[1]; auto c2 = x[1];
auto* c3 = x[2]; auto c3 = x[2];
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://condaforge.org/channels/conda-forge/") + platform, std::string("https://condaforge.org/channels/conda-forge/") + platform,
"https://condaforge.org/channels/conda-forge/noarch", "https://condaforge.org/channels/conda-forge/noarch",
}); });
CHECK_EQ(c1->urls(), exp_urls); CHECK_EQ(c1.urls(), exp_urls);
const UrlSet exp_urls2({ const UrlSet exp_urls2({
std::string("https://mydomain.com/bioconda/") + platform, std::string("https://mydomain.com/bioconda/") + platform,
"https://mydomain.com/bioconda/noarch", "https://mydomain.com/bioconda/noarch",
}); });
CHECK_EQ(c2->urls(), exp_urls2); CHECK_EQ(c2.urls(), exp_urls2);
const UrlSet exp_urls3({ const UrlSet exp_urls3({
std::string("https://mydomain.xyz/xyzchannel/xyz/") + platform, std::string("https://mydomain.xyz/xyzchannel/xyz/") + platform,
"https://mydomain.xyz/xyzchannel/xyz/noarch", "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.channel_alias = "https://conda.anaconda.org";
ctx.custom_multichannels.clear(); ctx.custom_multichannels.clear();
@ -250,26 +259,31 @@ namespace mamba
TEST_CASE("default_channels") TEST_CASE("default_channels")
{ {
auto& ctx = mambatests::context(); 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 }; ChannelContext channel_context{ ctx };
auto x = channel_context.get_channels({ "defaults" }); auto x = channel_context.get_channels({ "defaults" });
#if !defined(_WIN32) #if !defined(_WIN32)
const Channel* c1 = x[0]; const Channel c1 = x[0];
const Channel* c2 = x[1]; 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({ const UrlSet exp_urls({
std::string("https://repo.anaconda.com/pkgs/main/") + platform, std::string("https://repo.anaconda.com/pkgs/main/") + platform,
"https://repo.anaconda.com/pkgs/main/noarch", "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({ const UrlSet exp_urls2({
std::string("https://repo.anaconda.com/pkgs/r/") + platform, std::string("https://repo.anaconda.com/pkgs/r/") + platform,
"https://repo.anaconda.com/pkgs/r/noarch", "https://repo.anaconda.com/pkgs/r/noarch",
}); });
CHECK_EQ(c2->urls(), exp_urls2); CHECK_EQ(c2.urls(), exp_urls2);
#endif #endif
ctx.custom_channels.clear(); ctx.custom_channels.clear();
@ -282,25 +296,28 @@ namespace mamba
"https://mamba.com/test/channel", "https://mamba.com/test/channel",
"https://mamba.com/stable/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 }; ChannelContext channel_context{ ctx };
auto x = channel_context.get_channels({ "defaults" }); auto x = channel_context.get_channels({ "defaults" });
const Channel* c1 = x[0]; const Channel c1 = x[0];
const Channel* c2 = x[1]; 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({ const UrlSet exp_urls({
std::string("https://mamba.com/test/channel/") + platform, std::string("https://mamba.com/test/channel/") + platform,
"https://mamba.com/test/channel/noarch", "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({ const UrlSet exp_urls2({
std::string("https://mamba.com/stable/channel/") + platform, std::string("https://mamba.com/stable/channel/") + platform,
"https://mamba.com/stable/channel/noarch", "https://mamba.com/stable/channel/noarch",
}); });
CHECK_EQ(c2->urls(), exp_urls2); CHECK_EQ(c2.urls(), exp_urls2);
ctx.custom_channels.clear(); ctx.custom_channels.clear();
} }
@ -309,30 +326,20 @@ namespace mamba
{ {
auto& ctx = mambatests::context(); auto& ctx = mambatests::context();
// Create conda-bld directory to enable testing // Hard coded Anaconda multi channel names set in configuration after refactor
auto conda_bld_dir = env::home_directory() / "conda-bld"; // Should be moved to test_config
bool to_be_removed = fs::create_directories(conda_bld_dir); // 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 }; ChannelContext channel_context{ ctx };
const auto& custom = channel_context.get_custom_channels(); CHECK_EQ(channel_context.get_custom_multichannels().at("local").size(), 3);
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");
auto local_channels = channel_context.get_channels({ "local" }); auto local_channels = channel_context.get_channels({ "local" });
CHECK_EQ(local_channels.size(), 1); CHECK_EQ(local_channels.size(), 3);
// Cleaning
ctx.custom_channels.clear();
if (to_be_removed)
{
fs::remove_all(conda_bld_dir);
}
} }
TEST_CASE("custom_channels_with_labels") TEST_CASE("custom_channels_with_labels")
@ -348,7 +355,7 @@ namespace mamba
std::string value = "test_channel"; std::string value = "test_channel";
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://server.com/private/channels/test_channel")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://server.com/private/channels/test_channel/") + platform, std::string("https://server.com/private/channels/test_channel/") + platform,
@ -364,7 +371,7 @@ namespace mamba
c.url(), c.url(),
CondaURL::parse("https://server.com/private/channels/test_channel/mylabel/xyz") 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://server.com/private/channels/test_channel/mylabel/xyz/") std::string("https://server.com/private/channels/test_channel/mylabel/xyz/")
@ -382,7 +389,7 @@ namespace mamba
c.url(), c.url(),
CondaURL::parse("https://server.com/random/channels/random/test_channel/pkg") 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
const UrlSet exp_urls({ const UrlSet exp_urls({
std::string("https://server.com/random/channels/random/test_channel/pkg/") std::string("https://server.com/random/channels/random/test_channel/pkg/")
@ -405,7 +412,7 @@ namespace mamba
ChannelContext channel_context{ mambatests::context() }; ChannelContext channel_context{ mambatests::context() };
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://repo.mamba.pm/conda-forge")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
} }
@ -415,32 +422,32 @@ namespace mamba
ChannelContext channel_context{ mambatests::context() }; ChannelContext channel_context{ mambatests::context() };
const Channel& c = channel_context.make_channel(value); const Channel& c = channel_context.make_channel(value);
CHECK_EQ(c.url(), CondaURL::parse("https://conda.anaconda.org/conda-forge")); 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" })); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
std::string value2 = "https://repo.anaconda.com/pkgs/main[" + platform + "]"; std::string value2 = "https://repo.anaconda.com/pkgs/main[" + platform + "]";
const Channel& c2 = channel_context.make_channel(value2); const Channel& c2 = channel_context.make_channel(value2);
CHECK_EQ(c2.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); 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 })); CHECK_EQ(c2.platforms(), PlatformSet({ platform }));
std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]"; std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]";
const Channel& c3 = channel_context.make_channel(value3); const Channel& c3 = channel_context.make_channel(value3);
CHECK_EQ(c3.url(), c.url()); 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 })); CHECK_EQ(c3.platforms(), PlatformSet({ platform }));
std::string value4 = "/home/mamba/test/channel_b"; std::string value4 = "/home/mamba/test/channel_b";
const Channel& c4 = channel_context.make_channel(value4); const Channel& c4 = channel_context.make_channel(value4);
CHECK_EQ(c4.url(), CondaURL::parse(util::path_to_url(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" })); CHECK_EQ(c4.platforms(), PlatformSet({ platform, "noarch" }));
std::string path5 = "/home/mamba/test/channel_b"; std::string path5 = "/home/mamba/test/channel_b";
std::string value5 = util::concat(path5, '[', platform, ']'); std::string value5 = util::concat(path5, '[', platform, ']');
const Channel& c5 = channel_context.make_channel(value5); const Channel& c5 = channel_context.make_channel(value5);
CHECK_EQ(c5.url(), CondaURL::parse(util::path_to_url(path5))); 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 })); CHECK_EQ(c5.platforms(), PlatformSet({ platform }));
std::string value6a = "http://localhost:8000/conda-forge[noarch]"; 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 load_channels(MPool& pool, MultiPackageCache& cache, std::vector<std::string>&& channels)
{ {
auto sub_dirs = std::vector<MSubdirData>(); 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( 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)); 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 <doctest/doctest.h>
#include "mamba/fs/filesystem.hpp"
#include "mamba/specs/channel_spec.hpp" #include "mamba/specs/channel_spec.hpp"
#include "mamba/util/build.hpp" #include "mamba/util/build.hpp"
#include "mamba/util/path_manip.hpp"
#include "mamba/util/string.hpp"
using namespace mamba; using namespace mamba;
using namespace mamba::specs; using namespace mamba::specs;
TEST_SUITE("specs::channel_spec") 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") TEST_CASE("Parsing")
{ {
using Type = typename ChannelSpec::Type; using Type = typename ChannelSpec::Type;
using PlatformSet = typename util::flat_set<std::string>; 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") SUBCASE("https://repo.anaconda.com/conda-forge")
{ {
const auto spec = ChannelSpec::parse("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. // The full license is in the file LICENSE, distributed with this software.
#include <cstddef>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -14,7 +13,12 @@
#include "mamba/fs/filesystem.hpp" #include "mamba/fs/filesystem.hpp"
#include "mamba/util/string.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") 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") TEST_CASE("split")
{ {
std::string a = "hello.again.it's.me.mario"; std::string a = "hello.again.it's.me.mario";
@ -406,7 +444,7 @@ namespace mamba::util
CHECK_EQ(joined, "a-bc-d"); 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); auto joined = join("/", to_join);
static_assert(std::is_same<decltype(joined), decltype(to_join)::value_type>::value); static_assert(std::is_same<decltype(joined), decltype(to_join)::value_type>::value);
CHECK_EQ(joined, "/a/bc/d"); 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"); 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("", "", "/"), ""); for (std::string_view sep : { "/", "//", "/////", "./", "./." })
CHECK_EQ(get_common_parts("", "test", "/"), ""); {
CHECK_EQ(get_common_parts("test", "test", "/"), "test"); CAPTURE(sep);
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", "/"), "");
CHECK_EQ(get_common_parts("", "", "."), ""); CHECK_EQ(concat_dedup_splits("", "", sep), "");
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(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 } // namespace mamba

View File

@ -28,4 +28,26 @@ TEST_SUITE("util::tuple_hash")
const auto t3 = std::tuple{ std::string("hello"), 33 }; const auto t3 = std::tuple{ std::string("hello"), 33 };
CHECK_NE(hash_tuple(t1), hash_tuple(t3)); 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("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("urls", &Channel::urls, py::arg("with_credentials") = true)
.def("platform_urls", &Channel::platform_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) .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__", "__repr__",
[](const Channel& c) [](const Channel& c)
{ {
auto s = c.name(); auto s = c.display_name();
s += "["; s += "[";
bool first = true; bool first = true;
for (const auto& platform : c.platforms()) for (const auto& platform : c.platforms())

View File

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

View File

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