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
6 changes: 6 additions & 0 deletions src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ export namespace VenvManagerStrings {

export const installEditable = l10n.t('Install project as editable');
export const searchingDependencies = l10n.t('Searching for dependencies');

export const selectQuickOrCustomize = l10n.t('Select environment creation mode');
export const quickCreate = l10n.t('Quick Create');
export const quickCreateDescription = l10n.t('Create a virtual environment in the workspace root');
export const customize = l10n.t('Custom');
export const customizeDescription = l10n.t('Choose python version, location, packages, name, etc.');
}

export namespace SysManagerStrings {
Expand Down
154 changes: 107 additions & 47 deletions src/managers/builtin/venvUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
import { showErrorMessage } from '../../common/errors/utils';
import { Common, VenvManagerStrings } from '../../common/localize';
import { isUvInstalled, runUV, runPython } from './helpers';
import { getWorkspacePackagesToInstall } from './pipUtils';
import { getProjectInstallable, getWorkspacePackagesToInstall } from './pipUtils';

export const VENV_WORKSPACE_KEY = `${ENVS_EXTENSION_ID}:venv:WORKSPACE_SELECTED`;
export const VENV_GLOBAL_KEY = `${ENVS_EXTENSION_ID}:venv:GLOBAL_SELECTED`;
Expand Down Expand Up @@ -347,6 +347,90 @@ export async function getGlobalVenvLocation(): Promise<Uri | undefined> {
return undefined;
}

async function createWithCustomization(version: string): Promise<boolean | undefined> {
const selection: QuickPickItem | undefined = await showQuickPick(
[
{
label: VenvManagerStrings.quickCreate,
description: VenvManagerStrings.quickCreateDescription,
detail: l10n.t('Uses Python version {0} and installs workspace dependencies.', version),
},
{
label: VenvManagerStrings.customize,
description: VenvManagerStrings.customizeDescription,
},
],
{
placeHolder: VenvManagerStrings.selectQuickOrCustomize,
ignoreFocusOut: true,
},
);

if (selection === undefined) {
return undefined;
} else if (selection.label === VenvManagerStrings.quickCreate) {
return false;
}
return true;
}

async function createWithProgress(
nativeFinder: NativePythonFinder,
api: PythonEnvironmentApi,
log: LogOutputChannel,
manager: EnvironmentManager,
basePython: PythonEnvironment,
venvRoot: Uri,
envPath: string,
packages?: string[],
) {
const pythonPath =
os.platform() === 'win32' ? path.join(envPath, 'Scripts', 'python.exe') : path.join(envPath, 'bin', 'python');

return await withProgress(
{
location: ProgressLocation.Notification,
title: VenvManagerStrings.venvCreating,
},
async () => {
try {
const useUv = await isUvInstalled(log);
if (basePython.execInfo?.run.executable) {
if (useUv) {
await runUV(
['venv', '--verbose', '--seed', '--python', basePython.execInfo?.run.executable, envPath],
venvRoot.fsPath,
log,
);
} else {
await runPython(
basePython.execInfo.run.executable,
['-m', 'venv', envPath],
venvRoot.fsPath,
manager.log,
);
}
if (!(await fsapi.pathExists(pythonPath))) {
log.error('no python executable found in virtual environment');
throw new Error('no python executable found in virtual environment');
}
}

const resolved = await nativeFinder.resolve(pythonPath);
const env = api.createPythonEnvironmentItem(await getPythonInfo(resolved), manager);
if (packages && packages?.length > 0) {
await api.installPackages(env, packages, { upgrade: false });
}
return env;
} catch (e) {
log.error(`Failed to create virtual environment: ${e}`);
showErrorMessage(VenvManagerStrings.venvCreateFailed);
return;
}
},
);
}

export async function createPythonVenv(
nativeFinder: NativePythonFinder,
api: PythonEnvironmentApi,
Expand All @@ -371,7 +455,27 @@ export async function createPythonVenv(
return;
}

const basePython = await pickEnvironmentFrom(sortEnvironments(filtered));
const sortedEnvs = sortEnvironments(filtered);
const project = api.getPythonProject(venvRoot);

const customize = await createWithCustomization(sortedEnvs[0].version);
if (customize === undefined) {
return;
} else if (customize === false) {
const installables = await getProjectInstallable(api, project ? [project] : undefined);
return await createWithProgress(
nativeFinder,
api,
log,
manager,
sortedEnvs[0],
venvRoot,
path.join(venvRoot.fsPath, '.venv'),
installables?.flatMap((i) => i.args ?? []),
);
}

const basePython = await pickEnvironmentFrom(sortedEnvs);
if (!basePython || !basePython.execInfo) {
log.error('No base python selected, cannot create virtual environment.');
return;
Expand All @@ -396,58 +500,14 @@ export async function createPythonVenv(
}

const envPath = path.join(venvRoot.fsPath, name);
const pythonPath =
os.platform() === 'win32' ? path.join(envPath, 'Scripts', 'python.exe') : path.join(envPath, 'bin', 'python');

const project = api.getPythonProject(venvRoot);
const packages = await getWorkspacePackagesToInstall(
api,
{ showSkipOption: true },
project ? [project] : undefined,
);

return await withProgress(
{
location: ProgressLocation.Notification,
title: VenvManagerStrings.venvCreating,
},
async () => {
try {
const useUv = await isUvInstalled(log);
if (basePython.execInfo?.run.executable) {
if (useUv) {
await runUV(
['venv', '--verbose', '--seed', '--python', basePython.execInfo?.run.executable, envPath],
venvRoot.fsPath,
log,
);
} else {
await runPython(
basePython.execInfo.run.executable,
['-m', 'venv', envPath],
venvRoot.fsPath,
manager.log,
);
}
if (!(await fsapi.pathExists(pythonPath))) {
log.error('no python executable found in virtual environment');
throw new Error('no python executable found in virtual environment');
}
}

const resolved = await nativeFinder.resolve(pythonPath);
const env = api.createPythonEnvironmentItem(await getPythonInfo(resolved), manager);
if (packages && packages?.length > 0) {
await api.installPackages(env, packages, { upgrade: false });
}
return env;
} catch (e) {
log.error(`Failed to create virtual environment: ${e}`);
showErrorMessage(VenvManagerStrings.venvCreateFailed);
return;
}
},
);
return await createWithProgress(nativeFinder, api, log, manager, basePython, venvRoot, envPath, packages);
}

export async function removeVenv(environment: PythonEnvironment, log: LogOutputChannel): Promise<boolean> {
Expand Down