From ae6bda01dfddb74fbcd053db36d20a88d5de3c72 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 4 Oct 2024 00:37:36 +0200 Subject: [PATCH] Python: Make `createVirtualenv` `suspend`, process error and cover with tests. We now fail silently in case of any error. This commit fixes it. GitOrigin-RevId: 76977843a892dd450d8292378a7cefb522cb78c1 --- .../messages/PyBundle.properties | 4 +- .../python/sdk/add/v2/presentersExt.kt | 34 ++++++-------- .../jetbrains/python/sdk/add/v2/virtualenv.kt | 44 ++++++++++++------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index 32701c569abf..2071594b28ab 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -572,7 +572,7 @@ remote.interpreter.feature.is.not.available=Remote interpreter feature is not av # What to display of user entered junk commandLine.commandNotFound={0}: command not found -commandLine.directoryCantBeAccessed={0}: bad directory +commandLine.directoryCantBeAccessed={0}: Directory couldn't be found or doesn't contain virtual env # Window with actions # "X" button title @@ -1115,6 +1115,8 @@ pure.python.project=Pure Python project.cannot.be.generated=Project cannot be generated error.in.project.generation=Error in Project Generation +sdk.venv.process=Creating virtual env +sdk.venv.error=Error creating virtual env: {0} sdk.has.been.configured.as.the.project.interpreter={0} has been configured as a project interpreter sdk.has.been.configured.notification.name=Python interpreter configured configuring.interpreters.link=Configure Interpreters diff --git a/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt b/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt index 04d3f020e4b6..3aca46fdc515 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt @@ -2,7 +2,6 @@ package com.jetbrains.python.sdk.add.v2 import com.intellij.execution.ExecutionException -import com.intellij.openapi.application.EDT import com.intellij.openapi.application.writeAction import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.ProjectManager @@ -23,7 +22,6 @@ import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand import com.jetbrains.python.sdk.suggestAssociatedSdkName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.nio.file.InvalidPathException import java.nio.file.Path @@ -31,32 +29,28 @@ import java.nio.file.Path suspend fun PythonMutableTargetAddInterpreterModel.setupVirtualenv(venvPath: Path, projectPath: Path): Result { val baseSdk = state.baseInterpreter.get()!! - val venvPathOnTarget = venvPath.convertToPathOnTarget(targetEnvironmentConfiguration) - val baseSdkPath = when (baseSdk) { - is InstallableSelectableInterpreter -> installBaseSdk(baseSdk.sdk, this.existingSdks)?.homePath // todo handle errors - is ExistingSelectableInterpreter -> baseSdk.sdk.homePath - is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> baseSdk.homePath - } + val baseSdkPath = Path.of(when (baseSdk) { + is InstallableSelectableInterpreter -> installBaseSdk(baseSdk.sdk, this.existingSdks)?.homePath // todo handle errors + is ExistingSelectableInterpreter -> baseSdk.sdk.homePath + is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> baseSdk.homePath + }!!) - withContext(Dispatchers.EDT) { - createVirtualenv(baseSdkPath!!, - venvPathOnTarget, + try { + createVirtualenv(baseSdkPath, + venvPath, projectPath, inheritSitePackages = state.inheritSitePackages.get()) } - - if (targetEnvironmentConfiguration != null) error("Remote targets aren't supported") - val dir = try { - Path.of(venvPathOnTarget) - } - catch (e: InvalidPathException) { + catch (e: ExecutionException) { return Result.failure(e) } - val venvPython = VirtualEnvReader.Instance.findPythonInPythonRoot(dir) + + if (targetEnvironmentConfiguration != null) error("Remote targets aren't supported") + val venvPython = VirtualEnvReader.Instance.findPythonInPythonRoot(venvPath) if (venvPython == null) { - return failure(message("commandLine.directoryCantBeAccessed", venvPathOnTarget)) + return failure(message("commandLine.directoryCantBeAccessed", venvPath)) } val homeFile = try { @@ -69,7 +63,7 @@ suspend fun PythonMutableTargetAddInterpreterModel.setupVirtualenv(venvPath: Pat return Result.failure(e) } if (homeFile == null) { - return failure(message("commandLine.directoryCantBeAccessed", venvPathOnTarget)) + return failure(message("commandLine.directoryCantBeAccessed", venvPath)) } // "suggest name" calls external process and can't be called from EDT diff --git a/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt b/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt index 3221ce34b2ce..64ce273239c2 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt @@ -1,49 +1,54 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.sdk.add.v2 +import com.intellij.execution.ExecutionException import com.intellij.execution.process.CapturingProcessHandler import com.intellij.execution.target.TargetProgressIndicator import com.intellij.execution.target.TargetedCommandLineBuilder +import com.intellij.openapi.application.EDT import com.intellij.platform.ide.progress.ModalTaskOwner -import com.intellij.platform.ide.progress.runWithModalProgressBlocking -import com.intellij.util.concurrency.annotations.RequiresEdt +import com.intellij.platform.ide.progress.TaskCancellation +import com.intellij.platform.ide.progress.withModalProgress +import com.jetbrains.python.PyBundle import com.jetbrains.python.PythonHelper import com.jetbrains.python.run.PythonExecution import com.jetbrains.python.run.prepareHelperScriptExecution import com.jetbrains.python.run.target.HelpersAwareLocalTargetEnvironmentRequest import com.jetbrains.python.sdk.PySdkSettings +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus.Internal import java.nio.file.Path -@RequiresEdt // There is runBlockingModel down the code -fun createVirtualenv( - baseInterpreterPath: String, - venvRoot: String, +/** + * Creates a Python virtual environment, throws [ExecutionException] if creation process failed + */ +@Throws(ExecutionException::class) +@Internal +suspend fun createVirtualenv( + baseInterpreterPath: Path, + venvRoot: Path, projectBasePath: Path, inheritSitePackages: Boolean = false, ) { - // todo find request for targets (need sdk, maybe can work around it ) - //PythonInterpreterTargetEnvironmentFactory.findPythonTargetInterpreter() val request = HelpersAwareLocalTargetEnvironmentRequest() - //val targetRequest = request.targetEnvironmentRequest val execution = prepareHelperScriptExecution(PythonHelper.VIRTUALENV_ZIPAPP, request) // todo what about legacy pythons? if (inheritSitePackages) { execution.addParameter("--system-site-packages") } - execution.addParameter(venvRoot) + execution.addParameter(venvRoot.toString()) request.preparePyCharmHelpers() val targetEnvironment = request.targetEnvironmentRequest.prepareEnvironment(TargetProgressIndicator.EMPTY) - //val targetedCommandLine = execution.buildTargetedCommandLine(targetEnvironment, sdk = null, emptyList()) - val commandLineBuilder = TargetedCommandLineBuilder(targetEnvironment.request) commandLineBuilder.setWorkingDirectory(projectBasePath.toString()) - commandLineBuilder.setExePath(baseInterpreterPath) + commandLineBuilder.setExePath(baseInterpreterPath.toString()) execution.pythonScriptPath?.let { commandLineBuilder.addParameter(it.apply(targetEnvironment)) } ?: throw IllegalArgumentException("Python script path must be set") @@ -72,9 +77,16 @@ fun createVirtualenv( val handler = CapturingProcessHandler(process, targetedCommandLine.charset, targetedCommandLine.getCommandPresentation(targetEnvironment)) - val output = runWithModalProgressBlocking(ModalTaskOwner.guess(), "creating venv") { - handler.runProcess(60 * 1000) + val result = withModalProgress(ModalTaskOwner.guess(), PyBundle.message("sdk.venv.process"), TaskCancellation.nonCancellable()) { + withContext(Dispatchers.IO) { + handler.runProcess() + } + } + if (result.exitCode != 0) { + throw ExecutionException(PyBundle.message("sdk.venv.error", result.stderr)) } - PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseInterpreterPath + withContext(Dispatchers.EDT) { + PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseInterpreterPath.toString() + } } \ No newline at end of file