[python] PY-84777 Use system pythons as a fallback for SDK configuration

There was a problem with detecting system-wide pythons, which relied on
binary not being a part of conda env or virtualenv. But it led to
unrelated Hatch and Poetry pythons automatically configured as
interpreters in new projects. Another problem is that free-threaded
python was chosen as default interpreter with highest priority because
of the newest version.

This change uses SystemPythonService to detect system pythons properly,
also free-threaded python used as a default interpreter only if it's the
only available option.

Merge-request: IJ-MR-179008
Merged-by: Alexey Katsman <alexey.katsman@jetbrains.com>

GitOrigin-RevId: 73bc98aed2918c44832b57f22b86c9c7d17a4301
This commit is contained in:
Alexey Katsman
2025-10-21 12:52:02 +00:00
committed by intellij-monorepo-bot
parent 9140cb4e7b
commit a03643bb9c
44 changed files with 304 additions and 272 deletions

View File

@@ -124,7 +124,9 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
return@withContext
}
if (findPreviousUsedSdk(existingSdks, project, module)) {
val systemPythons = findSortedSystemPythons(module)
if (findPreviousUsedSdk(module, existingSdks, systemPythons)) {
return@withContext
}
@@ -132,48 +134,60 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
return@withContext
}
findSystemWideSdk(module, existingSdks, project)
findSystemWideSdk(module, existingSdks, systemPythons)
}
private suspend fun findSystemWideSdk(
module: Module,
existingSdks: List<Sdk>,
project: Project,
systemPythons: List<SystemPython>,
): Unit = reportRawProgress { indicator ->
indicator.text(PyBundle.message("looking.for.system.interpreter"))
thisLogger().debug("Looking for a system-wide interpreter")
val eelDescriptor = module.project.getEelDescriptor()
val homePaths = existingSdks
.mapNotNull { sdk -> sdk.homePath?.let { homePath -> Path.of(homePath) } }
.filter { it.getEelDescriptor() == eelDescriptor }
SystemPythonService().findSystemPythons(eelDescriptor.toEelApi())
.sortedWith(compareByDescending<SystemPython> { it.languageLevel }.thenBy { it.pythonBinary })
.sortedByDescending { it.pythonBinary }
.sortedByDescending { it.languageLevel }
.firstOrNull { it.pythonBinary !in homePaths }?.let {
thisLogger().debug { "Detected system-wide interpreter: $it" }
withContext(Dispatchers.EDT) {
SdkConfigurationUtil.createAndAddSDK(project, it.pythonBinary, PythonSdkType.getInstance())?.apply {
thisLogger().debug { "Created system-wide interpreter: $this" }
setReadyToUseSdk(project, module, this)
}
.mapNotNull { sdk -> sdk.takeIf { !it.isTargetBased() }?.homePath?.let { homePath -> Path.of(homePath) } }
.filter { it.getEelDescriptor() == module.project.getEelDescriptor() }
systemPythons.firstOrNull { it.pythonBinary !in homePaths }?.let {
thisLogger().debug { "Detected system-wide interpreter: $it" }
withContext(Dispatchers.EDT) {
SdkConfigurationUtil.createAndAddSDK(module.project, it.pythonBinary, PythonSdkType.getInstance())?.apply {
thisLogger().debug { "Created system-wide interpreter: $this" }
setReadyToUseSdk(module.project, module, this)
}
}
}
}
private suspend fun findPreviousUsedSdk(
existingSdks: List<Sdk>,
project: Project,
module: Module,
existingSdks: List<Sdk>,
systemPythons: List<SystemPython>,
): Boolean = reportRawProgress { indicator ->
indicator.text(PyBundle.message("looking.for.previous.system.interpreter"))
thisLogger().debug("Looking for the previously used system-wide interpreter")
val sdk = mostPreferred(filterSystemWideSdks(existingSdks)) ?: return@reportRawProgress false
val sdk = systemPythons.firstNotNullOfOrNull { systemPython ->
existingSdks.firstOrNull { sdk ->
val sdkHomePath = sdk.takeIf { !it.isTargetBased() }
?.homePath
?.let { Path.of(it) }
?.takeIf { it.getEelDescriptor() == module.project.getEelDescriptor() }
sdkHomePath == systemPython.pythonBinary
}
} ?: return@reportRawProgress false
thisLogger().debug { "Previously used system-wide interpreter: $sdk" }
setReadyToUseSdk(project, module, sdk)
setReadyToUseSdk(module.project, module, sdk)
return@reportRawProgress true
}
private suspend fun findSortedSystemPythons(module: Module) = reportRawProgress { indicator ->
indicator.text(PyBundle.message("looking.for.system.pythons"))
SystemPythonService().findSystemPythons(module.project.getEelDescriptor().toEelApi())
.sortedWith(
// Free-threaded Python is unstable, we don't want to have it selected by default if we have alternatives
compareBy<SystemPython> { it.pythonInfo.freeThreaded }.thenByDescending { it.pythonInfo.languageLevel }
)
}
private suspend fun findDefaultInterpreter(project: Project, module: Module): Boolean = reportRawProgress { indicator ->
indicator.text(PyBundle.message("looking.for.default.interpreter"))
thisLogger().debug("Looking for the default interpreter setting for a new project")

View File

@@ -5,10 +5,10 @@ import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.python.HelperName
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetInfo
import com.intellij.python.community.interpreters.ValidInterpreter
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
// This in advanced API, most probably you need "api.kt"
@@ -38,14 +38,14 @@ suspend fun <T> ExecService.executeHelperAdvanced(
): PyResult<T> = executeHelperAdvanced(python.asExecutablePython, helper, args, options, procListener, processOutputTransformer)
/**
* Ensures that this python is executable and returns its version. Error if python is broken.
* Ensures that this python is executable and returns its info. 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 ValidInterpreter.validatePythonAndGetVersion(): PyResult<LanguageLevel> =
ExecService().validatePythonAndGetVersion(asExecutablePython)
suspend fun ValidInterpreter.validatePythonAndGetInfo(): PyResult<PythonInfo> =
ExecService().validatePythonAndGetInfo(asExecutablePython)
/**
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).

View File

@@ -9,7 +9,7 @@ import com.intellij.python.community.execService.python.advancedApi.executeHelpe
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
import com.intellij.python.community.interpreters.impl.InterpreterFields
import com.intellij.python.community.interpreters.impl.InterpreterServiceImpl
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
import com.intellij.python.community.services.shared.PythonWithPythonInfo
import com.intellij.python.community.services.shared.PythonWithUi
import com.jetbrains.python.errorProcessing.PyResult
import org.jetbrains.annotations.Nls
@@ -25,7 +25,7 @@ sealed interface Interpreter : InterpreterFields
* Interpreter was usable at the moment it was loaded.
* It has [ui] and [getReadableName] and can be used to execute code against [ExecService] (see extension functions)
*/
interface ValidInterpreter : PythonWithLanguageLevel, PythonWithUi, Interpreter
interface ValidInterpreter : PythonWithPythonInfo, PythonWithUi, Interpreter
/**
* At the moment of loading this interpreter was invalid due to [invalidMessage].

View File

@@ -8,20 +8,20 @@ import com.intellij.python.community.interpreters.InvalidInterpreter
import com.intellij.python.community.interpreters.ValidInterpreter
import com.intellij.python.community.services.shared.PythonWithName
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.sdk.PythonSdkAdditionalData
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.util.*
internal class ValidInterpreterImpl(
override val languageLevel: LanguageLevel,
override val pythonInfo: PythonInfo,
override val asExecutablePython: ExecutablePython,
private val mixin: SdkMixin,
override val ui: PyToolUIInfo?,
) : ValidInterpreter, InterpreterFields by mixin {
override fun toString(): String {
return "ValidInterpreterImpl(languageLevel=$languageLevel, asExecutablePython=$asExecutablePython)"
return "ValidInterpreterImpl(pythonInfo=$pythonInfo, asExecutablePython=$asExecutablePython)"
}
}

View File

@@ -7,20 +7,21 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetInfo
import com.intellij.python.community.interpreters.Interpreter
import com.intellij.python.community.interpreters.InterpreterService
import com.intellij.python.community.interpreters.impl.PyInterpreterBundle.message
import com.intellij.python.community.interpreters.spi.InterpreterProvider
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.PythonSdkAdditionalData
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.legacy.PythonSdkUtil.isPythonSdk
import com.jetbrains.python.sdk.flavors.PyFlavorData
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.legacy.PythonSdkUtil.isPythonSdk
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.Path
@@ -88,11 +89,11 @@ private suspend fun <T : PyFlavorData> createInterpreter(provider: InterpreterPr
}
is Result.Success -> {
val executablePython = r.result
val languageLevel = sdk.versionString?.let { LanguageLevel.fromPythonVersionSafe(it) }
?: ExecService().validatePythonAndGetVersion(executablePython).getOr {
return InvalidInterpreterImpl(SdkMixin(sdk, additionalData), message("py.interpreter.no.version", it.error.message))
}
return ValidInterpreterImpl(languageLevel, executablePython, SdkMixin(sdk, additionalData), provider.ui)
val pythonInfo = sdk.versionString?.let { LanguageLevel.fromPythonVersionSafe(it) }?.let { PythonInfo(it) }
?: ExecService().validatePythonAndGetInfo(executablePython).getOr {
return InvalidInterpreterImpl(SdkMixin(sdk, additionalData), message("py.interpreter.no.version", it.error.message))
}
return ValidInterpreterImpl(pythonInfo, executablePython, SdkMixin(sdk, additionalData), provider.ui)
}
}
}

View File

@@ -0,0 +1,22 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
data class PythonInfo(
val languageLevel: LanguageLevel,
val freeThreaded: Boolean = false,
) : Comparable<PythonInfo> {
override fun compareTo(other: PythonInfo): Int {
// Backward, a newer version has higher priority
val versionComparison = -LanguageLevel.VERSION_COMPARATOR.compare(languageLevel, other.languageLevel)
if (versionComparison != 0) {
return versionComparison
}
// Not free threaded is more stable, hence it has higher priority
return freeThreaded.compareTo(other.freeThreaded)
}
}

View File

@@ -1170,6 +1170,7 @@ install.packages.from.pipfile=Install packages from Pipfile
looking.for.default.interpreter=Looking for the default interpreter setting for a new project
looking.for.previous.system.interpreter=Looking for the previously used system-wide interpreter
looking.for.system.interpreter=Looking for a system-wide interpreter
looking.for.system.pythons=Looking for system pythons
looking.for.shared.conda.environment=Looking for a shared conda environment
current.interpreter=Current Interpreter: {0}
switch.python.interpreter=Switch Python Interpreter

View File

@@ -5,9 +5,9 @@ import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.impl.transformerToHandler
import com.intellij.python.community.execService.python.HelperName
import com.intellij.python.community.execService.python.addHelper
import com.intellij.python.community.execService.python.impl.validatePythonAndGetVersionImpl
import com.intellij.python.community.execService.python.impl.validatePythonAndGetInfoImpl
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
// This in advanced API, most probably you need "api.kt"
@@ -44,11 +44,11 @@ suspend fun <T> ExecService.executeHelperAdvanced(
options, transformerToHandler(procListener, processOutputTransformer))
/**
* Ensures that this python is executable and returns its version. Error if python is broken.
* Ensures that this python is executable and returns its info. 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 ExecService.validatePythonAndGetVersion(python: ExecutablePython): PyResult<LanguageLevel> =
validatePythonAndGetVersionImpl(python)
suspend fun ExecService.validatePythonAndGetInfo(python: ExecutablePython): PyResult<PythonInfo> =
validatePythonAndGetInfoImpl(python)

View File

@@ -4,11 +4,11 @@ package com.intellij.python.community.execService.python
import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetInfo
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
/**
* Python binary itself (i.e python.exe)
@@ -30,17 +30,17 @@ suspend fun ExecService.executeHelper(
executeHelperAdvanced(ExecutablePython.vanillaExecutablePython(python), helper, args, options, procListener, ZeroCodeStdoutTransformer)
/**
* Ensures that this python is executable and returns its version. Error if python is broken.
* Ensures that this python is executable and returns its info. 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
*/
suspend fun ExecService.validatePythonAndGetVersion(python: PythonBinaryOnEelOrTarget): PyResult<LanguageLevel> =
validatePythonAndGetVersion(ExecutablePython.vanillaExecutablePython(python))
suspend fun ExecService.validatePythonAndGetInfo(python: PythonBinaryOnEelOrTarget): PyResult<PythonInfo> =
validatePythonAndGetInfo(ExecutablePython.vanillaExecutablePython(python))
suspend fun PythonBinaryOnEelOrTarget.validatePythonAndGetVersion(): PyResult<LanguageLevel> = ExecService().validatePythonAndGetVersion(this)
suspend fun ExecService.validatePythonAndGetVersion(python: PythonBinary): PyResult<LanguageLevel> = validatePythonAndGetVersion(python.asBinToExec())
suspend fun PythonBinary.validatePythonAndGetVersion(): PyResult<LanguageLevel> = asBinToExec().validatePythonAndGetVersion()
suspend fun PythonBinaryOnEelOrTarget.validatePythonAndGetInfo(): PyResult<PythonInfo> = ExecService().validatePythonAndGetInfo(this)
suspend fun ExecService.validatePythonAndGetInfo(python: PythonBinary): PyResult<PythonInfo> = validatePythonAndGetInfo(python.asBinToExec())
suspend fun PythonBinary.validatePythonAndGetInfo(): PyResult<PythonInfo> = asBinToExec().validatePythonAndGetInfo()
/**

View File

@@ -1,6 +1,7 @@
// 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.diagnostic.fileLogger
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.stderrString
@@ -11,9 +12,11 @@ import com.intellij.python.community.execService.python.advancedApi.ExecutablePy
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.onFailure
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe
import kotlinx.coroutines.Dispatchers
@@ -23,7 +26,7 @@ import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.minutes
@ApiStatus.Internal
internal suspend fun ExecService.validatePythonAndGetVersionImpl(python: ExecutablePython): PyResult<LanguageLevel> = withContext(Dispatchers.IO) {
internal suspend fun ExecService.validatePythonAndGetInfoImpl(python: ExecutablePython): PyResult<PythonInfo> = withContext(Dispatchers.IO) {
val options = ExecOptions(timeout = 1.minutes)
val smokeTestOutput = executePythonAdvanced(python, Args("-c", "print(1)"), processInteractiveHandler = transformerToHandler(null, ZeroCodeStdoutTransformer), options = options).getOr(message("python.cannot.exec", python.userReadableName)) { return@withContext it }.trim()
@@ -31,16 +34,28 @@ internal suspend fun ExecService.validatePythonAndGetVersionImpl(python: Executa
return@withContext PyResult.localizedError(message("python.get.version.error", python.userReadableName, smokeTestOutput))
}
val versionOutput: EelProcessExecutionResult = executePythonAdvanced(python, options = options, args = Args(PYTHON_VERSION_ARG), processInteractiveHandler = transformerToHandler<EelProcessExecutionResult>(null, { r ->
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 }
}).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(languageLevel)
val freeThreaded = if (languageLevel.isAtLeast(LanguageLevel.PYTHON313)) {
val gilEnabledOutput = executePythonAdvanced(
python,
Args("-c", "import sys; print(sys._is_gil_enabled())"),
processInteractiveHandler = transformerToHandler(null, ZeroCodeStdoutTransformer),
options = options
).onFailure { fileLogger().warn(it.toString()) }.successOrNull?.trim()
gilEnabledOutput == "False"
}
else false
return@withContext Result.success(PythonInfo(languageLevel, freeThreaded))
}
private val ExecutablePython.userReadableName: @NlsSafe String

View File

@@ -7,7 +7,7 @@ import com.intellij.platform.testFramework.junit5.eel.params.api.*
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.asBinToExec
import com.intellij.python.community.execService.python.executeHelper
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
@@ -53,7 +53,7 @@ class HelpersShowCaseTest() {
val output = ExecService().executeHelper(python.asBinToExec(), helper.name, listOf("--version")).orThrow().trim()
Assertions.assertEquals(hello, output, "wrong helper output")
val langLevel = ExecService().validatePythonAndGetVersion(python).getOrThrow()
val langLevel = ExecService().validatePythonAndGetInfo(python).getOrThrow().languageLevel
Assertions.assertTrue(langLevel.isPy3K, "Wrong lang level:$langLevel")
}
finally {

View File

@@ -1,7 +1,7 @@
// 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.junit5Tests.env
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
import com.intellij.python.junit5Tests.randomBinary
@@ -15,13 +15,13 @@ import org.junit.jupiter.api.Test
class PythonBinaryValidationTest {
@Test
fun sunnyDayTest(@PythonBinaryPath python: PythonBinary): Unit = runBlocking {
val level = python.validatePythonAndGetVersion().orThrow()
Assertions.assertNotNull(level, "Failed to get python level")
val info = python.validatePythonAndGetInfo().orThrow()
Assertions.assertNotNull(info, "Failed to get python info")
}
@Test
fun rainyDayTest(): Unit = runBlocking {
when (val r = randomBinary.validatePythonAndGetVersion()) {
when (val r = randomBinary.validatePythonAndGetInfo()) {
is Result.Success -> {
Assertions.fail("${randomBinary} isn't a python, should fail, but got ${r.result}")
}

View File

@@ -6,7 +6,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.community.services.systemPython.createVenvFromSystemPython
import com.intellij.python.featuresTrainer.ift.PythonLangSupport
@@ -86,7 +86,7 @@ class PythonLangSupportTest {
val sdk = project.pythonSdk!!
try {
val pythonBinary = Path.of(sdk.homePath!!)
Assertions.assertTrue(VanillaPythonWithLanguageLevelImpl.createByPythonBinary(pythonBinary).orThrow().languageLevel.isPy3K, "Sdk is broken")
Assertions.assertTrue(VanillaPythonWithPythonInfoImpl.createByPythonBinary(pythonBinary).orThrow().pythonInfo.languageLevel.isPy3K, "Sdk is broken")
}
finally {
writeAction {

View File

@@ -3,10 +3,13 @@ package com.intellij.python.community.impl.venv
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.BinaryToExec
import com.intellij.python.community.execService.ExecOptions
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.asBinToExec
import com.intellij.python.community.execService.python.HelperName
import com.intellij.python.community.execService.python.executeHelper
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
@@ -66,7 +69,7 @@ suspend fun createVenv(
}
add(venvDir)
}
val version = python.validatePythonAndGetVersion().getOr(PyVenvBundle.message("py.venv.error.cant.base.version")) { return it }
val version = python.validatePythonAndGetInfo().getOr(PyVenvBundle.message("py.venv.error.cant.base.version")) { return it }.languageLevel
val helper = if (version.isAtLeast(LanguageLevel.PYTHON38)) VIRTUALENV_ZIPAPP_NAME else LEGACY_VIRTUALENV_ZIPAPP_NAME
execService.executeHelper(python, helper, args, ExecOptions(timeout = 3.minutes))
.getOr(PyVenvBundle.message("py.venv.error.executing.script", helper)) { return it }

View File

@@ -4,16 +4,16 @@ package com.intellij.python.community.services.internal.impl
import com.intellij.platform.eel.EelPlatform
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl.Companion.concurrentLimit
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl.Companion.createByPythonBinary
import com.intellij.python.community.services.shared.LanguageLevelComparator
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl.Companion.concurrentLimit
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl.Companion.createByPythonBinary
import com.intellij.python.community.services.shared.PythonInfoComparator
import com.intellij.python.community.services.shared.PythonWithPythonInfo
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -25,10 +25,10 @@ import kotlin.io.path.pathString
import kotlin.io.path.relativeTo
@Internal
class VanillaPythonWithLanguageLevelImpl internal constructor(
class VanillaPythonWithPythonInfoImpl internal constructor(
override val pythonBinary: PythonBinary,
override val languageLevel: LanguageLevel,
) : VanillaPythonWithLanguageLevel, Comparable<PythonWithLanguageLevel> {
override val pythonInfo: PythonInfo,
) : VanillaPythonWithPythonInfo, Comparable<PythonWithPythonInfo> {
companion object {
@@ -38,7 +38,7 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
* Like [createByPythonBinary] but runs in parallel up to [concurrentLimit]
* @return python path -> python with language level sorted from highest to lowest.
*/
suspend fun createByPythonBinaries(pythonBinaries: Collection<PythonBinary>): Collection<Pair<PythonBinary, PyResult<VanillaPythonWithLanguageLevel>>> =
suspend fun createByPythonBinaries(pythonBinaries: Collection<PythonBinary>): Collection<Pair<PythonBinary, PyResult<VanillaPythonWithPythonInfo>>> =
coroutineScope {
pythonBinaries.map {
async {
@@ -49,9 +49,9 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
}.awaitAll()
}.sortedBy { it.first }
suspend fun createByPythonBinary(pythonBinary: PythonBinary): PyResult<VanillaPythonWithLanguageLevelImpl> {
val languageLevel = pythonBinary.validatePythonAndGetVersion().getOr { return it }
return Result.success(VanillaPythonWithLanguageLevelImpl(pythonBinary, languageLevel))
suspend fun createByPythonBinary(pythonBinary: PythonBinary): PyResult<VanillaPythonWithPythonInfoImpl> {
val pythonInfo = pythonBinary.validatePythonAndGetInfo().getOr { return it }
return Result.success(VanillaPythonWithPythonInfoImpl(pythonBinary, pythonInfo))
}
}
@@ -59,7 +59,7 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as VanillaPythonWithLanguageLevelImpl
other as VanillaPythonWithPythonInfoImpl
return pythonBinary == other.pythonBinary
}
@@ -69,7 +69,7 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
}
override fun toString(): String {
return "$pythonBinary ($languageLevel)"
return "$pythonBinary ($pythonInfo)"
}
override suspend fun getReadableName(): @Nls String {
@@ -81,8 +81,8 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
}
val pythonString = (if (pythonBinary.startsWith(home)) "~$separator" + pythonBinary.relativeTo(home).pathString
else pythonBinary.pathString)
return "$pythonString ($languageLevel)"
return "$pythonString ($pythonInfo)"
}
override fun compareTo(other: PythonWithLanguageLevel): Int = LanguageLevelComparator.compare(this, other)
override fun compareTo(other: PythonWithPythonInfo): Int = PythonInfoComparator.compare(this, other)
}

