Refactor env::which (#2997)

* Clenup env::which

* RAII C calls

* Refactor which functions

* Remove second param from which

* Change which namespace

* Fix which extension

* Add missing OSX headers

* Add util::which tests
This commit is contained in:
Antoine Prouvost 2023-11-20 15:13:22 +01:00 committed by GitHub
parent 7c5f0562c4
commit 139b7ef020
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 260 additions and 150 deletions

View File

@ -186,7 +186,6 @@ set(
${LIBMAMBA_SOURCE_DIR}/core/context.cpp
${LIBMAMBA_SOURCE_DIR}/core/download.cpp
${LIBMAMBA_SOURCE_DIR}/core/download_progress_bar.cpp
${LIBMAMBA_SOURCE_DIR}/core/environment.cpp
${LIBMAMBA_SOURCE_DIR}/core/environments_manager.cpp
${LIBMAMBA_SOURCE_DIR}/core/error_handling.cpp
${LIBMAMBA_SOURCE_DIR}/core/transaction_context.cpp
@ -285,7 +284,6 @@ set(
${LIBMAMBA_INCLUDE_DIR}/mamba/core/context.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/download.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/download_progress_bar.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/environment.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/environments_manager.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/error_handling.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/satisfiability_error.hpp

View File

@ -1,20 +0,0 @@
// Copyright (c) 2019-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_CORE_ENVIRONMENT_HPP
#define MAMBA_CORE_ENVIRONMENT_HPP
#include <string>
#include <vector>
#include "mamba/fs/filesystem.hpp"
namespace mamba::env
{
auto which(const std::string& exe, const std::string& override_path = "") -> fs::u8path;
auto which(const std::string& exe, const std::vector<fs::u8path>& search_paths) -> fs::u8path;
}
#endif

View File

@ -9,17 +9,14 @@
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include "mamba/fs/filesystem.hpp"
#include "mamba/util/build.hpp"
namespace mamba::util
{
/**
* Return the character use to separate paths.
*/
[[nodiscard]] constexpr auto pathsep() -> char;
/**
* Get an environment variable encoded in UTF8.
*/
@ -91,6 +88,31 @@ namespace mamba::util
*/
[[nodiscard]] auto user_cache_dir() -> std::string;
/**
* Return the character use to separate paths.
*/
[[nodiscard]] constexpr auto pathsep() -> char;
/**
* Return the full path of a program from its name.
*/
[[nodiscard]] auto which(std::string_view exe) -> fs::u8path;
/**
* Return the full path of a program from its name if found inside the given directories.
*/
template <typename Iter>
[[nodiscard]] auto which_in(std::string_view exe, Iter search_path_first, Iter search_path_last)
-> fs::u8path;
/**
* Return the full path of a program from its name if found inside the given directories.
*
* The directies can be given as a range or as a @ref pathsep separated list.
*/
template <typename Range>
[[nodiscard]] auto which_in(std::string_view exe, const Range& search_paths) -> fs::u8path;
/********************
* Implementation *
********************/
@ -106,5 +128,43 @@ namespace mamba::util
return ':';
}
}
namespace detail
{
[[nodiscard]] auto which_in_one(const fs::u8path& exe, const fs::u8path& dir) -> fs::u8path;
[[nodiscard]] auto which_in_split(const fs::u8path& exe, std::string_view paths)
-> fs::u8path;
}
template <typename Iter>
auto which_in(std::string_view exe, Iter first, Iter last) -> fs::u8path
{
for (; first != last; ++first)
{
if (auto p = detail::which_in_one(exe, *first); !p.empty())
{
return p;
}
}
return "";
}
template <typename Range>
auto which_in(std::string_view exe, const Range& search_paths) -> fs::u8path
{
if constexpr (std::is_same_v<Range, fs::u8path>)
{
return detail::which_in_one(exe, search_paths);
}
else if constexpr (std::is_convertible_v<Range, std::string_view>)
{
return detail::which_in_split(exe, search_paths);
}
else
{
return which_in(exe, search_paths.cbegin(), search_paths.cend());
}
}
}
#endif

View File

@ -19,7 +19,6 @@
#include "mamba/core/channel.hpp"
#include "mamba/core/download.hpp"
#include "mamba/core/env_lockfile.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/environments_manager.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/package_cache.hpp"
@ -43,7 +42,7 @@ namespace mamba
)
{
const auto get_python_path = [&]
{ return env::which("python", get_path_dirs(target_prefix)).string(); };
{ return util::which_in("python", get_path_dirs(target_prefix)).string(); };
const std::unordered_map<std::string, command_args> other_pkg_mgr_install_instructions{
{ "pip",

View File

@ -6,7 +6,6 @@
#include "mamba/core/activation.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/shell_init.hpp"
#include "mamba/core/util.hpp"

View File

@ -1,96 +0,0 @@
// 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 <cstdlib>
#ifdef _WIN32
#include <mutex>
#include <Shlobj.h>
#include "mamba/core/output.hpp"
#include "mamba/core/util_os.hpp"
#include "mamba/util/os_win.hpp"
#else
#include <pwd.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <wordexp.h>
extern "C"
{
extern char** environ;
}
#endif
#include "mamba/core/environment.hpp"
#include "mamba/util/environment.hpp"
#include "mamba/util/string.hpp"
namespace mamba::env
{
fs::u8path which(const std::string& exe, const std::string& override_path)
{
// TODO maybe add a cache?
auto env_path = override_path == "" ? util::get_env("PATH") : override_path;
if (env_path)
{
std::string path = env_path.value();
const auto parts = util::split(path, util::pathsep());
const std::vector<fs::u8path> search_paths(parts.begin(), parts.end());
return which(exe, search_paths);
}
#ifndef _WIN32
if (override_path == "")
{
char* pathbuf;
size_t n = confstr(_CS_PATH, NULL, static_cast<size_t>(0));
pathbuf = static_cast<char*>(malloc(n));
if (pathbuf != NULL)
{
confstr(_CS_PATH, pathbuf, n);
return which(exe, pathbuf);
}
}
#endif
return ""; // empty path
}
fs::u8path which(const std::string& exe, const std::vector<fs::u8path>& search_paths)
{
for (auto& p : search_paths)
{
std::error_code _ec; // ignore
if (!fs::exists(p, _ec) || !fs::is_directory(p, _ec))
{
continue;
}
#ifdef _WIN32
const auto exe_with_extension = exe + ".exe";
#endif
for (const auto& entry : fs::directory_iterator(p, _ec))
{
const auto filename = entry.path().filename();
if (filename == exe
#ifdef _WIN32
|| filename == exe_with_extension
#endif
)
{
return entry.path();
}
}
}
return ""; // empty path
}
}

View File

@ -13,18 +13,20 @@
#include <reproc++/reproc.hpp>
#include <reproc++/run.hpp>
#include "mamba/core/environment.hpp"
#include "mamba/core/link.hpp"
#include "mamba/core/match_spec.hpp"
#include "mamba/core/menuinst.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/transaction_context.hpp"
#include "mamba/core/util_os.hpp"
#include "mamba/core/validate.hpp"
#include "mamba/util/build.hpp"
#include "mamba/util/environment.hpp"
#include "mamba/util/string.hpp"
#ifdef __APPLE__
#include "mamba/core/util_os.hpp"
#endif
#if _WIN32
#include "../data/conda_exe.hpp"
#endif
@ -384,10 +386,10 @@ namespace mamba
else
{
// shell_path = 'sh' if 'bsd' in sys.platform else 'bash'
fs::u8path shell_path = env::which("bash");
fs::u8path shell_path = util::which("bash");
if (shell_path.empty())
{
shell_path = env::which("sh");
shell_path = util::which("sh");
}
if (activate)

View File

@ -8,7 +8,6 @@
#include "mamba/core/channel.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/match_spec.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util.hpp"

View File

@ -22,7 +22,6 @@
#include "mamba/core/activation.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/shell_init.hpp"
#include "mamba/core/util.hpp"
@ -251,11 +250,9 @@ namespace mamba
bash = parent_process_name;
}
else
#ifdef _WIN32
bash = env::which("bash.exe");
#else
bash = env::which("bash");
#endif
{
bash = util::which("bash");
}
const std::string command = bash.empty() ? "cygpath"
: (bash.parent_path() / "cygpath").string();
std::string out, err;

View File

@ -53,7 +53,6 @@ extern "C"
#include <tl/expected.hpp>
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/error_handling.hpp"
#include "mamba/core/execution.hpp"
#include "mamba/core/invoke.hpp"
@ -1541,10 +1540,10 @@ namespace mamba
else
{
// shell_path = 'sh' if 'bsd' in sys.platform else 'bash'
fs::u8path shell_path = env::which("bash");
fs::u8path shell_path = util::which("bash");
if (shell_path.empty())
{
shell_path = env::which("sh");
shell_path = util::which("sh");
}
if (shell_path.empty())
{

View File

@ -32,7 +32,6 @@
#include <fmt/ostream.h>
#include <reproc++/run.hpp>
#include "mamba/core/environment.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util_os.hpp"
#include "mamba/util/build.hpp"

View File

@ -16,7 +16,6 @@
#include <reproc++/run.hpp>
#include "mamba/core/context.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util_os.hpp"
#include "mamba/core/virtual_packages.hpp"

View File

@ -194,12 +194,23 @@ namespace mamba::util
}
return util::get_windows_known_user_folder(WindowsKnowUserFolder::LocalAppData);
}
auto which_system(std::string_view exe) -> fs::u8path
{
return "";
}
constexpr auto exec_extension() -> std::string_view
{
return ".exe";
};
}
#else // #ifdef _WIN32
#include <cstdlib>
#include <stdexcept>
#include <vector>
#include <fmt/format.h>
#include <pwd.h>
@ -305,11 +316,25 @@ namespace mamba::util
}
return path_concat(user_home_dir(), ".cache");
}
auto which_system(std::string_view exe) -> fs::u8path
{
const auto n = ::confstr(_CS_PATH, nullptr, static_cast<std::size_t>(0));
auto pathbuf = std::vector<char>(n, '\0');
::confstr(_CS_PATH, pathbuf.data(), n);
return which_in(exe, std::string_view(pathbuf.data(), n));
}
constexpr auto exec_extension() -> std::string_view
{
return "";
};
}
#endif // #ifdef _WIN32
#include "mamba/util/environment.hpp"
#include "mamba/util/string.hpp"
namespace mamba::util
{
@ -329,4 +354,82 @@ namespace mamba::util
}
update_env_map(env);
}
namespace
{
auto
which_in_one_impl(const fs::u8path& exe, const fs::u8path& dir, const fs::u8path& extension)
-> fs::u8path
{
std::error_code _ec; // ignore
if (!fs::exists(dir, _ec) || !fs::is_directory(dir, _ec))
{
return ""; // Not found
}
const auto strip_ext = [&extension](const fs::u8path& p)
{
if (p.extension() == extension)
{
return p.stem();
}
return p.filename();
};
const auto exe_striped = strip_ext(exe);
for (const auto& entry : fs::directory_iterator(dir, _ec))
{
if (auto p = entry.path(); strip_ext(p) == exe_striped)
{
return p;
}
}
return ""; // Not found
}
auto which_in_split_impl(
const fs::u8path& exe,
std::string_view paths,
const fs::u8path& extension,
char pathsep
) -> fs::u8path
{
auto elem = std::string_view();
auto rest = std::optional<std::string_view>(paths);
while (rest.has_value())
{
std::tie(elem, rest) = util::split_once(rest.value(), pathsep);
if (auto p = which_in_one_impl(exe, elem, extension); !p.empty())
{
return p;
};
}
return "";
}
}
auto which(std::string_view exe) -> fs::u8path
{
if (auto paths = get_env("PATH"))
{
if (auto p = which_in(exe, paths.value()); !p.empty())
{
return p;
}
}
return which_system(exe);
}
namespace detail
{
auto which_in_one(const fs::u8path& exe, const fs::u8path& dir) -> fs::u8path
{
return which_in_one_impl(exe, dir, exec_extension());
}
auto which_in_split(const fs::u8path& exe, std::string_view paths) -> fs::u8path
{
return which_in_split_impl(exe, paths, exec_extension(), util::pathsep());
}
}
}

