add micromamba self-update functionality (#2023)

This commit is contained in:
Wolf Vollprecht 2022-11-01 17:24:46 +01:00 committed by GitHub
parent 405cb607fa
commit c889fa6855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 454 additions and 46 deletions

View File

@ -97,12 +97,26 @@ function Invoke-Mamba() {
"deactivate" {
Exit-MambaEnvironment;
}
"self-update" {
& $Env:MAMBA_EXE $Command @OtherArgs;
$MAMBA_EXE_BKUP = $Env:MAMBA_EXE + ".bkup";
if (Test-Path $MAMBA_EXE_BKUP) {
Remove-Item $MAMBA_EXE_BKUP
}
}
default {
# There may be a command we don't know want to handle
# differently in the shell wrapper, pass it through
# verbatim.
& $Env:MAMBA_EXE $Command @OtherArgs;
# reactivate environment
if (@("install", "update", "remove").contains($Command))
{
$activateCommand = (& $Env:MAMBA_EXE shell reactivate -s powershell $Args | Out-String);
Write-Verbose "[micromamba shell reactivate --shell powershell $Args]`n$activateCommand";
Invoke-Expression -Command $activateCommand;
}
}
}
}

View File

@ -16,5 +16,6 @@ __MAMBA_INSERT_ROOT_PREFIX__
@IF [%1]==[upgrade] "%~dp0_mamba_activate" reactivate
@IF [%1]==[remove] "%~dp0_mamba_activate" reactivate
@IF [%1]==[uninstall] "%~dp0_mamba_activate" reactivate
@IF [%1]==[self-update] @CALL DEL /f %MAMBA_EXE%.bkup
@EXIT /B %errorlevel%

View File

@ -39,6 +39,14 @@ micromamba() {
__mamba_exe "$@" || \return
__mamba_reactivate
;;
self-update)
__mamba_exe "$@" || \return
# remove leftover backup file on Windows
if [ -f "$MAMBA_EXE.bkup" ]; then
rm -f $MAMBA_EXE.bkup
fi
;;
*)
__mamba_exe "$@"
;;

View File

@ -152,6 +152,7 @@ namespace mamba
bool on_ci = false;
bool no_progress_bars = false;
bool dry_run = false;
bool download_only = false;
bool always_yes = false;
bool allow_softlinks = false;

View File

@ -28,6 +28,7 @@ namespace mamba
openssl_failed,
internal_failure,
lockfile_failure,
selfupdate_failure,
};
class mamba_error : public std::runtime_error

View File

@ -39,7 +39,7 @@ namespace mamba
void set_debuglevel();
void create_whatprovides();
std::vector<Id> select_solvables(Id id) const;
std::vector<Id> select_solvables(Id id, bool sorted = false) const;
Id matchspec2id(const std::string& ms);
std::optional<PackageInfo> id2pkginfo(Id id);

View File

@ -66,6 +66,8 @@ namespace mamba
void init_shell(const std::string& shell, const fs::u8path& conda_prefix);
void deinit_shell(const std::string& shell, const fs::u8path& conda_prefix);
std::vector<std::string> find_initialized_shells();
}
#endif

View File

@ -50,6 +50,8 @@ namespace mamba
ConsoleFeatures get_console_features();
int get_console_width();
int get_console_height();
void codesign(const fs::u8path& path, bool verbose = false);
}
#endif

View File

