mirror of https://github.com/mamba-org/mamba.git
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:
parent
ddea95e16c
commit
cd744e765c
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]";
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue