add menu install shortcut and more long paths support for Windows (#975)

This commit is contained in:
Wolf Vollprecht 2021-06-17 13:28:36 +02:00 committed by GitHub
parent fec4633c8c
commit 81a490a046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1133 additions and 247 deletions

View File

@ -392,7 +392,7 @@ jobs:
run: |
conda config --add channels conda-forge
conda config --set channel_priority strict
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest menuinst
env:
PYTHON_VERSION: ${{ matrix.python-version }}
- name: micromamba python based tests
@ -433,7 +433,7 @@ jobs:
run: |
conda config --add channels conda-forge
conda config --set channel_priority strict
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest menuinst
env:
PYTHON_VERSION: ${{ matrix.python-version }}
- name: micromamba python based tests with pwsh
@ -472,7 +472,7 @@ jobs:
run: |
conda config --add channels conda-forge
conda config --set channel_priority strict
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest
conda create -q -y -n mamba-tests vs2017_win-64 python=$PYTHON_VERSION pip pybind11 libsolv libsodium libarchive "libcurl=7.76.1=*_0" nlohmann_json cpp-filesystem conda cmake gtest gmock ninja reproc-cpp yaml-cpp pyyaml cli11 pytest menuinst
env:
PYTHON_VERSION: ${{ matrix.python-version }}
- name: micromamba python based tests

View File

@ -92,6 +92,7 @@ set(MAMBA_SOURCES
${MAMBA_SOURCE_DIR}/core/link.cpp
${MAMBA_SOURCE_DIR}/core/history.cpp
${MAMBA_SOURCE_DIR}/core/match_spec.cpp
${MAMBA_SOURCE_DIR}/core/menuinst.cpp
${MAMBA_SOURCE_DIR}/core/url.cpp
${MAMBA_SOURCE_DIR}/core/output.cpp
${MAMBA_SOURCE_DIR}/core/package_handling.cpp
@ -110,6 +111,7 @@ set(MAMBA_SOURCES
${MAMBA_SOURCE_DIR}/core/thread_utils.cpp
${MAMBA_SOURCE_DIR}/core/transaction.cpp
${MAMBA_SOURCE_DIR}/core/util.cpp
${MAMBA_SOURCE_DIR}/core/util_os.cpp
${MAMBA_SOURCE_DIR}/core/validate.cpp
${MAMBA_SOURCE_DIR}/core/version.cpp
${MAMBA_SOURCE_DIR}/core/virtual_packages.cpp
@ -146,6 +148,7 @@ set(MAMBA_HEADERS
${MAMBA_INCLUDE_DIR}/mamba/core/link.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/mamba_fs.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/match_spec.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/menuinst.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/output.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/package_cache.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/package_handling.hpp
@ -165,6 +168,7 @@ set(MAMBA_HEADERS
${MAMBA_INCLUDE_DIR}/mamba/core/transaction_context.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/url.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/util.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/util_os.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/validate.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/version.hpp
${MAMBA_INCLUDE_DIR}/mamba/core/virtual_packages.hpp

View File

@ -1,6 +1,6 @@
R"MAMBARAW(
@REM Copyright (C) 2012 Anaconda, Inc
@REM Copyright (C) 2021 QuantStack
@REM SPDX-License-Identifier: BSD-3-Clause
@CALL "%~dp0..\condabin\conda_hook.bat"
conda.bat activate %*
@CALL "%~dp0..\condabin\mamba_hook.bat"
micromamba activate %*
)MAMBARAW"

View File

@ -1,8 +1,7 @@
R"MAMBARAW(
@REM Copyright (C) 2012 Anaconda, Inc
@REM Copyright (C) 2021 QuantStack
@REM SPDX-License-Identifier: BSD-3-Clause
@REM The file name is conda_hook.bat rather than conda-hook.bat because conda will see
@REM the latter as a 'conda hook' command.
@REM This file is derived from conda_hook.bat
@IF DEFINED CONDA_SHLVL GOTO :EOF

View File

@ -46,3 +46,23 @@ The workflow for that operation is:
click link href "./more_concepts.html#linking"
See also: :ref:`package tarball<tarball>`
The package installation process
--------------------------------
When a package gets installed, several steps are executed:
- the package is downloaded and placed into the ``$ROOT_PREFIX/pkgs`` folder
- the package is extracted
- the package is "linked" from the `pkgs` folder into the final destination
When the package is linked to the final destination (for example, some newly created environment), most files are "hard"-linked. That means, there is no copy of the file created. This saves a considerable amount of disk-space when re-using the same package in multiple environments.
However, sometimes a text-replacement is necessary. This is the case when a file contains a reference to the prefix. On Unix systems a very long prefix is used during build (you might have seen something like ``_h_123123_placeholder_placeholder_placeholder...``). This very long prefix is stored in the metadata of the package. When a file contains this prefix, the file is copied into the target prefix and the prefix it is replaced. In text files, this is done via a simple string replacement, and in binary files the string is padded with ``\0`` bytes (to keep the same total length of the file). Binary replacement on Windows is usually not necessary because dynamic library loading on Windows follows different rules.
When installing a ``noarch: python`` package, the installation process will compile all ``.py``-files into Python bytecode (``.pyc``) files.
All installed files are later referenced in the ``$TARGET_PREFIX/conda-meta/mypkg-version-build.json`` file, to facilitate the removal (e.g. when upgrading or removing a package).
If the package contains a ``menu/*.json`` entry that follows the spec introduced by ``menuinst``, a start-menu entry is created on Windows. This is currently not implemented on Linux or macOS but that might change in the future.

View File

@ -132,6 +132,9 @@ namespace mamba
bool always_copy = false;
bool always_softlink = false;
// add start menu shortcuts on Windows (not implemented on Linux / macOS)
bool shortcuts = true;
VerificationLevel safety_checks = VerificationLevel::kWarn;
bool extra_safety_checks = false;
bool verify_artifacts = false;

View File

@ -222,15 +222,6 @@ namespace mamba
return p;
}
inline bool is_admin()
{
#ifdef _WIN32
return IsUserAnAdmin();
#else
return geteuid() == 0 || getegid() == 0;
#endif
}
// inline fs::path expand_vars(const fs::path& path)
// {
// #ifndef _WIN32

View File

@ -0,0 +1,8 @@
#include "mamba_fs.hpp"
#include "transaction_context.hpp"
namespace mamba
{
void remove_menu_from_json(const fs::path& json_file, TransactionContext* context);
void create_menu_from_json(const fs::path& json_file, TransactionContext* context);
}

View File

@ -0,0 +1,32 @@
// 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_OS_HPP
#define MAMBA_CORE_UTIL_OS_HPP
#include "mamba/core/fsutil.hpp"
#include <string>
namespace mamba
{
bool is_admin();
fs::path get_self_exe_path();
#ifndef _WIN32
std::string get_process_name_by_pid(const int pid);
#else
DWORD getppid();
std::string get_process_name_by_pid(DWORD processId);
#endif
void run_as_admin(const std::string& args);
bool enable_long_paths_support(bool force);
std::string windows_version();
std::string macos_version();
std::string linux_version();
}
#endif

View File

@ -19,13 +19,11 @@
namespace mamba
{
std::string macos_version();
std::vector<PackageInfo> get_virtual_packages();
namespace detail
{
std::string cuda_version();
std::string macos_version();
std::string get_arch();
PackageInfo make_virtual_package(const std::string& name,

View File

@ -83,7 +83,7 @@ else:
libraries = ["archive", "solv", "solvext", "reproc++", CURL_LIB, CRYPTO_LIB]
if sys.platform == "win32":
libraries += ["advapi32"]
libraries += ["advapi32", "ole32", "shell32"]
ext_modules = [
Extension(
@ -96,6 +96,7 @@ ext_modules = [
"src/core/fetch.cpp",
"src/core/history.cpp",
"src/core/match_spec.cpp",
"src/core/menuinst.cpp",
"src/core/output.cpp",
"src/core/package_handling.cpp",
"src/core/package_cache.cpp",
@ -106,14 +107,15 @@ ext_modules = [
"src/core/pool.cpp",
"src/core/query.cpp",
"src/core/repo.cpp",
"src/core/solver.cpp",
"src/core/shell_init.cpp",
"src/core/solver.cpp",
"src/core/subdirdata.cpp",
"src/core/thread_utils.cpp",
"src/core/transaction.cpp",
"src/core/transaction_context.cpp",
"src/core/url.cpp",
"src/core/util.cpp",
"src/core/util_os.cpp",
"src/core/validate.cpp",
"src/core/version.cpp",
"src/core/link.cpp",

View File

@ -679,6 +679,14 @@ namespace mamba
!WARNING: Using this option can result in corruption of long-lived
environments due to broken links (deleted cache).)")));
insert(
Configurable("shortcuts", &ctx.shortcuts)
.group("Link & Install")
.set_rc_configurable()
.set_env_var_name()
.description(
"Install start-menu shortcuts on Windows (not implemented on Linux / macOS)"));
insert(Configurable("safety_checks", &ctx.safety_checks)
.group("Link & Install")
.set_rc_configurable()

View File

@ -12,6 +12,7 @@
#include "mamba/core/environment.hpp"
#include "mamba/core/mamba_fs.hpp"
#include "mamba/core/shell_init.hpp"
#include "mamba/core/util_os.hpp"
namespace mamba
@ -131,6 +132,15 @@ namespace mamba
{
std::cout << activator->deactivate();
}
#ifdef _WIN32
else if (action == "enable-long-paths-support")
{
if (!enable_long_paths_support(true))
{
exit(1);
}
}
#endif
else
{
throw std::runtime_error("Need an action (activate, deactivate or hook)");

View File

@ -13,6 +13,7 @@
#include <reproc++/run.hpp>
#include "mamba/core/environment.hpp"
#include "mamba/core/menuinst.hpp"
#include "mamba/core/link.hpp"
#include "mamba/core/match_spec.hpp"
#include "mamba/core/output.hpp"
@ -28,6 +29,8 @@
namespace mamba
{
static const std::regex MENU_PATH_REGEX("^menu[/\\\\].*\\.json$", std::regex_constants::icase);
void python_entry_point_template(std::ostream& out, const python_entry_point_parsed& p)
{
auto import_name = split(p.func, ".")[0];
@ -701,6 +704,12 @@ namespace mamba
for (auto& path : json_record["paths_data"]["paths"])
{
std::string fpath = path["_path"];
if (std::regex_match(fpath, MENU_PATH_REGEX))
{
remove_menu_from_json(m_context->target_prefix / fpath, m_context);
}
unlink_path(path);
}
@ -1094,7 +1103,6 @@ namespace mamba
std::vector<std::string> files_record;
// for (auto& path : paths_json["paths"])
nlohmann::json paths_json = nlohmann::json::object();
paths_json["paths"] = nlohmann::json::array();
paths_json["paths_version"] = 1;
@ -1211,6 +1219,19 @@ namespace mamba
}
}
// Create all start menu shortcuts if prefix name doesn't start with underscore
if (on_win && Context::instance().shortcuts
&& m_context->target_prefix.filename().string()[0] != '_')
{
for (auto& path : paths_data)
{
if (std::regex_match(path.path, MENU_PATH_REGEX))
{
create_menu_from_json(m_context->target_prefix / path.path, m_context);
}
}
}
run_script(m_context->target_prefix, m_pkg_info, "post-link", "", true);
fs::path prefix_meta = m_context->target_prefix / "conda-meta";

462
src/core/menuinst.cpp Normal file
View File

@ -0,0 +1,462 @@
#include <string>
#include <regex>
#include "mamba/core/context.hpp"
#include "mamba/core/transaction_context.hpp"
#include "mamba/core/mamba_fs.hpp"
#include "mamba/core/util.hpp"
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#endif
namespace mamba
{
namespace detail
{
std::string get_formatted_env_name(const fs::path& target_prefix)
{
std::string name = env_name(target_prefix);
if (name.find_first_of("\\/") != std::string::npos)
{
return "";
}
return name;
}
}
#ifdef _WIN32
namespace win
{
/*
* Create a shortcut on a Windows Machine (using COM)
* Place the shortcut in `programs` directory to make it appear in the
* startmenu.
*
* Args:
* path: Path to the executable
* description: string that is displayed
* filename: Link destination filename
* arguments: args to the executable (optional)
* work_dir: workdir for the executable
* icon_path: path to an .ico file
* icon_index: index for icon
*/
void create_shortcut(const fs::path& path,
const std::string& description,
const fs::path& filename,
const std::string& arguments,
const fs::path& work_dir,
const fs::path& icon_path,
int icon_index)
{
IShellLink* pShellLink = nullptr;
IPersistFile* pPersistFile = nullptr;
HRESULT hres;
LOG_DEBUG << "Creating shortcut with "
<< "\n Path: " << path << "\n Description: " << description
<< "\n Filename: " << filename << "\n Arguments: " << arguments
<< "\n Workdir: " << work_dir << "\n Icon Path: " << icon_path
<< "\n Icon Index: " << icon_index;
try
{
hres = CoInitialize(nullptr);
if (FAILED(hres))
{
throw std::runtime_error("Could not initialize COM");
}
hres = CoCreateInstance(CLSID_ShellLink,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pShellLink);
if (FAILED(hres))
{
throw std::runtime_error("CoCreateInstance failed.");
}
hres = pShellLink->QueryInterface(IID_IPersistFile, (void**) &pPersistFile);
if (FAILED(hres))
{
throw std::runtime_error("QueryInterface(IPersistFile) error 0x"
+ std::to_string(hres));
}
hres = pShellLink->SetPath(path.c_str());
if (FAILED(hres))
{
throw std::runtime_error("SetPath() failed, error 0x" + std::to_string(hres));
}
hres = pShellLink->SetDescription(description.c_str());
if (FAILED(hres))
{
throw std::runtime_error("SetDescription() failed, error 0x"
+ std::to_string(hres));
}
if (!arguments.empty())
{
hres = pShellLink->SetArguments(arguments.c_str());
if (FAILED(hres))
{
throw std::runtime_error("SetArguments() error 0x" + std::to_string(hres));
}
}
if (!icon_path.empty())
{
hres = pShellLink->SetIconLocation(icon_path.c_str(), icon_index);
if (FAILED(hres))
{
throw std::runtime_error("SetIconLocation() error 0x"
+ std::to_string(hres));
}
}
if (!work_dir.empty())
{
hres = pShellLink->SetWorkingDirectory(work_dir.c_str());
if (FAILED(hres))
{
throw std::runtime_error("SetWorkingDirectory() error 0x"
+ std::to_string(hres));
}
}
hres = pPersistFile->Save(filename.wstring().c_str(), true);
if (FAILED(hres))
{
throw std::runtime_error(concat(
"Failed to create shortcut: ", filename.string(), std::to_string(hres)));
}
}
catch (const std::runtime_error& e)
{
if (pPersistFile)
{
pPersistFile->Release();
}
if (pShellLink)
{
pShellLink->Release();
}
CoUninitialize();
LOG_ERROR << e.what();
}
pPersistFile->Release();
pShellLink->Release();
CoUninitialize();
}
const std::map<std::string, KNOWNFOLDERID> knownfolders = {
{ "programs", FOLDERID_Programs },
{ "profile", FOLDERID_Profile },
{ "documents", FOLDERID_Documents },
};
fs::path get_folder(const std::string& id)
{
wchar_t* localAppData;
HRESULT hres;
hres = SHGetKnownFolderPath(
knownfolders.at(id), KF_FLAG_DONT_VERIFY, nullptr, &localAppData);
if (FAILED(hres))
{
throw std::runtime_error("Could not retrieve known folder");
}
std::wstring tmp(localAppData);
fs::path res(tmp);
CoTaskMemFree(localAppData);
return res;
}
void remove_shortcut(const fs::path& filename)
{
try
{
if (fs::exists(filename))
{
fs::remove(filename);
}
}
catch (...)
{
LOG_ERROR << "Could not remove shortcut";
}
if (fs::is_empty(filename.parent_path()))
{
fs::remove(filename.parent_path());
}
}
} // namespace win
#endif
void replace_variables(std::string& text, TransactionContext* transaction_context)
{
auto& ctx = mamba::Context::instance();
fs::path root_prefix = ctx.root_prefix;
fs::path target_prefix;
std::string py_ver;
if (transaction_context)
{
target_prefix = transaction_context->target_prefix;
py_ver = transaction_context->python_version;
}
std::string distribution_name = root_prefix.filename();
if (distribution_name.size() > 1)
{
distribution_name[0] = std::toupper(distribution_name[0]);
}
auto to_forward_slash = [](const fs::path& p) {
std::string ps = p.string();
replace_all(ps, "\\", "/");
return ps;
};
auto platform_split = split(ctx.platform, "-");
std::string platform_bitness;
if (platform_split.size() >= 2)
{
platform_bitness = concat("(", platform_split.back(), "-bit)");
}
if (py_ver.size())
{
py_ver = split(py_ver, ".")[0];
}
std::map<std::string, std::string> vars = {
{ "${PREFIX}", to_forward_slash(target_prefix) },
{ "${ROOT_PREFIX}", to_forward_slash(root_prefix) },
{ "${PY_VER}", py_ver },
{ "${MENU_DIR}", to_forward_slash(target_prefix / "Menu") },
{ "${DISTRIBUTION_NAME}", distribution_name },
{ "${ENV_NAME}", detail::get_formatted_env_name(target_prefix) },
{ "${PLATFORM}", platform_bitness },
};
#ifdef _WIN32
vars["${PERSONALDIR}"] = to_forward_slash(win::get_folder("documents"));
vars["${USERPROFILE}"] = to_forward_slash(win::get_folder("profile"));
#endif
for (auto& [key, val] : vars)
{
replace_all(text, key, val);
}
}
namespace detail
{
void create_remove_shortcut_impl(const fs::path& json_file,
TransactionContext* transaction_context,
bool remove)
{
std::string json_content = mamba::read_contents(json_file);
replace_variables(json_content, transaction_context);
auto j = nlohmann::json::parse(json_content);
std::string menu_name = j.value("menu_name", "Mamba Shortcuts");
std::string name_suffix;
std::string e_name = detail::get_formatted_env_name(transaction_context->target_prefix);
if (e_name.size())
{
name_suffix = concat(" (", e_name, ")");
}
#ifdef _WIN32
// {
// "menu_name": "Miniforge${PY_VER}",
// "menu_items":
// [
// {
// "name": "Miniforge Prompt",
// "system": "%windir%\\system32\\cmd.exe",
// "scriptarguments": ["/K", "${ROOT_PREFIX}\\Scripts\\activate.bat",
// "${PREFIX}"], "icon": "${MENU_DIR}/console_shortcut.ico"
// }
// ]
// }
auto& ctx = mamba::Context::instance();
fs::path root_prefix = ctx.root_prefix;
fs::path target_prefix = ctx.target_prefix;
// using legacy stuff here
fs::path root_py = root_prefix / "python.exe";
fs::path root_pyw = root_prefix / "pythonw.exe";
fs::path env_py = target_prefix / "python.exe";
fs::path env_pyw = target_prefix / "pythonw.exe";
std::vector<std::string> cwp_py_args({ root_prefix / "cwp.py", target_prefix, env_py });
std::vector<std::string> cwp_pyw_args(
{ root_prefix / "cwp.py", target_prefix, env_pyw });
fs::path target_dir = win::get_folder("programs") / menu_name;
if (!fs::exists(target_dir))
{
fs::create_directories(target_dir);
}
auto extend_script_args = [](const auto& item, auto& arguments) {
// this is pretty bad ...
if (item.contains("scriptargument"))
{
arguments.push_back(item["scriptargument"]);
}
if (item.contains("scriptarguments"))
{
std::vector<std::string> tmp_args = item["scriptarguments"];
for (auto& arg : tmp_args)
{
arguments.push_back(arg);
}
}
};
for (auto& item : j["menu_items"])
{
std::string name = item["name"];
std::string full_name = concat(name, name_suffix);
std::vector<std::string> arguments;
fs::path script;
if (item.contains("pywscript"))
{
script = root_pyw;
arguments = cwp_pyw_args;
auto tmp = split(item["pywscript"], " ");
std::copy(tmp.begin(), tmp.end(), back_inserter(arguments));
}
else if (item.contains("pyscript"))
{
script = root_py;
arguments = cwp_py_args;
auto tmp = split(item["pyscript"], " ");
std::copy(tmp.begin(), tmp.end(), back_inserter(arguments));
}
else if (item.contains("webbrowser"))
{
script = root_pyw;
arguments = { "-m", "webbrowser", "-t", item["webbrowser"] };
}
else if (item.contains("script"))
{
script = root_py;
arguments = { root_prefix / "cwp.py", target_prefix };
auto tmp = split(item["script"], " ");
std::copy(tmp.begin(), tmp.end(), back_inserter(arguments));
extend_script_args(item, arguments);
}
else if (item.contains("system"))
{
auto tmp = split(item["system"], " ");
script = tmp[0];
if (tmp.size() > 1)
{
std::copy(tmp.begin() + 1, tmp.end(), back_inserter(arguments));
}
extend_script_args(item, arguments);
}
else
{
LOG_ERROR << "Unknown shortcut type found in " << json_file;
throw std::runtime_error("Unknown shortcut type.");
}
fs::path dst = target_dir / (full_name + ".lnk");
fs::path workdir = item.value("workdir", "");
fs::path iconpath = item.value("icon", "");
if (remove == false)
{
std::string argstring;
std::string lscript = to_lower(script.string());
for (auto& arg : arguments)
{
if (arg.size() >= 1 && arg[0] != '/')
{
mamba::replace_all(arg, "/", "\\");
}
}
if (lscript.find("cmd.exe") != std::string::npos
|| lscript.find("%comspec%") != std::string::npos)
{
if (arguments.size() > 1)
{
if (to_upper(arguments[0]) == "/K" || to_upper(arguments[0]) == "/C")
{
}
}
argstring = quote_for_shell(arguments, "cmdexe");
}
else
{
argstring = quote_for_shell(arguments, "");
}
if (workdir.string().size())
{
if (!(fs::exists(workdir) && fs::is_directory(workdir)))
{
fs::create_directories(workdir);
}
}
else
{
workdir = "%HOMEPATH%";
}
mamba::win::create_shortcut(
script, full_name, dst, argstring, workdir, iconpath, 0);
}
else
{
mamba::win::remove_shortcut(dst);
}
}
#endif
}
}
void remove_menu_from_json(const fs::path& json_file, TransactionContext* context)
{
try
{
detail::create_remove_shortcut_impl(json_file, context, true);
}
catch (const std::exception& e)
{
LOG_ERROR << "Removal of shortcut was not successful " << e.what();
}
}
void create_menu_from_json(const fs::path& json_file, TransactionContext* context)
{
try
{
detail::create_remove_shortcut_impl(json_file, context, false);
}
catch (const std::exception& e)
{
LOG_ERROR << "Creation of shortcut was not successful " << e.what();
}
}
}

View File

@ -9,28 +9,16 @@
#include "mamba/core/shell_init.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util.hpp"
#include "mamba/core/util_os.hpp"
#include "mamba/core/activation.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/virtual_packages.hpp"
#include "thirdparty/termcolor.hpp"
#include <reproc++/run.hpp>
#ifndef _WIN32
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#include <libProc.h>
#endif
#include <inttypes.h>
#if defined(__linux__)
#include <linux/limits.h>
#else
#include <limits.h>
#endif
#else
#include <windows.h>
#include <intrin.h>
#include <tlhelp32.h>
#ifdef _WIN32
#include "thirdparty/WinReg.hpp"
#endif
@ -45,6 +33,9 @@ namespace mamba
constexpr const char mamba_bat[] =
#include "../data/micromamba.bat"
;
constexpr const char activate_bat[] =
#include "../data/activate.bat"
;
constexpr const char _mamba_activate_bat[] =
#include "../data/_mamba_activate.bat"
;
@ -70,94 +61,6 @@ namespace mamba
"#endregion(?:\n|\r\n)?");
}
#ifdef _WIN32
DWORD getppid()
{
HANDLE hSnapshot;
PROCESSENTRY32 pe32;
DWORD ppid = 0, pid = GetCurrentProcessId();
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
__try
{
if (hSnapshot == INVALID_HANDLE_VALUE)
__leave;
ZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
if (!Process32First(hSnapshot, &pe32))
__leave;
do
{
if (pe32.th32ProcessID == pid)
{
ppid = pe32.th32ParentProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
__finally
{
if (hSnapshot != INVALID_HANDLE_VALUE)
CloseHandle(hSnapshot);
}
return ppid;
}
std::string get_process_name_by_pid(DWORD processId)
{
std::string ret;
HANDLE handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
processId /* This is the PID, you can find one from windows task manager */
);
if (handle)
{
DWORD buffSize = 1024;
CHAR buffer[1024];
if (QueryFullProcessImageNameA(handle, 0, buffer, &buffSize))
{
ret = buffer;
}
else
{
printf("Error GetModuleBaseNameA : %lu", GetLastError());
}
CloseHandle(handle);
}
else
{
printf("Error OpenProcess : %lu", GetLastError());
}
return ret;
}
#elif defined(__APPLE__)
std::string get_process_name_by_pid(const int pid)
{
std::string ret;
char name[1024];
proc_name(pid, name, sizeof(name));
ret = name;
return ret;
}
#elif defined(__linux__)
std::string get_process_name_by_pid(const int pid)
{
std::ifstream f(concat("/proc/", std::to_string(pid), "/status"));
if (f.good())
{
std::string l;
std::getline(f, l);
return l;
}
return "";
}
#endif
std::string guess_shell()
{
std::string parent_process_name = get_process_name_by_pid(getppid());
@ -198,7 +101,7 @@ namespace mamba
}
catch (const std::exception& e)
{
std::cerr << e.what() << '\n';
LOG_INFO << "No AutoRun key detected.";
}
// std::wstring hook_path = '"%s"' % join(conda_prefix, 'condabin', 'conda_hook.bat')
std::wstring hook_string = std::wstring(L"\"")
@ -241,54 +144,14 @@ namespace mamba
std::cout << termcolor::reset << std::endl;
key.SetStringValue(L"AutoRun", new_value);
}
}
}
#endif
// Heavily inspired by https://github.com/gpakosz/whereami/
// check their source to add support for other OS
fs::path get_self_exe_path()
{
#ifdef _WIN32
DWORD size;
std::wstring buffer(MAX_PATH, '\0');
size = GetModuleFileNameW(NULL, (wchar_t*) buffer.c_str(), (DWORD) buffer.size());
if (size == 0)
{
throw std::runtime_error("Could find location of the micromamba executable!");
}
else if (size == buffer.size())
{
DWORD new_size = size;
do
else
{
new_size *= 2;
buffer.reserve(new_size);
size = GetModuleFileNameW(NULL, (wchar_t*) buffer.c_str(), (DWORD) buffer.size());
} while (new_size == size);
}
buffer.resize(buffer.find(L'\0'));
return fs::absolute(buffer);
#elif defined(__APPLE__)
uint32_t size = PATH_MAX;
std::vector<char> buffer(size);
if (_NSGetExecutablePath(buffer.data(), &size) == -1)
{
buffer.reserve(size);
if (!_NSGetExecutablePath(buffer.data(), &size))
{
throw std::runtime_error("Couldn't find location the micromamba executable!");
std::cout << termcolor::green << "cmd.exe already initialized." << termcolor::reset
<< std::endl;
}
}
return fs::absolute(buffer.data());
#else
#if defined(__sun)
return fs::read_symlink("/proc/self/path/a.out");
#else
return fs::read_symlink("/proc/self/exe");
#endif
#endif
}
#endif
// this function calls cygpath to convert win path to unix
std::string native_path_to_unix(const std::string& path, bool is_a_path_env)
@ -492,7 +355,10 @@ namespace mamba
void init_root_prefix_cmdexe(const fs::path& root_prefix)
{
fs::path exe = get_self_exe_path();
fs::create_directories(root_prefix / "condabin");
fs::create_directories(root_prefix / "Scripts");
std::ofstream mamba_bat_f(root_prefix / "condabin" / "micromamba.bat");
std::string mamba_bat_contents(mamba_bat);
replace_all(mamba_bat_contents,
@ -506,6 +372,22 @@ namespace mamba
std::ofstream _mamba_activate_bat_f(root_prefix / "condabin" / "_mamba_activate.bat");
_mamba_activate_bat_f << _mamba_activate_bat;
std::string activate_bat_contents(activate_bat);
replace_all(activate_bat_contents,
std::string("__MAMBA_INSERT_ROOT_PREFIX__"),
std::string("@SET \"MAMBA_ROOT_PREFIX=" + root_prefix.string() + "\""));
replace_all(activate_bat_contents,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
std::string("@SET \"MAMBA_EXE=" + exe.string() + "\""));
std::ofstream condabin_activate_bat_f(root_prefix / "condabin" / "activate.bat");
condabin_activate_bat_f << activate_bat_contents;
std::ofstream scripts_activate_bat_f(root_prefix / "Scripts" / "activate.bat");
scripts_activate_bat_f << activate_bat_contents;
std::string hook_content = mamba_hook_bat;
replace_all(hook_content,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
@ -707,5 +589,8 @@ namespace mamba
{
throw std::runtime_error("Support for other shells not yet implemented.");
}
#ifdef _WIN32
enable_long_paths_support(false);
#endif
}
}

393
src/core/util_os.cpp Normal file
View File

@ -0,0 +1,393 @@
#include <regex>
#include "thirdparty/termcolor.hpp"
#include "mamba/core/environment.hpp"
#include "mamba/core/util_os.hpp"
#include <reproc++/run.hpp>
#ifndef _WIN32
#include <unistd.h>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#include <libProc.h>
#endif
#include <inttypes.h>
#if defined(__linux__)
#include <linux/limits.h>
#else
#include <limits.h>
#endif
#else
#include <windows.h>
#include <intrin.h>
#include <tlhelp32.h>
#include "thirdparty/WinReg.hpp"
#endif
namespace mamba
{
// Heavily inspired by https://github.com/gpakosz/whereami/
// check their source to add support for other OS
fs::path get_self_exe_path()
{
#ifdef _WIN32
DWORD size;
std::wstring buffer(MAX_PATH, '\0');
size = GetModuleFileNameW(NULL, (wchar_t*) buffer.c_str(), (DWORD) buffer.size());
if (size == 0)
{
throw std::runtime_error("Could find location of the micromamba executable!");
}
else if (size == buffer.size())
{
DWORD new_size = size;
do
{
new_size *= 2;
buffer.reserve(new_size);
size = GetModuleFileNameW(NULL, (wchar_t*) buffer.c_str(), (DWORD) buffer.size());
} while (new_size == size);
}
buffer.resize(buffer.find(L'\0'));
return fs::absolute(buffer);
#elif defined(__APPLE__)
uint32_t size = PATH_MAX;
std::vector<char> buffer(size);
if (_NSGetExecutablePath(buffer.data(), &size) == -1)
{
buffer.reserve(size);
if (!_NSGetExecutablePath(buffer.data(), &size))
{
throw std::runtime_error("Couldn't find location the micromamba executable!");
}
}
return fs::absolute(buffer.data());
#else
#if defined(__sun)
return fs::read_symlink("/proc/self/path/a.out");
#else
return fs::read_symlink("/proc/self/exe");
#endif
#endif
}
bool is_admin()
{
#ifdef _WIN32
return IsUserAnAdmin();
#else
return geteuid() == 0 || getegid() == 0;
#endif
}
#ifdef _WIN32
bool run_as_admin(const std::string& exe, const std::string& args)
{
SHELLEXECUTEINFO excinfo;
// I've tried for quite some time and could not get this to work at all
// excinfo.lpFile="micromamba.exe";
// excinfo.lpParameters="shell enable-long-paths-support";
ZeroMemory(&excinfo, sizeof(SHELLEXECUTEINFO));
excinfo.cbSize = sizeof(SHELLEXECUTEINFO);
excinfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC;
excinfo.lpVerb = "runas";
excinfo.lpFile = exe.c_str();
excinfo.lpParameters = args.c_str();
excinfo.lpDirectory = NULL;
excinfo.nShow = SW_HIDE;
if (!ShellExecuteEx(&excinfo))
{
LOG_WARNING << "Could not start process as admin.";
return false;
}
// Wait for the process to complete, then get its exit code
DWORD ExitCode = 0;
WaitForSingleObject(excinfo.hProcess, INFINITE);
GetExitCodeProcess(excinfo.hProcess, &ExitCode);
CloseHandle(excinfo.hProcess);
if (ExitCode != 0)
{
LOG_WARNING << "Process exited with code != 0.";
return false;
}
return true;
}
bool enable_long_paths_support(bool force)
{
// Needs to be set system-wide & can only be run as admin ...
std::string win_ver = windows_version();
auto splitted = split(win_ver, ".");
if (!(splitted.size() >= 3 && std::stoull(splitted[0]) >= 10
&& std::stoull(splitted[2]) >= 14352))
{
LOG_WARNING
<< "Not setting long path registry key; Windows version must be at least 10 "
"with the fall 2016 \"Anniversary update\" or newer.";
return false;
}
winreg::RegKey key;
key.Open(
HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\FileSystem", KEY_QUERY_VALUE);
DWORD prev_value;
try
{
prev_value = key.GetDwordValue(L"LongPathsEnabled");
}
catch (const winreg::RegException& e)
{
LOG_INFO << "No LongPathsEnabled key detected.";
return false;
}
if (prev_value == 1)
{
std::cout << termcolor::green << "Windows long-path support already enabled."
<< termcolor::reset << std::endl;
return true;
}
if (force || is_admin())
{
winreg::RegKey key_for_write(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\FileSystem");
key_for_write.SetDwordValue(L"LongPathsEnabled", 1);
}
else
{
if (Console::prompt("Enter admin mode to enable long paths support?", 'n'))
{
if (!run_as_admin(
"reg.exe",
"ADD HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem /v LongPathsEnabled /d 1 /t REG_DWORD /f"))
{
return false;
}
}
else
{
LOG_WARNING << "Did not enable long paths support.";
return false;
}
}
prev_value = key.GetDwordValue(L"LongPathsEnabled");
if (prev_value == 1)
{
std::cout << termcolor::green << "Windows long-path support enabled."
<< termcolor::reset << std::endl;
return true;
}
LOG_WARNING << "Changing registry value did not succeed.";
return false;
}
#endif
std::string windows_version()
{
if (!env::get("CONDA_OVERRIDE_WIN").empty())
{
return env::get("CONDA_OVERRIDE_WIN");
}
if (!on_win)
{
return "";
}
std::string out, err;
std::vector<std::string> args = { env::get("COMSPEC"), "/c", "ver" };
auto [status, ec] = reproc::run(
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
if (ec)
{
LOG_WARNING << "Could not find Windows version by calling 'ver'\n"
<< "Please file a bug report.\nError: " << ec.message();
return "";
}
std::string xout(strip(out));
// from python
std::regex ver_output_regex("(?:([\\w ]+) ([\\w.]+) .*\\[.* ([\\d.]+)\\])");
std::smatch rmatch;
std::string full_version, norm_version;
if (std::regex_match(xout, rmatch, ver_output_regex))
{
full_version = rmatch[3];
auto version_els = split(full_version, ".");
norm_version = concat(version_els[0], ".", version_els[1], ".", version_els[2]);
}
else
{
norm_version = "0.0.0";
}
return norm_version;
}
std::string macos_version()
{
if (!env::get("CONDA_OVERRIDE_OSX").empty())
{
return env::get("CONDA_OVERRIDE_OSX");
}
if (!on_mac)
{
return "";
}
std::string out, err;
// Note: we could also inspect /System/Library/CoreServices/SystemVersion.plist which is
// an XML file
// that contains the same information. However, then we'd either need an xml
// parser or some other crude method to read the data
std::vector<std::string> args = { "sw_vers", "-productVersion" };
auto [status, ec] = reproc::run(
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
if (ec)
{
LOG_WARNING
<< "Could not find macOS version by calling 'sw_vers -productVersion'\nPlease file a bug report.\nError: "
<< ec.message();
return "";
}
return std::string(strip(out));
}
std::string linux_version()
{
if (!env::get("CONDA_OVERRIDE_LINUX").empty())
{
return env::get("CONDA_OVERRIDE_LINUX");
}
if (!on_linux)
{
return "";
}
std::string out, err;
std::vector<std::string> args = { "uname", "-r" };
auto [status, ec] = reproc::run(
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
if (ec)
{
LOG_INFO << "Could not find linux version by calling 'uname -r' (skipped)";
return "";
}
std::regex re("([0-9]+\\.[0-9]+\\.[0-9]+)-.*");
std::smatch m;
if (std::regex_search(out, m, re))
{
if (m.size() == 2)
{
std::ssub_match linux_version = m[1];
LOG_DEBUG << "linux version found: " << linux_version;
return linux_version.str();
}
}
return "";
}
#ifdef _WIN32
DWORD getppid()
{
HANDLE hSnapshot;
PROCESSENTRY32 pe32;
DWORD ppid = 0, pid = GetCurrentProcessId();
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
__try
{
if (hSnapshot == INVALID_HANDLE_VALUE)
__leave;
ZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
if (!Process32First(hSnapshot, &pe32))
__leave;
do
{
if (pe32.th32ProcessID == pid)
{
ppid = pe32.th32ParentProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
__finally
{
if (hSnapshot != INVALID_HANDLE_VALUE)
CloseHandle(hSnapshot);
}
return ppid;
}
std::string get_process_name_by_pid(DWORD processId)
{
std::string ret;
HANDLE handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
processId /* This is the PID, you can find one from windows task manager */
);
if (handle)
{
DWORD buffSize = 1024;
CHAR buffer[1024];
if (QueryFullProcessImageNameA(handle, 0, buffer, &buffSize))
{
ret = buffer;
}
else
{
printf("Error GetModuleBaseNameA : %lu", GetLastError());
}
CloseHandle(handle);
}
else
{
printf("Error OpenProcess : %lu", GetLastError());
}
return ret;
}
#elif defined(__APPLE__)
std::string get_process_name_by_pid(const int pid)
{
std::string ret;
char name[1024];
proc_name(pid, name, sizeof(name));
ret = name;
return ret;
}
#elif defined(__linux__)
std::string get_process_name_by_pid(const int pid)
{
std::ifstream f(concat("/proc/", std::to_string(pid), "/status"));
if (f.good())
{
std::string l;
std::getline(f, l);
return l;
}
return "";
}
#endif
}

View File

@ -3,6 +3,7 @@
#include "mamba/core/context.hpp"
#include "mamba/core/util.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util_os.hpp"
#ifdef _WIN32
#include <windows.h>
@ -18,75 +19,6 @@ namespace mamba
{
namespace detail
{
std::string macos_version()
{
if (!env::get("CONDA_OVERRIDE_OSX").empty())
{
return env::get("CONDA_OVERRIDE_OSX");
}
if (!on_mac)
{
return "";
}
std::string out, err;
// Note: we could also inspect /System/Library/CoreServices/SystemVersion.plist which is
// an XML file
// that contains the same information. However, then we'd either need an xml
// parser or some other crude method to read the data
std::vector<std::string> args = { "sw_vers", "-productVersion" };
auto [status, ec] = reproc::run(
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
if (ec)
{
LOG_DEBUG
<< "Could not find macOS version by calling 'sw_vers -productVersion'\nPlease file a bug report.\nError: "
<< ec.message();
return "";
}
return std::string(strip(out));
}
std::string linux_version()
{
if (!env::get("CONDA_OVERRIDE_LINUX").empty())
{
return env::get("CONDA_OVERRIDE_LINUX");
}
if (!on_linux)
{
return "";
}
std::string out, err;
std::vector<std::string> args = { "uname", "-r" };
auto [status, ec] = reproc::run(
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
if (ec)
{
LOG_INFO << "Could not find linux version by calling 'uname -r' (skipped)";
return "";
}
std::regex re("([0-9]+\\.[0-9]+\\.[0-9]+)-.*");
std::smatch m;
if (std::regex_search(out, m, re))
{
if (m.size() == 2)
{
std::ssub_match linux_version = m[1];
LOG_DEBUG << "linux version found: " << linux_version;
return linux_version.str();
}
}
return "";
}
std::string glibc_version()
{
if (!env::get("CONDA_OVERRIDE_GLIBC").empty())
@ -232,7 +164,7 @@ namespace mamba
{
res.push_back(make_virtual_package("__unix"));
std::string linux_ver = detail::linux_version();
std::string linux_ver = linux_version();
if (!linux_ver.empty())
{
res.push_back(make_virtual_package("__linux", linux_ver));
@ -256,7 +188,7 @@ namespace mamba
{
res.push_back(make_virtual_package("__unix"));
std::string osx_ver = detail::macos_version();
std::string osx_ver = macos_version();
if (!osx_ver.empty())
{
res.push_back(make_virtual_package("__osx", osx_ver));

View File

@ -321,6 +321,10 @@ init_install_options(CLI::App* subcom)
extra_safety_checks.set_cli_config(0),
extra_safety_checks.description());
auto& shortcuts = config.at("shortcuts").get_wrapped<bool>();
subcom->add_flag(
"--shortcuts,!--no-shortcuts", shortcuts.set_cli_config(0), shortcuts.description());
auto& safety_checks = config.at("safety_checks").get_wrapped<VerificationLevel>();
subcom->add_set("--safety-checks",
safety_checks.set_cli_config(""),

View File

@ -43,7 +43,17 @@ init_shell_parser(CLI::App* subcom)
.description("The action to complete"));
subcom->add_set("action",
action.set_cli_config(""),
{ "init", "activate", "deactivate", "hook", "reactivate", "deactivate" },
{ "init",
"activate",
"deactivate",
"hook",
"reactivate",
"deactivate"
#ifdef _WIN32
,
"enable-long-paths-support"
#endif
},
action.description());
auto& prefix = config.insert(

View File

@ -0,0 +1,103 @@
import json
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
if not sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
import menuinst
import win32com.client
from .helpers import create, get_env, get_umamba, random_string, remove, umamba_list
class TestMenuinst:
root_prefix = os.environ["MAMBA_ROOT_PREFIX"]
current_prefix = os.environ["CONDA_PREFIX"]
dirs = menuinst.win32.dirs_src
@classmethod
def setup_class(cls):
pass
@classmethod
def teardown_class(cls):
pass
def test_simple_shortcut(self):
env_name = random_string()
# "--json"
create("miniforge_console_shortcut", "-n", env_name, no_dry_run=True)
prefix = os.path.join(self.root_prefix, "envs", env_name)
d = self.dirs["user"]["start"][0]
lnk = os.path.join(d, "Miniforge", "Miniforge Prompt (" + env_name + ").lnk")
assert os.path.exists(lnk)
shell = win32com.client.Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(lnk)
assert shortcut.TargetPath.lower() == os.getenv("COMSPEC").lower()
icon_location = shortcut.IconLocation
icon_location_path, icon_location_index = icon_location.split(",")
assert Path(icon_location_path) == (
Path(prefix) / "Menu" / "console_shortcut.ico"
)
assert (icon_location_index, "0")
assert shortcut.Description == "Miniforge Prompt (" + env_name + ")"
assert shortcut.Arguments == "/K " + str(
Path(self.root_prefix, "Scripts", "activate.bat")
) + " " + str(Path(prefix))
remove("miniforge_console_shortcut", "-n", env_name, no_dry_run=True)
assert not os.path.exists(lnk)
def test_shortcut_weird_env(self):
# note Umlauts do not work yet
os.environ["MAMBA_ROOT_PREFIX"] = str(Path("./compl i c ted").absolute())
root_prefix = os.environ["MAMBA_ROOT_PREFIX"]
env_name = random_string()
# "--json"
create("miniforge_console_shortcut", "-n", env_name, no_dry_run=True)
prefix = os.path.join(root_prefix, "envs", env_name)
d = self.dirs["user"]["start"][0]
lnk = os.path.join(d, "Miniforge", "Miniforge Prompt (" + env_name + ").lnk")
assert os.path.exists(lnk)
shell = win32com.client.Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(lnk)
assert shortcut.TargetPath.lower() == os.getenv("COMSPEC").lower()
icon_location = shortcut.IconLocation
icon_location_path, icon_location_index = icon_location.split(",")
assert Path(icon_location_path) == (
Path(prefix) / "Menu" / "console_shortcut.ico"
)
assert (icon_location_index, "0")
assert shortcut.Description == "Miniforge Prompt (" + env_name + ")"
assert (
shortcut.Arguments
== '/K "'
+ str(Path(root_prefix) / "Scripts" / "activate.bat")
+ '" "'
+ str(Path(prefix))
+ '"'
)
remove("miniforge_console_shortcut", "-n", env_name, no_dry_run=True)
assert not os.path.exists(lnk)
shutil.rmtree(root_prefix)
os.environ["MAMBA_ROOT_PREFIX"] = self.root_prefix

View File

@ -229,10 +229,11 @@ class TestShell:
if multiple_time:
if same_prefix and shell_type == "cmd.exe":
assert (
shell("-y", "init", "-s", shell_type, "-p", TestShell.root_prefix)
== ""
)
res = shell("-y", "init", "-s", shell_type, "-p", TestShell.root_prefix)
assert res.splitlines() == [
"cmd.exe already initialized.",
"Windows long-path support already enabled.",
]
else:
assert shell(
"-y",