Skip to content

Commit

Permalink
windows shell init files use executable name (#3546)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Klaim and JohanMabille authored Oct 21, 2024
1 parent 3d9486f commit 651622b
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 86 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ installed.json
tmp/
test_7.json.state
_skbuild/


/vcpkg_installed/
2 changes: 1 addition & 1 deletion libmamba/data/activate.bat
Original file line number Diff line number Diff line change
@@ -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 %*
18 changes: 9 additions & 9 deletions libmamba/data/mamba.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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%
4 changes: 2 additions & 2 deletions libmamba/data/mamba_hook.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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=

Expand Down
195 changes: 132 additions & 63 deletions libmamba/src/core/shell_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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
Expand All @@ -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
{
Expand Down Expand Up @@ -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() + "\""
);
util::replace_all(
activate_bat_contents,
std::string("__MAMBA_INSERT_MAMBA_EXE__"),
"@SET \"MAMBA_EXE=" + exe.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_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;
}

Expand All @@ -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
Expand All @@ -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.";
}
}
}
}
Expand Down Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 651622b

Please sign in to comment.