Fix windows paths and add tests (#3787)

This commit is contained in:
Hind-M 2025-02-18 13:14:55 +01:00 committed by GitHub
parent 402b2d474b
commit 211d79db77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 219 additions and 14 deletions

View File

@ -73,11 +73,16 @@ namespace mamba::fs
{
};
struct Utf8Options
{
bool normalize_sep = true;
};
// Maintain `\` on Windows, `/` on other platforms
std::filesystem::path normalized_separators(std::filesystem::path path);
// Returns a UTF-8 string given a standard path.
std::string to_utf8(const std::filesystem::path& path);
std::string to_utf8(const std::filesystem::path& path, Utf8Options utf8_options = {});
// Returns standard path given a UTF-8 string.
std::filesystem::path from_utf8(std::string_view u8string);
@ -300,10 +305,10 @@ namespace mamba::fs
//---- Conversions ----
// Returns a UTF-8 string.
// Returns a UTF-8 string with normalized separators.
std::string string() const
{
return to_utf8(m_path);
return to_utf8(m_path, { /*normalize_sep=*/true });
}
// Returns a default encoded string.
@ -333,7 +338,7 @@ namespace mamba::fs
// Returns a UTF-8 string using the ``/`` on all systems.
std::string generic_string() const
{
return to_utf8(m_path.generic_string());
return to_utf8(m_path.generic_string(), { /*normalize_sep=*/false });
}
// Implicit conversion to standard path.

View File

@ -147,6 +147,7 @@ namespace mamba
// We add -script.py to WIN32, and link the conda.exe launcher which will
// automatically find the correct script to launch
std::string win_script = path.string() + "-script.py";
std::string win_script_gen_str = path.generic_string() + "-script.py";
fs::u8path script_path = m_context->target_prefix / win_script;
#else
fs::u8path script_path = m_context->target_prefix / path;
@ -192,7 +193,7 @@ namespace mamba
conda_exe_f.write(reinterpret_cast<char*>(conda_exe), conda_exe_len);
conda_exe_f.close();
make_executable(m_context->target_prefix / script_exe);
return std::array<std::string, 2>{ win_script, script_exe.string() };
return std::array<std::string, 2>{ win_script_gen_str, script_exe.generic_string() };
#else
if (!python_path.empty())
{
@ -607,7 +608,7 @@ namespace mamba
// Sometimes we might want to raise here ...
m_clobber_warnings.push_back(rel_dst.string());
#ifdef _WIN32
return std::make_tuple(std::string(validation::sha256sum(dst)), rel_dst.string());
return std::make_tuple(std::string(validation::sha256sum(dst)), rel_dst.generic_string());
#endif
fs::remove(dst);
}
@ -699,7 +700,10 @@ namespace mamba
fo << launcher << shebang << (buffer.c_str() + arc_pos);
fo.close();
}
return std::make_tuple(std::string(validation::sha256sum(dst)), rel_dst.string());
return std::make_tuple(
std::string(validation::sha256sum(dst)),
rel_dst.generic_string()
);
}
#else
std::size_t padding_size = (path_data.prefix_placeholder.size() > new_prefix.size())
@ -748,7 +752,7 @@ namespace mamba
codesign(dst, m_context->context().output_params.verbosity > 1);
}
#endif
return std::make_tuple(std::string(validation::sha256sum(dst)), rel_dst.string());
return std::make_tuple(std::string(validation::sha256sum(dst)), rel_dst.generic_string());
}
if ((path_data.path_type == PathType::HARDLINK) || path_data.no_link)
@ -800,7 +804,7 @@ namespace mamba
fs::copy_symlink(src, dst);
// we need to wait until all files are linked to compute the SHA256 sum!
// otherwise the file that's pointed to might not be linked yet.
return std::make_tuple("", rel_dst.string());
return std::make_tuple("", rel_dst.generic_string());
}
else
{
@ -811,7 +815,7 @@ namespace mamba
}
return std::make_tuple(
path_data.sha256.empty() ? std::string(validation::sha256sum(dst)) : path_data.sha256,
rel_dst.string()
rel_dst.generic_string()
);
}
@ -1032,10 +1036,10 @@ namespace mamba
std::vector<fs::u8path> pyc_files = compile_pyc_files(for_compilation);
for (const fs::u8path& pyc_path : pyc_files)
{
out_json["paths_data"]["paths"].push_back({ { "_path", pyc_path.string() },
out_json["paths_data"]["paths"].push_back({ { "_path", pyc_path.generic_string() },
{ "path_type", "pyc_file" } });
out_json["files"].push_back(pyc_path.string());
out_json["files"].push_back(pyc_path.generic_string());
}
if (link_json.find("noarch") != link_json.end()

View File

@ -45,9 +45,16 @@ namespace mamba::fs
#endif
#if __cplusplus == 201703L
std::string to_utf8(const std::filesystem::path& path)
std::string to_utf8(const std::filesystem::path& path, Utf8Options utf8_options)
{
return normalized_separators(path).u8string();
if (utf8_options.normalize_sep)
{
return normalized_separators(path).u8string();
}
else
{
return path.u8string();
}
}
std::filesystem::path from_utf8(std::string_view u8string)

View File

@ -90,6 +90,7 @@ set(
src/core/test_shell_init.cpp
src/core/test_tasksync.cpp
src/core/test_thread_utils.cpp
src/core/test_transaction_context.cpp
src/core/test_util.cpp
src/core/test_virtual_packages.cpp
)

View File

@ -37,6 +37,68 @@ namespace mamba
REQUIRE(y.u8string() == u8"日本語");
}
TEST_CASE("to_utf8_check_separators")
{
static constexpr auto some_path_str = u8"a/b/c";
std::filesystem::path some_path = std::filesystem::u8path(some_path_str);
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/false }) == some_path_str);
#if defined(_WIN32)
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/true }) == u8"a\\b\\c");
#else
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/true }) == some_path_str);
#endif
}
TEST_CASE("to_utf8_check_separators_unicode")
{
static constexpr auto some_path_str = u8"日/本/語";
std::filesystem::path some_path = std::filesystem::u8path(some_path_str);
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/false }) == some_path_str);
#if defined(_WIN32)
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/true }) == u8"\\\\");
#else
REQUIRE(fs::to_utf8(some_path, { /*normalize_sep=*/true }) == some_path_str);
#endif
}
TEST_CASE("from_utf8_check_separators")
{
static constexpr auto some_path_str = u8"a/b/c";
#if defined(_WIN32)
REQUIRE(fs::from_utf8(some_path_str) == std::filesystem::u8path(u8"a\\b\\c"));
#else
REQUIRE(fs::from_utf8(some_path_str) == std::filesystem::u8path(u8"a/b/c"));
#endif
}
TEST_CASE("from_utf8_check_separators_unicode")
{
static constexpr auto some_path_str = u8"日/本/語";
#if defined(_WIN32)
REQUIRE(fs::from_utf8(some_path_str) == std::filesystem::u8path(u8"\\\\"));
#else
REQUIRE(fs::from_utf8(some_path_str) == std::filesystem::u8path(u8"日/本/語"));
#endif
}
TEST_CASE("u8path_separators_formatting")
{
static constexpr auto some_path_str = u8"a/b/c";
std::filesystem::path some_path = std::filesystem::u8path(some_path_str);
const fs::u8path u8_path(some_path);
#if defined(_WIN32)
REQUIRE(u8_path.string() == u8"a\\b\\c");
#else
REQUIRE(u8_path.string() == some_path_str);
#endif
REQUIRE(u8_path.generic_string() == some_path_str);
}
TEST_CASE("consistent_encoding")
{
const auto utf8_string = u8"日本語";

View File

@ -0,0 +1,96 @@
// Copyright (c) 2025, 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 <catch2/catch_all.hpp>
#include "mamba/core/transaction_context.hpp"
namespace mamba
{
namespace
{
TEST_CASE("compute_short_python_version")
{
REQUIRE(compute_short_python_version("") == "");
REQUIRE(compute_short_python_version("3.5") == "3.5");
REQUIRE(compute_short_python_version("3.5.0") == "3.5");
}
TEST_CASE("get_python_short_path")
{
auto path_empty_ver = get_python_short_path("").string();
auto path = get_python_short_path("3.5.0").string();
#ifdef _WIN32
REQUIRE(path_empty_ver == "python.exe");
REQUIRE(path == "python.exe");
#else
REQUIRE(path_empty_ver == "bin/python");
REQUIRE(path == "bin/python3.5.0");
#endif
}
TEST_CASE("get_python_site_packages_short_path")
{
REQUIRE(get_python_site_packages_short_path("").string() == "");
auto path = get_python_site_packages_short_path("3.5.0");
auto path_str = path.string();
auto path_gen_str = path.generic_string();
#ifdef _WIN32
REQUIRE(path_str == "Lib\\site-packages");
REQUIRE(path_gen_str == "Lib/site-packages");
#else
REQUIRE(path_str == "lib/python3.5.0/site-packages");
REQUIRE(path_gen_str == "lib/python3.5.0/site-packages");
#endif
}
TEST_CASE("get_bin_directory_short_path")
{
auto path = get_bin_directory_short_path().string();
#ifdef _WIN32
REQUIRE(path == "Scripts");
#else
REQUIRE(path == "bin");
#endif
}
TEST_CASE("get_python_noarch_target_path")
{
auto random_path = get_python_noarch_target_path("some_lib/some_folder", "bla");
auto random_path_str = random_path.string();
auto random_path_gen_str = random_path.generic_string();
auto sp_path = get_python_noarch_target_path(
"site-packages/some_random_package",
"target_site_packages_short_path"
);
auto sp_path_str = sp_path.string();
auto sp_path_gen_str = sp_path.generic_string();
auto ps_path = get_python_noarch_target_path(
"python-scripts/some_random_file",
"target_site_packages_short_path"
);
auto ps_path_str = ps_path.string();
auto ps_path_gen_str = ps_path.generic_string();
REQUIRE(random_path_gen_str == "some_lib/some_folder");
REQUIRE(sp_path_gen_str == "target_site_packages_short_path/some_random_package");
#ifdef _WIN32
REQUIRE(random_path_str == "some_lib\\some_folder");
REQUIRE(sp_path_str == "target_site_packages_short_path\\some_random_package");
REQUIRE(ps_path_str == "Scripts\\some_random_file");
REQUIRE(ps_path_gen_str == "Scripts/some_random_file");
#else
REQUIRE(random_path_str == "some_lib/some_folder");
REQUIRE(sp_path_str == "target_site_packages_short_path/some_random_package");
REQUIRE(ps_path_str == "bin/some_random_file");
REQUIRE(ps_path_gen_str == "bin/some_random_file");
#endif
}
}
} // namespace mamba

View File

@ -1,3 +1,4 @@
import json
import os
import platform
import shutil
@ -1437,6 +1438,35 @@ def test_create_from_oci_mirrored_channels_pkg_name_mapping(
assert pkg["channel"] == "https://pkg-containers.githubusercontent.com/ghcr1/blobs"
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_create_with_norm_path(tmp_home, tmp_root_prefix):
env_name = "myenv"
env_prefix = tmp_root_prefix / "envs" / env_name
res = helpers.create("-n", env_name, "conda-smithy", "--json")
assert res["success"]
conda_smithy = next((env_prefix / "conda-meta").glob("conda-smithy*.json")).read_text()
conda_smithy_data = json.loads(conda_smithy)
if platform.system() == "Windows":
assert all(
file_path.startswith(("Lib/site-packages", "Scripts"))
for file_path in conda_smithy_data["files"]
)
assert all(
py_path["_path"].startswith(("Lib/site-packages", "Scripts"))
for py_path in conda_smithy_data["paths_data"]["paths"]
)
else:
assert all(
file_path.startswith(("lib/python", "bin")) for file_path in conda_smithy_data["files"]
)
assert all(
py_path["_path"].startswith(("lib/python", "bin"))
for py_path in conda_smithy_data["paths_data"]["paths"]
)
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_create_with_unicode(tmp_home, tmp_root_prefix):
env_name = "320 áγђß家固êôōçñ한"