Skip to content

Commit b68ddee

Browse files
author
Kartik Raj
authored
Support deactivating virtual environments without user intervention (#22405)
Closes #22448 Adds deactivate script to `PATH`
1 parent 8d174a8 commit b68ddee

21 files changed

+359
-680
lines changed

pythonFiles/deactivate.csh

-6
This file was deleted.

pythonFiles/deactivate.fish

-36
This file was deleted.

pythonFiles/deactivate.ps1

-31
This file was deleted.
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Same as deactivate in "<venv>/bin/activate"
2+
deactivate () {
3+
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
4+
PATH="${_OLD_VIRTUAL_PATH:-}"
5+
export PATH
6+
unset _OLD_VIRTUAL_PATH
7+
fi
8+
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
9+
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
10+
export PYTHONHOME
11+
unset _OLD_VIRTUAL_PYTHONHOME
12+
fi
13+
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
14+
hash -r 2> /dev/null
15+
fi
16+
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
17+
PS1="${_OLD_VIRTUAL_PS1:-}"
18+
export PS1
19+
unset _OLD_VIRTUAL_PS1
20+
fi
21+
unset VIRTUAL_ENV
22+
unset VIRTUAL_ENV_PROMPT
23+
if [ ! "${1:-}" = "nondestructive" ] ; then
24+
unset -f deactivate
25+
fi
26+
}
27+
28+
# Get the directory of the current script
29+
SCRIPT_DIR=$(dirname "$0")
30+
# Construct the path to envVars.txt relative to the script directory
31+
ENV_FILE="$SCRIPT_DIR/envVars.txt"
32+
33+
# Read the JSON file and set the variables
34+
TEMP_PS1=$(grep '^PS1=' $ENV_FILE | cut -d '=' -f 2)
35+
TEMP_PATH=$(grep '^PATH=' $ENV_FILE | cut -d '=' -f 2)
36+
TEMP_PYTHONHOME=$(grep '^PYTHONHOME=' $ENV_FILE | cut -d '=' -f 2)
37+
# Initialize the variables required by deactivate function
38+
_OLD_VIRTUAL_PS1="${TEMP_PS1:-}"
39+
_OLD_VIRTUAL_PATH="$TEMP_PATH"
40+
if [ -n "${PYTHONHOME:-}" ] ; then
41+
_OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}"
42+
fi
43+
deactivate
44+
bash
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Same as deactivate in "<venv>/bin/activate"
2+
deactivate () {
3+
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
4+
PATH="${_OLD_VIRTUAL_PATH:-}"
5+
export PATH
6+
unset _OLD_VIRTUAL_PATH
7+
fi
8+
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
9+
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
10+
export PYTHONHOME
11+
unset _OLD_VIRTUAL_PYTHONHOME
12+
fi
13+
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
14+
hash -r 2> /dev/null
15+
fi
16+
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
17+
PS1="${_OLD_VIRTUAL_PS1:-}"
18+
export PS1
19+
unset _OLD_VIRTUAL_PS1
20+
fi
21+
unset VIRTUAL_ENV
22+
unset VIRTUAL_ENV_PROMPT
23+
if [ ! "${1:-}" = "nondestructive" ] ; then
24+
unset -f deactivate
25+
fi
26+
}
27+
28+
# Get the directory of the current script
29+
SCRIPT_DIR=$(dirname "$0")
30+
# Construct the path to envVars.txt relative to the script directory
31+
ENV_FILE="$SCRIPT_DIR/envVars.txt"
32+
33+
# Read the JSON file and set the variables
34+
TEMP_PS1=$(grep '^PS1=' $ENV_FILE | cut -d '=' -f 2)
35+
TEMP_PATH=$(grep '^PATH=' $ENV_FILE | cut -d '=' -f 2)
36+
TEMP_PYTHONHOME=$(grep '^PYTHONHOME=' $ENV_FILE | cut -d '=' -f 2)
37+
# Initialize the variables required by deactivate function
38+
_OLD_VIRTUAL_PS1="${TEMP_PS1:-}"
39+
_OLD_VIRTUAL_PATH="$TEMP_PATH"
40+
if [ -n "${PYTHONHOME:-}" ] ; then
41+
_OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}"
42+
fi
43+
deactivate
44+
fish
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Load dotenv-style file and restore environment variables
2+
Get-Content -Path "$PSScriptRoot\envVars.txt" | ForEach-Object {
3+
# Split each line into key and value at the first '='
4+
$parts = $_ -split '=', 2
5+
if ($parts.Count -eq 2) {
6+
$key = $parts[0].Trim()
7+
$value = $parts[1].Trim()
8+
# Set the environment variable
9+
Set-Item -Path "env:$key" -Value $value
10+
}
11+
}

pythonFiles/deactivate renamed to pythonFiles/deactivate/zsh/deactivate

+14-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,20 @@ deactivate () {
2525
fi
2626
}
2727