View File

@@ -1,7 +1,7 @@
// 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.junit5Tests.env.services.internal.impl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
import com.intellij.python.junit5Tests.randomBinary
@@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test
class PythonWithLanguageLevelImplTest {
@Test
fun testRainyDay(): Unit = runBlocking {
when (val r = VanillaPythonWithLanguageLevelImpl.createByPythonBinary(randomBinary)) {
when (val r = VanillaPythonWithPythonInfoImpl.createByPythonBinary(randomBinary)) {
is Result.Failure -> Unit
is Result.Success -> fail("Unexpected success ${r.result}")
}
@@ -23,8 +23,8 @@ class PythonWithLanguageLevelImplTest {
@Test
fun testSunnyDay(@PythonBinaryPath pythonBinary: PythonBinary): Unit = runBlocking {
val python = VanillaPythonWithLanguageLevelImpl.createByPythonBinary(pythonBinary).orThrow()
val python = VanillaPythonWithPythonInfoImpl.createByPythonBinary(pythonBinary).orThrow()
assertEquals(pythonBinary, python.pythonBinary, "Wrong python binary")
assertTrue(python.languageLevel.isPy3K, "Wrong python version")
assertTrue(python.pythonInfo.languageLevel.isPy3K, "Wrong python version")
}
}

View File

@@ -1,8 +1,9 @@
// 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.junit5Tests.unit
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.testFramework.junit5.TestApplication
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.psi.LanguageLevel
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Test
@@ -14,12 +15,12 @@ class CompareByLanguageLevelTest {
@Test
fun testCompareByLanguageLevel(@TempDir path: Path) {
val list = listOf(
VanillaPythonWithLanguageLevelImpl(path, LanguageLevel.PYTHON38),
VanillaPythonWithLanguageLevelImpl(path, LanguageLevel.PYTHON312),
VanillaPythonWithLanguageLevelImpl(path, LanguageLevel.PYTHON311),
VanillaPythonWithPythonInfoImpl(path, PythonInfo(LanguageLevel.PYTHON38)),
VanillaPythonWithPythonInfoImpl(path, PythonInfo(LanguageLevel.PYTHON312)),
VanillaPythonWithPythonInfoImpl(path, PythonInfo(LanguageLevel.PYTHON311)),
)
val sortedLevels = list.sorted().map { it.languageLevel }.toTypedArray()
val sortedLevels = list.sorted().map { it.pythonInfo.languageLevel }.toTypedArray()
assertArrayEquals(arrayOf(LanguageLevel.PYTHON312, LanguageLevel.PYTHON311, LanguageLevel.PYTHON38), sortedLevels,
"Highest python goes first")
}

View File

@@ -1,8 +1,9 @@
// 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.junit5Tests.unit.alsoWin.services.internal.impl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.testFramework.junit5.TestApplication
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.psi.LanguageLevel
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.allOf
@@ -23,7 +24,7 @@ class ReadableNameTest {
@Test
fun testNoHomePath(@TempDir path: Path): Unit = runBlocking {
val fakePython = path.resolve(PYTHON_FILE_NAME)
val name = VanillaPythonWithLanguageLevelImpl(fakePython, LanguageLevel.PYTHON312).getReadableName()
val name = VanillaPythonWithPythonInfoImpl(fakePython, PythonInfo(LanguageLevel.PYTHON312)).getReadableName()
assertThat("Wrong name generated", name, allOf(containsString("3.12"), containsString(fakePython.pathString)))
}
@@ -33,11 +34,11 @@ class ReadableNameTest {
var fakePython = home.resolve(PYTHON_FILE_NAME)
var name = VanillaPythonWithLanguageLevelImpl(fakePython, LanguageLevel.PYTHON312).getReadableName()
var name = VanillaPythonWithPythonInfoImpl(fakePython, PythonInfo(LanguageLevel.PYTHON312)).getReadableName()
assertThat("Wrong name generated", name, allOf(containsString("3.12"), matchesPattern(".*~[\\\\/]$PYTHON_FILE_NAME.*")))
fakePython = home.resolve("deep").resolve(PYTHON_FILE_NAME)
name = VanillaPythonWithLanguageLevelImpl(fakePython, LanguageLevel.PYTHON312).getReadableName()
name = VanillaPythonWithPythonInfoImpl(fakePython, PythonInfo(LanguageLevel.PYTHON312)).getReadableName()
assertThat("Wrong name generated", name, allOf(containsString("3.12"), matchesPattern(".*~[\\\\/]deep[\\\\/]$PYTHON_FILE_NAME.*")))
}
}

View File

@@ -1,11 +1,11 @@
// 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.services.shared
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.PythonInfo
/**
* Something with language level
* Something with python info
*/
interface LanguageLevelHolder {
val languageLevel: LanguageLevel
interface PythonInfoHolder {
val pythonInfo: PythonInfo
}

View File

@@ -4,9 +4,9 @@ package com.intellij.python.community.services.shared
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
/**
* Python (vanilla, conda, whatever) with known language level.
* Python (vanilla, conda, whatever) with known python info.
*/
interface PythonWithLanguageLevel : PythonWithName, LanguageLevelHolder {
interface PythonWithPythonInfo : PythonWithName, PythonInfoHolder {
/**
* Convert python to something that can be executed on [java.util.concurrent.ExecutorService]

View File

@@ -2,6 +2,6 @@
package com.intellij.python.community.services.shared
/**
* Python that has both [languageLevel] and [ui]
* Python that has both [pythonInfo] and [ui]
*/
interface PythonWithUi : PythonWithLanguageLevel, UiHolder
interface PythonWithUi : PythonWithPythonInfo, UiHolder

View File

@@ -8,7 +8,7 @@ import com.jetbrains.python.PythonBinary
/**
* Vanilla (not conda) has [pythonBinary]
*/
interface VanillaPythonWithLanguageLevel : PythonWithLanguageLevel {
interface VanillaPythonWithPythonInfo : PythonWithPythonInfo {
val pythonBinary: PythonBinary
override val asExecutablePython: ExecutablePython get() = ExecutablePython.vanillaExecutablePython(pythonBinary)

View File

@@ -3,22 +3,11 @@ package com.intellij.python.community.services.shared
import com.intellij.openapi.diagnostic.fileLogger
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.psi.LanguageLevel
import java.util.*
private val logger = fileLogger()
object LanguageLevelComparator : Comparator<LanguageLevelHolder> {
override fun compare(o1: LanguageLevelHolder, o2: LanguageLevelHolder): Int {
// Backward: first python is the highest
if (logger.isDebugEnabled) {
logger.debug("langLevel ${o1.languageLevel} vs ${o2.languageLevel}")
}
return LanguageLevel.VERSION_COMPARATOR.compare(o1.languageLevel, o2.languageLevel) * -1
}
}
object UiComparator : Comparator<UiHolder> {
override fun compare(o1: UiHolder, o2: UiHolder): Int {
if (logger.isDebugEnabled) {
@@ -28,13 +17,24 @@ object UiComparator : Comparator<UiHolder> {
}
}
class LanguageLevelWithUiComparator<T> : Comparator<T> where T : LanguageLevelHolder, T : UiHolder {
object PythonInfoComparator : Comparator<PythonInfoHolder> {
override fun compare(o1: PythonInfoHolder, o2: PythonInfoHolder): Int {
// Backward: first python is the highest
if (logger.isDebugEnabled) {
logger.debug("pythonInfo ${o1.pythonInfo} vs ${o2.pythonInfo}")
}
return o1.pythonInfo.compareTo(o2.pythonInfo)
}
}
class PythonInfoWithUiComparator<T> : Comparator<T> where T : PythonInfoHolder, T : UiHolder {
override fun compare(o1: T, o2: T): Int {
if (logger.isDebugEnabled) {
logger.debug("full ${o1.string()} vs ${o2.string()}")
}
return LanguageLevelComparator.compare(o1, o2) * 10 + UiComparator.compare(o1, o2)
return PythonInfoComparator.compare(o1, o2) * 10 + UiComparator.compare(o1, o2)
}
}
private fun <T> T.string(): String where T : LanguageLevelHolder, T : UiHolder = "($languageLevel,${ui?.toolName})"
private fun <T> T.string(): String where T : PythonInfoHolder, T : UiHolder =
"(${pythonInfo.languageLevel},${ui?.toolName},free-threaded:${pythonInfo.freeThreaded})"

View File

@@ -1,10 +1,11 @@
// 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.junit5Tests.unit.comparators
import com.intellij.python.community.services.shared.LanguageLevelHolder
import com.intellij.python.community.services.shared.LanguageLevelWithUiComparator
import com.jetbrains.python.PyToolUIInfo
import com.intellij.python.community.services.shared.PythonInfoHolder
import com.intellij.python.community.services.shared.PythonInfoWithUiComparator
import com.intellij.python.community.services.shared.UiHolder
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.psi.LanguageLevel
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
@@ -16,29 +17,33 @@ class ComparatorsTest {
@Test
fun testComparators() {
val mocks = arrayOf(
MockLevel(LanguageLevel.PYTHON314),
MockLevel(LanguageLevel.PYTHON310),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("A")),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("Z")),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("B")),
MockLevel(LanguageLevel.PYTHON27),
MockLevel(LanguageLevel.PYTHON313),
MockInfo(PythonInfo(LanguageLevel.PYTHON314)),
MockInfo(PythonInfo(LanguageLevel.PYTHON314, true)),
MockInfo(PythonInfo(LanguageLevel.PYTHON310)),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("A")),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("Z")),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("B")),
MockInfo(PythonInfo(LanguageLevel.PYTHON27)),
MockInfo(PythonInfo(LanguageLevel.PYTHON313, true)),
MockInfo(PythonInfo(LanguageLevel.PYTHON313)),
)
val set = TreeSet(LanguageLevelWithUiComparator<MockLevel>())
val set = TreeSet(PythonInfoWithUiComparator<MockInfo>())
set.addAll(mocks)
MatcherAssert.assertThat("", set, Matchers.contains(
MockLevel(LanguageLevel.PYTHON314),
MockLevel(LanguageLevel.PYTHON313),
MockLevel(LanguageLevel.PYTHON310),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("A")),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("B")),
MockLevel(LanguageLevel.PYTHON310, ui = PyToolUIInfo("Z")),
MockLevel(LanguageLevel.PYTHON27)
MockInfo(PythonInfo(LanguageLevel.PYTHON314)),
MockInfo(PythonInfo(LanguageLevel.PYTHON314, true)),
MockInfo(PythonInfo(LanguageLevel.PYTHON313)),
MockInfo(PythonInfo(LanguageLevel.PYTHON313, true)),
MockInfo(PythonInfo(LanguageLevel.PYTHON310)),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("A")),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("B")),
MockInfo(PythonInfo(LanguageLevel.PYTHON310), ui = PyToolUIInfo("Z")),
MockInfo(PythonInfo(LanguageLevel.PYTHON27))
))
}
}
private data class MockLevel(
override val languageLevel: LanguageLevel,
private data class MockInfo(
override val pythonInfo: PythonInfo,
override val ui: PyToolUIInfo? = null,
) : LanguageLevelHolder, UiHolder
) : PythonInfoHolder, UiHolder

View File

@@ -6,9 +6,9 @@ import com.intellij.openapi.components.service
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.impl.venv.createVenv
import com.intellij.python.community.services.shared.LanguageLevelWithUiComparator
import com.intellij.python.community.services.shared.PythonInfoWithUiComparator
import com.intellij.python.community.services.shared.PythonWithUi
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
@@ -56,10 +56,10 @@ fun SystemPythonService(): SystemPythonService = ApplicationManager.getApplicati
*
* Instances could be obtained with [SystemPythonService]
*/
class SystemPython internal constructor(private val delegate: VanillaPythonWithLanguageLevel, override val ui: PyToolUIInfo?) : VanillaPythonWithLanguageLevel by delegate, PythonWithUi, Comparable<SystemPython> {
class SystemPython internal constructor(private val delegate: VanillaPythonWithPythonInfo, override val ui: PyToolUIInfo?) : VanillaPythonWithPythonInfo by delegate, PythonWithUi, Comparable<SystemPython> {
private companion object {
val comparator = LanguageLevelWithUiComparator<SystemPython>()
val comparator = PythonInfoWithUiComparator<SystemPython>()
}
override fun equals(other: Any?): Boolean {

View File

@@ -11,12 +11,12 @@ import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.impl.installer.PySdkToInstallManager
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.jetbrains.python.PyToolUIInfo
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.python.community.services.systemPython.SystemPythonServiceImpl.MyServiceState
import com.intellij.python.community.services.systemPython.impl.Cache
import com.intellij.python.community.services.systemPython.impl.PySystemPythonBundle
import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
@@ -66,7 +66,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
}
override suspend fun registerSystemPython(pythonPath: PythonBinary): PyResult<SystemPython> {
val pythonWithLangLevel = VanillaPythonWithLanguageLevelImpl.createByPythonBinary(pythonPath)
val pythonWithLangLevel = VanillaPythonWithPythonInfoImpl.createByPythonBinary(pythonPath)
.getOr(PySystemPythonBundle.message("py.system.python.service.python.is.broken", pythonPath)) { return it }
val systemPython = SystemPython(pythonWithLangLevel, null)
state.userProvidedPythons.add(pythonPath.pathString)
@@ -124,7 +124,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
val badPythons = mutableSetOf<PythonBinary>()
val pythons = pythonsFromExtensions + state.userProvidedPythonsAsPath.filter { it.getEelDescriptor() == eelApi.descriptor }
val result = VanillaPythonWithLanguageLevelImpl.createByPythonBinaries(pythons.toSet())
val result = VanillaPythonWithPythonInfoImpl.createByPythonBinaries(pythons.toSet())
.mapNotNull { (python, r) ->
when (r) {
is Result.Success -> SystemPython(r.result, pythonsUi[r.result.pythonBinary])
@@ -159,4 +159,4 @@ private object LocalPythonInstaller : PythonInstallerService {
}
return Result.Companion.success(Unit)
}
}
}

View File

@@ -13,7 +13,8 @@ class Py27Test {
@Test
fun testPy27(): Unit = timeoutRunBlocking {
val testEnvironments = SystemPythonService().findSystemPythons()
val python27 = testEnvironments.firstOrNull { it.languageLevel == LanguageLevel.PYTHON27 } ?: error("No 2.7 found in $testEnvironments")
val python27 = testEnvironments.firstOrNull { it.pythonInfo.languageLevel == LanguageLevel.PYTHON27 }
?: error("No 2.7 found in $testEnvironments")
SystemPythonService().registerSystemPython(python27.pythonBinary).getOrThrow()
}
}

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.ExecuteProcessException
import com.intellij.platform.eel.ThrowsChecked
import com.intellij.platform.eel.provider.getEelDescriptor
@@ -23,8 +24,11 @@ import com.intellij.testFramework.common.timeoutRunBlocking
import com.intellij.testFramework.junit5.RegistryKey
import com.intellij.testFramework.junit5.TestDisposable
import com.intellij.testFramework.registerExtension
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.venvReader.VirtualEnvReader
@@ -35,10 +39,6 @@ import org.hamcrest.Matchers.not
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import com.intellij.platform.eel.EelApi
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import java.nio.file.Path
import kotlin.io.path.deleteExisting
import kotlin.io.path.pathString
@@ -55,11 +55,11 @@ class SystemPythonServiceShowCaseTest {
val eelApi = systemPython.pythonBinary.getEelDescriptor().toEelApi()
val process = eelApi.exec.spawnProcess(systemPython.pythonBinary.pathString, "--version").eelIt()
val output = async {
(if (systemPython.languageLevel.isPy3K) process.stdout else process.stderr).readWholeText()
(if (systemPython.pythonInfo.languageLevel.isPy3K) process.stdout else process.stderr).readWholeText()
}
Assertions.assertTrue(process.exitCode.await() == 0)
val versionString = PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe(output.await())!!
Assertions.assertEquals(systemPython.languageLevel, versionString, "Wrong version")
Assertions.assertEquals(systemPython.pythonInfo.languageLevel, versionString, "Wrong version")
}
}

View File

@@ -3,8 +3,8 @@ package com.intellij.python.junit5Tests.env.systemPython.impl
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.platform.eel.EelApi
import com.intellij.python.community.impl.venv.createVenv
import com.jetbrains.python.PyToolUIInfo
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
@@ -12,6 +12,7 @@ import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
import com.intellij.testFramework.common.timeoutRunBlocking
import com.intellij.testFramework.junit5.TestDisposable
import com.intellij.testFramework.registerExtension
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.getOrThrow
@@ -20,7 +21,6 @@ import org.hamcrest.Matchers
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import com.intellij.platform.eel.EelApi
import java.nio.file.Path
@PyEnvTestCase
@@ -35,7 +35,7 @@ class EnvProviderTest {
if (systemPythons.size > 1) {
val best = systemPythons.first()
for (python in systemPythons.subList(1, systemPythonBinaries.size)) {
assertTrue(python.languageLevel <= best.languageLevel, "$best is the first, bust worse than $python")
assertTrue(python.pythonInfo.languageLevel <= best.pythonInfo.languageLevel, "$best is the first, bust worse than $python")
}
}
}
@@ -56,7 +56,7 @@ class EnvProviderTest {
private class InlineTestProvider(
private val pythons: Set<PythonBinary>,
override val uiCustomization: PyToolUIInfo?
override val uiCustomization: PyToolUIInfo?,
) : SystemPythonProvider {
override suspend fun findSystemPythons(eelApi: EelApi) = Result.success(pythons)
}

View File

@@ -161,6 +161,7 @@ public final class PyInterpreterInspection extends PyInspection {
}
}
// TODO: We should use SystemPythonService here as well, postponing as it's quite unlikely we get here (although we can)
final var systemWideSdk = PySdkExtKt.mostPreferred(PySdkExtKt.filterSystemWideSdks(existingSdks));
if (systemWideSdk != null) {
return new UseExistingInterpreterFix(systemWideSdk, module);

View File

@@ -95,9 +95,9 @@ class PoetryPyProjectTomlPythonVersionsService : Disposable {
fun validateSdkVersions(moduleFile: VirtualFile, sdks: List<Sdk>): List<Sdk> =
sdks.filter { getVersion(moduleFile).isValid(it.versionString) }
fun <P: PathHolder> validateInterpretersVersions(moduleFile: VirtualFile, interpreters: Flow<List<PythonSelectableInterpreter<P>>?>): Flow<List<PythonSelectableInterpreter<P>>?> {
fun <P : PathHolder> validateInterpretersVersions(moduleFile: VirtualFile, interpreters: Flow<List<PythonSelectableInterpreter<P>>?>): Flow<List<PythonSelectableInterpreter<P>>?> {
val version = getVersion(moduleFile)
return interpreters.map { list -> list?.filter { version.isValid(it.languageLevel) } }
return interpreters.map { list -> list?.filter { version.isValid(it.pythonInfo.languageLevel) } }
}
private fun getVersion(moduleFile: VirtualFile): PoetryPythonVersion =

View File

@@ -7,6 +7,7 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.ui.popup.ListSeparator
import com.intellij.openapi.vfs.readText
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.python.sdk.ui.evolution.sdk.EvoModuleSdk
import com.intellij.python.sdk.ui.evolution.sdk.resolvePythonExecutable
import com.intellij.python.sdk.ui.evolution.tool.pip.sdk.getPythonVersion
@@ -15,7 +16,6 @@ import com.intellij.python.sdk.ui.evolution.ui.components.EvoTreeElement
import com.intellij.python.sdk.ui.evolution.ui.components.EvoTreeLazyNodeElement
import com.intellij.python.sdk.ui.evolution.ui.components.EvoTreeLeafElement
import com.intellij.python.sdk.ui.evolution.ui.components.EvoTreeSection
import com.intellij.python.pyproject.PyProjectToml
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.Result
@@ -33,7 +33,7 @@ import kotlin.io.path.name
private class PoetrySelectSdkProvider() : EvoSelectSdkProvider {
override fun getTreeElement(evoModuleSdk: EvoModuleSdk): EvoTreeElement = EvoTreeLazyNodeElement("Poetry", PythonIcons.Python.Origami) {
val poetryExecutable = getPoetryExecutable().getOr {
getPoetryExecutable().getOr {
return@EvoTreeLazyNodeElement it
}
@@ -65,7 +65,7 @@ private class PoetrySelectSdkProvider() : EvoSelectSdkProvider {
EvoTreeSection(ListSeparator(label), leafs)
}
val systemPythons = SystemPythonService().findSystemPythons().groupBy { it.languageLevel }.keys.sortedDescending()
val systemPythons = SystemPythonService().findSystemPythons().groupBy { it.pythonInfo.languageLevel }.keys.sortedDescending()
val prefix = specials?.firstOrNull()?.name?.substringBeforeLast("-") ?: "$projectName-sha256"
val specialSection = EvoTreeSection(
label = ListSeparator("$poetryVirtualenvsPath/$prefix"),
@@ -84,20 +84,6 @@ private class PoetrySelectSdkProvider() : EvoSelectSdkProvider {
Result.success(sections)
}
//override fun getTreeElement(evoModuleSdk: EvoModuleSdk): EvoTreeElement {
// val path = evoModuleSdk.module.which("poetry") ?: return null
// return null
// val header = object : AnAction("Base: $path", null, PythonIcons.Python.Origami) {
// override fun actionPerformed(e: AnActionEvent) = Unit
// //}
// //
// //val evoLazyActionGroup = EvoLazyActionGroup(
// // ExternalToolActionGroup("Poetry", "Poetry", EvolutionIcons.Tools.Poetry)
// //) {
// // Result.success(listOf(header))
// //}
// //return evoLazyActionGroup
//}
}
private class SelectPoetryEnvAction(

View File

@@ -12,19 +12,22 @@ import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.util.progress.withProgressText
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.python.community.impl.venv.createVenv
import com.intellij.python.community.services.systemPython.SystemPython
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.community.services.systemPython.createVenvFromSystemPython
import com.jetbrains.python.*
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.getOr
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.configurePythonSdk
import com.jetbrains.python.sdk.createSdk
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.setAssociationToModule
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
@@ -106,7 +109,7 @@ private suspend fun findExistingVenv(
logger.warn("No flavor found for $pythonPath")
return@withContext null
}
return@withContext when (val p = pythonPath.validatePythonAndGetVersion()) {
return@withContext when (val p = pythonPath.validatePythonAndGetInfo()) {
is Result.Success -> pythonPath
is Result.Failure -> {
logger.warn("No version string. python seems to be broken: $pythonPath. ${p.error}")

View File

@@ -511,7 +511,6 @@ fun PyDetectedSdk.pyvenvContains(pattern: String): Boolean = runReadAction {
if (isTargetBased()) {
return@runReadAction false
}
homeDirectory?.toNioPathOrNull()?.parent?.parent?.resolve("pyvenv.cfg")
val pyvenvFile = homeDirectory?.parent?.parent?.findFile("pyvenv.cfg") ?: return@runReadAction false
val text = FileDocumentManager.getInstance().getDocument(pyvenvFile)?.text ?: return@runReadAction false
pattern in text

View File

@@ -25,7 +25,7 @@ import org.jetbrains.annotations.ApiStatus.Internal
import java.nio.file.Path
@Internal
internal abstract class CustomNewEnvironmentCreator<P: PathHolder>(
internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
private val name: String,
model: PythonMutableTargetAddInterpreterModel<P>,
protected val errorSink: ErrorSink,
@@ -152,7 +152,7 @@ internal abstract class CustomNewEnvironmentCreator<P: PathHolder>(
is InstallableSelectableInterpreter -> installBaseSdk(baseInterpreter.sdk, model.existingSdks)
?.let {
val sdkWrapper = model.fileSystem.wrapSdk(it)
val installed = model.addInstalledInterpreter(sdkWrapper.homePath, baseInterpreter.languageLevel)
val installed = model.addInstalledInterpreter(sdkWrapper.homePath, baseInterpreter.pythonInfo)
model.state.baseInterpreter.set(installed)
installed
}

View File

@@ -9,13 +9,17 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.FileUtil
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
import com.intellij.python.community.execService.BinOnEel
import com.intellij.python.community.execService.BinOnTarget
import com.intellij.python.community.execService.BinaryToExec
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
import com.intellij.python.community.services.systemPython.SystemPython
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
@@ -47,7 +51,7 @@ data class SdkWrapper<P>(val sdk: Sdk, val homePath: P)
internal class VenvAlreadyExistsError<P : PathHolder>(
val detectedSelectableInterpreter: DetectedSelectableInterpreter<P>,
) : MessageError(message("python.add.sdk.already.contains.python.with.version", detectedSelectableInterpreter.languageLevel))
) : MessageError(message("python.add.sdk.already.contains.python.with.version", detectedSelectableInterpreter.pythonInfo.languageLevel))
sealed interface FileSystem<P : PathHolder> {
val isReadOnly: Boolean
@@ -111,7 +115,7 @@ sealed interface FileSystem<P : PathHolder> {
val systemPython = SystemPythonService().registerSystemPython(pathToPython.path).getOr { return it }
val interpreter = DetectedSelectableInterpreter(
homePath = PathHolder.Eel(systemPython.pythonBinary),
languageLevel = systemPython.languageLevel,
pythonInfo = systemPython.pythonInfo,
isBase = true
)
@@ -125,9 +129,9 @@ sealed interface FileSystem<P : PathHolder> {
override suspend fun detectSelectableVenv(): List<DetectedSelectableInterpreter<PathHolder.Eel>> {
// Venvs are not detected manually, but must migrate to VenvService or so
val pythonBinaries = VirtualEnvSdkFlavor.getInstance().suggestLocalHomePaths(null, null)
val suggestedPythonBinaries = VanillaPythonWithLanguageLevelImpl.createByPythonBinaries(pythonBinaries)
val suggestedPythonBinaries = VanillaPythonWithPythonInfoImpl.createByPythonBinaries(pythonBinaries)
val venvs: List<VanillaPythonWithLanguageLevel> = suggestedPythonBinaries.mapNotNull { (venv, r) ->
val venvs: List<VanillaPythonWithPythonInfo> = suggestedPythonBinaries.mapNotNull { (venv, r) ->
when (r) {
is Result.Failure -> {
fileLogger().warn("Skipping $venv : ${r.error}")
@@ -147,7 +151,7 @@ sealed interface FileSystem<P : PathHolder> {
}.map { (python, base, ui) ->
DetectedSelectableInterpreter(
homePath = PathHolder.Eel(python.pythonBinary),
languageLevel = python.languageLevel,
pythonInfo = python.pythonInfo,
isBase = base,
ui = ui
)
@@ -223,13 +227,13 @@ sealed interface FileSystem<P : PathHolder> {
private suspend fun registerSystemPython(pathToPython: PathHolder.Target): PyResult<DetectedSelectableInterpreter<PathHolder.Target>> {
val pythonBinaryToExec = getBinaryToExec(pathToPython)
val languageLevel = pythonBinaryToExec.validatePythonAndGetVersion().getOr {
val pythonInfo = pythonBinaryToExec.validatePythonAndGetInfo().getOr {
return it
}
val interpreter = DetectedSelectableInterpreter(
homePath = pathToPython,
languageLevel = languageLevel,
pythonInfo = pythonInfo,
true,
).also {
systemPythonCache.add(it)
@@ -277,7 +281,7 @@ internal fun <P : PathHolder> FileSystem<P>.getInstallableInterpreters(): List<I
}
.sortedByDescending { it.first }
.map { (languageLevel, sdk) ->
InstallableSelectableInterpreter(languageLevel, sdk)
InstallableSelectableInterpreter(PythonInfo(languageLevel), sdk)
}
}
else -> emptyList()
@@ -305,11 +309,11 @@ internal suspend fun <P : PathHolder> FileSystem<P>.getExistingSelectableInterpr
val languageLevel = sdk.versionString?.let {
PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe(it)
} ?: run {
ExecService().validatePythonAndGetVersion(sdk.asBinToExecute()).getOrLogException(LOG)
ExecService().validatePythonAndGetInfo(sdk.asBinToExecute()).getOrLogException(LOG)?.languageLevel
}
languageLevel?.let {
ExistingSelectableInterpreter<P>(wrapSdk(sdk), it, sdk.isSystemWide)
ExistingSelectableInterpreter<P>(wrapSdk(sdk), PythonInfo(it), sdk.isSystemWide)
}
}
allValidSdks

View File

@@ -167,4 +167,4 @@ internal class PythonAddCustomInterpreter<P : PathHolder>(
fun createStatisticsInfo(): InterpreterStatisticsInfo {
return currentSdkManager.createStatisticsInfo(PythonInterpreterCreationTargets.LOCAL_MACHINE)
}
}
}

View File

@@ -257,7 +257,7 @@ internal suspend fun <P : PathHolder> PythonSelectableInterpreter<P>.setupSdk(
allSdks = allSdks,
fileSystem = fileSystem,
pythonBinaryPath = homePath!!,
languageLevel = languageLevel,
languageLevel = pythonInfo.languageLevel,
targetPanelExtension = targetPanelExtension
).getOr { return it }
@@ -306,4 +306,4 @@ internal suspend fun BinaryToExec.getToolVersion(toolVersionPrefix: String): PyR
val versionPresentation = StringUtil.shortenTextWithEllipsis(version, 250, 0, true)
PyResult.localizedError(message("selected.tool.is.wrong", toolVersionPrefix.trim(), versionPresentation))
}
}
}

View File

@@ -9,20 +9,19 @@ import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.PropertyGraph
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.python.community.services.shared.LanguageLevelHolder
import com.intellij.python.community.services.shared.LanguageLevelWithUiComparator
import com.intellij.python.community.services.shared.PythonInfoHolder
import com.intellij.python.community.services.shared.PythonInfoWithUiComparator
import com.intellij.python.community.services.shared.UiHolder
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.*
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.Result.Companion.success
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.TraceContext
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.emit
import com.jetbrains.python.getOrNull
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.PySdkToInstall
import com.jetbrains.python.sdk.PySdkUtil
import com.jetbrains.python.sdk.add.v2.conda.CondaViewModel
@@ -119,8 +118,8 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
manuallyAddedInterpreters
) { detected, manual ->
val base = detected?.filter { it.isBase } ?: return@combine null
val existingLanguageLevels = base.map { it.languageLevel }.toSet()
val nonExistingInstallable = installable.filter { it.languageLevel !in existingLanguageLevels }
val existingLanguageLevels = base.map { it.pythonInfo.languageLevel }.toSet()
val nonExistingInstallable = installable.filter { it.pythonInfo.languageLevel !in existingLanguageLevels }
manual + base.sorted() + nonExistingInstallable
}.stateIn(scope, started = SharingStarted.Eagerly, initialValue = null)
}
@@ -134,7 +133,7 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
internal suspend fun addManuallyAddedInterpreter(homePath: P): PyResult<ManuallyAddedSelectableInterpreter<P>> {
val python = homePath.let { fileSystem.getSystemPythonFromSelection(it) }.getOr { return it }
val interpreter = ManuallyAddedSelectableInterpreter(homePath, python.languageLevel).also {
val interpreter = ManuallyAddedSelectableInterpreter(homePath, python.pythonInfo).also {
this@PythonAddInterpreterModel.addManuallyAddedInterpreter(it)
}
return PyResult.success(interpreter)
@@ -143,15 +142,15 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
open fun addInterpreter(sdk: Sdk) {
val interpreter = ExistingSelectableInterpreter(
fileSystem.wrapSdk(sdk),
PySdkUtil.getLanguageLevelForSdk(sdk),
PythonInfo(PySdkUtil.getLanguageLevelForSdk(sdk)),
sdk.isSystemWide
)
this@PythonAddInterpreterModel.addManuallyAddedInterpreter(interpreter)
}
@RequiresEdt
internal fun addInstalledInterpreter(homePath: P, languageLevel: LanguageLevel): DetectedSelectableInterpreter<P> {
val installedInterpreter = DetectedSelectableInterpreter(homePath, languageLevel, true)
internal fun addInstalledInterpreter(homePath: P, pythonInfo: PythonInfo): DetectedSelectableInterpreter<P> {
val installedInterpreter = DetectedSelectableInterpreter(homePath, pythonInfo, true)
_detectedInterpreters.value = (_detectedInterpreters.value ?: emptyList()) + installedInterpreter
return installedInterpreter
}
@@ -169,7 +168,7 @@ class PythonLocalAddInterpreterModel<P : PathHolder>(projectPathFlows: ProjectPa
val interpreterToSelect = preferredInterpreterBasePath?.let { path ->
detectedInterpreters.value?.find { it.homePath == path }
} ?: baseInterpreters.value?.filterIsInstance<ExistingSelectableInterpreter<P>>()?.maxByOrNull { it.languageLevel }
} ?: baseInterpreters.value?.filterIsInstance<ExistingSelectableInterpreter<P>>()?.maxByOrNull { it.pythonInfo.languageLevel }
if (interpreterToSelect != null) {
state.baseInterpreter.set(interpreterToSelect)
@@ -178,13 +177,13 @@ class PythonLocalAddInterpreterModel<P : PathHolder>(projectPathFlows: ProjectPa
}
sealed class PythonSelectableInterpreter<P : PathHolder> : Comparable<PythonSelectableInterpreter<*>>, UiHolder, LanguageLevelHolder {
sealed class PythonSelectableInterpreter<P : PathHolder> : Comparable<PythonSelectableInterpreter<*>>, UiHolder, PythonInfoHolder {
companion object {
private val comparator = LanguageLevelWithUiComparator<PythonSelectableInterpreter<*>>()
private val comparator = PythonInfoWithUiComparator<PythonSelectableInterpreter<*>>()
}
abstract val homePath: P?
abstract override val languageLevel: LanguageLevel
abstract override val pythonInfo: PythonInfo
override val ui: PyToolUIInfo? = null
override fun toString(): String = "PythonSelectableInterpreter(homePath='$homePath')"
@@ -193,14 +192,14 @@ sealed class PythonSelectableInterpreter<P : PathHolder> : Comparable<PythonSele
class ExistingSelectableInterpreter<P : PathHolder>(
val sdkWrapper: SdkWrapper<P>,
override val languageLevel: LanguageLevel,
override val pythonInfo: PythonInfo,
val isSystemWide: Boolean,
) : PythonSelectableInterpreter<P>() {
override val homePath: P
get() = sdkWrapper.homePath
override fun toString(): String {
return "ExistingSelectableInterpreter(sdk=${sdkWrapper.sdk}, languageLevel=$languageLevel, isSystemWide=$isSystemWide, homePath='$homePath')"
return "ExistingSelectableInterpreter(sdk=${sdkWrapper.sdk}, pythonInfo=$pythonInfo, isSystemWide=$isSystemWide, homePath='$homePath')"
}
}
@@ -209,26 +208,26 @@ class ExistingSelectableInterpreter<P : PathHolder>(
*/
class DetectedSelectableInterpreter<P : PathHolder>(
override val homePath: P,
override val languageLevel: LanguageLevel,
override val pythonInfo: PythonInfo,
val isBase: Boolean,
override val ui: PyToolUIInfo? = null,
) : PythonSelectableInterpreter<P>() {
override fun toString(): String {
return "DetectedSelectableInterpreter(homePath='$homePath', languageLevel=$languageLevel, isBase=$isBase, uiCustomization=$ui)"
return "DetectedSelectableInterpreter(homePath='$homePath', pythonInfo=$pythonInfo, isBase=$isBase, uiCustomization=$ui)"
}
}
class ManuallyAddedSelectableInterpreter<P : PathHolder>(
override val homePath: P,
override val languageLevel: LanguageLevel,
override val pythonInfo: PythonInfo,
) : PythonSelectableInterpreter<P>() {
override fun toString(): String {
return "ManuallyAddedSelectableInterpreter(homePath='$homePath', languageLevel=$languageLevel)"
return "ManuallyAddedSelectableInterpreter(homePath='$homePath', pythonInfo=$pythonInfo)"
}
}
class InstallableSelectableInterpreter<P : PathHolder>(
override val languageLevel: LanguageLevel,
override val pythonInfo: PythonInfo,
val sdk: PySdkToInstall,
) : PythonSelectableInterpreter<P>() {
override val homePath: P? = null

View File

@@ -5,18 +5,13 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.add.v2.CustomExistingEnvironmentSelector
import com.jetbrains.python.sdk.add.v2.DetectedSelectableInterpreter
import com.jetbrains.python.sdk.add.v2.PathHolder
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
import com.jetbrains.python.sdk.add.v2.PathValidator
import com.jetbrains.python.sdk.add.v2.ValidatedPath
import com.jetbrains.python.sdk.add.v2.Version
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.poetry.createPoetrySdk
import com.jetbrains.python.sdk.poetry.detectPoetryEnvs
import com.jetbrains.python.sdk.poetry.isPoetry
@@ -25,7 +20,7 @@ import com.jetbrains.python.statistics.version
import java.nio.file.Path
import kotlin.io.path.pathString
internal class PoetryExistingEnvironmentSelector<P: PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?) : CustomExistingEnvironmentSelector<P>("poetry", model, module) {
internal class PoetryExistingEnvironmentSelector<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?) : CustomExistingEnvironmentSelector<P>("poetry", model, module) {
override val toolState: PathValidator<Version, P, ValidatedPath.Executable<P>> = model.poetryViewModel.toolValidator
override val interpreterType: InterpreterType = InterpreterType.POETRY
@@ -55,10 +50,10 @@ internal class PoetryExistingEnvironmentSelector<P: PathHolder>(model: PythonMut
val existingEnvs = detectPoetryEnvs(null, null, modulePath.pathString).mapNotNull { env ->
env.homePath?.let { path ->
model.fileSystem.parsePath(path).successOrNull?.let { fsPath ->
DetectedSelectableInterpreter<P>(fsPath, env.version, false)
DetectedSelectableInterpreter<P>(fsPath, PythonInfo(env.version), false)
}
}
}
return existingEnvs
}
}
}

View File

@@ -152,7 +152,7 @@ internal fun <P : PathHolder> SimpleColoredComponent.customizeForPythonInterpret
is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> {
icon = IconLoader.getTransparentIcon(interpreter.ui?.icon ?: PythonParserIcons.PythonFile)
val title = interpreter.ui?.toolName ?: message("sdk.rendering.detected.grey.text")
append(String.format("Python %-4s", interpreter.languageLevel))
append(String.format("Python %-4s", interpreter.pythonInfo.languageLevel))
append(" (" + replaceHomePathToTilde(interpreter.homePath.toString()) + ") $title", SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
}
is InstallableSelectableInterpreter -> {

View File

@@ -4,20 +4,15 @@ package com.jetbrains.python.sdk.add.v2.uv
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.add.v2.CustomExistingEnvironmentSelector
import com.jetbrains.python.sdk.add.v2.DetectedSelectableInterpreter
import com.jetbrains.python.sdk.add.v2.PathHolder
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
import com.jetbrains.python.sdk.add.v2.PathValidator
import com.jetbrains.python.sdk.add.v2.ValidatedPath
import com.jetbrains.python.sdk.add.v2.Version
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.sdk.associatedModulePath
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.isAssociatedWithModule
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.uv.isUv
import com.jetbrains.python.sdk.uv.setupExistingEnvAndSdk
import com.jetbrains.python.statistics.InterpreterType
@@ -27,7 +22,7 @@ import com.jetbrains.python.venvReader.tryResolvePath
import java.nio.file.Path
import kotlin.io.path.pathString
internal class UvExistingEnvironmentSelector<P: PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?)
internal class UvExistingEnvironmentSelector<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?)
: CustomExistingEnvironmentSelector<P>("uv", model, module) {
override val toolState: PathValidator<Version, P, ValidatedPath.Executable<P>> = model.uvViewModel.toolValidator
override val interpreterType: InterpreterType = InterpreterType.UV
@@ -68,7 +63,7 @@ internal class UvExistingEnvironmentSelector<P: PathHolder>(model: PythonMutable
}.mapNotNull { env ->
env.homePath?.let { path ->
model.fileSystem.parsePath(path).successOrNull?.let { homePath ->
DetectedSelectableInterpreter(homePath, env.version, false)
DetectedSelectableInterpreter(homePath, PythonInfo(env.version), false)
}
}
}
@@ -80,4 +75,4 @@ internal class UvExistingEnvironmentSelector<P: PathHolder>(model: PythonMutable
is ModuleOrProject.ModuleAndProject -> moduleOrProject.module
else -> null
}
}
}

View File

@@ -3,10 +3,9 @@ package com.jetbrains.python.sdk.poetry
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkAdditionalData
import com.intellij.openapi.util.UserDataHolder
import com.jetbrains.python.PyBundle
import com.jetbrains.python.poetry.PoetryPyProjectTomlPythonVersionsService
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.PyInterpreterInspectionQuickFixData
import com.jetbrains.python.sdk.PySdkProvider
import com.jetbrains.python.sdk.poetry.quickFixes.PoetryAssociationQuickFix
import org.jdom.Element
import javax.swing.Icon
@@ -43,19 +42,4 @@ class PoetrySdkProvider : PySdkProvider {
override fun loadAdditionalDataForSdk(element: Element): SdkAdditionalData? {
return PyPoetrySdkAdditionalData.load(element)
}
}
// TODO: PythonInterpreterService: validate system python
internal fun validateSdks(module: Module?, existingSdks: List<Sdk>, context: UserDataHolder): List<Sdk> {
val moduleFile = module?.baseDir
val sdks = findBaseSdks(existingSdks, module, context).takeIf { it.isNotEmpty() }
?: detectSystemWideSdks(module, existingSdks, context)
return if (moduleFile != null) {
PoetryPyProjectTomlPythonVersionsService.instance.validateSdkVersions(moduleFile, sdks)
}
else {
sdks
}.filter { it.sdkSeemsValid && !it.isPoetry }
}

View File

@@ -6,7 +6,7 @@ package com.jetbrains.python.target
import com.intellij.python.community.execService.BinOnEel
import com.intellij.python.community.execService.BinOnTarget
import com.intellij.python.community.execService.BinaryToExec
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.intellij.remote.RemoteSdkException
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
@@ -19,14 +19,16 @@ private fun PyTargetAwareAdditionalData.getBinaryToExec(): BinaryToExec {
val configuration = targetEnvironmentConfiguration
val binaryToExec = if (configuration == null) {
BinOnEel(Path.of(interpreterPath))
} else {
}
else {
BinOnTarget(interpreterPath, configuration)
}
return binaryToExec
}
@ApiStatus.Internal
suspend fun PyTargetAwareAdditionalData.getInterpreterVersion(): PyResult<LanguageLevel> = getBinaryToExec().validatePythonAndGetVersion()
suspend fun PyTargetAwareAdditionalData.getInterpreterVersion(): PyResult<LanguageLevel> =
getBinaryToExec().validatePythonAndGetInfo().mapSuccess { it.languageLevel }
@ApiStatus.Internal