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
This commit is contained in:
Ilya.Kazakevich
2024-10-04 00:37:36 +02:00
committed by intellij-monorepo-bot
parent 42f7566596
commit ae6bda01df
3 changed files with 45 additions and 37 deletions

View File

@@ -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

View File

@@ -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<Sdk> {
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

View File

@@ -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()
}
}