28+
# Get the directory of the current script
29+
SCRIPT_DIR=$(dirname "$0")
30+
# Construct the path to envVars.txt relative to the script directory
31+
ENV_FILE="$SCRIPT_DIR/envVars.txt"
32+
33+
# Read the JSON file and set the variables
34+
TEMP_PS1=$(grep '^PS1=' $ENV_FILE | cut -d '=' -f 2)
35+
TEMP_PATH=$(grep '^PATH=' $ENV_FILE | cut -d '=' -f 2)
36+
TEMP_PYTHONHOME=$(grep '^PYTHONHOME=' $ENV_FILE | cut -d '=' -f 2)
2837
# Initialize the variables required by deactivate function
29-
_OLD_VIRTUAL_PS1="${PS1:-}"
30-
_OLD_VIRTUAL_PATH="$PATH"
38+
_OLD_VIRTUAL_PS1="${TEMP_PS1:-}"
39+
_OLD_VIRTUAL_PATH="$TEMP_PATH"
3140
if [ -n "${PYTHONHOME:-}" ] ; then
32-
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
41+
_OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}"
3342
fi
43+
deactivate
44+
zsh

pythonFiles/printEnvVariablesToFile.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
# Licensed under the MIT License.
33

44
import os
5-
import json
65
import sys
76

7+
# Last argument is the target file into which we'll write the env variables line by line.
8+
output_file = sys.argv[-1]
89

9-
# Last argument is the target file into which we'll write the env variables as json.
10-
json_file = sys.argv[-1]
11-
12-
with open(json_file, "w") as outfile:
13-
json.dump(dict(os.environ), outfile)
10+
with open(output_file, "w") as outfile:
11+
for key, val in os.environ.items():
12+
outfile.write(f"{key}={val}\n")

src/client/common/utils/async.ts

+34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable @typescript-eslint/no-use-before-define */
2+
/* eslint-disable no-async-promise-executor */
13
// Copyright (c) Microsoft Corporation. All rights reserved.
24
// Licensed under the MIT License.
35

@@ -228,3 +230,35 @@ export async function flattenIterator<T>(iterator: IAsyncIterator<T>): Promise<T
228230
}
229231
return results;
230232
}
233+
234+
/**
235+
* Wait for a condition to be fulfilled within a timeout.
236+
*
237+
* @export
238+
* @param {() => Promise<boolean>} condition
239+
* @param {number} timeoutMs
240+
* @param {string} errorMessage
241+
* @returns {Promise<void>}
242+
*/
243+
export async function waitForCondition(
244+
condition: () => Promise<boolean>,
245+
timeoutMs: number,
246+
errorMessage: string,
247+
): Promise<void> {
248+
return new Promise<void>(async (resolve, reject) => {
249+
const timeout = setTimeout(() => {
250+
clearTimeout(timeout);
251+
252+
clearTimeout(timer);
253+
reject(new Error(errorMessage));
254+
}, timeoutMs);
255+
const timer = setInterval(async () => {
256+
if (!(await condition().catch(() => false))) {
257+
return;
258+
}
259+
clearTimeout(timeout);
260+
clearTimeout(timer);
261+
resolve();
262+
}, 10);
263+
});
264+
}

src/client/common/utils/localize.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export namespace Interpreters {
197197
export const activatingTerminals = l10n.t('Reactivating terminals...');
198198
export const activateTerminalDescription = l10n.t('Activated environment for');
199199
export const terminalEnvVarCollectionPrompt = l10n.t(
200-
'{0} environment was successfully activated, even though {1} may not be present in the terminal prompt. [Learn more](https://aka.ms/vscodePythonTerminalActivation).',
200+
'{0} environment was successfully activated, even though {1} indicator may not be present in the terminal prompt. [Learn more](https://aka.ms/vscodePythonTerminalActivation).',
201201
);
202202
export const terminalDeactivateProgress = l10n.t('Editing {0}...');
203203
export const restartingTerminal = l10n.t('Restarting terminal and deactivating...');

src/client/interpreter/activation/service.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,15 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
273273
}
274274
return undefined;
275275
}
276-
// Run the activate command collect the environment from it.
277-
const activationCommand = fixActivationCommands(activationCommands).join(' && ');
278-
// In order to make sure we know where the environment output is,
279-
// put in a dummy echo we can look for
280276
const commandSeparator = [TerminalShellType.powershell, TerminalShellType.powershellCore].includes(
281277
shellInfo.shellType,
282278
)
283279
? ';'
284280
: '&&';
281+
// Run the activate command collect the environment from it.
282+
const activationCommand = fixActivationCommands(activationCommands).join(` ${commandSeparator} `);
283+
// In order to make sure we know where the environment output is,
284+
// put in a dummy echo we can look for
285285
command = `${activationCommand} ${commandSeparator} echo '${ENVIRONMENT_PREFIX}' ${commandSeparator} python ${args.join(
286286
' ',
287287
)}`;

0 commit comments

Comments
 (0)