Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions micromamba/src/activate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ namespace
}
return "";
}

// When 'activate' / 'deactivate' is run as subprocess, it cannot modify the parent shell.
// In that case, print instructions to initialize the shell and throw an exception to stop
// execution. If the shell is initialized, the 'activate' / 'deactivate' command are intercepted
// by the shell. The shell calls 'shell activate' / 'shell deactivate' instead, in that case the
// code below is never executed.
[[noreturn]] void notify_shell_not_initialized()
{
const std::string guessed_shell = guess_shell();

const std::string message = fmt::format(
"\n'{exe}' is running as a subprocess and can't modify the parent shell.\n"
"Thus you must initialize your shell before using activate and deactivate.\n"
"\n"
"{0}\n"
"To automatically initialize all future ({1}) shells, run:\n"
" $ {exe} shell init --shell {1} --root-prefix=~/.local/share/mamba\n"
"If your shell was already initialized, reinitialize your shell with:\n"
" $ {exe} shell reinit --shell {1}\n"
"Otherwise, this may be an issue. In the meantime you can run commands. See:\n"
" $ {exe} run --help\n"
"\n"
"Supported shells are {{bash, zsh, csh, posix, xonsh, cmd.exe, powershell, fish, nu}}.\n",
get_shell_hook(guessed_shell),
guessed_shell,
fmt::arg("exe", get_self_exe_path().stem().string())
);

std::cout << message;
throw std::runtime_error("Shell not initialized");
}
}

void
Expand All @@ -61,31 +92,11 @@ set_activate_command(CLI::App* subcom)
"Activate the specified environment without first deactivating the current one"
);

subcom->callback(
[&]()
{
const std::string guessed_shell = guess_shell();

const std::string message = fmt::format(
"\n'{exe}' is running as a subprocess and can't modify the parent shell.\n"
"Thus you must initialize your shell before using activate and deactivate.\n"
"\n"
"{0}\n"
"To automatically initialize all future ({1}) shells, run:\n"
" $ {exe} shell init --shell {1} --root-prefix=~/.local/share/mamba\n"
"If your shell was already initialized, reinitialize your shell with:\n"
" $ {exe} shell reinit --shell {1}\n"
"Otherwise, this may be an issue. In the meantime you can run commands. See:\n"
" $ {exe} run --help\n"
"\n"
"Supported shells are {{bash, zsh, csh, posix, xonsh, cmd.exe, powershell, fish, nu}}.\n",
get_shell_hook(guessed_shell),
guessed_shell,
fmt::arg("exe", get_self_exe_path().stem().string())
);
subcom->callback([&]() { notify_shell_not_initialized(); });
}

std::cout << message;
throw std::runtime_error("Shell not initialized");
}
);
void
set_deactivate_command(CLI::App* subcom)
{
subcom->callback([]() { notify_shell_not_initialized(); });
}
3 changes: 3 additions & 0 deletions micromamba/src/umamba.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ set_umamba_command(CLI::App* com, mamba::Configuration& config)
CLI::App* activate_subcom = com->add_subcommand("activate", "Activate an environment");
set_activate_command(activate_subcom);

CLI::App* deactivate_subcom = com->add_subcommand("deactivate", "Deactivate the active environment");
set_deactivate_command(deactivate_subcom);

CLI::App* run_subcom = com->add_subcommand("run", "Run an executable in an environment");
set_run_command(run_subcom, config);

Expand Down
3 changes: 3 additions & 0 deletions micromamba/src/umamba.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ set_env_command(CLI::App* subcom, mamba::Configuration& config);
void
set_activate_command(CLI::App* subcom);

void
set_deactivate_command(CLI::App* subcom);

void
set_run_command(CLI::App* subcom, mamba::Configuration& config);

Expand Down
23 changes: 23 additions & 0 deletions micromamba/tests/test_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,29 @@ def test_activate_target_prefix_checks(tmp_home, tmp_root_prefix):
assert not res["use_root_prefix_fallback"]


@pytest.mark.parametrize(
"command, description",
[
("activate", "Activate an environment"),
("deactivate", "Deactivate the active environment"),
],
)
def test_top_level_activate_deactivate_not_initialized(
tmp_home, tmp_root_prefix, command, description
):
"""Test that top-level activate/deactivate commands are not initialized."""
umamba = helpers.get_umamba()

# Commands must be listed in top-level help.
top_help = helpers.subprocess_run(umamba, "--help").decode()
assert description in top_help

# Commands run directly with no shell function aborts with error message.
with pytest.raises(subprocess.CalledProcessError) as excinfo:
helpers.subprocess_run(umamba, command)
assert "Shell not initialized" in excinfo.value.stderr.decode()


@pytest.mark.parametrize("shell_type", ["bash", "powershell", "cmd.exe"])
@pytest.mark.parametrize("prefix_selector", [None, "prefix"])
@pytest.mark.parametrize("multiple_time,same_prefix", ((False, None), (True, False), (True, True)))
Expand Down
Loading