windows shell init files use executable name (#3546)

* shell-init: Windows bat paths are factorized and depend on exe name (micromamba or mamba) except for mamba_hook.bat which for now needs to keep that name whatever the exe name;
* Fixed windows version detection: When more than one line of output appears at the start of cmd (because of AUTORUN), we parsed only the first line which failed the version detection. This version simply search every line instead of just the first one.
It also clarifies through more precise logs the exact reason why enabling long-paths actually failed.
* Improved error output for clarity on failure to create shell-init files (.bat) or directories.

---------

Co-authored-by: Johan Mabille <johan.mabille@gmail.com>
This commit is contained in:
Klaim (Joël Lamotte) 2024-10-21 17:31:18 +02:00 committed by GitHub
parent 3d9486fea5
commit 651622b72d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 173 additions and 86 deletions

3
.gitignore vendored
View File

@ -89,3 +89,6 @@ installed.json
tmp/
test_7.json.state
_skbuild/
/vcpkg_installed/

View File

@ -1,5 +1,5 @@
@REM Copyright (C) 2021 QuantStack
@REM SPDX-License-Identifier: BSD-3-Clause
@CALL "%~dp0..\condabin\mamba_hook.bat"
@CALL "%~dp0..\condabin\__MAMBA_INSERT_HOOK_BAT_NAME__"
__MAMBA_INSERT_EXE_NAME__ activate %*

View File

@ -3,21 +3,21 @@
@REM Replaced by mamba executable with the MAMBA_EXE and MAMBA_ROOT_PREFIX variable pointing
@REM to the correct locations.
__MAMBA_INSERT_MAMBA_EXE__
__MAMBA_INSERT_ROOT_PREFIX__
__MAMBA_DEFINE_MAMBA_EXE__
__MAMBA_DEFINE_ROOT_PREFIX__
@IF [%1]==[activate] "%~dp0_mamba_activate" %*
@IF [%1]==[deactivate] "%~dp0_mamba_activate" %*
@IF [%1]==[activate] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" %*
@IF [%1]==[deactivate] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" %*
@CALL "%MAMBA_EXE%" %*
@IF %errorlevel% NEQ 0 EXIT /B %errorlevel%
@IF [%1]==[install] "%~dp0_mamba_activate" reactivate
@IF [%1]==[update] "%~dp0_mamba_activate" reactivate
@IF [%1]==[upgrade] "%~dp0_mamba_activate" reactivate
@IF [%1]==[remove] "%~dp0_mamba_activate" reactivate
@IF [%1]==[uninstall] "%~dp0_mamba_activate" reactivate
@IF [%1]==[install] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" reactivate
@IF [%1]==[update] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" reactivate
@IF [%1]==[upgrade] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" reactivate
@IF [%1]==[remove] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" reactivate
@IF [%1]==[uninstall] "%~dp0__MAMBA_INSERT_ACTIVATE_BAT_NAME__" reactivate
@IF [%1]==[self-update] @CALL DEL /f %MAMBA_EXE%.bkup
@EXIT /B %errorlevel%

View File

@ -7,9 +7,9 @@
@FOR %%F in ("%~dp0") do @SET "__mambabin_dir=%%~dpF"
@SET "__mambabin_dir=%__mambabin_dir:~0,-1%"
@SET "PATH=%__mambabin_dir%;%PATH%"
@SET "MAMBA_BAT=%__mambabin_dir%\mamba.bat"
@SET "MAMBA_BAT=%__mambabin_dir%\__MAMBA_INSERT_BAT_NAME__"
@FOR %%F in ("%__mambabin_dir%") do @SET "__mamba_root=%%~dpF"
__MAMBA_INSERT_MAMBA_EXE__
__MAMBA_DEFINE_MAMBA_EXE__
@SET __mambabin_dir=
@SET __mamba_root=

View File

@ -13,6 +13,7 @@
#include <fmt/color.h>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/xchar.h>
#include <reproc++/run.hpp>
#ifdef _WIN32
#include <WinReg.hpp>
@ -42,9 +43,6 @@ namespace mamba
static const std::regex MAMBA_INITIALIZE_PS_RE_BLOCK("\n?#region mamba initialize(?:\n|\r\n)?"
"([\\s\\S]*?)"
"#endregion(?:\n|\r\n)?");
static const std::wregex
MAMBA_CMDEXE_HOOK_REGEX(L"(\"[^\"]*?mamba[-_]hook\\.bat\")", std::regex_constants::icase);
}
std::string guess_shell()
@ -102,7 +100,67 @@ namespace mamba
return "";
}
namespace // Windows-specific but must be available for cli on all platforms
{
struct RunInfo
{
fs::u8path this_exe_path = get_self_exe_path();
fs::u8path this_exe_name_path = this_exe_path.stem();
std::string this_exe_name = this_exe_name_path;
};
const RunInfo& run_info()
{
static const RunInfo info;
return info;
}
struct ShellInitPathsWindowsCmd
{
fs::u8path condabin;
fs::u8path scripts;
fs::u8path mamba_bat;
fs::u8path _mamba_activate_bat;
fs::u8path condabin_activate_bat;
fs::u8path scripts_activate_bat;
fs::u8path mamba_hook_bat;
explicit ShellInitPathsWindowsCmd(fs::u8path root_prefix)
: condabin(root_prefix / "condabin")
, scripts(root_prefix / "Scripts")
, mamba_bat(condabin / (run_info().this_exe_name + ".bat"))
, _mamba_activate_bat(condabin / ("_" + run_info().this_exe_name + "_activate.bat"))
, condabin_activate_bat(condabin / "activate.bat")
, scripts_activate_bat(scripts / "activate.bat")
, mamba_hook_bat(condabin / "mamba_hook.bat")
{
}
auto every_generated_files_paths() const -> std::vector<fs::u8path>
{
return { mamba_bat,
_mamba_activate_bat,
condabin_activate_bat,
scripts_activate_bat,
mamba_hook_bat };
}
auto every_generated_directories_paths() const -> std::vector<fs::u8path>
{
return { condabin, scripts };
}
};
}
#ifdef _WIN32
static const std::wregex
MAMBA_CMDEXE_HOOK_REGEX(L"(\"[^\"]*?mamba[-_]hook\\.bat\")", std::regex_constants::icase);
std::wstring get_autorun_registry_key(const std::wstring& reg_path)
{
winreg::RegKey key{ HKEY_CURRENT_USER, reg_path };
@ -137,9 +195,9 @@ namespace mamba
std::wstring get_hook_string(const fs::u8path& conda_prefix)
{
// '"%s"' % join(conda_prefix, 'condabin', 'conda_hook.bat')
return std::wstring(L"\"") + (conda_prefix / "condabin" / "mamba_hook.bat").wstring()
+ std::wstring(L"\"");
const ShellInitPathsWindowsCmd paths{ conda_prefix };
auto hook_path = fs::canonical(paths.mamba_hook_bat).std_path();
return fmt::format(LR"("{}")", hook_path.make_preferred().wstring());
}
void
@ -163,7 +221,11 @@ namespace mamba
{
if (!new_value.empty())
{
new_value += L" & " + hook_string;
if (new_value.find(hook_string) == std::wstring::npos)
{
new_value += L" & " + hook_string;
}
// else the hook path already exists in the string
}
else
{
@ -685,76 +747,92 @@ namespace mamba
void init_root_prefix_cmdexe(const Context&, const fs::u8path& root_prefix)
{
fs::u8path exe = get_self_exe_path();
fs::u8path exe_name = exe.stem();
const ShellInitPathsWindowsCmd paths{ root_prefix };
try
{
fs::create_directories(root_prefix / "condabin");
fs::create_directories(root_prefix / "Scripts");
}
catch (...)
for (const auto directory : paths.every_generated_directories_paths())
{
// Maybe the prefix isn't writable. No big deal, just keep going.
std::error_code maybe_error [[maybe_unused]];
fs::create_directories(directory, maybe_error);
if (maybe_error)
{
LOG_ERROR << "Failed to create directory '" << directory.string()
<< "' : " << maybe_error.message();
}
}
const auto replace_insert_root_prefix = [&](auto& text)
{
return util::replace_all(
text,
std::string("__MAMBA_DEFINE_ROOT_PREFIX__"),
"@SET \"MAMBA_ROOT_PREFIX=" + root_prefix.string() + "\""
);
};
const auto replace_insert_mamba_exe = [&](auto& text)
{
return util::replace_all(
text,
std::string("__MAMBA_DEFINE_MAMBA_EXE__"),
"@SET \"MAMBA_EXE=" + run_info().this_exe_path.string() + "\""
);
};
static const auto MARKER_INSERT_EXE_NAME = std::string("__MAMBA_INSERT_EXE_NAME__");
static const auto MARKER_INSERT_MAMBA_BAT_NAME = std::string("__MAMBA_INSERT_BAT_NAME__");
// mamba.bat
std::string mamba_bat_contents(data_mamba_bat);
util::replace_all(
mamba_bat_contents,
std::string("__MAMBA_INSERT_ROOT_PREFIX__"),
"@SET \"MAMBA_ROOT_PREFIX=" + root_prefix.string() + "\""
replace_insert_root_prefix(mamba_bat_contents);
replace_insert_mamba_exe(mamba_bat_contents);
static const auto MARKER_MAMBA_INSERT_ACTIVATE_BAT_NAME = std::string(
"__MAMBA_INSERT_ACTIVATE_BAT_NAME__"
);
util::replace_all(
mamba_bat_contents,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
"@SET \"MAMBA_EXE=" + exe.string() + "\""
MARKER_MAMBA_INSERT_ACTIVATE_BAT_NAME,
paths._mamba_activate_bat.stem().string()
);
std::ofstream mamba_bat_f = open_ofstream(root_prefix / "condabin" / "mamba.bat");
std::ofstream mamba_bat_f = open_ofstream(paths.mamba_bat);
mamba_bat_f << mamba_bat_contents;
// _mamba_activate.bat
std::ofstream _mamba_activate_bat_f = open_ofstream(
root_prefix / "condabin" / "_mamba_activate.bat"
);
std::ofstream _mamba_activate_bat_f = open_ofstream(paths._mamba_activate_bat);
_mamba_activate_bat_f << data__mamba_activate_bat;
// condabin/activate.bat
std::string activate_bat_contents(data_activate_bat);
util::replace_all(
activate_bat_contents,
std::string("__MAMBA_INSERT_ROOT_PREFIX__"),
"@SET \"MAMBA_ROOT_PREFIX=" + root_prefix.string() + "\""
replace_insert_root_prefix(activate_bat_contents);
replace_insert_mamba_exe(activate_bat_contents);
util::replace_all(activate_bat_contents, MARKER_INSERT_EXE_NAME, run_info().this_exe_name);
static const auto MARKER_MAMBA_INSERT_HOOK_BAT_NAME = std::string(
"__MAMBA_INSERT_HOOK_BAT_NAME__"
);
util::replace_all(
activate_bat_contents,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
"@SET \"MAMBA_EXE=" + exe.string() + "\""
);
util::replace_all(
activate_bat_contents,
std::string("__MAMBA_INSERT_EXE_NAME__"),
exe_name.string()
);
std::ofstream condabin_activate_bat_f = open_ofstream(
root_prefix / "condabin" / "activate.bat"
MARKER_MAMBA_INSERT_HOOK_BAT_NAME,
paths.mamba_hook_bat.filename().string()
);
std::ofstream condabin_activate_bat_f = open_ofstream(paths.condabin_activate_bat);
condabin_activate_bat_f << activate_bat_contents;
// Scripts/activate.bat
std::ofstream scripts_activate_bat_f = open_ofstream(root_prefix / "Scripts" / "activate.bat");
std::ofstream scripts_activate_bat_f = open_ofstream(paths.scripts_activate_bat);
scripts_activate_bat_f << activate_bat_contents;
// mamba_hook.bat
std::string hook_content = data_mamba_hook_bat;
replace_insert_mamba_exe(hook_content);
util::replace_all(hook_content, MARKER_INSERT_EXE_NAME, run_info().this_exe_name);
util::replace_all(
hook_content,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
"@SET \"MAMBA_EXE=" + exe.string() + "\""
MARKER_INSERT_MAMBA_BAT_NAME,
paths.mamba_bat.filename().string()
);
util::replace_all(hook_content, std::string("__MAMBA_INSERT_EXE_NAME__"), exe_name.string());
std::ofstream mamba_hook_bat_f = open_ofstream(root_prefix / "condabin" / "mamba_hook.bat");
std::ofstream mamba_hook_bat_f = open_ofstream(paths.mamba_hook_bat);
mamba_hook_bat_f << hook_content;
}
@ -765,21 +843,12 @@ namespace mamba
return;
}
auto mamba_bat = root_prefix / "condabin" / "mamba.bat";
auto _mamba_activate_bat = root_prefix / "condabin" / "_mamba_activate.bat";
auto condabin_activate_bat = root_prefix / "condabin" / "activate.bat";
auto scripts_activate_bat = root_prefix / "Scripts" / "activate.bat";
auto mamba_hook_bat = root_prefix / "condabin" / "mamba_hook.bat";
const ShellInitPathsWindowsCmd paths{ root_prefix };
for (auto& f : { mamba_bat,
_mamba_activate_bat,
condabin_activate_bat,
scripts_activate_bat,
mamba_hook_bat })
for (auto& f : paths.every_generated_files_paths())
{
if (fs::exists(f))
if (fs::remove(f))
{
fs::remove(f);
LOG_INFO << "Removed " << f << " file.";
}
else
@ -789,14 +858,14 @@ namespace mamba
}
// remove condabin and Scripts if empty
auto condabin = root_prefix / "condabin";
auto scripts = root_prefix / "Scripts";
for (auto& d : { condabin, scripts })
for (auto& d : paths.every_generated_directories_paths())
{
if (fs::exists(d) && fs::is_empty(d))
if (fs::is_empty(d))
{
fs::remove(d);
LOG_INFO << "Removed " << d << " directory.";
if (fs::remove(d))
{
LOG_INFO << "Removed " << d << " directory.";
}
}
}
}
@ -1340,7 +1409,7 @@ namespace mamba
#ifdef _WIN32
// cmd.exe
std::wstring reg = get_autorun_registry_key(L"Software\\Microsoft\\Command Processor");
const std::wstring reg = get_autorun_registry_key(L"Software\\Microsoft\\Command Processor");
if (std::regex_match(reg, MAMBA_CMDEXE_HOOK_REGEX))
{
result.push_back("cmd.exe");

View File

@ -50,9 +50,8 @@ namespace mamba
fs::u8path 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());
DWORD 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!");
@ -140,19 +139,28 @@ namespace mamba
{
// Needs to be set system-wide & can only be run as admin ...
auto win_ver = util::windows_version();
const auto win_ver = util::windows_version();
LOG_DEBUG << fmt::format(
"Windows version : {}",
win_ver ? win_ver.value() : win_ver.error().message
);
static constexpr auto error_message_wrong_version = "Not setting long path registry key;"
"Windows version must be at least 10 with the fall 2016 \"Anniversary update\" or newer.";
if (!win_ver.has_value())
{
LOG_WARNING << "Not setting long path registry key; Windows version must be at least 10 "
"with the fall 2016 \"Anniversary update\" or newer.";
LOG_WARNING << "failed to acquire Windows version - " << error_message_wrong_version;
return false;
}
auto split_out = util::split(win_ver.value(), ".");
if (!(split_out.size() >= 3 && std::stoull(split_out[0]) >= 10
&& std::stoull(split_out[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.";
LOG_WARNING << "Windows version found:" << win_ver.value() << " - "
<< error_message_wrong_version;
return false;
}
@ -165,7 +173,8 @@ namespace mamba
}
catch (const winreg::RegException& /*e*/)
{
LOG_INFO << "No LongPathsEnabled key detected.";
LOG_INFO << "No LongPathsEnabled key detected. (Windows version = " << win_ver.value()
<< ")";
return false;
}
@ -174,8 +183,9 @@ namespace mamba
auto out = Console::stream();
fmt::print(
out,
"{}",
fmt::styled("Windows long-path support already enabled.", palette.ignored)
"{} (Windows version = {})",
fmt::styled("Windows long-path support already enabled.", palette.ignored),
win_ver.value()
);
return true;
}

View File

@ -218,7 +218,12 @@ namespace mamba::util
// from python
static const auto ver_output_regex = std::regex(R"((?:([\w ]+) ([\w.]+) .*\[.* ([\d.]+)\]))");
if (auto rmatch = std::smatch(); std::regex_match(out, rmatch, ver_output_regex))
// The output of the command could contain multiple unrelated lines, so we need to check
// every lines, which is why we need to search in a loop until reaching the end of the
// output.
std::smatch rmatch;
auto start_it = out.cbegin();
while (std::regex_search(start_it, out.cend(), rmatch, ver_output_regex))
{
std::string full_version = rmatch[3];
auto version_elems = util::split(full_version, ".");