mirror of https://github.com/mamba-org/mamba.git
add powershell init and activation
This commit is contained in:
parent
110add4c46
commit
f0ac1b2132
|
@ -0,0 +1,301 @@
|
|||
R"MAMBARAW(
|
||||
## ENVIRONMENT MANAGEMENT ######################################################
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Obtains a list of valid conda environments.
|
||||
|
||||
.EXAMPLE
|
||||
Get-CondaEnvironment
|
||||
|
||||
.EXAMPLE
|
||||
genv
|
||||
#>
|
||||
function Get-CondaEnvironment {
|
||||
[CmdletBinding()]
|
||||
param();
|
||||
|
||||
begin {}
|
||||
|
||||
process {
|
||||
# NB: the JSON output of conda env list does not include the names
|
||||
# of each env, so we need to parse the fragile output instead.
|
||||
& $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA env list | `
|
||||
Where-Object { -not $_.StartsWith("#") } | `
|
||||
Where-Object { -not $_.Trim().Length -eq 0 } | `
|
||||
ForEach-Object {
|
||||
$envLine = $_ -split "\s+";
|
||||
$Active = $envLine[1] -eq "*";
|
||||
[PSCustomObject] @{
|
||||
Name = $envLine[0];
|
||||
Active = $Active;
|
||||
Path = if ($Active) {$envLine[2]} else {$envLine[1]};
|
||||
} | Write-Output;
|
||||
}
|
||||
}
|
||||
|
||||
end {}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Adds the entries of sys.prefix to PATH and returns the old PATH.
|
||||
|
||||
.EXAMPLE
|
||||
$OldPath = Add-Sys-Prefix-To-Path
|
||||
#>
|
||||
function Add-Sys-Prefix-To-Path() {
|
||||
$OldPath = $Env:PATH;
|
||||
if ($Env:OS -eq 'Windows_NT') {
|
||||
$Env:PATH = $Env:MAMBA_ROOT_PREFIX + ';' +
|
||||
$Env:MAMBA_ROOT_PREFIX + '\Library\mingw-w64\bin;' +
|
||||
$Env:MAMBA_ROOT_PREFIX + '\Library\usr\bin;' +
|
||||
$Env:MAMBA_ROOT_PREFIX + '\Library\bin;' +
|
||||
$Env:MAMBA_ROOT_PREFIX + '\Scripts;' +
|
||||
$Env:MAMBA_ROOT_PREFIX + '\bin;' + $Env:PATH;
|
||||
} else {
|
||||
$Env:PATH = $Env:MAMBA_ROOT_PREFIX + '/bin:' + $Env:PATH;
|
||||
}
|
||||
return $OldPath;
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Activates a conda environment, placing its commands and packages at
|
||||
the head of $Env:PATH.
|
||||
|
||||
.EXAMPLE
|
||||
Enter-MambaEnvironment base
|
||||
|
||||
.EXAMPLE
|
||||
etenv base
|
||||
|
||||
.NOTES
|
||||
This command does not currently support activating environments stored
|
||||
in a non-standard location.
|
||||
#>
|
||||
function Enter-MambaEnvironment {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$false)][switch]$Stack,
|
||||
[Parameter(Position=0)][string]$Name
|
||||
);
|
||||
|
||||
begin {
|
||||
$OldPath = Add-Sys-Prefix-To-Path;
|
||||
If (-Not $Name) {
|
||||
$Name = "base";
|
||||
}
|
||||
If ($Stack) {
|
||||
$activateCommand = (& $Env:MAMBA_EXE shell activate -s powershell --stack --prefix $Name | Out-String);
|
||||
} Else {
|
||||
$activateCommand = (& $Env:MAMBA_EXE shell activate -s powershell --prefix $Name | Out-String);
|
||||
}
|
||||
$Env:PATH = $OldPath;
|
||||
|
||||
Write-Verbose "[micromamba shell activate --shell powershell --prefix $Name]`n$activateCommand";
|
||||
Invoke-Expression -Command $activateCommand;
|
||||
}
|
||||
|
||||
process {}
|
||||
|
||||
end {}
|
||||
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Deactivates the current conda environment, if any.
|
||||
|
||||
.EXAMPLE
|
||||
Exit-MambaEnvironment
|
||||
|
||||
.EXAMPLE
|
||||
exenv
|
||||
#>
|
||||
function Exit-MambaEnvironment {
|
||||
[CmdletBinding()]
|
||||
param();
|
||||
|
||||
begin {
|
||||
$OldPath = Add-Sys-Prefix-To-Path;
|
||||
$deactivateCommand = (& $Env:MAMBA_EXE shell deactivate -s powershell | Out-String);
|
||||
$Env:PATH = $OldPath;
|
||||
|
||||
# If deactivate returns an empty string, we have nothing more to do,
|
||||
# so return early.
|
||||
if ($deactivateCommand.Trim().Length -eq 0) {
|
||||
return;
|
||||
}
|
||||
Write-Verbose "[micromamba shell deactivate -s powershell]`n$deactivateCommand";
|
||||
Invoke-Expression -Command $deactivateCommand;
|
||||
}
|
||||
process {}
|
||||
end {}
|
||||
}
|
||||
|
||||
## CONDA WRAPPER ###############################################################
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
conda is a tool for managing and deploying applications, environments
|
||||
and packages.
|
||||
|
||||
.PARAMETER Command
|
||||
Subcommand to invoke.
|
||||
|
||||
.EXAMPLE
|
||||
conda install toolz
|
||||
#>
|
||||
function Invoke-Mamba() {
|
||||
# Don't use any explicit args here, we'll use $args and tab completion
|
||||
# so that we can capture everything, INCLUDING short options (e.g. -n).
|
||||
if ($Args.Count -eq 0) {
|
||||
# No args, just call the underlying conda executable.
|
||||
& $Env:MAMBA_EXE;
|
||||
}
|
||||
else {
|
||||
$Command = $Args[0];
|
||||
if ($Args.Count -ge 2) {
|
||||
$OtherArgs = $Args[1..($Args.Count - 1)];
|
||||
} else {
|
||||
$OtherArgs = @();
|
||||
}
|
||||
switch ($Command) {
|
||||
"activate" {
|
||||
Enter-MambaEnvironment @OtherArgs;
|
||||
}
|
||||
"deactivate" {
|
||||
Exit-MambaEnvironment;
|
||||
}
|
||||
|
||||
default {
|
||||
# There may be a command we don't know want to handle
|
||||
# differently in the shell wrapper, pass it through
|
||||
# verbatim.
|
||||
$OldPath = Add-Sys-Prefix-To-Path;
|
||||
& $Env:MAMBA_EXE $Command @OtherArgs;
|
||||
$Env:PATH = $OldPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## TAB COMPLETION ##############################################################
|
||||
# We borrow the approach used by posh-git, in which we override any existing
|
||||
# functions named TabExpansion, look for commands we can complete on, and then
|
||||
# default to the previously defined TabExpansion function for everything else.
|
||||
|
||||
if (Test-Path Function:\TabExpansion) {
|
||||
# Since this technique is common, we encounter an infinite loop if it's
|
||||
# used more than once unless we give our backup a unique name.
|
||||
Rename-Item Function:\TabExpansion CondaTabExpansionBackup
|
||||
}
|
||||
|
||||
function Expand-CondaEnv() {
|
||||
param(
|
||||
[string]
|
||||
$Filter
|
||||
);
|
||||
|
||||
$ValidEnvs = Get-CondaEnvironment;
|
||||
$ValidEnvs `
|
||||
| Where-Object { $_.Name -like "$filter*" } `
|
||||
| ForEach-Object { $_.Name } `
|
||||
| Write-Output;
|
||||
$ValidEnvs `
|
||||
| Where-Object { $_.Path -like "$filter*" } `
|
||||
| ForEach-Object { $_.Path } `
|
||||
| Write-Output;
|
||||
|
||||
}
|
||||
|
||||
function Expand-CondaSubcommands() {
|
||||
param(
|
||||
[string]
|
||||
$Filter
|
||||
);
|
||||
|
||||
$ValidCommands = Invoke-Mamba shell.powershell commands;
|
||||
|
||||
# Add in the commands defined within this wrapper, filter, sort, and return.
|
||||
$ValidCommands + @('activate', 'deactivate') `
|
||||
| Where-Object { $_ -like "$Filter*" } `
|
||||
| Sort-Object `
|
||||
| Write-Output;
|
||||
|
||||
}
|
||||
|
||||
function TabExpansion($line, $lastWord) {
|
||||
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
|
||||
|
||||
switch -regex ($lastBlock) {
|
||||
# Pull out conda commands we recognize first before falling through
|
||||
# to the general patterns for conda itself.
|
||||
"^micromamba activate (.*)" { Expand-CondaEnv $lastWord; break; }
|
||||
"^etenv (.*)" { Expand-CondaEnv $lastWord; break; }
|
||||
|
||||
# If we got down to here, check arguments to conda itself.
|
||||
"^conda (.*)" { Expand-CondaSubcommands $lastWord; break; }
|
||||
|
||||
# Finally, fall back on existing tab expansion.
|
||||
default {
|
||||
if (Test-Path Function:\CondaTabExpansionBackup) {
|
||||
CondaTabExpansionBackup $line $lastWord
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## PROMPT MANAGEMENT ###########################################################
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Modifies the current prompt to show the currently activated conda
|
||||
environment, if any.
|
||||
.EXAMPLE
|
||||
Add-CondaEnvironmentToPrompt
|
||||
|
||||
Causes the current session's prompt to display the currently activated
|
||||
conda environment.
|
||||
#>
|
||||
|
||||
# We use the same procedure to nest prompts as we did for nested tab completion.
|
||||
if (Test-Path Function:\prompt) {
|
||||
Rename-Item Function:\prompt CondaPromptBackup
|
||||
} else {
|
||||
function CondaPromptBackup() {
|
||||
# Restore a basic prompt if the definition is missing.
|
||||
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
|
||||
}
|
||||
}
|
||||
|
||||
function Add-CondaEnvironmentToPrompt() {
|
||||
function global:prompt() {
|
||||
if ($Env:CONDA_PROMPT_MODIFIER) {
|
||||
$Env:CONDA_PROMPT_MODIFIER | Write-Host -NoNewline
|
||||
}
|
||||
CondaPromptBackup;
|
||||
}
|
||||
}
|
||||
|
||||
## ALIASES #####################################################################
|
||||
|
||||
New-Alias micromamba Invoke-Mamba -Force
|
||||
New-Alias genv Get-CondaEnvironment -Force
|
||||
New-Alias etenv Enter-MambaEnvironment -Force
|
||||
New-Alias exenv Exit-MambaEnvironment -Force
|
||||
|
||||
## EXPORTS ###################################################################
|
||||
|
||||
Export-ModuleMember `
|
||||
-Alias * `
|
||||
-Function `
|
||||
Invoke-Mamba, `
|
||||
Get-CondaEnvironment, Add-CondaEnvironmentToPrompt, `
|
||||
Enter-MambaEnvironment, Exit-MambaEnvironment, `
|
||||
prompt
|
||||
|
||||
# We don't export TabExpansion as it's currently not implemented for Micromamba
|
||||
# TabExpansion
|
||||
)MAMBARAW"
|
|
@ -0,0 +1,3 @@
|
|||
R"MAMBARAW(
|
||||
Import-Module "$Env:MAMBA_ROOT_PREFIX\condabin\Mamba.psm1"
|
||||
)MAMBARAW"
|
|
@ -121,6 +121,7 @@ namespace mamba
|
|||
class PowerShellActivator
|
||||
: public Activator
|
||||
{
|
||||
public:
|
||||
PowerShellActivator() = default;
|
||||
virtual ~PowerShellActivator() = default;
|
||||
|
||||
|
|
|
@ -44,6 +44,13 @@ constexpr const char _mamba_activate_bat[] =
|
|||
constexpr const char mamba_hook_bat[] =
|
||||
#include "../data/mamba_hook.bat"
|
||||
;
|
||||
constexpr const char mamba_hook_ps1[] =
|
||||
#include "../data/mamba_hook.ps1"
|
||||
;
|
||||
constexpr const char mamba_psm1[] =
|
||||
#include "../data/Mamba.psm1"
|
||||
;
|
||||
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
@ -145,7 +152,7 @@ namespace mamba
|
|||
"([\\s\\S]*?)"
|
||||
"# <<< mamba initialize <<<(?:\n|\r\n)?");
|
||||
|
||||
static std::regex CONDA_INITIALIZE_PS_RE_BLOCK("^#region mamba initialize(?:\n|\r\n)"
|
||||
static std::regex CONDA_INITIALIZE_PS_RE_BLOCK("#region mamba initialize(?:\n|\r\n)?"
|
||||
"([\\s\\S]*?)"
|
||||
"#endregion(?:\n|\r\n)?");
|
||||
|
||||
|
@ -220,7 +227,7 @@ namespace mamba
|
|||
std::string original_content = rc_content;
|
||||
std::string conda_init_content = rcfile_content(conda_prefix, shell, mamba_exe);
|
||||
|
||||
Console::stream() << "Adding (or replacing) the following in your " <<file_path << " file\n"
|
||||
Console::stream() << "Adding (or replacing) the following in your " << file_path << " file\n"
|
||||
<< termcolor::colorize << termcolor::green << conda_init_content << termcolor::reset;
|
||||
|
||||
std::string result = std::regex_replace(
|
||||
|
@ -289,6 +296,86 @@ namespace mamba
|
|||
std::ofstream mamba_hook_bat_f(root_prefix / "condabin" / "mamba_hook.bat");
|
||||
mamba_hook_bat_f << hook_content;
|
||||
}
|
||||
else if (shell == "powershell")
|
||||
{
|
||||
fs::create_directories(root_prefix / "condabin");
|
||||
std::ofstream mamba_hook_f(root_prefix / "condabin" / "mamba_hook.ps1");
|
||||
mamba_hook_f << mamba_hook_ps1;
|
||||
std::ofstream mamba_psm1_f(root_prefix / "condabin" / "Mamba.psm1");
|
||||
mamba_psm1_f << mamba_psm1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string powershell_contents(const fs::path& conda_prefix)
|
||||
{
|
||||
fs::path self_exe = get_self_exe_path();
|
||||
|
||||
std::stringstream out;
|
||||
|
||||
out << "#region mamba initialize\n";
|
||||
out << "# !! Contents within this block are managed by 'mamba shell init' !!\n";
|
||||
out << "$Env:MAMBA_ROOT_PREFIX = " << conda_prefix << "\n";
|
||||
out << "$Env:MAMBA_EXE = " << self_exe << "\n";
|
||||
out << "(& " << self_exe << " 'shell' 'hook' -s 'powershell' -p " << conda_prefix << ") | Out-String | Invoke-Expression\n";
|
||||
out << "#endregion\n";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool init_powershell(const fs::path& profile_path, const fs::path& conda_prefix, bool reverse=false)
|
||||
{
|
||||
// NB: the user may not have created a profile. We need to check
|
||||
// if the file exists first.
|
||||
std::string profile_content, profile_original_content;
|
||||
if (fs::exists(profile_path))
|
||||
{
|
||||
profile_content = read_contents(profile_path);
|
||||
profile_original_content = profile_content;
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
profile_content = std::regex_replace(
|
||||
profile_content,
|
||||
CONDA_INITIALIZE_PS_RE_BLOCK,
|
||||
""
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// # Find what content we need to add.
|
||||
std::string conda_init_content = powershell_contents(conda_prefix);
|
||||
std::cout << "Adding: \n" << conda_init_content << std::endl;
|
||||
|
||||
Console::stream() << "Adding (or replacing) the following in your " << profile_path << " file\n"
|
||||
<< termcolor::colorize << termcolor::green << conda_init_content << termcolor::reset;
|
||||
|
||||
if (profile_content.find("#region mamba initialize") == profile_content.npos)
|
||||
{
|
||||
profile_content += "\n" + conda_init_content + "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
profile_content = std::regex_replace(
|
||||
profile_content,
|
||||
CONDA_INITIALIZE_PS_RE_BLOCK,
|
||||
conda_init_content
|
||||
);
|
||||
}
|
||||
}
|
||||
if (profile_content != profile_original_content)
|
||||
{
|
||||
if (!Context::instance().dry_run)
|
||||
{
|
||||
if (!fs::exists(profile_path.parent_path()))
|
||||
{
|
||||
fs::create_directories(profile_path.parent_path());
|
||||
}
|
||||
std::ofstream out(profile_path);
|
||||
out << profile_content;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void init_shell(const std::string& shell, const fs::path& conda_prefix)
|
||||
|
@ -314,6 +401,46 @@ namespace mamba
|
|||
init_cmd_exe_registry(L"Software\\Microsoft\\Command Processor", conda_prefix, false);
|
||||
#endif
|
||||
}
|
||||
else if (shell == "powershell")
|
||||
{
|
||||
std::string profile_var("$PROFILE.CurrentUserAllHosts");
|
||||
// if (for_system)
|
||||
// profile = "$PROFILE.AllUsersAllHosts"
|
||||
|
||||
// There's several places PowerShell can store its path, depending
|
||||
// on if it's Windows PowerShell, PowerShell Core on Windows, or
|
||||
// PowerShell Core on macOS/Linux. The easiest way to resolve it is to
|
||||
// just ask different possible installations of PowerShell where their
|
||||
// profiles are.
|
||||
|
||||
auto find_powershell_paths = [&profile_var](const std::string& exe) -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
auto obuf = subprocess::check_output({exe, "-NoProfile", "-Command", profile_var});
|
||||
std::string res(obuf.buf.data());
|
||||
return std::string(strip(res));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
std::string profile_path, exe;
|
||||
for (auto& iter_exe : std::vector<std::string>{"powershell", "pwsh", "pwsh-preview"})
|
||||
{
|
||||
auto res = find_powershell_paths(iter_exe);
|
||||
if (!res.empty())
|
||||
{
|
||||
profile_path = res;
|
||||
exe = iter_exe;
|
||||
}
|
||||
}
|
||||
std::cout << "Found powershell at " << exe << " and user profile at " << profile_path << std::endl;
|
||||
|
||||
init_powershell(profile_path, conda_prefix, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Support for other shells not yet implemented.");
|
||||
|
|
|
@ -263,6 +263,7 @@ namespace mamba
|
|||
// path_list[0:0] = list(self.path_conversion(self._get_path_dirs(prefix)))
|
||||
std::vector<fs::path> final_path = get_path_dirs(prefix);
|
||||
final_path.insert(final_path.end(), path_list.begin(), path_list.end());
|
||||
final_path.erase(std::unique(final_path.begin(), final_path.end()), final_path.end());
|
||||
|
||||
std::string result = join(env::pathsep(), final_path);
|
||||
return result;
|
||||
|
@ -292,11 +293,14 @@ namespace mamba
|
|||
{
|
||||
final_path = get_path_dirs(new_prefix);
|
||||
final_path.insert(final_path.end(), current_path.begin(), current_path.end());
|
||||
// remove duplicates
|
||||
final_path.erase(std::unique(final_path.begin(), final_path.end()), final_path.end());
|
||||
std::string result = join(env::pathsep(), final_path);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_path.erase(std::unique(current_path.begin(), current_path.end()), current_path.end());
|
||||
std::string result = join(env::pathsep(), current_path);
|
||||
return result;
|
||||
}
|
||||
|
@ -831,7 +835,7 @@ namespace mamba
|
|||
|
||||
fs::path PowerShellActivator::hook_source_path()
|
||||
{
|
||||
return Context::instance().root_prefix / "shell" / "condabin" / "mamba-hook.ps1";
|
||||
return Context::instance().root_prefix / "condabin" / "mamba_hook.ps1";
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> PowerShellActivator::update_prompt(const std::string& conda_prompt_modifier)
|
||||
|
@ -855,7 +859,7 @@ namespace mamba
|
|||
|
||||
for (const std::string& uvar : env_transform.unset_vars)
|
||||
{
|
||||
out << "Remove-Item Env:/" << uvar << "=\n";
|
||||
out << "Remove-Item Env:/" << uvar << "\n";
|
||||
}
|
||||
|
||||
for (const auto& [skey, svar] : env_transform.set_vars)
|
||||
|
|
|
@ -152,6 +152,15 @@ void init_shell_parser(CLI::App* subcom)
|
|||
{
|
||||
activator = std::make_unique<mamba::CmdExeActivator>();
|
||||
}
|
||||
else if (shell_options.shell_type == "powershell")
|
||||
{
|
||||
activator = std::make_unique<mamba::PowerShellActivator>();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Currently allowed values are: bash, zsh, cmd.exe & powershell" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (shell_options.action == "init")
|
||||
{
|
||||
init_shell(shell_options.shell_type, shell_options.prefix);
|
||||
|
|
Loading…
Reference in New Issue