@ -1425,6 +1425,12 @@ namespace mamba
.set_env_var_names()
.description("Only display what would have been done"));
insert(Configurable("download_only", &ctx.download_only)
.group("Output, Prompt and Flow Control")
.set_env_var_names()
.description(
"Only download and extract packages, do not link them into environment."));
insert(Configurable("log_level", &ctx.logging_level)
.group("Output, Prompt and Flow Control")
.set_rc_configurable()

View File

@ -110,6 +110,14 @@ namespace mamba
deinit_shell(shell_type, shell_prefix);
}
else if (action == "reinit")
{
// re-initialize all the shell scripts after update
for (auto& shell_type : find_initialized_shells())
{
shell("init", shell_type, prefix, false);
}
}
else if (action == "hook")
{
// TODO do we need to do something wtih `shell_prefix -> root_prefix?`?

View File

@ -21,6 +21,7 @@
#include "mamba/core/transaction_context.hpp"
#include "mamba/core/util.hpp"
#include "mamba/core/validate.hpp"
#include "mamba/core/util_os.hpp"
#if _WIN32
#include "../data/conda_exe.hpp"
@ -706,24 +707,7 @@ namespace mamba
#if defined(__APPLE__)
if (binary_changed && m_pkg_info.subdir == "osx-arm64")
{
reproc::options options;
options.env.behavior = reproc::env::empty;
if (Context::instance().verbosity <= 1)
{
reproc::redirect silence;
silence.type = reproc::redirect::discard;
options.redirect.out = silence;
options.redirect.err = silence;
}
std::vector<std::string> cmd
= { "/usr/bin/codesign", "-s", "-", "-f", dst.string() };
auto [status, ec] = reproc::run(cmd, options);
if (ec)
{
throw std::runtime_error(std::string("Could not codesign executable")
+ ec.message());
}
codesign(dst, Context::instance().verbosity > 1);
}
#endif
return std::make_tuple(validate::sha256sum(dst), rel_dst.string());

View File

@ -9,6 +9,7 @@ extern "C"
#include <solv/pool.h>
#include <solv/solver.h>
#include <solv/selection.h>
#include <solv/evr.h>
}
#include "spdlog/spdlog.h"
@ -87,11 +88,23 @@ namespace mamba
return m_pool;
}
std::vector<Id> MPool::select_solvables(Id matchspec) const
std::vector<Id> MPool::select_solvables(Id matchspec, bool sorted) const
{
MQueue job, solvables;
job.push(SOLVER_SOLVABLE_PROVIDES, matchspec);
selection_solvables(m_pool, job, solvables);
if (sorted)
{
std::sort(solvables.begin(),
solvables.end(),
[this](Id a, Id b)
{
Solvable* sa = pool_id2solvable(this->m_pool, a);
Solvable* sb = pool_id2solvable(this->m_pool, b);
return (pool_evrcmp(this->m_pool, sa->evr, sb->evr, EVRCMP_COMPARE) > 0);
});
}
return solvables.as<std::vector>();
}

View File

