Files
openide/python/python-exec-service/execService.python/src/impl/impl.kt
Ilya.Kazakevich 3388536d2c [python]: PY-85585 : Do not display vens as system pythons in "Add new interpreter" window.
Instead of old `addManuallyAddedInterpreter` we now have two functions: one that requires system python and one that doesn't.

Both functions register system python if provided, but the latter one accepts any python (venv included).

Various "selectors" use these functions.

We also make sure no non-system python is set to `baseInterpreters`: base are always system!

As a bonus, we show "system" or "virtual env" title near interpreter.

It now checks that python is system (see `ensureSystemPython`).

Non-system pythons are never reported, and `registerSystemPython` also returns an error for non-system pythons

We need `execGetBoolFromStdout` for the further changes

Merge-request: IJ-MR-182415
Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com>

(cherry picked from commit 2950f5f0cd2745c12987a92e40774d366568f312)

GitOrigin-RevId: f30e9a6cd7b5d103454d66f26a0c2282d7c587fc
2025-11-26 13:52:46 +00:00

68 lines
3.9 KiB
Kotlin

// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.execService.python.impl
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.stderrString
import com.intellij.platform.eel.provider.utils.stdoutString
import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.impl.transformerToHandler
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
import com.intellij.python.community.execService.python.impl.PyExecPythonBundle.message
import com.jetbrains.python.PYTHON_VERSION_ARG
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.getOr
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.minutes
private const val SYS_MODULE = "sys"
private const val IS_GIL_ENABLED_FUNCTION = "_is_gil_enabled"
private const val GIL_CHECK_CMD = "from __future__ import print_function; import $SYS_MODULE; print($SYS_MODULE.$IS_GIL_ENABLED_FUNCTION()) if hasattr($SYS_MODULE, '$IS_GIL_ENABLED_FUNCTION') and callable(getattr($SYS_MODULE, '$IS_GIL_ENABLED_FUNCTION')) else print(True)"
@ApiStatus.Internal
internal suspend fun ExecService.validatePythonAndGetInfoImpl(python: ExecutablePython): PyResult<PythonInfo> = withContext(Dispatchers.IO) {
val options = ExecOptions(timeout = 1.minutes)
val freeThreaded = !execGetStdoutBoolImpl(python, GIL_CHECK_CMD).getOr(message("python.check.threading.fail")) { return@withContext it }
val versionOutput: EelProcessExecutionResult = executePythonAdvanced(python, options = options, args = Args(PYTHON_VERSION_ARG), processInteractiveHandler = transformerToHandler<EelProcessExecutionResult>(null) { r ->
if (r.exitCode == 0) Result.success(r) else Result.failure(message("python.get.version.error", python.userReadableName, r.exitCode))
}).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", python.userReadableName, versionString))
}
return@withContext Result.success(PythonInfo(languageLevel, freeThreaded))
}
@ApiStatus.Internal
internal suspend fun ExecService.execGetStdoutBoolImpl(python: ExecutablePython, command: @NlsSafe String): PyResult<Boolean> = execGetStdoutImpl(python, command, ZeroCodeStdoutTransformerBool)
@ApiStatus.Internal
internal suspend fun <T : Any> ExecService.execGetStdoutImpl(python: ExecutablePython, command: @NlsSafe String, transformer: ZeroCodeStdoutTransformerTyped<T>): PyResult<T> = withContext(Dispatchers.IO) {
val options = ExecOptions(timeout = 1.minutes)
val result = executePythonAdvanced(
python,
Args("-c", command),
processInteractiveHandler = transformerToHandler(null, transformer),
options = options
).getOr(message("python.cannot.exec", python.userReadableName)) { return@withContext it }
return@withContext Result.success(result)
}
private val ExecutablePython.userReadableName: @NlsSafe String
get() =
(listOf(when (binary) {
is BinOnEel -> binary.path.pathString
is BinOnTarget -> binary
}) + args).joinToString(" ")