Python: extract python-specific extensions from exec service to simplify API and make it extendable for intepreters.

Use `ExecService` `api.kt` to exec any binary and extensions from `execService.python/api.kt` for python-specific things (i.e helpers)

GitOrigin-RevId: bb217798a9d1ee886c4b12220ec1f66a5ef08336
This commit is contained in:
Ilya.Kazakevich
2025-06-07 05:00:09 +02:00
committed by intellij-monorepo-bot
parent fd2ad62299
commit 2e14347844
55 changed files with 606 additions and 229 deletions

View File

@@ -83,7 +83,3 @@ path.validation.ends.with.whitespace=Path ends with a whitespace
path.validation.file.not.found=File {0} is not found
path.validation.invalid=Path is invalid: {0}
path.validation.inaccessible=Path is inaccessible
python.get.version.error={0} returned error: {1}
python.get.version.too.long={0} took too long
python.get.version.wrong.version={0} has a wrong version: {1}

View File

@@ -1,68 +1,13 @@
package com.jetbrains.python
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.EelPlatform
import com.intellij.platform.eel.ExecuteProcessException
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.exec
import com.intellij.platform.eel.provider.utils.stderrString
import com.intellij.platform.eel.provider.utils.stdoutString
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.PySdkBundle.message
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.PYTHON_VERSION_ARG
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import kotlin.io.path.name
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.seconds
/**
* Ensures that this python is executable and returns its version. Error if python is broken.
*
* Some pythons might be broken: they may be executable, even return a version, but still fail to execute it.
* As we need workable pythons, we validate it by executing
*/
@ApiStatus.Internal
suspend fun PythonBinary.validatePythonAndGetVersion(): PyResult<LanguageLevel> = withContext(Dispatchers.IO) {
val smokeTestOutput = executeWithResult("-c", "print(1)").getOr { return@withContext it }.stdoutString.trim()
if (smokeTestOutput != "1") {
return@withContext PyResult.localizedError(message("python.get.version.error", pathString, smokeTestOutput))
}
val versionOutput = executeWithResult(PYTHON_VERSION_ARG).getOr { return@withContext it }
// Python 2 might return version as stderr, see https://bugs.python.org/issue18338
val versionString = versionOutput.stdoutString.let { it.ifBlank { versionOutput.stderrString } }
val languageLevel = getLanguageLevelFromVersionStringStaticSafe(versionString.trim())
if (languageLevel == null) {
return@withContext PyResult.localizedError(message("python.get.version.wrong.version", pathString, versionOutput))
}
return@withContext Result.success(languageLevel)
}
/**
* Executes [this] with [args], returns either output or error (if execution failed or exit code != 0)
*/
private suspend fun PythonBinary.executeWithResult(vararg args: String): PyResult<@NlsSafe EelProcessExecutionResult> {
try {
val output = exec(*args, timeout = 5.seconds)
return if (output.exitCode != 0) {
PyResult.localizedError(message("python.get.version.error", pathString, "code ${output.exitCode}, ${output.stderrString}"))
}
else {
Result.success(output)
}
} catch (e : ExecuteProcessException) {
return PyResult.localizedError(e.localizedMessage)
}
}
@RequiresBackgroundThread
@ApiStatus.Internal
fun PythonBinary.resolvePythonHome(): PythonHomePath = when (getEelDescriptor().platform) {

View File

@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.jetbrains.python.PythonBinaryKt.PYTHON_VERSION_ARG;
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionError;
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionErrorOnEdt;
import static com.jetbrains.python.venvReader.ResolveUtilKt.tryResolvePath;
@@ -65,12 +66,6 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
private static final Pattern VERSION_RE = Pattern.compile("(Python \\S+).*");
private static final Logger LOG = Logger.getInstance(PythonSdkFlavor.class);
/**
* <code>
* python --version
* </code>
*/
public static final String PYTHON_VERSION_ARG = "--version";
/**