@ -29,13 +29,16 @@ namespace mamba
{
namespace
{
std::regex CONDA_INITIALIZE_RE_BLOCK("\n# >>> mamba initialize >>>(?:\n|\r\n)?"
std::regex MAMBA_INITIALIZE_RE_BLOCK("\n?# >>> mamba initialize >>>(?:\n|\r\n)?"
"([\\s\\S]*?)"
"# <<< mamba initialize <<<(?:\n|\r\n)?");
std::regex CONDA_INITIALIZE_PS_RE_BLOCK("\n#region mamba initialize(?:\n|\r\n)?"
std::regex MAMBA_INITIALIZE_PS_RE_BLOCK("\n?#region mamba initialize(?:\n|\r\n)?"
"([\\s\\S]*?)"
"#endregion(?:\n|\r\n)?");
std::wregex MAMBA_CMDEXE_HOOK_REGEX(L"(\"[^\"]*?mamba[-_]hook\\.bat\")",
std::regex_constants::icase);
}
std::string guess_shell()
@ -129,9 +132,10 @@ namespace mamba
// modify registry key
std::wstring replace_str(L"__CONDA_REPLACE_ME_123__");
std::wregex hook_regex(L"(\"[^\"]*?mamba[-_]hook\\.bat\")", std::regex_constants::icase);
std::wstring replaced_value = std::regex_replace(
prev_value, hook_regex, replace_str, std::regex_constants::format_first_only);
std::wstring replaced_value = std::regex_replace(prev_value,
MAMBA_CMDEXE_HOOK_REGEX,
replace_str,
std::regex_constants::format_first_only);
std::wstring new_value = replaced_value;
@ -429,7 +433,7 @@ namespace mamba
}
std::string result
= std::regex_replace(rc_content, CONDA_INITIALIZE_RE_BLOCK, conda_init_content);
= std::regex_replace(rc_content, MAMBA_INITIALIZE_RE_BLOCK, conda_init_content);
if (result.find("# >>> mamba initialize >>>") == std::string::npos)
{
@ -474,7 +478,7 @@ namespace mamba
return;
}
std::string result = std::regex_replace(rc_content, CONDA_INITIALIZE_RE_BLOCK, "");
std::string result = std::regex_replace(rc_content, MAMBA_INITIALIZE_RE_BLOCK, "");
if (Context::instance().dry_run)
{
@ -822,7 +826,7 @@ namespace mamba
{
LOG_DEBUG << "Found mamba initialize. Replacing mamba initialize block.";
profile_content = std::regex_replace(
profile_content, CONDA_INITIALIZE_PS_RE_BLOCK, conda_init_content);
profile_content, MAMBA_INITIALIZE_PS_RE_BLOCK, conda_init_content);
}
LOG_DEBUG << "Original profile content:\n" << profile_original_content;
@ -873,7 +877,7 @@ namespace mamba
<< "#region mamba initialize\n...\n#endregion\n"
<< termcolor::reset;
profile_content = std::regex_replace(profile_content, CONDA_INITIALIZE_PS_RE_BLOCK, "");
profile_content = std::regex_replace(profile_content, MAMBA_INITIALIZE_PS_RE_BLOCK, "");
LOG_DEBUG << "Profile content:\n" << profile_content;
if (Context::instance().dry_run)
@ -1066,4 +1070,74 @@ namespace mamba
deinit_root_prefix(shell, conda_prefix);
}
fs::u8path config_path_for_shell(const std::string& shell)
{
fs::u8path home = env::home_directory();
fs::u8path config_path;
if (shell == "bash")
{
config_path = (on_mac || on_win) ? home / ".bash_profile" : home / ".bashrc";
}
else if (shell == "zsh")
{
config_path = home / ".zshrc";
}
else if (shell == "xonsh")
{
config_path = home / ".xonshrc";
}
else if (shell == "csh")
{
config_path = home / ".tcshrc";
}
else if (shell == "fish")
{
config_path = home / ".config" / "fish" / "config.fish";
}
return config_path;
}
std::vector<std::string> find_initialized_shells()
{
fs::u8path home = env::home_directory();
std::vector<std::string> result;
std::vector<std::string> supported_shells = { "bash", "zsh", "xonsh", "csh", "fish" };
for (const std::string& shell : supported_shells)
{
fs::u8path config_path = config_path_for_shell(shell);
if (fs::exists(config_path))
{
auto contents = read_contents(config_path);
if (contents.find("# >>> mamba initialize >>>") != std::string::npos)
{
result.push_back(shell);
}
}
}
#ifdef _WIN32
// cmd.exe
std::wstring reg = get_autorun_registry_key(L"Software\\Microsoft\\Command Processor");
if (std::regex_match(reg, MAMBA_CMDEXE_HOOK_REGEX) != std::wstring::npos)
{
result.push_back("cmd.exe");
}
#endif
// powershell
{
std::set<std::string> pwsh_profiles;
for (auto& exe : std::vector<std::string>{ "powershell", "pwsh", "pwsh-preview" })
{
auto profile_path = find_powershell_paths(exe);
if (!profile_path.empty() && fs::exists(profile_path))
{
result.push_back("powershell");
}
}
}
return result;
}
}

View File

