mirror of https://github.com/mamba-org/mamba.git
432 lines
14 KiB
C++
432 lines
14 KiB
C++
// 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_CORE_UTIL_HPP
|
|
#define MAMBA_CORE_UTIL_HPP
|
|
|
|
#include "mamba/core/mamba_fs.hpp"
|
|
#include "mamba/core/error_handling.hpp"
|
|
|
|
#include "nlohmann/json.hpp"
|
|
|
|
#include <array>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <time.h>
|
|
#include <vector>
|
|
#include <chrono>
|
|
|
|
#if defined(__PPC64__) || defined(__ppc64__) || defined(_ARCH_PPC64)
|
|
#include <iomanip>
|
|
#endif
|
|
|
|
#define MAMBA_EMPTY_SHA "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
|
|
namespace mamba
|
|
{
|
|
#if __APPLE__ || __MACH__
|
|
static constexpr bool on_win = false;
|
|
static constexpr bool on_linux = false;
|
|
static constexpr bool on_mac = true;
|
|
#elif __linux__
|
|
static constexpr bool on_win = false;
|
|
static constexpr bool on_linux = true;
|
|
static constexpr bool on_mac = false;
|
|
#elif _WIN32
|
|
static constexpr bool on_win = true;
|
|
static constexpr bool on_linux = false;
|
|
static constexpr bool on_mac = false;
|
|
#else
|
|
#error "no supported OS detected"
|
|
#endif
|
|
|
|
bool is_package_file(const std::string_view& fn);
|
|
|
|
bool lexists(const fs::u8path& p);
|
|
bool lexists(const fs::u8path& p, std::error_code& ec);
|
|
std::vector<fs::u8path> filter_dir(const fs::u8path& dir, const std::string& suffix);
|
|
bool paths_equal(const fs::u8path& lhs, const fs::u8path& rhs);
|
|
|
|
std::string read_contents(const fs::u8path& path,
|
|
std::ios::openmode mode = std::ios::in | std::ios::binary);
|
|
std::vector<std::string> read_lines(const fs::u8path& path);
|
|
|
|
inline void make_executable(const fs::u8path& p)
|
|
{
|
|
fs::permissions(p,
|
|
fs::perms::owner_all | fs::perms::group_all | fs::perms::others_read
|
|
| fs::perms::others_exec);
|
|
}
|
|
|
|
class TemporaryDirectory
|
|
{
|
|
public:
|
|
TemporaryDirectory();
|
|
~TemporaryDirectory();
|
|
|
|
TemporaryDirectory(const TemporaryDirectory&) = delete;
|
|
TemporaryDirectory& operator=(const TemporaryDirectory&) = delete;
|
|
TemporaryDirectory& operator=(TemporaryDirectory&&) = default;
|
|
|
|
const fs::u8path& path() const;
|
|
operator fs::u8path();
|
|
|
|
private:
|
|
fs::u8path m_path;
|
|
};
|
|
|
|
class TemporaryFile
|
|
{
|
|
public:
|
|
TemporaryFile(const std::string& prefix = "mambaf", const std::string& suffix = "");
|
|
~TemporaryFile();
|
|
|
|
TemporaryFile(const TemporaryFile&) = delete;
|
|
TemporaryFile& operator=(const TemporaryFile&) = delete;
|
|
TemporaryFile& operator=(TemporaryFile&&) = default;
|
|
|
|
fs::u8path& path();
|
|
operator fs::u8path();
|
|
|
|
private:
|
|
fs::u8path m_path;
|
|
};
|
|
|
|
const std::size_t MAMBA_LOCK_POS = 21;
|
|
|
|
class LockFile
|
|
{
|
|
public:
|
|
LockFile(const fs::u8path& path);
|
|
LockFile(const fs::u8path& path, const std::chrono::seconds& timeout);
|
|
~LockFile();
|
|
|
|
LockFile(const LockFile&) = delete;
|
|
LockFile& operator=(const LockFile&) = delete;
|
|
LockFile& operator=(LockFile&&) = default;
|
|
|
|
static std::unique_ptr<LockFile> try_lock(const fs::u8path& path) noexcept;
|
|
|
|
int fd() const;
|
|
fs::u8path path() const;
|
|
fs::u8path lockfile_path() const;
|
|
|
|
#ifdef _WIN32
|
|
// Using file descriptor on Windows may cause false negative
|
|
static bool is_locked(const fs::u8path& path);
|
|
#else
|
|
// Opening a new file descriptor on Unix would clear locks
|
|
static bool is_locked(int fd);
|
|
#endif
|
|
static int read_pid(int fd);
|
|
|
|
private:
|
|
fs::u8path m_path;
|
|
fs::u8path m_lock;
|
|
std::chrono::seconds m_timeout;
|
|
int m_fd = -1;
|
|
bool m_locked;
|
|
bool m_lockfile_existed;
|
|
|
|
#if defined(__APPLE__) || defined(__linux__)
|
|
pid_t m_pid;
|
|
#else
|
|
int m_pid;
|
|
#endif
|
|
int read_pid() const;
|
|
bool write_pid(int pid) const;
|
|
|
|
bool set_fd_lock(bool blocking) const;
|
|
bool lock_non_blocking();
|
|
bool lock_blocking();
|
|
bool lock(int pid, bool blocking) const;
|
|
|
|
void remove_lockfile() noexcept;
|
|
int close_fd();
|
|
bool unlock();
|
|
};
|
|
|
|
/*************************
|
|
* utils for std::string *
|
|
*************************/
|
|
|
|
inline const char* check_char(const char* ptr)
|
|
{
|
|
return ptr ? ptr : "";
|
|
}
|
|
|
|
constexpr const char* WHITESPACES(" \r\n\t\f\v");
|
|
constexpr const wchar_t* WHITESPACES_WSTR(L" \r\n\t\f\v");
|
|
|
|
bool starts_with(const std::string_view& str, const std::string_view& prefix);
|
|
bool ends_with(const std::string_view& str, const std::string_view& suffix);
|
|
bool contains(const std::string_view& str, const std::string_view& sub_str);
|
|
|
|
// TODO: add concepts here, or at least some contraints
|
|
template <typename T, typename AssociativeContainer>
|
|
auto contains(const AssociativeContainer& values, const T& value_to_find)
|
|
-> decltype(values.find(value_to_find)
|
|
!= values.end()) // this should make invalid usage SFINAE
|
|
{
|
|
return values.find(value_to_find) != values.end();
|
|
}
|
|
|
|
bool any_starts_with(const std::vector<std::string_view>& str, const std::string_view& prefix);
|
|
|
|
bool starts_with_any(const std::string_view& str, const std::vector<std::string_view>& prefix);
|
|
|
|
template <class CharType>
|
|
inline std::basic_string_view<CharType> strip(const std::basic_string_view<CharType>& input,
|
|
const std::basic_string_view<CharType>& chars)
|
|
{
|
|
size_t start = input.find_first_not_of(chars);
|
|
if (start == std::basic_string<CharType>::npos)
|
|
{
|
|
return std::basic_string_view<CharType>();
|
|
}
|
|
size_t stop = input.find_last_not_of(chars) + 1;
|
|
size_t length = stop - start;
|
|
return length == 0 ? std::basic_string_view<CharType>() : input.substr(start, length);
|
|
}
|
|
|
|
std::string_view strip(const std::string_view& input);
|
|
std::wstring_view strip(const std::wstring_view& input);
|
|
std::string_view lstrip(const std::string_view& input);
|
|
std::string_view rstrip(const std::string_view& input);
|
|
|
|
std::string_view strip(const std::string_view& input, const std::string_view& chars);
|
|
std::string_view lstrip(const std::string_view& input, const std::string_view& chars);
|
|
std::string_view rstrip(const std::string_view& input, const std::string_view& chars);
|
|
|
|
template <class CharType>
|
|
inline std::vector<std::basic_string<CharType>> split(
|
|
const std::basic_string_view<CharType>& input,
|
|
const std::basic_string_view<CharType>& sep,
|
|
std::size_t max_split = SIZE_MAX)
|
|
{
|
|
std::vector<std::basic_string<CharType>> result;
|
|
std::size_t i = 0, j = 0, len = input.size(), n = sep.size();
|
|
|
|
while (i + n <= len)
|
|
{
|
|
if (input[i] == sep[0] && input.substr(i, n) == sep)
|
|
{
|
|
if (max_split-- <= 0)
|
|
break;
|
|
result.emplace_back(input.substr(j, i - j));
|
|
i = j = i + n;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
result.emplace_back(input.substr(j, len - j));
|
|
return result;
|
|
}
|
|
|
|
// unfortunately, c++ doesn't support templating function s. t. strip(std::string, std::string)
|
|
// works, so this is a workaround that fixes it
|
|
inline std::vector<std::string> split(const std::string_view& input,
|
|
const std::string_view& sep,
|
|
std::size_t max_split = SIZE_MAX)
|
|
{
|
|
return split<char>(input, sep, max_split);
|
|
}
|
|
|
|
std::vector<std::string> rsplit(const std::string_view& input,
|
|
const std::string_view& sep,
|
|
std::size_t max_split = SIZE_MAX);
|
|
|
|
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;
|
|
}
|
|
|
|
namespace details
|
|
{
|
|
struct PlusEqual
|
|
{
|
|
template <typename T, typename U>
|
|
auto operator()(T& left, const U& right)
|
|
{
|
|
left += right;
|
|
}
|
|
};
|
|
}
|
|
|
|
template <class S, class CharType, class Joiner = details::PlusEqual>
|
|
auto join(const CharType* sep, const S& container, Joiner joiner = details::PlusEqual{})
|
|
-> typename S::value_type
|
|
{
|
|
if (container.empty())
|
|
return {};
|
|
auto result = container[0];
|
|
for (std::size_t i = 1; i < container.size(); ++i)
|
|
{
|
|
joiner(result, sep);
|
|
joiner(result, container[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void replace_all(std::string& data, const std::string& search, const std::string& replace);
|
|
|
|
void replace_all(std::wstring& data, const std::wstring& search, const std::wstring& replace);
|
|
|
|
template <typename T>
|
|
auto without_duplicates(std::vector<T> values)
|
|
{
|
|
const auto end_it = std::unique(values.begin(), values.end());
|
|
values.erase(end_it, values.end());
|
|
return values;
|
|
}
|
|
|
|
// Note: this function only works for non-unicode!
|
|
std::string to_upper(const std::string_view& input);
|
|
std::string to_lower(const std::string_view& input);
|
|
|
|
tl::expected<std::string, mamba_error> encode_base64(const std::string_view& input);
|
|
tl::expected<std::string, mamba_error> decode_base64(const std::string_view& input);
|
|
|
|
namespace concat_impl
|
|
{
|
|
template <class T>
|
|
inline void concat_foreach(std::string& result, const T& rhs)
|
|
{
|
|
result += rhs;
|
|
}
|
|
|
|
template <class T, class... Rest>
|
|
inline void concat_foreach(std::string& result, const T& rhs, const Rest&... rest)
|
|
{
|
|
result += rhs;
|
|
concat_foreach(result, rest...);
|
|
}
|
|
|
|
struct sizer
|
|
{
|
|
inline sizer(const char* s)
|
|
: size(strlen(s))
|
|
{
|
|
}
|
|
|
|
inline sizer(const char)
|
|
: size(1)
|
|
{
|
|
}
|
|
|
|
template <class T>
|
|
inline sizer(T& s)
|
|
: size(s.size())
|
|
{
|
|
}
|
|
|
|
std::size_t size;
|
|
};
|
|
} // namespace concat_impl
|
|
|
|
template <typename... Args>
|
|
inline std::string concat(const Args&... args)
|
|
{
|
|
size_t len = 0;
|
|
for (auto s : std::initializer_list<concat_impl::sizer>{ args... })
|
|
len += s.size;
|
|
|
|
std::string result;
|
|
result.reserve(len);
|
|
concat_impl::concat_foreach(result, args...);
|
|
return result;
|
|
}
|
|
|
|
template <class B>
|
|
inline std::string hex_string(const B& buffer, std::size_t size)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << std::hex;
|
|
for (std::size_t i = 0; i < size; ++i)
|
|
{
|
|
oss << std::setw(2) << std::setfill('0') << static_cast<int>(buffer[i]);
|
|
}
|
|
return oss.str();
|
|
}
|
|
|
|
template <class B>
|
|
inline std::string hex_string(const B& buffer)
|
|
{
|
|
return hex_string(buffer, buffer.size());
|
|
}
|
|
|
|
// get the value corresponding to a key in a JSON object and assign it to target
|
|
// if the key is not found, assign default_value to target
|
|
template <typename T>
|
|
void assign_or(const nlohmann::json& j, const char* key, T& target, T default_value)
|
|
{
|
|
if (j.contains(key))
|
|
target = j[key];
|
|
else
|
|
target = default_value;
|
|
}
|
|
|
|
std::string quote_for_shell(const std::vector<std::string>& arguments,
|
|
const std::string& shell = "");
|
|
|
|
std::size_t clean_trash_files(const fs::u8path& prefix, bool deep_clean);
|
|
std::size_t remove_or_rename(const fs::u8path& path);
|
|
|
|
// Unindent a string literal
|
|
std::string unindent(const char* p);
|
|
|
|
std::string prepend(const std::string& p, const char* start, const char* newline = "");
|
|
|
|
std::string prepend(const char* p, const char* start, const char* newline = "");
|
|
|
|
std::string timestamp(const std::time_t& time);
|
|
|
|
std::time_t utc_time_now();
|
|
|
|
std::string utc_timestamp_now();
|
|
|
|
std::time_t parse_utc_timestamp(const std::string& timestamp, int& error_code) noexcept;
|
|
|
|
std::time_t parse_utc_timestamp(const std::string& timestamp);
|
|
|
|
std::ofstream open_ofstream(const fs::u8path& path,
|
|
std::ios::openmode mode = std::ios::out | std::ios::binary);
|
|
|
|
std::ifstream open_ifstream(const fs::u8path& path,
|
|
std::ios::openmode mode = std::ios::in | std::ios::binary);
|
|
|
|
bool ensure_comspec_set();
|
|
std::unique_ptr<TemporaryFile> wrap_call(const fs::u8path& root_prefix,
|
|
const fs::u8path& prefix,
|
|
bool dev_mode,
|
|
bool debug_wrapper_scripts,
|
|
const std::vector<std::string>& arguments);
|
|
|
|
std::tuple<std::vector<std::string>, std::unique_ptr<TemporaryFile>> prepare_wrapped_call(
|
|
const fs::u8path& prefix, const std::vector<std::string>& cmd);
|
|
|
|
/// Returns `true` if the filename matches names of files which should be interpreted as YAML.
|
|
/// NOTE: this does not check if the file exists.
|
|
inline bool is_yaml_file_name(const std::string_view filename)
|
|
{
|
|
return ends_with(filename, ".yml") || ends_with(filename, ".yaml");
|
|
}
|
|
|
|
} // namespace mamba
|
|
|
|
#endif // MAMBA_UTIL_HPP
|