diff --git a/src/common/localize.ts b/src/common/localize.ts index f9864f7c..57df9447 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -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 { diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index e3460746..a89dcd6e 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -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`; @@ -347,6 +347,90 @@ export async function getGlobalVenvLocation(): Promise { return undefined; } +async function createWithCustomization(version: string): Promise { + 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, @@ -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; @@ -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 {