View File

@ -24,6 +24,11 @@ namespace mambatests
#endif
inline static const mamba::fs::u8path test_data_dir = MAMBA_TEST_DATA_DIR;
#ifndef MAMBA_TEST_LOCK_EXE
#error "MAMBA_TEST_LOCK_EXE must be defined pointing to testing_libmamba_lock"
#endif
inline static const mamba::fs::u8path testing_libmamba_lock_exe = MAMBA_TEST_LOCK_EXE;
struct Singletons
{
// mamba::MainExecutor main_executor; // FIXME: reactivate once the tests are not indirectly

View File

@ -27,15 +27,10 @@ extern "C"
}
#endif
#include "mambatests.hpp"
namespace mamba
{
#ifndef MAMBA_TEST_LOCK_EXE
#error "MAMBA_TEST_LOCK_EXE must be defined pointing to testing_libmamba_lock"
#endif
inline static const fs::u8path testing_libmamba_lock_exe = MAMBA_TEST_LOCK_EXE;
namespace testing
{
@ -123,7 +118,7 @@ namespace mamba
TEST_CASE_FIXTURE(LockDirTest, "different_pid")
{
std::string const lock_exe = testing_libmamba_lock_exe.string();
std::string const lock_exe = mambatests::testing_libmamba_lock_exe.string();
std::string out, err;
std::vector<std::string> args;
@ -247,7 +242,7 @@ namespace mamba
TEST_CASE_FIXTURE(LockFileTest, "different_pid")
{
std::string const lock_exe = testing_libmamba_lock_exe.string();
std::string const lock_exe = mambatests::testing_libmamba_lock_exe.string();
std::string out, err;
std::vector<std::string> args;
{

View File

@ -192,4 +192,72 @@ TEST_SUITE("util::environment")
}
}
}
TEST_CASE("which_in")
{
SUBCASE("Inexistant search dirs")
{
CHECK_EQ(which_in("echo", "/obviously/does/not/exist"), "");
}
SUBCASE("testing_libmamba_lock")
{
const auto test_exe = which_in(
"testing_libmamba_lock",
mambatests::testing_libmamba_lock_exe.parent_path()
);
CHECK_EQ(test_exe.stem(), "testing_libmamba_lock");
CHECK(mamba::fs::exists(test_exe));
}
SUBCASE("testing_libmamba_lock.exe")
{
if (on_win)
{
const auto test_exe = which_in(
"testing_libmamba_lock.exe",
mambatests::testing_libmamba_lock_exe.parent_path()
);
CHECK_EQ(test_exe.stem(), "testing_libmamba_lock");
CHECK(mamba::fs::exists(test_exe));
}
}
}
TEST_CASE("which")
{
SUBCASE("echo")
{
const auto echo = which("echo");
CHECK_EQ(echo.stem(), "echo");
CHECK(mamba::fs::exists(echo));
if (!on_win)
{
const auto dir = echo.parent_path();
constexpr auto reasonable_locations = std::array{
"/bin",
"/sbin",
"/usr/bin",
"/usr/sbin",
};
CHECK(starts_with_any(echo.string(), reasonable_locations));
}
}
SUBCASE("echo.exe")
{
if (on_win)
{
const auto echo = which("echo.exe");
CHECK_EQ(echo.stem(), "echo");
CHECK(mamba::fs::exists(echo));
}
}
SUBCASE("Inexistant path")
{
CHECK_EQ(which("obvisously-does-not-exist"), "");
}
}
}

View File

@ -18,6 +18,10 @@
#include "mamba/core/util_os.hpp"
#include "mamba/util/build.hpp"
#ifdef __APPLE__
#include "mamba/core/util_os.hpp"
#endif
#include "common_options.hpp"
#include "version.hpp"