mirror of https://github.com/mamba-org/mamba.git
Resolve ChannelSpec into a Channel (#2899)
* Change URL custom_channel matcher * Cannot change location at once * Simpler channel_alias_location * Change URL channel_alias matcher * Refactor ChannelContext::from_url * Add Windows URL path encoding test * Simplify URL::set_path * Fix URL encoding parsing * Refactor platforms urls * Fix missing forward declaration * Fix Win file URL encoding * Split Channel url functions * Remove no-URL ctor from ChannelContext::from_name * Remove no-URL ctor from ChannelContext::make_simple_channel * Remove old Channel ctor * Remove channel_alias_location * Refactor Channel::base_url * Try not to use location mamba.py * Remove Channel comparison * Try Channel::platform_url * Refactor Channel::platform_urls to use platform_url * Remove build_url * Remove concat_scheme_url * Use spec in from_name * Tuple equality for URL * Add tuple_hash * Add std::hash<mamba::util::URL> * Make CondaURL hashable * Refactor make_channel * New algorithm for authetification map * Add specs::AutheticationDataBase * Fix typo authentification > authentication * Use AuthenticationDataBase * Fix AuthenticationDataBase * use AuthenticationDataBase::find_compatible * Use canonical_name * Add Credential check to URL * Simplify crendential check in channel * Refactor AuthDB credential setters * No Channel::location in tests * Remove Channel user scheme password * Document AuthenticationInfo * Remove Channel token * Remove Channel::auth * Remove Channel::package_name * More replacement name > canonical_name * Remove unused function * No credential from URLs * Remove unneeded header * Disable Channel::repo_checker This is disbled as TUF implementation is not well defined and this function is not used except in experimental conditions. * Apply auth info as needed * Simplify custom_channels instanciation * Simplify custom_multi_channels instanciation * Review improvements
This commit is contained in:
parent
f23e078f2c
commit
c7aba975b5
|
@ -138,6 +138,7 @@ set(LIBMAMBA_SOURCES
|
|||
${LIBMAMBA_SOURCE_DIR}/core/compression.cpp
|
||||
# Implementation of version and matching specs
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/archive.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/authentication_info.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version.cpp
|
||||
|
@ -227,10 +228,11 @@ set(LIBMAMBA_PUBLIC_HEADERS
|
|||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/iterator.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/string.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/path_manip.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/tuple_hash.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/url_manip.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/util/url.hpp
|
||||
# Implementation of version and matching specs
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentification_info.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentication_info.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/archive.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/platform.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#define MAMBA_CORE_CHANNEL_HPP
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
@ -44,21 +42,14 @@ namespace mamba
|
|||
|
||||
~Channel();
|
||||
|
||||
std::string_view scheme() const;
|
||||
const std::string& location() const;
|
||||
const std::string& name() const;
|
||||
const std::string& canonical_name() const;
|
||||
const util::flat_set<std::string>& platforms() const;
|
||||
std::optional<std::string> auth() const;
|
||||
std::optional<std::string> user() const;
|
||||
std::optional<std::string> password() const;
|
||||
std::optional<std::string> token() const;
|
||||
std::optional<std::string> package_filename() const;
|
||||
const validation::RepoChecker&
|
||||
repo_checker(Context& context, MultiPackageCache& caches) const;
|
||||
const specs::CondaURL& url() const;
|
||||
|
||||
std::string base_url() const;
|
||||
std::string platform_url(std::string platform, bool with_credential = true) const;
|
||||
std::string platform_url(std::string_view platform, bool with_credential = true) const;
|
||||
// The pairs consist of (platform,url)
|
||||
util::flat_set<std::pair<std::string, std::string>>
|
||||
platform_urls(bool with_credential = true) const;
|
||||
|
@ -66,18 +57,6 @@ namespace mamba
|
|||
|
||||
private:
|
||||
|
||||
Channel(
|
||||
std::string_view scheme,
|
||||
std::string location,
|
||||
std::string name,
|
||||
std::string canonical_name,
|
||||
std::string_view user = {},
|
||||
std::string_view password = {},
|
||||
std::string_view token = {},
|
||||
std::string_view package_filename = {},
|
||||
util::flat_set<std::string> platforms = {}
|
||||
);
|
||||
|
||||
Channel(
|
||||
specs::CondaURL url,
|
||||
std::string location,
|
||||
|
@ -86,17 +65,12 @@ namespace mamba
|
|||
util::flat_set<std::string> platforms = {}
|
||||
);
|
||||
|
||||
const specs::CondaURL& url() const;
|
||||
|
||||
specs::CondaURL m_url;
|
||||
std::string m_location;
|
||||
std::string m_name;
|
||||
std::string m_canonical_name;
|
||||
util::flat_set<std::string> m_platforms;
|
||||
|
||||
// This is used to make sure that there is a unique repo for every channel
|
||||
mutable std::unique_ptr<validation::RepoChecker> p_repo_checker;
|
||||
|
||||
// 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,
|
||||
|
@ -106,10 +80,6 @@ namespace mamba
|
|||
friend class ChannelContext;
|
||||
};
|
||||
|
||||
bool operator==(const Channel& lhs, const Channel& rhs);
|
||||
bool operator!=(const Channel& lhs, const Channel& rhs);
|
||||
|
||||
|
||||
using ChannelCache = std::map<std::string, Channel>;
|
||||
|
||||
class ChannelContext
|
||||
|
@ -160,8 +130,10 @@ namespace mamba
|
|||
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(const std::string& name);
|
||||
Channel from_name(specs::ChannelSpec&& spec);
|
||||
Channel from_value(const std::string& value);
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include "mamba/core/palette.hpp"
|
||||
#include "mamba/core/tasksync.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/specs/authentification_info.hpp"
|
||||
#include "mamba/specs/authentication_info.hpp"
|
||||
#include "mamba/specs/platform.hpp"
|
||||
#include "mamba/version.hpp"
|
||||
|
||||
|
@ -214,9 +214,8 @@ namespace mamba
|
|||
};
|
||||
|
||||
std::string channel_alias = "https://conda.anaconda.org";
|
||||
using authentication_info_map_t = std::map<std::string, specs::AuthenticationInfo>;
|
||||
authentication_info_map_t& authentication_info();
|
||||
const authentication_info_map_t& authentication_info() const;
|
||||
specs::AuthenticationDataBase& authentication_info();
|
||||
const specs::AuthenticationDataBase& authentication_info() const;
|
||||
std::vector<fs::u8path> token_locations{ "~/.continuum/anaconda-client/tokens" };
|
||||
|
||||
bool override_channels_enabled = true;
|
||||
|
@ -253,7 +252,7 @@ namespace mamba
|
|||
bool on_ci = false;
|
||||
|
||||
void load_authentication_info();
|
||||
std::map<std::string, specs::AuthenticationInfo> m_authentication_info;
|
||||
specs::AuthenticationDataBase m_authentication_info;
|
||||
bool m_authentication_infos_loaded = false;
|
||||
|
||||
std::shared_ptr<Logger> logger;
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "mamba/core/pool.hpp"
|
||||
#include "mamba/util/graph.hpp"
|
||||
|
||||
typedef struct s_Solvable Solvable;
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
using GraphicsParams = Context::GraphicsParams;
|
||||
|
|
|
@ -331,13 +331,6 @@ namespace mamba
|
|||
void split_package_extension(const std::string& file, std::string& name, std::string& extension);
|
||||
fs::u8path strip_package_extension(const std::string& file);
|
||||
|
||||
template <class T>
|
||||
inline bool vector_is_prefix(const std::vector<T>& prefix, const std::vector<T>& vec)
|
||||
{
|
||||
return vec.size() >= prefix.size()
|
||||
&& prefix.end() == std::mismatch(prefix.begin(), prefix.end(), vec.begin()).first;
|
||||
}
|
||||
|
||||
tl::expected<std::string, mamba_error> encode_base64(std::string_view input);
|
||||
tl::expected<std::string, mamba_error> decode_base64(std::string_view input);
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// 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.
|
||||
|
||||
#ifndef MAMBA_SPECS_AUTHENTICATION_INFO_HPP
|
||||
#define MAMBA_SPECS_AUTHENTICATION_INFO_HPP
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
class CondaURL;
|
||||
|
||||
/** User and password authetification set in the URL. */
|
||||
struct BasicHTTPAuthentication
|
||||
{
|
||||
std::string user;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
/** HTTP Bearer token set in the request headers. */
|
||||
struct BearerToken
|
||||
{
|
||||
std::string token;
|
||||
};
|
||||
|
||||
/** A Conda token set in the URL path. */
|
||||
struct CondaToken
|
||||
{
|
||||
std::string token;
|
||||
};
|
||||
|
||||
using AuthenticationInfo = std::variant<BasicHTTPAuthentication, BearerToken, CondaToken>;
|
||||
|
||||
/**
|
||||
* A class that holds the authentication info stored by users.
|
||||
*
|
||||
* Essentially a map, except that some keys can match mutliple queries.
|
||||
* For instance "mamba.org/private" should be matched by queries "mamba.org/private",
|
||||
* "mamba.org/private/channel", but not "mamba.org/public".
|
||||
*
|
||||
* A best effort is made to satifiy this with `xxx_compatible`.
|
||||
*
|
||||
* Future development of this class should aim to replace the map and keys with a
|
||||
* `AuthenticationSpec`, that can decide whether or not a URL should benefit from such
|
||||
* its authentication.
|
||||
* Possibly, a string reprensentation such as "*.mamba.org/private/channel*" could be added
|
||||
* to parse users intentions, rather than relying on the assumptions made here.
|
||||
*/
|
||||
class AuthenticationDataBase : private std::unordered_map<std::string, AuthenticationInfo>
|
||||
{
|
||||
public:
|
||||
|
||||
using Base = std::unordered_map<std::string, AuthenticationInfo>;
|
||||
|
||||
using typename Base::key_type;
|
||||
using typename Base::mapped_type;
|
||||
using typename Base::value_type;
|
||||
using typename Base::size_type;
|
||||
using typename Base::iterator;
|
||||
using typename Base::const_iterator;
|
||||
|
||||
using Base::Base;
|
||||
|
||||
using Base::begin;
|
||||
using Base::end;
|
||||
using Base::cbegin;
|
||||
using Base::cend;
|
||||
|
||||
using Base::empty;
|
||||
using Base::size;
|
||||
using Base::max_size;
|
||||
|
||||
using Base::clear;
|
||||
using Base::insert;
|
||||
using Base::insert_or_assign;
|
||||
using Base::emplace;
|
||||
using Base::emplace_hint;
|
||||
using Base::try_emplace;
|
||||
using Base::erase;
|
||||
using Base::swap;
|
||||
using Base::extract;
|
||||
using Base::merge;
|
||||
|
||||
using Base::reserve;
|
||||
|
||||
using Base::at;
|
||||
|
||||
[[nodiscard]] auto at_compatible(const key_type& key) -> mapped_type&;
|
||||
[[nodiscard]] auto at_compatible(const key_type& key) const -> const mapped_type&;
|
||||
|
||||
using Base::find;
|
||||
|
||||
auto find_compatible(const key_type& key) -> iterator;
|
||||
auto find_compatible(const key_type& key) const -> const_iterator;
|
||||
|
||||
[[nodiscard]] auto contains(const key_type& key) const -> bool;
|
||||
|
||||
[[nodiscard]] auto contains_compatible(const key_type& key) const -> bool;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -1,36 +0,0 @@
|
|||
// 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.
|
||||
|
||||
#ifndef MAMBA_SPECS_AUTHENTIFICATION_INFO_HPP
|
||||
#define MAMBA_SPECS_AUTHENTIFICATION_INFO_HPP
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/** User and password authetification set in the URL. */
|
||||
struct BasicHTTPAuthentication
|
||||
{
|
||||
std::string user;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
/** HTTP Bearer token set in the request headers. */
|
||||
struct BearerToken
|
||||
{
|
||||
std::string token;
|
||||
};
|
||||
|
||||
/** A Conda token set in the URL path. */
|
||||
struct CondaToken
|
||||
{
|
||||
std::string token;
|
||||
};
|
||||
|
||||
using AuthenticationInfo = std::variant<BasicHTTPAuthentication, BearerToken, CondaToken>;
|
||||
}
|
||||
#endif
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef MAMBA_SPECS_CONDA_URL_HPP
|
||||
#define MAMBA_SPECS_CONDA_URL_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include "mamba/specs/platform.hpp"
|
||||
|
@ -34,13 +35,17 @@ namespace mamba::specs
|
|||
explicit CondaURL(util::URL&& url);
|
||||
explicit CondaURL(const util::URL& url);
|
||||
|
||||
auto base() const -> const util::URL&;
|
||||
|
||||
using Base::scheme_is_defaulted;
|
||||
using Base::scheme;
|
||||
using Base::set_scheme;
|
||||
using Base::clear_scheme;
|
||||
using Base::has_user;
|
||||
using Base::user;
|
||||
using Base::set_user;
|
||||
using Base::clear_user;
|
||||
using Base::has_password;
|
||||
using Base::password;
|
||||
using Base::set_password;
|
||||
using Base::clear_password;
|
||||
|
@ -99,6 +104,9 @@ namespace mamba::specs
|
|||
*/
|
||||
void append_path(std::string_view path, Encode::no_type);
|
||||
|
||||
/** Return wether a token is set. */
|
||||
[[nodiscard]] auto has_token() const -> bool;
|
||||
|
||||
/** Return the Conda token, as delimited with "/t/", or empty if there isn't any. */
|
||||
[[nodiscard]] auto token() const -> std::string_view;
|
||||
|
||||
|
@ -226,7 +234,7 @@ namespace mamba::specs
|
|||
friend auto operator==(const CondaURL&, const CondaURL&) -> bool;
|
||||
};
|
||||
|
||||
/** Compare two CondaURL. */
|
||||
/** Tuple-like equality of all observable members */
|
||||
auto operator==(const CondaURL& a, const CondaURL& b) -> bool;
|
||||
auto operator!=(const CondaURL& a, const CondaURL& b) -> bool;
|
||||
|
||||
|
@ -234,4 +242,11 @@ namespace mamba::specs
|
|||
auto operator/(const CondaURL& url, std::string_view subpath) -> CondaURL;
|
||||
auto operator/(CondaURL&& url, std::string_view subpath) -> CondaURL;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::hash<mamba::specs::CondaURL>
|
||||
{
|
||||
auto operator()(const mamba::specs::CondaURL& p) const -> std::size_t;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
|
||||
#ifndef MAMBA_UTIL_TUPLE_HASH_HPP
|
||||
#define MAMBA_UTIL_TUPLE_HASH_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
namespace mamba::util
|
||||
{
|
||||
constexpr void hash_combine(std::size_t& seed, std::size_t other)
|
||||
{
|
||||
const auto boost_magic_num = 0x9e3779b9;
|
||||
seed ^= other + boost_magic_num + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template <class T, typename Hasher = std::hash<T>>
|
||||
constexpr void hash_combine_val(std::size_t& seed, const T& val, const Hasher& hasher = {})
|
||||
{
|
||||
hash_combine(seed, hasher(val));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
constexpr auto hash_vals(const T&... vals) -> std::size_t
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
(hash_combine_val(seed, vals), ...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
constexpr auto hash_tuple(const std::tuple<T...>& t) -> std::size_t
|
||||
{
|
||||
return std::apply([](const auto&... vals) { return hash_vals(vals...); }, t);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
struct Tuplehasher
|
||||
{
|
||||
constexpr auto operator()(const std::tuple<T...>& t) const -> std::size_t
|
||||
{
|
||||
return hash_tuple(t);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -8,6 +8,7 @@
|
|||
#define MAMBA_UTIL_URL_HPP
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -98,6 +99,9 @@ namespace mamba::util
|
|||
/** Clear the scheme back to a defaulted value and return the old value. */
|
||||
auto clear_scheme() -> std::string;
|
||||
|
||||
/** Return wether the user is empty. */
|
||||
[[nodiscard]] auto has_user() const -> bool;
|
||||
|
||||
/** Return the encoded user, or empty if none. */
|
||||
[[nodiscard]] auto user(Decode::no_type) const -> const std::string&;
|
||||
|
||||
|
@ -113,6 +117,9 @@ namespace mamba::util
|
|||
/** Clear and return the encoded user. */
|
||||
auto clear_user() -> std::string;
|
||||
|
||||
/** Return wether the password is empty. */
|
||||
[[nodiscard]] auto has_password() const -> bool;
|
||||
|
||||
/** Return the encoded password, or empty if none. */
|
||||
[[nodiscard]] auto password(Decode::no_type) const -> const std::string&;
|
||||
|
||||
|
@ -270,6 +277,7 @@ namespace mamba::util
|
|||
std::string m_fragment = {};
|
||||
};
|
||||
|
||||
/** Tuple-like equality of all observable members */
|
||||
auto operator==(URL const& a, URL const& b) -> bool;
|
||||
auto operator!=(URL const& a, URL const& b) -> bool;
|
||||
|
||||
|
@ -277,4 +285,10 @@ namespace mamba::util
|
|||
auto operator/(URL const& url, std::string_view subpath) -> URL;
|
||||
auto operator/(URL&& url, std::string_view subpath) -> URL;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::hash<mamba::util::URL>
|
||||
{
|
||||
auto operator()(const mamba::util::URL& p) const -> std::size_t;
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#ifndef MAMBA_UTIL_URL_MANIP_HPP
|
||||
#define MAMBA_UTIL_URL_MANIP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
@ -34,15 +33,6 @@ namespace mamba::util
|
|||
*/
|
||||
[[nodiscard]] auto url_decode(std::string_view url) -> std::string;
|
||||
|
||||
std::string concat_scheme_url(const std::string& scheme, const std::string& location);
|
||||
|
||||
std::string build_url(
|
||||
const std::optional<std::string>& auth,
|
||||
const std::string& scheme,
|
||||
const std::string& base,
|
||||
bool with_credential
|
||||
);
|
||||
|
||||
void split_platform(
|
||||
const std::vector<std::string>& known_platforms,
|
||||
const std::string& url,
|
||||
|
|
|
@ -89,10 +89,10 @@ namespace mamba
|
|||
else
|
||||
{
|
||||
// Consider 'flexible' and 'strict' the same way
|
||||
if (channel->name() != prev_channel_name)
|
||||
if (channel->canonical_name() != prev_channel_name)
|
||||
{
|
||||
max_prio--;
|
||||
prev_channel_name = channel->name();
|
||||
prev_channel_name = channel->canonical_name();
|
||||
}
|
||||
priorities.push_back(std::make_pair(max_prio, 0));
|
||||
}
|
||||
|
|
|
@ -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.name();
|
||||
obj["channel"] = channel.canonical_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.name();
|
||||
formatted_pkgs.channel = channel.canonical_name();
|
||||
}
|
||||
packages.push_back(formatted_pkgs);
|
||||
}
|
||||
|
|
|
@ -38,19 +38,6 @@ namespace mamba
|
|||
|
||||
const char LOCAL_CHANNELS_NAME[] = "local";
|
||||
const char DEFAULT_CHANNELS_NAME[] = "defaults";
|
||||
|
||||
std::optional<std::string> nonempty_str(std::string&& s)
|
||||
{
|
||||
return s.empty() ? std::optional<std::string>() : std::make_optional(s);
|
||||
}
|
||||
|
||||
auto channel_alias_location(specs::CondaURL url) -> std::string
|
||||
{
|
||||
url.clear_user();
|
||||
url.clear_password();
|
||||
url.clear_token();
|
||||
return url.pretty_str(specs::CondaURL::StripScheme::yes, '/');
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> get_known_platforms()
|
||||
|
@ -63,48 +50,6 @@ namespace mamba
|
|||
* Channel implementation *
|
||||
**************************/
|
||||
|
||||
Channel::Channel(
|
||||
std::string_view scheme,
|
||||
std::string location,
|
||||
std::string name,
|
||||
std::string canonical_name,
|
||||
std::string_view user,
|
||||
std::string_view password,
|
||||
std::string_view token,
|
||||
std::string_view package_filename,
|
||||
util::flat_set<std::string> platforms
|
||||
)
|
||||
: m_url()
|
||||
, m_location(std::move(location))
|
||||
, m_name(std::move(name))
|
||||
, m_canonical_name(std::move(canonical_name))
|
||||
, m_platforms(std::move(platforms))
|
||||
{
|
||||
if (m_name != UNKNOWN_CHANNEL)
|
||||
{
|
||||
if (scheme == "file")
|
||||
{
|
||||
m_url.set_path(m_location);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_url = specs::CondaURL::parse(m_location);
|
||||
if (!token.empty())
|
||||
{
|
||||
m_url.set_token(token);
|
||||
}
|
||||
m_url.set_user(user);
|
||||
m_url.set_password(password);
|
||||
}
|
||||
m_url.set_scheme(scheme);
|
||||
m_url.append_path(m_name);
|
||||
if (!package_filename.empty())
|
||||
{
|
||||
m_url.set_package(package_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
specs::CondaURL url,
|
||||
std::string location,
|
||||
|
@ -127,11 +72,6 @@ namespace mamba
|
|||
return m_url;
|
||||
}
|
||||
|
||||
std::string_view Channel::scheme() const
|
||||
{
|
||||
return m_url.scheme();
|
||||
}
|
||||
|
||||
const std::string& Channel::location() const
|
||||
{
|
||||
return m_location;
|
||||
|
@ -147,51 +87,6 @@ namespace mamba
|
|||
return m_platforms;
|
||||
}
|
||||
|
||||
std::optional<std::string> Channel::auth() const
|
||||
{
|
||||
return nonempty_str(m_url.authentication());
|
||||
}
|
||||
|
||||
std::optional<std::string> Channel::user() const
|
||||
{
|
||||
return nonempty_str(m_url.user());
|
||||
}
|
||||
|
||||
std::optional<std::string> Channel::password() const
|
||||
{
|
||||
return nonempty_str(m_url.password());
|
||||
}
|
||||
|
||||
std::optional<std::string> Channel::token() const
|
||||
{
|
||||
return nonempty_str(std::string(m_url.token()));
|
||||
}
|
||||
|
||||
std::optional<std::string> Channel::package_filename() const
|
||||
{
|
||||
return nonempty_str(m_url.package());
|
||||
}
|
||||
|
||||
const validation::RepoChecker&
|
||||
Channel::repo_checker(Context& context, MultiPackageCache& caches) const
|
||||
{
|
||||
if (p_repo_checker == nullptr)
|
||||
{
|
||||
p_repo_checker = std::make_unique<validation::RepoChecker>(
|
||||
context,
|
||||
util::rsplit(base_url(), "/", 1).front(),
|
||||
context.prefix_params.root_prefix / "etc" / "trusted-repos"
|
||||
/ util::cache_name_from_url(base_url()),
|
||||
caches.first_writable_path() / "cache" / util::cache_name_from_url(base_url())
|
||||
);
|
||||
|
||||
fs::create_directories(p_repo_checker->cache_path());
|
||||
p_repo_checker->generate_index_checker();
|
||||
}
|
||||
|
||||
return *p_repo_checker;
|
||||
}
|
||||
|
||||
const std::string& Channel::canonical_name() const
|
||||
{
|
||||
return m_canonical_name;
|
||||
|
@ -199,36 +94,17 @@ namespace mamba
|
|||
|
||||
std::string Channel::base_url() const
|
||||
{
|
||||
if (name() == UNKNOWN_CHANNEL)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return util::concat_scheme_url(std::string(scheme()), util::join_url(location(), name()));
|
||||
}
|
||||
return url().str(specs::CondaURL::Credentials::Remove);
|
||||
}
|
||||
|
||||
util::flat_set<std::string> Channel::urls(bool with_credential) const
|
||||
{
|
||||
if (auto fn = package_filename())
|
||||
if (!url().package().empty())
|
||||
{
|
||||
std::string base = {};
|
||||
if (with_credential && token())
|
||||
{
|
||||
base = util::join_url(location(), "t", *token());
|
||||
}
|
||||
else
|
||||
{
|
||||
base = location();
|
||||
}
|
||||
|
||||
return { { util::build_url(
|
||||
auth(),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), std::move(fn).value()),
|
||||
with_credential
|
||||
) } };
|
||||
return { url().str(
|
||||
with_credential ? specs::CondaURL::Credentials::Show
|
||||
: specs::CondaURL::Credentials::Remove
|
||||
) };
|
||||
}
|
||||
|
||||
auto out = util::flat_set<std::string>{};
|
||||
|
@ -242,51 +118,29 @@ namespace mamba
|
|||
util::flat_set<std::pair<std::string, std::string>>
|
||||
Channel::platform_urls(bool with_credential) const
|
||||
{
|
||||
std::string base = location();
|
||||
if (with_credential && token())
|
||||
if (!url().package().empty())
|
||||
{
|
||||
base = util::join_url(base, "t", *token());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto out = util::flat_set<std::pair<std::string, std::string>>{};
|
||||
for (const auto& platform : platforms())
|
||||
{
|
||||
out.insert({
|
||||
platform,
|
||||
util::build_url(
|
||||
auth(),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), platform),
|
||||
with_credential
|
||||
),
|
||||
});
|
||||
out.insert({ platform, platform_url(platform, with_credential) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Channel::platform_url(std::string platform, bool with_credential) const
|
||||
std::string Channel::platform_url(std::string_view platform, bool with_credential) const
|
||||
{
|
||||
std::string base = location();
|
||||
if (with_credential && token())
|
||||
auto cred = with_credential ? specs::CondaURL::Credentials::Show
|
||||
: specs::CondaURL::Credentials::Remove;
|
||||
|
||||
if (!url().package().empty())
|
||||
{
|
||||
base = util::join_url(base, "t", *token());
|
||||
return url().str(cred);
|
||||
}
|
||||
return util::build_url(
|
||||
auth(),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), platform),
|
||||
with_credential
|
||||
);
|
||||
}
|
||||
|
||||
bool operator==(const Channel& lhs, const Channel& rhs)
|
||||
{
|
||||
return lhs.location() == rhs.location() && lhs.name() == rhs.name();
|
||||
}
|
||||
|
||||
bool operator!=(const Channel& lhs, const Channel& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
return (url() / platform).str(cred);
|
||||
}
|
||||
|
||||
/*********************************
|
||||
|
@ -302,34 +156,36 @@ namespace mamba
|
|||
{
|
||||
if (!util::url_has_scheme(channel_url))
|
||||
{
|
||||
auto ca_location = channel_alias_location(channel_alias);
|
||||
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(
|
||||
/* scheme= */ channel_alias.scheme(),
|
||||
/* location= */ std::move(ca_location),
|
||||
/* name= */ std::string(util::strip(channel_name.empty() ? channel_url : channel_name, '/')),
|
||||
/* canonical_name= */ channel_canonical_name,
|
||||
/* user= */ channel_alias.user(),
|
||||
/* password= */ channel_alias.password(),
|
||||
/* token= */ channel_alias.token(),
|
||||
/* package_filename= */ {}
|
||||
/* 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 token = std::string(url.token());
|
||||
url.clear_token();
|
||||
std::string user = url.user(); // % encoded
|
||||
url.clear_user();
|
||||
std::string password = url.password(); // % encoded
|
||||
url.clear_password();
|
||||
std::string location = url.pretty_str(specs::CondaURL::StripScheme::yes, '/');
|
||||
|
||||
std::string location = url.pretty_str(
|
||||
specs::CondaURL::StripScheme::yes,
|
||||
'/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
std::string name(channel_name);
|
||||
if (name.empty())
|
||||
|
||||
if (channel_name.empty())
|
||||
{
|
||||
if (auto ca_location = channel_alias_location(channel_alias);
|
||||
util::starts_with(location, ca_location))
|
||||
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);
|
||||
|
@ -342,20 +198,21 @@ namespace mamba
|
|||
}
|
||||
else
|
||||
{
|
||||
location = util::concat(url.host(), url.port().empty() ? "" : ":", url.port());
|
||||
name = url.path();
|
||||
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(
|
||||
/* scheme= */ url.scheme(),
|
||||
/* location= */ location,
|
||||
/* name= */ name,
|
||||
/* canonical_name= */ channel_canonical_name,
|
||||
/* user= */ user,
|
||||
/* password= */ password,
|
||||
/* token= */ token,
|
||||
/* package_filename= */ {}
|
||||
/* url = */ std::move(url),
|
||||
/* location= */ std::move(location),
|
||||
/* name= */ std::move(name),
|
||||
/* canonical_name= */ channel_canonical_name
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -380,6 +237,50 @@ namespace mamba
|
|||
);
|
||||
}
|
||||
|
||||
void
|
||||
set_fallback_credential_from_auth(specs::CondaURL& url, const specs::AuthenticationInfo& auth)
|
||||
{
|
||||
std::visit(
|
||||
[&](const auto& info)
|
||||
{
|
||||
using Info = std::decay_t<decltype(info)>;
|
||||
if constexpr (std::is_same_v<Info, specs::BasicHTTPAuthentication>)
|
||||
{
|
||||
if (!url.has_user() && !url.has_password())
|
||||
{
|
||||
url.set_user(info.user, specs::CondaURL::Encode::yes);
|
||||
url.set_password(info.password, specs::CondaURL::Encode::yes);
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<Info, specs::CondaToken>)
|
||||
{
|
||||
if (!url.has_token())
|
||||
{
|
||||
url.set_token(info.token);
|
||||
}
|
||||
}
|
||||
},
|
||||
auth
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
set_fallback_credential_from_db(specs::CondaURL& url, const specs::AuthenticationDataBase& db)
|
||||
{
|
||||
if (!url.has_token() || !url.has_user() || !url.has_password())
|
||||
{
|
||||
const auto key = url.pretty_str(
|
||||
specs::CondaURL::StripScheme::yes,
|
||||
'/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
if (auto it = db.find_compatible(key); it != db.end())
|
||||
{
|
||||
set_fallback_credential_from_auth(url, it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto rsplit_once(std::string_view str, char sep)
|
||||
{
|
||||
auto [head, tail] = util::rstrip_if_parts(str, [sep](char c) { return c != sep; });
|
||||
|
@ -458,159 +359,102 @@ namespace mamba
|
|||
return chan;
|
||||
}
|
||||
|
||||
namespace
|
||||
Channel ChannelContext::from_any_url(specs::ChannelSpec&& spec)
|
||||
{
|
||||
// Channel configuration
|
||||
struct channel_configuration
|
||||
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())
|
||||
{
|
||||
specs::CondaURL url;
|
||||
std::string location;
|
||||
std::string name;
|
||||
std::string canonical_name;
|
||||
};
|
||||
|
||||
channel_configuration
|
||||
read_channel_configuration(ChannelContext& channel_context, specs::CondaURL url)
|
||||
{
|
||||
assert(url.scheme() != "file");
|
||||
|
||||
url.clear_package();
|
||||
url.clear_token();
|
||||
url.clear_password();
|
||||
url.clear_user();
|
||||
|
||||
std::string default_location = url.pretty_str(
|
||||
specs::CondaURL::StripScheme::yes,
|
||||
'/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
|
||||
// Case 4: custom_channels matches
|
||||
for (const auto& [canonical_name, chan] : channel_context.get_custom_channels())
|
||||
if (url_match(chan.url(), url))
|
||||
{
|
||||
std::string test_url = util::join_url(chan.location(), chan.name());
|
||||
if (vector_is_prefix(util::split(test_url, "/"), util::split(default_location, "/")))
|
||||
{
|
||||
auto subname = std::string(
|
||||
util::strip(default_location.replace(0u, test_url.size(), ""), '/')
|
||||
);
|
||||
|
||||
auto location = chan.location();
|
||||
auto name = util::join_url(chan.name(), subname);
|
||||
auto l_url = specs::CondaURL::parse(chan.base_url());
|
||||
l_url.append_path(subname);
|
||||
l_url.set_scheme(url.scheme());
|
||||
l_url.set_user(chan.user().value_or(""));
|
||||
l_url.set_password(chan.password().value_or(""));
|
||||
if (auto token = chan.token().value_or(""); !token.empty())
|
||||
{
|
||||
l_url.set_token(std::move(token));
|
||||
}
|
||||
return channel_configuration{
|
||||
/* .url= */ std::move(l_url),
|
||||
/* .location= */ std::move(location),
|
||||
/* .name= */ std::move(name),
|
||||
/* .canonical_name= */ std::move(canonical_name),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Case 5: channel_alias match
|
||||
const auto& ca = channel_context.get_channel_alias();
|
||||
if (auto ca_location = channel_alias_location(ca);
|
||||
util::starts_with(default_location, ca_location))
|
||||
{
|
||||
auto name = std::string(
|
||||
util::strip(util::remove_prefix(default_location, ca_location), '/')
|
||||
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), '/')
|
||||
);
|
||||
return Channel(
|
||||
/* url= */ std::move(url),
|
||||
/* location= */ std::move(location),
|
||||
/* name= */ std::move(name),
|
||||
/* canonical_name= */ std::string(canonical_name)
|
||||
);
|
||||
auto l_url = specs::CondaURL::parse(util::join_url(ca_location, name));
|
||||
l_url.set_scheme(url.scheme());
|
||||
l_url.set_user(ca.user());
|
||||
l_url.set_password(ca.password());
|
||||
if (auto token = ca.token(); !token.empty())
|
||||
{
|
||||
l_url.set_token(std::move(token));
|
||||
}
|
||||
return channel_configuration{
|
||||
/*. .url= */ std::move(l_url),
|
||||
/* .location= */ std::move(ca_location),
|
||||
/* .name= */ name,
|
||||
/* .canonical_name= */ name,
|
||||
};
|
||||
}
|
||||
|
||||
// Case 2: migrated_custom_channels not implemented yet
|
||||
// Case 3: migrated_channel_aliases not implemented yet
|
||||
|
||||
// Case 1: No path given, channel name is ""
|
||||
// Case 7: fallback, channel_location = host:port and channel_name = path
|
||||
auto name = std::string(util::strip(url.path_without_token(), '/'));
|
||||
auto location = url.authority(specs::CondaURL::Credentials::Remove);
|
||||
auto canonical_name = url.pretty_str(
|
||||
util::URL::StripScheme::no,
|
||||
/* rstrip_path/ */ '/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
return channel_configuration{
|
||||
/* .url= */ std::move(url),
|
||||
/* .location= */ std::move(location),
|
||||
/* .name= */ std::move(name),
|
||||
/* .canonical_name= */ std::move(canonical_name),
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
assert(util::url_has_scheme(spec.location()));
|
||||
auto url = specs::CondaURL::parse(spec.location());
|
||||
|
||||
auto config = read_channel_configuration(*this, url);
|
||||
|
||||
using Decode = typename specs::CondaURL::Decode;
|
||||
using Encode = typename specs::CondaURL::Encode;
|
||||
if (url.user(Decode::no).empty() && !config.url.user(Decode::no).empty())
|
||||
{
|
||||
url.set_user(config.url.clear_user(), Encode::no);
|
||||
}
|
||||
if (url.password(Decode::no).empty() && !config.url.password(Decode::no).empty())
|
||||
{
|
||||
url.set_password(config.url.clear_password(), Encode::no);
|
||||
}
|
||||
if (url.token().empty() && !config.url.token().empty())
|
||||
{
|
||||
url.set_token(config.url.token());
|
||||
}
|
||||
return Channel(
|
||||
/* url= */ std::move(url),
|
||||
/* location= */ std::move(config.location),
|
||||
/* name= */ std::move(config.name),
|
||||
/* canonical_name= */ std::move(config.canonical_name)
|
||||
);
|
||||
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(const std::string& name)
|
||||
Channel ChannelContext::from_name(specs::ChannelSpec&& spec)
|
||||
{
|
||||
std::string tmp_stripped = name;
|
||||
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(tmp_stripped);
|
||||
while (it == it_end)
|
||||
auto it = custom_channels.find(name);
|
||||
{
|
||||
const auto pos = tmp_stripped.rfind("/");
|
||||
if (pos == std::string::npos)
|
||||
std::string considered_name = name;
|
||||
while (it == it_end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp_stripped = tmp_stripped.substr(0, pos);
|
||||
it = custom_channels.find(tmp_stripped);
|
||||
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
|
||||
|
@ -624,6 +468,7 @@ namespace mamba
|
|||
// Combine names properly
|
||||
if (common_str.empty())
|
||||
{
|
||||
url.append_path(name);
|
||||
combined_name += "/" + name;
|
||||
}
|
||||
else
|
||||
|
@ -632,33 +477,31 @@ namespace mamba
|
|||
// 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(
|
||||
/* scheme= */ it->second.scheme(),
|
||||
/* location= */ it->second.location(),
|
||||
/* name= */ combined_name,
|
||||
/* canonical_name= */ name,
|
||||
/* user= */ it->second.user().value_or(""),
|
||||
/* password= */ it->second.password().value_or(""),
|
||||
/* token= */ it->second.token().value_or(""),
|
||||
/* package_filename= */ it->second.package_filename().value_or("")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& alias = get_channel_alias();
|
||||
return Channel(
|
||||
/* scheme= */ alias.scheme(),
|
||||
/* location= */ channel_alias_location(alias),
|
||||
/* name= */ name,
|
||||
/* canonical_name= */ name,
|
||||
/* user= */ alias.user(),
|
||||
/* password= */ alias.password(),
|
||||
/* token= */ alias.token()
|
||||
/* 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())
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -666,98 +509,51 @@ namespace mamba
|
|||
if (INVALID_CHANNELS.count(in_value) > 0)
|
||||
{
|
||||
return Channel(
|
||||
/* scheme= */ "",
|
||||
/* location= */ "",
|
||||
/* name= */ UNKNOWN_CHANNEL,
|
||||
/* canonical_name= */ ""
|
||||
/* url= */ specs::CondaURL{},
|
||||
/* location= */ "",
|
||||
/* name= */ UNKNOWN_CHANNEL,
|
||||
/* canonical_name= */ UNKNOWN_CHANNEL
|
||||
);
|
||||
}
|
||||
|
||||
auto spec = specs::ChannelSpec::parse(in_value);
|
||||
|
||||
auto get_platforms = [&]()
|
||||
switch (spec.type())
|
||||
{
|
||||
auto out = spec.platform_filters();
|
||||
|
||||
if (out.empty())
|
||||
case specs::ChannelSpec::Type::PackagePath:
|
||||
{
|
||||
for (const auto& plat : m_context.platforms())
|
||||
{
|
||||
out.insert(plat);
|
||||
}
|
||||
return from_package_path(std::move(spec));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
return [&](specs::ChannelSpec&& l_spec) -> Channel
|
||||
{
|
||||
switch (l_spec.type())
|
||||
case specs::ChannelSpec::Type::Path:
|
||||
{
|
||||
case specs::ChannelSpec::Type::PackagePath:
|
||||
{
|
||||
return from_package_path(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::Path:
|
||||
{
|
||||
return from_path(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::PackageURL:
|
||||
{
|
||||
return from_url(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::URL:
|
||||
{
|
||||
auto plats = get_platforms();
|
||||
auto chan = from_url(std::move(l_spec));
|
||||
chan.m_platforms = std::move(plats);
|
||||
return chan;
|
||||
}
|
||||
case specs::ChannelSpec::Type::Name:
|
||||
{
|
||||
auto chan = from_name(l_spec.location());
|
||||
chan.m_platforms = get_platforms();
|
||||
return chan;
|
||||
}
|
||||
return from_path(std::move(spec));
|
||||
}
|
||||
throw std::invalid_argument("Invalid ChannelSpec::Type");
|
||||
}(std::move(spec));
|
||||
case specs::ChannelSpec::Type::PackageURL:
|
||||
{
|
||||
return from_package_url(std::move(spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::URL:
|
||||
{
|
||||
return from_url(std::move(spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::Name:
|
||||
{
|
||||
return from_name(std::move(spec));
|
||||
}
|
||||
}
|
||||
throw std::invalid_argument("Invalid ChannelSpec::Type");
|
||||
}
|
||||
|
||||
const Channel& ChannelContext::make_channel(const std::string& value)
|
||||
{
|
||||
auto res = m_channel_cache.find(value);
|
||||
if (res == m_channel_cache.end())
|
||||
if (const auto it = m_channel_cache.find(value); it != m_channel_cache.end())
|
||||
{
|
||||
auto chan = from_value(value);
|
||||
if (!chan.token())
|
||||
{
|
||||
const auto& with_channel = util::join_url(
|
||||
chan.location(),
|
||||
chan.name() == UNKNOWN_CHANNEL ? "" : chan.name()
|
||||
);
|
||||
const auto& without_channel = chan.location();
|
||||
for (const auto& auth : { with_channel, without_channel })
|
||||
{
|
||||
const auto& authentication_info = m_context.authentication_info();
|
||||
auto it = authentication_info.find(auth);
|
||||
if (it != authentication_info.end()
|
||||
&& std::holds_alternative<specs::CondaToken>(it->second))
|
||||
{
|
||||
chan.m_url.set_token(std::get<specs::CondaToken>(it->second).token);
|
||||
break;
|
||||
}
|
||||
else if (it != authentication_info.end() && std::holds_alternative<specs::BasicHTTPAuthentication>(it->second))
|
||||
{
|
||||
const auto& l_auth = std::get<specs::BasicHTTPAuthentication>(it->second);
|
||||
chan.m_url.set_user(l_auth.user);
|
||||
chan.m_url.set_password(l_auth.password);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
res = m_channel_cache.insert(std::make_pair(value, std::move(chan))).first;
|
||||
return it->second;
|
||||
}
|
||||
return res->second;
|
||||
|
||||
auto [it, inserted] = m_channel_cache.emplace(value, from_value(value));
|
||||
assert(inserted);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::vector<const Channel*>
|
||||
|
@ -877,31 +673,24 @@ namespace mamba
|
|||
m_custom_multichannels.emplace(LOCAL_CHANNELS_NAME, std::move(local_names));
|
||||
|
||||
const auto& context_custom_channels = m_context.custom_channels;
|
||||
for (const auto& [n, p] : context_custom_channels)
|
||||
for (const auto& [name, location] : context_custom_channels)
|
||||
{
|
||||
std::string url = p;
|
||||
if (!util::starts_with(url, "http"))
|
||||
{
|
||||
url = util::path_or_url_to_url(url);
|
||||
}
|
||||
|
||||
auto channel = make_simple_channel(m_channel_alias, url, n, n);
|
||||
m_custom_channels.emplace(n, std::move(channel));
|
||||
auto channel = from_value(location);
|
||||
channel.m_canonical_name = name;
|
||||
m_custom_channels.emplace(name, std::move(channel));
|
||||
}
|
||||
|
||||
auto& multichannels = m_context.custom_multichannels;
|
||||
for (auto& [multichannelname, urllist] : multichannels)
|
||||
for (const auto& [multi_name, location_list] : m_context.custom_multichannels)
|
||||
{
|
||||
std::vector<std::string> names(urllist.size());
|
||||
auto name_iter = names.begin();
|
||||
for (auto& url : urllist)
|
||||
std::vector<std::string> names = {};
|
||||
names.reserve(location_list.size());
|
||||
for (auto& location : location_list)
|
||||
{
|
||||
auto channel = make_simple_channel(m_channel_alias, url, "", multichannelname);
|
||||
std::string name = channel.name();
|
||||
m_custom_channels.emplace(std::move(name), std::move(channel));
|
||||
*name_iter++ = url;
|
||||
auto channel = from_value(location);
|
||||
// No cannonical name give to mulit_channels
|
||||
names.push_back(location);
|
||||
}
|
||||
m_custom_multichannels.emplace(multichannelname, std::move(names));
|
||||
m_custom_multichannels.emplace(multi_name, std::move(names));
|
||||
}
|
||||
|
||||
/*******************
|
||||
|
|
|
@ -179,7 +179,7 @@ namespace mamba
|
|||
return { platform, "noarch" };
|
||||
}
|
||||
|
||||
Context::authentication_info_map_t& Context::authentication_info()
|
||||
specs::AuthenticationDataBase& Context::authentication_info()
|
||||
{
|
||||
if (!m_authentication_infos_loaded)
|
||||
{
|
||||
|
@ -188,7 +188,7 @@ namespace mamba
|
|||
return m_authentication_info;
|
||||
}
|
||||
|
||||
const Context::authentication_info_map_t& Context::authentication_info() const
|
||||
const specs::AuthenticationDataBase& Context::authentication_info() const
|
||||
{
|
||||
return const_cast<Context*>(this)->authentication_info();
|
||||
}
|
||||
|
@ -223,9 +223,11 @@ namespace mamba
|
|||
// cut ".token" ending
|
||||
token_url = token_url.substr(0, token_url.size() - 6);
|
||||
|
||||
m_authentication_info[token_url] = specs::CondaToken{ read_contents(entry.path()
|
||||
) };
|
||||
LOG_INFO << "Found token for " << token_url << " at " << entry.path();
|
||||
m_authentication_info.emplace(
|
||||
std::move(token_url),
|
||||
specs::CondaToken{ read_contents(entry.path()) }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +243,7 @@ namespace mamba
|
|||
infile >> j;
|
||||
for (auto& [key, el] : j.items())
|
||||
{
|
||||
std::string const host = key;
|
||||
const std::string host = key;
|
||||
const auto type = el["type"].get<std::string_view>();
|
||||
specs::AuthenticationInfo info;
|
||||
if (type == "CondaToken")
|
||||
|
@ -277,7 +279,7 @@ namespace mamba
|
|||
LOG_INFO << "Found bearer token for host " << host
|
||||
<< " in ~/.mamba/auth/authentication.json";
|
||||
}
|
||||
m_authentication_info[host] = info;
|
||||
m_authentication_info.emplace(std::move(host), std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,10 +317,10 @@ namespace mamba
|
|||
host += ":" + port;
|
||||
}
|
||||
|
||||
if (context.authentication_info().count(host))
|
||||
const auto& auth_info = context.authentication_info();
|
||||
if (auto it = auth_info.find_compatible(host); it != auth_info.end())
|
||||
{
|
||||
const auto& auth = context.authentication_info().at(host);
|
||||
if (std::holds_alternative<specs::BearerToken>(auth))
|
||||
if (const auto& auth = it->second; std::holds_alternative<specs::BearerToken>(auth))
|
||||
{
|
||||
m_handle.add_header(
|
||||
fmt::format("Authorization: Bearer {}", std::get<specs::BearerToken>(auth).token)
|
||||
|
|
|
@ -90,11 +90,12 @@ namespace mamba
|
|||
LOG_INFO << "need to expand path!";
|
||||
spec_str = util::path_or_url_to_url(fs::absolute(env::expand_user(spec_str)).string());
|
||||
}
|
||||
auto& parsed_channel = channel_context.make_channel(spec_str);
|
||||
|
||||
if (parsed_channel.package_filename())
|
||||
const auto& parsed_channel = channel_context.make_channel(spec_str);
|
||||
|
||||
if (auto pkg = parsed_channel.url().package(); !pkg.empty())
|
||||
{
|
||||
auto dist = parse_legacy_dist(*parsed_channel.package_filename());
|
||||
auto dist = parse_legacy_dist(pkg);
|
||||
|
||||
name = dist[0];
|
||||
version = dist[1];
|
||||
|
@ -106,7 +107,7 @@ namespace mamba
|
|||
{
|
||||
subdir = plats.front();
|
||||
}
|
||||
fn = *parsed_channel.package_filename();
|
||||
fn = std::move(pkg);
|
||||
url = spec_str;
|
||||
is_file = true;
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ namespace mamba
|
|||
bool
|
||||
channel_match(ChannelContext& channel_context, const Channel& chan, const Channel& needle)
|
||||
{
|
||||
if ((chan) == needle)
|
||||
if (chan.base_url() == needle.base_url())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ namespace mamba
|
|||
for (auto el : (x->second))
|
||||
{
|
||||
const Channel& inner = channel_context.make_channel(el);
|
||||
if ((chan) == inner)
|
||||
if (chan.base_url() == inner.base_url())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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.name();
|
||||
modified_spec.channel = chan.canonical_name();
|
||||
|
||||
modified_spec.version = solvable->version();
|
||||
modified_spec.build_string = solvable->build_string();
|
||||
|
|
|
@ -380,7 +380,7 @@ namespace mamba
|
|||
{
|
||||
for (const auto& c : channel_context.context().repodata_has_zst)
|
||||
{
|
||||
if (channel_context.make_channel(c) == channel)
|
||||
if (channel_context.make_channel(c).base_url() == channel.base_url())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <fmt/ostream.h>
|
||||
#include <solv/selection.h>
|
||||
|
||||
#include "mamba/core/validate.hpp"
|
||||
extern "C" // Incomplete header
|
||||
{
|
||||
#include <solv/conda.h>
|
||||
|
@ -34,7 +33,6 @@ extern "C" // Incomplete header
|
|||
#include "mamba/core/pool.hpp"
|
||||
#include "mamba/core/thread_utils.hpp"
|
||||
#include "mamba/core/transaction.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "solv-cpp/pool.hpp"
|
||||
|
@ -849,17 +847,46 @@ namespace mamba
|
|||
solution.actions,
|
||||
[&](const auto& pkg)
|
||||
{
|
||||
if (ctx.experimental && ctx.validation_params.verify_artifacts)
|
||||
{
|
||||
const auto& repo_checker = channel_context.make_channel(pkg.channel)
|
||||
.repo_checker(ctx, multi_cache);
|
||||
repo_checker.verify_package(
|
||||
pkg.json_signable(),
|
||||
nlohmann::json::parse(pkg.signatures)
|
||||
);
|
||||
// The following was used for the The Update Framework (TUF) / package signing
|
||||
// proof of concept.
|
||||
//
|
||||
// Due to uncertainties on how TUF would be implemented, this was left commented
|
||||
// out as in was getting in the way of the Channel refactoring.
|
||||
|
||||
LOG_DEBUG << "'" << pkg.name << "' trusted from '" << pkg.channel << "'";
|
||||
}
|
||||
// In channel.cpp, repo-checkers were instanciated with the folowing:
|
||||
// const validation::RepoChecker&
|
||||
// Channel::repo_checker(Context& context, MultiPackageCache& caches) const
|
||||
// {
|
||||
// if (p_repo_checker == nullptr)
|
||||
// {
|
||||
// p_repo_checker = std::make_unique<validation::RepoChecker>(
|
||||
// context,
|
||||
// util::rsplit(base_url(), "/", 1).front(),
|
||||
// context.prefix_params.root_prefix / "etc" / "trusted-repos"
|
||||
// / util::cache_name_from_url(base_url()),
|
||||
// caches.first_writable_path() / "cache" /
|
||||
// util::cache_name_from_url(base_url())
|
||||
// );
|
||||
//
|
||||
// fs::create_directories(p_repo_checker->cache_path());
|
||||
// p_repo_checker->generate_index_checker();
|
||||
// }
|
||||
//
|
||||
// return *p_repo_checker;
|
||||
// }
|
||||
|
||||
// Here, the repo-checker would be fetched the following way:
|
||||
// if (ctx.experimental && ctx.validation_params.verify_artifacts)
|
||||
// {
|
||||
// const auto& repo_checker = channel_context.make_channel(pkg.channel)
|
||||
// .repo_checker(ctx, multi_cache);
|
||||
// repo_checker.verify_package(
|
||||
// pkg.json_signable(),
|
||||
// nlohmann::json::parse(pkg.signatures)
|
||||
// );
|
||||
//
|
||||
// LOG_DEBUG << "'" << pkg.name << "' trusted from '" << pkg.channel << "'";
|
||||
// }
|
||||
fetchers.emplace_back(pkg, channel_context, multi_cache);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2019, 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 <stdexcept>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/authentication_info.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
|
||||
auto AuthenticationDataBase::at_compatible(const key_type& key) -> mapped_type&
|
||||
{
|
||||
if (auto it = find_compatible(key); it != end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
throw std::out_of_range(fmt::format(R"(No entry for key "{}")", key));
|
||||
}
|
||||
|
||||
auto AuthenticationDataBase::at_compatible(const key_type& key) const -> const mapped_type&
|
||||
{
|
||||
return const_cast<AuthenticationDataBase*>(this)->at_compatible(key);
|
||||
}
|
||||
|
||||
auto AuthenticationDataBase::find_compatible(const key_type& key) -> iterator
|
||||
{
|
||||
// First we start by adding a final '/' if absent, so that "mamba.org/" can
|
||||
// be find by query "mamba.org".
|
||||
std::string maybe_key = {};
|
||||
if (!util::ends_with(key, '/'))
|
||||
{
|
||||
maybe_key = util::concat(key, '/');
|
||||
}
|
||||
else
|
||||
{
|
||||
maybe_key = key;
|
||||
}
|
||||
|
||||
// We look for successsive parent dirs as queries with and without trailing '/';
|
||||
// "mamba.org/channel/" > "mamba.org/channel" > "mamba.org/" > "mamba.org"
|
||||
auto it = Base::find(maybe_key);
|
||||
while (it == Base::end())
|
||||
{
|
||||
const auto pos = maybe_key.rfind('/');
|
||||
if (maybe_key.empty() || (pos == std::string::npos))
|
||||
{
|
||||
// Nothing else to try
|
||||
break;
|
||||
}
|
||||
else if ((pos + 1) == maybe_key.size())
|
||||
{
|
||||
// Try again without final '/'
|
||||
maybe_key.erase(pos);
|
||||
it = Base::find(maybe_key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without final element
|
||||
maybe_key.erase(pos + 1);
|
||||
it = Base::find(maybe_key);
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
auto AuthenticationDataBase::find_compatible(const key_type& key) const -> const_iterator
|
||||
{
|
||||
return static_cast<const_iterator>(
|
||||
const_cast<AuthenticationDataBase*>(this)->find_compatible(key)
|
||||
);
|
||||
}
|
||||
|
||||
auto AuthenticationDataBase::contains(const key_type& key) const -> bool
|
||||
{
|
||||
return find(key) != end();
|
||||
}
|
||||
|
||||
auto AuthenticationDataBase::contains_compatible(const key_type& key) const -> bool
|
||||
{
|
||||
return find_compatible(key) != end();
|
||||
}
|
||||
}
|
|
@ -113,6 +113,11 @@ namespace mamba::specs
|
|||
{
|
||||
}
|
||||
|
||||
auto CondaURL::base() const -> const util::URL&
|
||||
{
|
||||
return static_cast<const util::URL&>(*this);
|
||||
}
|
||||
|
||||
auto CondaURL::parse(std::string_view url) -> CondaURL
|
||||
{
|
||||
return CondaURL(URL::parse(url));
|
||||
|
@ -142,6 +147,16 @@ namespace mamba::specs
|
|||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
auto CondaURL::has_token() const -> bool
|
||||
{
|
||||
const auto& p = path(Decode::no);
|
||||
return
|
||||
// Fast return for easy cases
|
||||
(p.size() > token_prefix.size())
|
||||
// The actual check
|
||||
&& util::starts_with(p, token_prefix);
|
||||
}
|
||||
|
||||
auto CondaURL::token() const -> std::string_view
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
@ -491,7 +506,7 @@ namespace mamba::specs
|
|||
|
||||
auto operator==(const CondaURL& a, const CondaURL& b) -> bool
|
||||
{
|
||||
return static_cast<const util::URL&>(a) == static_cast<const util::URL&>(b);
|
||||
return a.base() == b.base();
|
||||
}
|
||||
|
||||
auto operator!=(const CondaURL& a, const CondaURL& b) -> bool
|
||||
|
@ -510,3 +525,9 @@ namespace mamba::specs
|
|||
return std::move(url);
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
std::hash<mamba::specs::CondaURL>::operator()(const mamba::specs::CondaURL& u) const -> std::size_t
|
||||
{
|
||||
return std::hash<mamba::util::URL>()(u.base());
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "mamba/util/build.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"
|
||||
|
||||
|
@ -185,8 +186,8 @@ namespace mamba::util
|
|||
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(""));
|
||||
out.set_user(handle.get_part(CURLUPART_USER).value_or(""), Encode::no);
|
||||
out.set_password(handle.get_part(CURLUPART_PASSWORD).value_or(""), Encode::no);
|
||||
out.set_host(handle.get_part(CURLUPART_HOST).value_or(""));
|
||||
out.set_path(handle.get_part(CURLUPART_PATH).value_or("/"));
|
||||
out.set_host(handle.get_part(CURLUPART_HOST).value_or(""), Encode::no);
|
||||
out.set_path(handle.get_part(CURLUPART_PATH).value_or("/"), Encode::no);
|
||||
out.set_port(handle.get_part(CURLUPART_PORT).value_or(""));
|
||||
out.set_query(handle.get_part(CURLUPART_QUERY).value_or(""));
|
||||
out.set_fragment(handle.get_part(CURLUPART_FRAGMENT).value_or(""));
|
||||
|
@ -222,6 +223,11 @@ namespace mamba::util
|
|||
return std::exchange(m_scheme, "");
|
||||
}
|
||||
|
||||
auto URL::has_user() const -> bool
|
||||
{
|
||||
return !m_user.empty();
|
||||
}
|
||||
|
||||
auto URL::user(Decode::no_type) const -> const std::string&
|
||||
{
|
||||
return m_user;
|
||||
|
@ -247,6 +253,11 @@ namespace mamba::util
|
|||
return std::exchange(m_user, "");
|
||||
}
|
||||
|
||||
auto URL::has_password() const -> bool
|
||||
{
|
||||
return !m_password.empty();
|
||||
}
|
||||
|
||||
auto URL::password(Decode::no_type) const -> const std::string&
|
||||
{
|
||||
return m_password;
|
||||
|
@ -458,27 +469,19 @@ namespace mamba::util
|
|||
if (on_win && (scheme() == "file"))
|
||||
{
|
||||
auto [slashes, no_slash_path] = lstrip_parts(path, '/');
|
||||
if (slashes.empty())
|
||||
{
|
||||
slashes = "/";
|
||||
}
|
||||
if ((no_slash_path.size() >= 2) && path_has_drive_letter(no_slash_path))
|
||||
{
|
||||
m_path = concat(
|
||||
slashes,
|
||||
no_slash_path.substr(0, 2),
|
||||
url_encode(no_slash_path.substr(2), '/')
|
||||
return set_path(
|
||||
concat(
|
||||
slashes.empty() ? "/" : slashes,
|
||||
no_slash_path.substr(0, 2),
|
||||
url_encode(no_slash_path.substr(2), '/')
|
||||
),
|
||||
Encode::no
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path = concat(slashes, url_encode(no_slash_path, '/'));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return set_path(url_encode(path, '/'), Encode::no);
|
||||
}
|
||||
return set_path(url_encode(path, '/'), Encode::no);
|
||||
}
|
||||
|
||||
void URL::set_path(std::string path, Encode::no_type)
|
||||
|
@ -512,9 +515,9 @@ namespace mamba::util
|
|||
|
||||
void URL::append_path(std::string_view subpath, Encode::yes_type)
|
||||
{
|
||||
if (path(Decode::no) == "/")
|
||||
if (util::lstrip(path(Decode::no), '/').empty())
|
||||
{
|
||||
// Allow hanldling of Windows drive letter encoding
|
||||
// Allow handling of Windows drive letter encoding
|
||||
return set_path(std::string(subpath), Encode::yes);
|
||||
}
|
||||
return append_path(url_encode(subpath, '/'), Encode::no);
|
||||
|
@ -625,15 +628,34 @@ namespace mamba::util
|
|||
);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
auto attrs(URL const& url)
|
||||
{
|
||||
return std::tuple<
|
||||
std::string_view,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
std::string_view,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
const std::string&>{
|
||||
url.scheme(),
|
||||
url.user(URL::Decode::no),
|
||||
url.password(URL::Decode::no),
|
||||
url.host(URL::Decode::no),
|
||||
url.port(),
|
||||
url.path(URL::Decode::no),
|
||||
url.query(),
|
||||
url.fragment(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
auto operator==(URL const& a, URL const& b) -> bool
|
||||
{
|
||||
return (a.scheme() == b.scheme())
|
||||
&& (a.user() == b.user())
|
||||
// omitting password, is that desirable?
|
||||
&& (a.host() == b.host())
|
||||
// Would it be desirable to account for default ports?
|
||||
&& (a.port() == b.port()) && (a.path() == b.path()) && (a.query() == b.query())
|
||||
&& (a.fragment() == b.fragment());
|
||||
return attrs(a) == attrs(b);
|
||||
}
|
||||
|
||||
auto operator!=(URL const& a, URL const& b) -> bool
|
||||
|
@ -653,3 +675,9 @@ namespace mamba::util
|
|||
}
|
||||
|
||||
} // namespace mamba
|
||||
|
||||
auto
|
||||
std::hash<mamba::util::URL>::operator()(const mamba::util::URL& u) const -> std::size_t
|
||||
{
|
||||
return mamba::util::hash_tuple(mamba::util::attrs(u));
|
||||
}
|
||||
|
|
|
@ -126,37 +126,6 @@ namespace mamba::util
|
|||
return out;
|
||||
}
|
||||
|
||||
// proper file scheme on Windows is `file:///C:/blabla`
|
||||
// https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/
|
||||
std::string concat_scheme_url(const std::string& scheme, const std::string& location)
|
||||
{
|
||||
if (scheme == "file" && location.size() > 1 && location[1] == ':')
|
||||
{
|
||||
return util::concat("file:///", location);
|
||||
}
|
||||
else
|
||||
{
|
||||
return util::concat(scheme, "://", location);
|
||||
}
|
||||
}
|
||||
|
||||
std::string build_url(
|
||||
const std::optional<std::string>& auth,
|
||||
const std::string& scheme,
|
||||
const std::string& base,
|
||||
bool with_credential
|
||||
)
|
||||
{
|
||||
if (with_credential && auth)
|
||||
{
|
||||
return concat_scheme_url(scheme, util::concat(*auth, "@", base));
|
||||
}
|
||||
else
|
||||
{
|
||||
return concat_scheme_url(scheme, base);
|
||||
}
|
||||
}
|
||||
|
||||
void split_platform(
|
||||
const std::vector<std::string>& known_platforms,
|
||||
const std::string& url,
|
||||
|
|
|
@ -37,9 +37,11 @@ set(LIBMAMBA_TEST_SRCS
|
|||
src/util/test_graph.cpp
|
||||
src/util/test_iterator.cpp
|
||||
src/util/test_path_manip.cpp
|
||||
src/util/test_tuple_hash.cpp
|
||||
src/util/test_url_manip.cpp
|
||||
src/util/test_url.cpp
|
||||
# Implementation of version and matching specs
|
||||
src/specs/test_authentication_info.cpp
|
||||
src/specs/test_archive.cpp
|
||||
src/specs/test_platform.cpp
|
||||
src/specs/test_conda_url.cpp
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
#include "mamba/core/context.hpp"
|
||||
#include "mamba/core/environment.hpp"
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/util/url_manip.hpp"
|
||||
|
||||
#include "doctest-printer/conda_url.hpp"
|
||||
#include "doctest-printer/flat_set.hpp"
|
||||
|
||||
#include "mambatests.hpp"
|
||||
|
||||
|
@ -21,10 +26,12 @@ namespace mamba
|
|||
static const std::string platform = std::string(specs::build_platform_name());
|
||||
using PlatformSet = typename util::flat_set<std::string>;
|
||||
using UrlSet = typename util::flat_set<std::string>;
|
||||
using CondaURL = specs::CondaURL;
|
||||
|
||||
static_assert(std::is_move_constructible_v<mamba::Channel>);
|
||||
static_assert(std::is_move_assignable_v<mamba::Channel>);
|
||||
|
||||
|
||||
TEST_SUITE("ChannelContext")
|
||||
{
|
||||
TEST_CASE("init")
|
||||
|
@ -38,21 +45,18 @@ namespace mamba
|
|||
const auto& custom = channel_context.get_custom_channels();
|
||||
|
||||
auto it = custom.find("pkgs/main");
|
||||
CHECK_NE(it, custom.end());
|
||||
CHECK_EQ(it->second.name(), "pkgs/main");
|
||||
CHECK_EQ(it->second.location(), "repo.anaconda.com");
|
||||
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");
|
||||
|
||||
it = custom.find("pkgs/pro");
|
||||
CHECK_NE(it, custom.end());
|
||||
CHECK_EQ(it->second.name(), "pkgs/pro");
|
||||
CHECK_EQ(it->second.location(), "repo.anaconda.com");
|
||||
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");
|
||||
|
||||
it = custom.find("pkgs/r");
|
||||
CHECK_NE(it, custom.end());
|
||||
CHECK_EQ(it->second.name(), "pkgs/r");
|
||||
CHECK_EQ(it->second.location(), "repo.anaconda.com");
|
||||
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");
|
||||
}
|
||||
|
||||
|
@ -71,18 +75,14 @@ namespace mamba
|
|||
const auto& custom = channel_context.get_custom_channels();
|
||||
|
||||
auto it = custom.find("pkgs/main");
|
||||
CHECK_NE(it, custom.end());
|
||||
CHECK_EQ(it->second.name(), "pkgs/main");
|
||||
CHECK_EQ(it->second.location(), "repo.anaconda.com");
|
||||
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");
|
||||
|
||||
std::string value = "conda-forge";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "mydomain.com/channels");
|
||||
CHECK_EQ(c.name(), "conda-forge");
|
||||
CHECK_EQ(c.url(), CondaURL::parse("https://mydomain.com/channels/conda-forge"));
|
||||
CHECK_EQ(c.canonical_name(), "conda-forge");
|
||||
// CHECK_EQ(c.url(), "conda-forge");
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
|
||||
ctx.channel_alias = "https://conda.anaconda.org";
|
||||
|
@ -125,9 +125,7 @@ namespace mamba
|
|||
{
|
||||
std::string value = "test_channel";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "file");
|
||||
CHECK_EQ(c.location(), "/tmp");
|
||||
CHECK_EQ(c.name(), "test_channel");
|
||||
CHECK_EQ(c.url(), CondaURL::parse("file:///tmp/test_channel"));
|
||||
CHECK_EQ(c.canonical_name(), "test_channel");
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
|
@ -140,9 +138,7 @@ namespace mamba
|
|||
{
|
||||
std::string value = "some_channel";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "conda.mydomain.xyz");
|
||||
CHECK_EQ(c.name(), "some_channel");
|
||||
CHECK_EQ(c.url(), CondaURL::parse("https://conda.mydomain.xyz/some_channel"));
|
||||
CHECK_EQ(c.canonical_name(), "some_channel");
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
|
@ -261,23 +257,20 @@ namespace mamba
|
|||
const Channel* c1 = x[0];
|
||||
const Channel* c2 = x[1];
|
||||
|
||||
CHECK_EQ(c1->name(), "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(c2->name(), "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(c1->location(), "repo.anaconda.com");
|
||||
CHECK_EQ(c1->scheme(), "https");
|
||||
|
||||
#endif
|
||||
ctx.custom_channels.clear();
|
||||
}
|
||||
|
@ -295,22 +288,20 @@ namespace mamba
|
|||
const Channel* c1 = x[0];
|
||||
const Channel* c2 = x[1];
|
||||
|
||||
CHECK_EQ(c1->name(), "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(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->name(), "stable/channel");
|
||||
CHECK_EQ(c2->location(), "mamba.com");
|
||||
CHECK_EQ(c2->scheme(), "https");
|
||||
|
||||
ctx.custom_channels.clear();
|
||||
}
|
||||
|
||||
|
@ -329,11 +320,9 @@ namespace mamba
|
|||
CHECK_EQ(custom.size(), 4);
|
||||
|
||||
auto it = custom.find("conda-bld");
|
||||
CHECK_NE(it, custom.end());
|
||||
CHECK_EQ(it->second.name(), "conda-bld");
|
||||
CHECK_EQ(it->second.location(), env::home_directory());
|
||||
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(it->second.scheme(), "file");
|
||||
|
||||
auto local_channels = channel_context.get_channels({ "local" });
|
||||
CHECK_EQ(local_channels.size(), 1);
|
||||
|
@ -358,9 +347,7 @@ namespace mamba
|
|||
{
|
||||
std::string value = "test_channel";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "server.com/private/channels");
|
||||
CHECK_EQ(c.name(), "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.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
|
@ -373,9 +360,10 @@ namespace mamba
|
|||
{
|
||||
std::string value = "test_channel/mylabel/xyz";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "server.com/private/channels");
|
||||
CHECK_EQ(c.name(), "test_channel/mylabel/xyz");
|
||||
CHECK_EQ(
|
||||
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.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
|
@ -387,11 +375,13 @@ namespace mamba
|
|||
}
|
||||
|
||||
{
|
||||
// https://github.com/mamba-org/mamba/issues/2553
|
||||
std::string value = "random/test_channel/pkg";
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "server.com/random/channels");
|
||||
CHECK_EQ(c.name(), "random/test_channel/pkg");
|
||||
CHECK_EQ(
|
||||
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.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
|
@ -414,9 +404,7 @@ namespace mamba
|
|||
std::string value = "https://repo.mamba.pm/conda-forge";
|
||||
ChannelContext channel_context{ mambatests::context() };
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "repo.mamba.pm");
|
||||
CHECK_EQ(c.name(), "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.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
}
|
||||
|
@ -426,53 +414,33 @@ namespace mamba
|
|||
std::string value = "conda-forge";
|
||||
ChannelContext channel_context{ mambatests::context() };
|
||||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(c.scheme(), "https");
|
||||
CHECK_EQ(c.location(), "conda.anaconda.org");
|
||||
CHECK_EQ(c.name(), "conda-forge");
|
||||
CHECK_EQ(c.url(), CondaURL::parse("https://conda.anaconda.org/conda-forge"));
|
||||
CHECK_EQ(c.canonical_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.scheme(), "https");
|
||||
CHECK_EQ(c2.location(), "repo.anaconda.com");
|
||||
CHECK_EQ(c2.name(), "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.platforms(), PlatformSet({ platform }));
|
||||
|
||||
std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]";
|
||||
const Channel& c3 = channel_context.make_channel(value3);
|
||||
CHECK_EQ(c3.scheme(), c.scheme());
|
||||
CHECK_EQ(c3.location(), c.location());
|
||||
CHECK_EQ(c3.name(), c.name());
|
||||
CHECK_EQ(c3.url(), c.url());
|
||||
CHECK_EQ(c3.canonical_name(), c.canonical_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.scheme(), "file");
|
||||
#ifdef _WIN32
|
||||
std::string driveletter = fs::absolute(fs::u8path("/")).string().substr(0, 1);
|
||||
CHECK_EQ(c4.location(), driveletter + ":/home/mamba/test");
|
||||
CHECK_EQ(c4.canonical_name(), "file:///" + driveletter + ":/home/mamba/test/channel_b");
|
||||
#else
|
||||
CHECK_EQ(c4.location(), "/home/mamba/test");
|
||||
CHECK_EQ(c4.canonical_name(), "file:///home/mamba/test/channel_b");
|
||||
#endif
|
||||
CHECK_EQ(c4.name(), "channel_b");
|
||||
CHECK_EQ(c4.url(), CondaURL::parse(util::path_to_url(value4)));
|
||||
CHECK_EQ(c4.canonical_name(), c4.url().pretty_str());
|
||||
CHECK_EQ(c4.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
|
||||
std::string value5 = "/home/mamba/test/channel_b[" + platform + "]";
|
||||
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.scheme(), "file");
|
||||
#ifdef _WIN32
|
||||
CHECK_EQ(c5.location(), driveletter + ":/home/mamba/test");
|
||||
CHECK_EQ(c5.canonical_name(), "file:///" + driveletter + ":/home/mamba/test/channel_b");
|
||||
#else
|
||||
CHECK_EQ(c5.location(), "/home/mamba/test");
|
||||
CHECK_EQ(c5.canonical_name(), "file:///home/mamba/test/channel_b");
|
||||
#endif
|
||||
CHECK_EQ(c5.name(), "channel_b");
|
||||
CHECK_EQ(c5.url(), CondaURL::parse(util::path_to_url(path5)));
|
||||
CHECK_EQ(c5.canonical_name(), c5.url().pretty_str());
|
||||
CHECK_EQ(c5.platforms(), PlatformSet({ platform }));
|
||||
|
||||
std::string value6a = "http://localhost:8000/conda-forge[noarch]";
|
||||
|
@ -518,12 +486,15 @@ namespace mamba
|
|||
TEST_CASE("add_token")
|
||||
{
|
||||
auto& ctx = mambatests::context();
|
||||
ctx.authentication_info()["conda.anaconda.org"] = specs::CondaToken{ "my-12345-token" };
|
||||
ctx.authentication_info().emplace(
|
||||
"conda.anaconda.org",
|
||||
specs::CondaToken{ "my-12345-token" }
|
||||
);
|
||||
|
||||
ChannelContext channel_context{ ctx };
|
||||
|
||||
const auto& chan = channel_context.make_channel("conda-forge[noarch]");
|
||||
CHECK_EQ(chan.token(), "my-12345-token");
|
||||
CHECK_EQ(chan.url().token(), "my-12345-token");
|
||||
CHECK_EQ(
|
||||
chan.urls(true),
|
||||
UrlSet({ "https://conda.anaconda.org/t/my-12345-token/conda-forge/noarch" })
|
||||
|
@ -534,15 +505,16 @@ namespace mamba
|
|||
TEST_CASE("add_multiple_tokens")
|
||||
{
|
||||
auto& ctx = mambatests::context();
|
||||
ctx.authentication_info()["conda.anaconda.org"] = specs::CondaToken{ "base-token" };
|
||||
ctx.authentication_info()["conda.anaconda.org/conda-forge"] = specs::CondaToken{
|
||||
"channel-token"
|
||||
};
|
||||
ctx.authentication_info().emplace("conda.anaconda.org", specs::CondaToken{ "base-token" });
|
||||
ctx.authentication_info().emplace(
|
||||
"conda.anaconda.org/conda-forge",
|
||||
specs::CondaToken{ "channel-token" }
|
||||
);
|
||||
|
||||
ChannelContext channel_context{ ctx };
|
||||
|
||||
const auto& chan = channel_context.make_channel("conda-forge[noarch]");
|
||||
CHECK_EQ(chan.token(), "channel-token");
|
||||
CHECK_EQ(chan.url().token(), "channel-token");
|
||||
}
|
||||
|
||||
TEST_CASE("fix_win_file_path")
|
||||
|
@ -572,7 +544,7 @@ namespace mamba
|
|||
ChannelContext channel_context{ mambatests::context() };
|
||||
const Channel& c = channel_context.make_channel("http://localhost:8000/");
|
||||
CHECK_EQ(c.platform_url("win-64", false), "http://localhost:8000/win-64");
|
||||
CHECK_EQ(c.base_url(), "http://localhost:8000");
|
||||
CHECK_EQ(c.base_url(), "http://localhost:8000/");
|
||||
const UrlSet expected_urls({ std::string("http://localhost:8000/") + platform,
|
||||
"http://localhost:8000/noarch" });
|
||||
CHECK_EQ(c.urls(true), expected_urls);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) 2019, 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 <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/authentication_info.hpp"
|
||||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("specs::AuthticationDataBase")
|
||||
{
|
||||
TEST_CASE("mamba.org")
|
||||
{
|
||||
auto db = AuthenticationDataBase{ { "mamba.org", BearerToken{ "mytoken" } } };
|
||||
|
||||
CHECK(db.contains("mamba.org"));
|
||||
CHECK_FALSE(db.contains("mamba.org/"));
|
||||
|
||||
CHECK(db.contains_compatible("mamba.org"));
|
||||
CHECK(db.contains_compatible("mamba.org/"));
|
||||
CHECK(db.contains_compatible("mamba.org/channel"));
|
||||
CHECK_FALSE(db.contains_compatible("repo.mamba.org"));
|
||||
CHECK_FALSE(db.contains_compatible("/folder"));
|
||||
}
|
||||
|
||||
TEST_CASE("mamba.org/")
|
||||
{
|
||||
auto db = AuthenticationDataBase{ { "mamba.org/", BearerToken{ "mytoken" } } };
|
||||
|
||||
CHECK(db.contains("mamba.org/"));
|
||||
CHECK_FALSE(db.contains("mamba.org"));
|
||||
|
||||
CHECK(db.contains_compatible("mamba.org"));
|
||||
CHECK(db.contains_compatible("mamba.org/"));
|
||||
CHECK(db.contains_compatible("mamba.org/channel"));
|
||||
CHECK_FALSE(db.contains_compatible("repo.mamba.org/"));
|
||||
CHECK_FALSE(db.contains_compatible("/folder"));
|
||||
}
|
||||
|
||||
TEST_CASE("mamba.org/channel")
|
||||
{
|
||||
auto db = AuthenticationDataBase{ { "mamba.org/channel", BearerToken{ "mytoken" } } };
|
||||
|
||||
CHECK(db.contains("mamba.org/channel"));
|
||||
CHECK_FALSE(db.contains("mamba.org"));
|
||||
|
||||
CHECK_FALSE(db.contains_compatible("mamba.org"));
|
||||
CHECK_FALSE(db.contains_compatible("mamba.org/"));
|
||||
CHECK(db.contains_compatible("mamba.org/channel"));
|
||||
CHECK_FALSE(db.contains_compatible("repo.mamba.org/"));
|
||||
CHECK_FALSE(db.contains_compatible("/folder"));
|
||||
}
|
||||
}
|
|
@ -22,15 +22,18 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("https://repo.mamba.pm/folder/file.txt")
|
||||
{
|
||||
url.set_path("/folder/file.txt");
|
||||
CHECK_FALSE(url.has_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
|
||||
url.set_token("mytoken");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "mytoken");
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/t/mytoken/folder/file.txt");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_FALSE(url.has_token());
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/folder/file.txt");
|
||||
}
|
||||
|
@ -38,6 +41,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("https://repo.mamba.pm/t/xy-12345678-1234/conda-forge/linux-64")
|
||||
{
|
||||
url.set_path("/t/xy-12345678-1234/conda-forge/linux-64");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
|
||||
|
@ -45,6 +49,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
{
|
||||
CHECK_THROWS_AS(url.set_token(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_token("?fds:g"), std::invalid_argument);
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/t/xy-12345678-1234/conda-forge/linux-64");
|
||||
|
@ -53,6 +58,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("Clear token")
|
||||
{
|
||||
CHECK(url.clear_token());
|
||||
CHECK_FALSE(url.has_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64");
|
||||
|
@ -61,6 +67,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("Set token")
|
||||
{
|
||||
url.set_token("abcd");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/t/abcd/conda-forge/linux-64");
|
||||
|
@ -70,14 +77,17 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("https://repo.mamba.pm/t/xy-12345678-1234-1234-1234-123456789012")
|
||||
{
|
||||
url.set_path("/t/xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
|
||||
url.set_token("abcd");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/t/abcd/");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_FALSE(url.has_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
|
@ -86,9 +96,11 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("https://repo.mamba.pm/bar/t/xy-12345678-1234-1234-1234-123456789012/")
|
||||
{
|
||||
url.set_path("/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
CHECK_FALSE(url.has_token());
|
||||
CHECK_EQ(url.token(), ""); // Not at begining of path
|
||||
|
||||
url.set_token("abcd");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path_without_token(), "/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
CHECK_EQ(url.path(), "/t/abcd/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
|
@ -118,6 +130,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
SUBCASE("Parse")
|
||||
{
|
||||
url = CondaURL::parse("mamba.org/t/xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK(url.has_token());
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
|
@ -365,7 +378,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
CHECK_EQ(url.pretty_str(CondaURL::StripScheme::no, '/'), "https://mamba.org/page");
|
||||
}
|
||||
|
||||
SUBCASE("Credentail option")
|
||||
SUBCASE("Credential option")
|
||||
{
|
||||
CondaURL url = {};
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// 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 <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/util/tuple_hash.hpp"
|
||||
|
||||
using namespace mamba::util;
|
||||
|
||||
|
||||
TEST_SUITE("util::tuple_hash")
|
||||
{
|
||||
TEST_CASE("hash_tuple")
|
||||
{
|
||||
const auto t1 = std::tuple{ 33, std::string("hello") };
|
||||
CHECK_NE(hash_tuple(t1), 0);
|
||||
|
||||
// Hash colision are hard to predict, but this is so trivial it is likely a bug if it fails.
|
||||
const auto t2 = std::tuple{ 0, std::string("hello") };
|
||||
CHECK_NE(hash_tuple(t1), hash_tuple(t2));
|
||||
|
||||
const auto t3 = std::tuple{ std::string("hello"), 33 };
|
||||
CHECK_NE(hash_tuple(t1), hash_tuple(t3));
|
||||
}
|
||||
}
|
|
@ -22,7 +22,9 @@ TEST_SUITE("util::URL")
|
|||
{
|
||||
URL url{};
|
||||
CHECK_EQ(url.scheme(), URL::https);
|
||||
CHECK_FALSE(url.has_user());
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_FALSE(url.has_password());
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
|
@ -60,7 +62,9 @@ TEST_SUITE("util::URL")
|
|||
|
||||
CHECK_EQ(url.scheme(), "https");
|
||||
CHECK_EQ(url.host(), "mamba.org");
|
||||
CHECK(url.has_user());
|
||||
CHECK_EQ(url.user(), "user");
|
||||
CHECK(url.has_password());
|
||||
CHECK_EQ(url.password(), "pass:word");
|
||||
CHECK_EQ(url.port(), "8080");
|
||||
CHECK_EQ(url.path(), "/folder/file.html");
|
||||
|
@ -110,10 +114,12 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.path(), "/C:/folder/file.txt");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.path(URL::Decode::no), "/C:/folder/file.txt");
|
||||
CHECK_EQ(url.pretty_path(), "C:/folder/file.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.path(URL::Decode::no), "/C%3A/folder/file.txt");
|
||||
CHECK_EQ(url.pretty_path(), "/C:/folder/file.txt");
|
||||
}
|
||||
}
|
||||
|
@ -292,16 +298,10 @@ TEST_SUITE("util::URL")
|
|||
|
||||
SUBCASE("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333")
|
||||
{
|
||||
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
|
||||
// Undefined behavior, no assumptions are made
|
||||
const URL url = URL::parse("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333");
|
||||
CHECK_EQ(url.scheme(), "https");
|
||||
CHECK_EQ(url.host(), "mamba🆒🔬.org");
|
||||
CHECK_EQ(url.path(), "/this/is/a/path/");
|
||||
CHECK_EQ(url.pretty_path(), "/this/is/a/path/");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.query(), "query=123&xyz=3333");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
CHECK_NE(url.host(URL::Decode::no), "mamba%f0%9f%86%92%f0%9f%94%ac.org");
|
||||
}
|
||||
|
||||
SUBCASE("file://C:/Users/wolfv/test/document.json")
|
||||
|
@ -312,14 +312,8 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/C:/Users/wolfv/test/document.json");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "/C:/Users/wolfv/test/document.json");
|
||||
}
|
||||
CHECK_EQ(url.path(URL::Decode::no), "/C:/Users/wolfv/test/document.json");
|
||||
CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
|
@ -330,19 +324,39 @@ TEST_SUITE("util::URL")
|
|||
|
||||
SUBCASE("file:///home/wolfv/test/document.json")
|
||||
{
|
||||
if (!on_win)
|
||||
{
|
||||
const URL url = URL::parse("file:///home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.pretty_path(), "/home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
const URL url = URL::parse("file:///home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.pretty_path(), "/home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
|
||||
SUBCASE("file:///home/great:doc.json")
|
||||
{
|
||||
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
|
||||
// Undefined behavior, no assumptions are made
|
||||
const URL url = URL::parse("file:///home/great:doc.json");
|
||||
CHECK_NE(url.path(URL::Decode::no), "/home/great%3Adoc.json");
|
||||
}
|
||||
|
||||
SUBCASE("file:///home/great%3Adoc.json")
|
||||
{
|
||||
const URL url = URL::parse("file:///home/great%3Adoc.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/home/great:doc.json");
|
||||
CHECK_EQ(url.path(URL::Decode::no), "/home/great%3Adoc.json");
|
||||
CHECK_EQ(url.pretty_path(), "/home/great:doc.json");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
|
||||
SUBCASE("https://169.254.0.0/page")
|
||||
|
@ -679,4 +693,54 @@ TEST_SUITE("util::URL")
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Comparison")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_user("user");
|
||||
url.set_password("password");
|
||||
url.set_host("mamba.org");
|
||||
url.set_port("33");
|
||||
url.set_path("/folder/file.html");
|
||||
auto other = url;
|
||||
|
||||
CHECK_EQ(url, other);
|
||||
|
||||
SUBCASE("Different scheme")
|
||||
{
|
||||
other.set_scheme("ftp");
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
|
||||
SUBCASE("Different hosts")
|
||||
{
|
||||
other.clear_host();
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
|
||||
SUBCASE("Different users")
|
||||
{
|
||||
other.clear_user();
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
|
||||
SUBCASE("Different passwords")
|
||||
{
|
||||
other.clear_password();
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
|
||||
SUBCASE("Different ports")
|
||||
{
|
||||
other.clear_port();
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
|
||||
SUBCASE("Different path")
|
||||
{
|
||||
other.clear_path();
|
||||
CHECK_NE(url, other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,36 +48,6 @@ TEST_SUITE("util::url_manip")
|
|||
);
|
||||
}
|
||||
|
||||
TEST_CASE("concat_scheme_url")
|
||||
{
|
||||
auto url = concat_scheme_url("https", "mamba.com");
|
||||
CHECK_EQ(url, "https://mamba.com");
|
||||
|
||||
url = concat_scheme_url("file", "C:/some_folder");
|
||||
CHECK_EQ(url, "file:///C:/some_folder");
|
||||
|
||||
url = concat_scheme_url("file", "some_folder");
|
||||
CHECK_EQ(url, "file://some_folder");
|
||||
}
|
||||
|
||||
TEST_CASE("build_url")
|
||||
{
|
||||
auto url = build_url(std::nullopt, "https", "mamba.com", true);
|
||||
CHECK_EQ(url, "https://mamba.com");
|
||||
|
||||
url = build_url(std::nullopt, "https", "mamba.com", false);
|
||||
CHECK_EQ(url, "https://mamba.com");
|
||||
|
||||
url = build_url(std::optional<std::string>("auth"), "https", "mamba.com", false);
|
||||
CHECK_EQ(url, "https://mamba.com");
|
||||
|
||||
url = build_url(std::optional<std::string>("auth"), "https", "mamba.com", true);
|
||||
CHECK_EQ(url, "https://auth@mamba.com");
|
||||
|
||||
url = build_url(std::optional<std::string>(""), "https", "mamba.com", true);
|
||||
CHECK_EQ(url, "https://@mamba.com");
|
||||
}
|
||||
|
||||
TEST_CASE("split_platform")
|
||||
{
|
||||
std::string platform_found, cleaned_url;
|
||||
|
|
|
@ -136,11 +136,6 @@ class Channel:
|
|||
) -> typing.Set[typing.Tuple[str, str]]: ...
|
||||
def urls(self, with_credentials: bool = True) -> typing.Set[str]: ...
|
||||
@property
|
||||
def auth(self) -> typing.Optional[str]:
|
||||
"""
|
||||
:type: typing.Optional[str]
|
||||
"""
|
||||
@property
|
||||
def canonical_name(self) -> str:
|
||||
"""
|
||||
:type: str
|
||||
|
@ -156,25 +151,10 @@ class Channel:
|
|||
:type: str
|
||||
"""
|
||||
@property
|
||||
def package_filename(self) -> typing.Optional[str]:
|
||||
"""
|
||||
:type: typing.Optional[str]
|
||||
"""
|
||||
@property
|
||||
def platforms(self) -> typing.Set[str]:
|
||||
"""
|
||||
:type: typing.Set[str]
|
||||
"""
|
||||
@property
|
||||
def scheme(self) -> str:
|
||||
"""
|
||||
:type: str
|
||||
"""
|
||||
@property
|
||||
def token(self) -> typing.Optional[str]:
|
||||
"""
|
||||
:type: typing.Optional[str]
|
||||
"""
|
||||
pass
|
||||
|
||||
class ChannelPriority:
|
||||
|
|
|
@ -1147,12 +1147,8 @@ PYBIND11_MODULE(bindings, m)
|
|||
);
|
||||
}
|
||||
))
|
||||
.def_property_readonly("scheme", &Channel::scheme)
|
||||
.def_property_readonly("location", &Channel::location)
|
||||
.def_property_readonly("name", &Channel::name)
|
||||
.def_property_readonly("auth", &Channel::auth)
|
||||
.def_property_readonly("token", &Channel::token)
|
||||
.def_property_readonly("package_filename", &Channel::package_filename)
|
||||
.def_property_readonly("platforms", &Channel::platforms)
|
||||
.def_property_readonly("canonical_name", &Channel::canonical_name)
|
||||
.def("urls", &Channel::urls, py::arg("with_credentials") = true)
|
||||
|
|
|
@ -106,7 +106,7 @@ class MambaSolver:
|
|||
pkg_cache_path = self.context.pkgs_dirs
|
||||
|
||||
package_cache = libmambapy.MultiPackageCache(pkg_cache_path)
|
||||
return libmambapy.Transaction(api_solver, package_cache)
|
||||
return libmambapy.Transaction(self.pool, api_solver, package_cache)
|
||||
|
||||
|
||||
def install(
|
||||
|
|
|
@ -263,19 +263,8 @@ def init_api_context(use_mamba_experimental=False):
|
|||
|
||||
|
||||
def to_conda_channel(channel, platform):
|
||||
if channel.scheme == "file":
|
||||
return CondaChannel.from_value(
|
||||
channel.platform_url(platform, with_credentials=False)
|
||||
)
|
||||
|
||||
return CondaChannel(
|
||||
channel.scheme,
|
||||
channel.auth,
|
||||
channel.location,
|
||||
channel.token,
|
||||
channel.name,
|
||||
platform,
|
||||
channel.package_filename,
|
||||
return CondaChannel.from_value(
|
||||
channel.platform_url(platform, with_credentials=False)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ set_env_command(CLI::App* com, Configuration& config)
|
|||
dependencies << "- ";
|
||||
if (channel_subdir)
|
||||
{
|
||||
dependencies << channel.name() << "/" << v.subdir << "::";
|
||||
dependencies << channel.canonical_name() << "/" << v.subdir << "::";
|
||||
}
|
||||
dependencies << v.name << "=" << v.version;
|
||||
if (!no_build)
|
||||
|
@ -200,7 +200,7 @@ set_env_command(CLI::App* com, Configuration& config)
|
|||
dependencies << "\n";
|
||||
}
|
||||
|
||||
channels.insert(channel.name());
|
||||
channels.insert(channel.canonical_name());
|
||||
}
|
||||
|
||||
for (const auto& c : channels)
|
||||
|
|
Loading…
Reference in New Issue