@ -927,6 +927,13 @@ namespace mamba
Console::stream() << "\nTransaction starting";
fetch_extract_packages();
if (ctx.download_only)
{
Console::stream()
<< "Download only - packages are downloaded and extracted. Skipping the linking phase.";
return true;
}
History::UserRequest ur = History::UserRequest::prefilled();
TransactionRollback rollback;

View File

@ -542,4 +542,25 @@ namespace mamba
return coninfo.srWindow.Bottom - coninfo.srWindow.Top + 1;
#endif
}
void codesign(const fs::u8path& path, bool verbose)
{
reproc::options options;
options.env.behavior = reproc::env::empty;
if (!verbose)
{
reproc::redirect silence;
silence.type = reproc::redirect::discard;
options.redirect.out = silence;
options.redirect.err = silence;
}
const std::vector<std::string> cmd
= { "/usr/bin/codesign", "-s", "-", "-f", path.string() };
auto [status, ec] = reproc::run(cmd, options);
if (ec)
{
throw std::runtime_error(std::string("Could not codesign executable: ") + ec.message());
}
}
}

View File

@ -307,6 +307,14 @@ class Context:
def default_channels(self, arg0: typing.List[str]) -> None:
pass
@property
def download_only(self) -> bool:
"""
:type: bool
"""
@download_only.setter
def download_only(self, arg0: bool) -> None:
pass
@property
def download_threads(self) -> int:
"""
:type: int
@ -830,7 +838,7 @@ class Pool:
def create_whatprovides(self) -> None: ...
def id2pkginfo(self, id: int) -> typing.Optional[PackageInfo]: ...
def matchspec2id(self, ms: str) -> int: ...
def select_solvables(self, id: int) -> typing.List[int]: ...
def select_solvables(self, id: int, sorted: bool = False) -> typing.List[int]: ...
def set_debuglevel(self) -> None: ...
pass

View File

@ -76,7 +76,7 @@ PYBIND11_MODULE(bindings, m)
.def(py::init<>())
.def("set_debuglevel", &MPool::set_debuglevel)
.def("create_whatprovides", &MPool::create_whatprovides)
.def("select_solvables", &MPool::select_solvables, py::arg("id"))
.def("select_solvables", &MPool::select_solvables, py::arg("id"), py::arg("sorted") = false)
.def("matchspec2id", &MPool::matchspec2id, py::arg("ms"))
.def("id2pkginfo", &MPool::id2pkginfo, py::arg("id"));
@ -322,6 +322,7 @@ PYBIND11_MODULE(bindings, m)
.def_readwrite("extract_threads", &Context::extract_threads)
.def_readwrite("always_yes", &Context::always_yes)
.def_readwrite("dry_run", &Context::dry_run)
.def_readwrite("download_only", &Context::download_only)
.def_readwrite("ssl_verify", &Context::ssl_verify)
.def_readwrite("proxy_servers", &Context::proxy_servers)
.def_readwrite("max_retries", &Context::max_retries)

View File

@ -82,6 +82,12 @@ init_general_options(CLI::App* subcom)
subcom->add_flag("--dry-run", dry_run.get_cli_config<bool>(), dry_run.description())
->group(cli_group);
auto& download_only = config.at("download_only");
subcom
->add_flag(
"--download-only", download_only.get_cli_config<bool>(), download_only.description())
->group(cli_group);
auto& experimental = config.at("experimental");
subcom
->add_flag(

View File

@ -48,6 +48,7 @@ init_shell_parser(CLI::App* subcom)
subcom->add_option("action", action.get_cli_config<std::string>(), action.description())
->check(CLI::IsMember(std::vector<std::string>({ "init",
"deinit",
"reinit",
"hook",
"activate",
"deactivate",

View File

@ -52,6 +52,9 @@ set_umamba_command(CLI::App* com)
= com->add_subcommand("update", "Update packages in active environment");
set_update_command(update_subcom);
CLI::App* self_update_subcom = com->add_subcommand("self-update", "Update micromamba");
set_self_update_command(self_update_subcom);
CLI::App* repoquery_subcom = com->add_subcommand(
"repoquery", "Find and analyze packages in active environment or channels");
set_repoquery_command(repoquery_subcom);

View File

@ -55,6 +55,9 @@ set_umamba_command(CLI::App* com);
void
set_update_command(CLI::App* subcom);
void
set_self_update_command(CLI::App* subcom);
void
set_repoquery_command(CLI::App* subcom);

View File

@ -4,14 +4,135 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <termcolor/termcolor.hpp>
#include "common_options.hpp"
#include "version.hpp"
#include "mamba/api/configuration.hpp"
#include "mamba/api/channel_loader.hpp"
#include "mamba/api/shell.hpp"
#include "mamba/api/update.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/transaction.hpp"
#include "mamba/core/util_os.hpp"
using namespace mamba; // NOLINT(build/namespaces)
int
update_self(const std::optional<std::string>& version)
{
auto& config = mamba::Configuration::instance();
auto& ctx = mamba::Context::instance();
config.load();
// set target_prefix to root_prefix (irrelevant, but transaction tries to lock
// the conda-meta folder of the target_prefix)
ctx.target_prefix = ctx.root_prefix;
mamba::MPool pool;
mamba::MultiPackageCache package_caches(ctx.pkgs_dirs);
auto exp_loaded = load_channels(pool, package_caches, 0);
if (!exp_loaded)
{
throw exp_loaded.error();
}
pool.create_whatprovides();
std::string matchspec = version ? fmt::format("micromamba={}", version.value())
: fmt::format("micromamba>{}", umamba::version());
auto solvable_ids = pool.select_solvables(pool.matchspec2id(matchspec), true);
if (solvable_ids.empty())
{
if (pool.select_solvables(pool.matchspec2id("micromamba")).empty())
{
throw mamba::mamba_error(
"No micromamba found in the loaded channels. Add 'conda-forge' to your config file.",
mamba_error_code::selfupdate_failure);
}
else
{
Console::instance().print(fmt::format(
"\nYour micromamba version ({}) is already up to date.", umamba::version()));
return 0;
}
}
std::optional<PackageInfo> latest_micromamba = pool.id2pkginfo(solvable_ids[0]);
if (!latest_micromamba)
{
throw mamba::mamba_error("Could not convert solvable to PackageInfo",
mamba_error_code::internal_failure);
}
Console::instance().stream()
<< termcolor::green
<< fmt::format("\n Installing micromamba version: {} (currently installed {})",
latest_micromamba.value().version,
umamba::version())
<< termcolor::reset;
Console::instance().print(
fmt::format(" Fetching micromamba from {}\n", latest_micromamba.value().url));
ctx.download_only = true;
MTransaction t(pool, { latest_micromamba.value() }, package_caches);
auto exp_prefix_data = PrefixData::create(ctx.root_prefix);
if (!exp_prefix_data)
{
throw exp_prefix_data.error();
}
PrefixData& prefix_data = exp_prefix_data.value();
t.execute(prefix_data);
fs::u8path mamba_exe = get_self_exe_path();
fs::u8path mamba_exe_bkup = mamba_exe;
mamba_exe_bkup.replace_extension(mamba_exe.extension().string() + ".bkup");
fs::u8path cache_path = package_caches.get_extracted_dir_path(latest_micromamba.value())
/ latest_micromamba.value().str();
fs::rename(mamba_exe, mamba_exe_bkup);
try
{
if (on_win)
{
fs::copy_file(cache_path / "Library" / "bin" / "micromamba.exe",
mamba_exe,
fs::copy_options::overwrite_existing);
}
else
{
fs::copy_file(
cache_path / "bin" / "micromamba", mamba_exe, fs::copy_options::overwrite_existing);
#ifdef __APPLE__
codesign(mamba_exe, false);
#endif
fs::remove(mamba_exe_bkup);
}
}
catch (std::exception& e)
{
LOG_ERROR << "Error while updating micromamba: " << e.what();
LOG_ERROR << "Restoring backup";
fs::remove(mamba_exe);
fs::rename(mamba_exe_bkup, mamba_exe);
throw;
}
Console::instance().print("\nReinitializing all previously initialized shells\n");
std::string shell_type = "";
mamba::shell("reinit", shell_type, ctx.root_prefix, false);
return 0;
}
void
set_update_command(CLI::App* subcom)
@ -29,3 +150,16 @@ set_update_command(CLI::App* subcom)
subcom->callback([&]() { update(update_all, prune); });
}
void
set_self_update_command(CLI::App* subcom)
{
Configuration::instance();
init_install_options(subcom);
static std::optional<std::string> version;
subcom->add_option("--version", version, "Install specific micromamba version");
subcom->callback([&]() { return update_self(version); });
}

View File

@ -12,8 +12,8 @@ from . import helpers
def tmp_home(tmp_path: pathlib.Path) -> Generator[pathlib.Path, None, None]:
"""Change the home directory to a tmp folder for the duration of a test."""
# Try multiple combination for Unix/Windows
home_envs = [k for k in ("HOME", "USERPROFILE") if k in os.environ]
old_homes = {name: os.environ[name] for name in home_envs}
home_envs = ["HOME", "USERPROFILE"]
old_homes = {name: os.environ.get(name) for name in home_envs}
if len(home_envs) > 0:
new_home = tmp_path / "home"
@ -22,7 +22,10 @@ def tmp_home(tmp_path: pathlib.Path) -> Generator[pathlib.Path, None, None]:
os.environ[env] = str(new_home)
yield new_home
for env, home in old_homes.items():
os.environ[env] = home
if old_homes[env] is None:
del os.environ[env]
else:
os.environ[env] = home
else:
yield pathlib.Path.home()

View File

@ -2,10 +2,9 @@ import os
import pathlib
import platform
import shutil
import string
import subprocess
import sys
import tempfile
from pathlib import PurePosixPath, PureWindowsPath
import pytest
@ -101,7 +100,7 @@ def write_script(interpreter, lines, path):
possible_interpreters = {
"win": {"powershell", "cmd.exe"},
"win": {"powershell", "cmd.exe", "bash"},
"unix": {"bash", "zsh", "fish", "xonsh", "tcsh"},
}
@ -140,6 +139,13 @@ def find_path_in_str(p, s):
return False
def format_path(p, interpreter):
if plat == "win" and interpreter == "bash":
return str(PurePosixPath(PureWindowsPath(p)))
else:
return str(p)
def call_interpreter(s, tmp_path, interpreter, interactive=False, env=None):
if interactive and interpreter == "powershell":
# "Get-Content -Path $PROFILE.CurrentUserAllHosts | Invoke-Expression"
@ -166,6 +172,8 @@ def call_interpreter(s, tmp_path, interpreter, interactive=False, env=None):
args = ["cmd.exe", "/Q", "/C", f]
elif interpreter == "powershell":
args = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", f]
elif interpreter == "bash" and plat == "win":
args = [os.path.join(os.environ["PROGRAMFILES"], "Git", "bin", "bash.exe"), f]
else:
args = [interpreter, f]
if interactive:
@ -243,6 +251,26 @@ def get_valid_interpreters():
valid_interpreters = get_valid_interpreters()
@pytest.fixture
def backup_umamba():
mamba_exe = get_umamba()
shutil.copyfile(mamba_exe, mamba_exe + ".orig")
yield mamba_exe
shutil.move(mamba_exe + ".orig", mamba_exe)
os.chmod(mamba_exe, 0o755)
def get_self_update_interpreters():
if plat == "win":
return ["cmd.exe", "powershell", "bash"]
if plat == "osx":
return ["zsh", "bash"]
else:
return ["bash"]
def shvar(v, interpreter):
if interpreter in ["bash", "zsh", "xonsh", "fish", "tcsh", "dash"]:
return f"${v}"
@ -286,7 +314,10 @@ class TestActivation:
tmp_path,
interpreter,
):
if interpreter not in valid_interpreters:
# TODO enable these tests also on win + bash!
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -387,7 +418,9 @@ class TestActivation:
tmp_path,
interpreter,
):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -479,7 +512,9 @@ class TestActivation:
tmp_path,
interpreter,
):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -529,7 +564,9 @@ class TestActivation:
def test_env_activation(
self, tmp_home, winreg_value, tmp_root_prefix, tmp_path, interpreter
):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -626,7 +663,9 @@ class TestActivation:
tmp_path,
interpreter,
):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -747,7 +786,9 @@ class TestActivation:
tmp_path,
interpreter,
):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
umamba = get_umamba()
@ -849,7 +890,9 @@ class TestActivation:
@pytest.mark.parametrize("interpreter", get_interpreters())
def test_activate_path(self, tmp_empty_env, tmp_env_name, interpreter, tmp_path):
if interpreter not in valid_interpreters:
if interpreter not in valid_interpreters or (
plat == "win" and interpreter == "bash"
):
pytest.skip(f"{interpreter} not available")
# Activate env name
@ -868,3 +911,67 @@ class TestActivation:
res = shell("activate", prefix_short, "-s", interpreter)
dict_res = self.to_dict(res, interpreter)
assert any([str(tmp_empty_env) in p for p in dict_res.values()])
@pytest.mark.parametrize("interpreter", get_self_update_interpreters())
def test_self_update(
self,
backup_umamba,
tmp_home,
tmp_path,
tmp_root_prefix,
winreg_value,
interpreter,
):
mamba_exe = backup_umamba
shell_init = [
f"{format_path(mamba_exe, interpreter)} shell init -s {interpreter} -p {format_path(tmp_root_prefix, interpreter)}"
]
call_interpreter(shell_init, tmp_path, interpreter)
if interpreter == "bash":
assert (
Path(tmp_root_prefix) / "etc" / "profile.d" / "micromamba.sh"
).exists()
extra_start_code = []
if interpreter == "powershell":
extra_start_code = [
f'$Env:MAMBA_EXE="{mamba_exe}"',
"$MambaModuleArgs = @{ChangePs1 = $True}",
f'Import-Module "{tmp_root_prefix}\\condabin\\Mamba.psm1" -ArgumentList $MambaModuleArgs',
"Remove-Variable MambaModuleArgs",
]
elif interpreter == "bash":
if plat == "linux":
extra_start_code = ["source ~/.bashrc"]
else:
print(mamba_exe)
extra_start_code = [
"source ~/.bash_profile",
"micromamba info",
"echo $MAMBA_ROOT_PREFIX",
"echo $HOME",
"ls ~",
"echo $MAMBA_EXE",
]
elif interpreter == "zsh":
extra_start_code = ["source ~/.zshrc"]
call_interpreter(
extra_start_code
+ ["micromamba self-update --version 0.25.1 -c conda-forge"],
tmp_path,
interpreter,
interactive=False,
)
assert Path(mamba_exe).exists()
version = subprocess.check_output([mamba_exe, "--version"])
assert version.decode("utf8").strip() == "0.25.1"
assert not Path(mamba_exe + ".bkup").exists()
shutil.copyfile(mamba_exe + ".orig", mamba_exe)
os.chmod(mamba_exe, 0o755)

View File

@ -68,7 +68,7 @@ class TestShell:
if shell_type == "powershell":
assert f"$Env:MAMBA_EXE='{mamba_exe}'" in res
elif shell_type in ("zsh", "bash", "posix"):
assert res.count(mamba_exe) == 1
assert res.count(mamba_exe) == 3
elif shell_type == "xonsh":
assert res.count(mamba_exe) == 8
elif shell_type == "fish":