[python] PY-83881 Provide python info during environment detection

GitOrigin-RevId: 4e424c8fa9c08ce3cd497586f5f13e5a5816f52a
This commit is contained in:
Alexey Katsman
2025-10-31 17:55:16 +00:00
committed by intellij-monorepo-bot
parent 8ae63f6126
commit ea82fe5cc2
24 changed files with 152 additions and 78 deletions
+3
View File
@@ -71,6 +71,7 @@ jvm_library(
"//python/python-sdk-ui:sdk-ui",
"//python/python-poetry/common",
"//python/python-uv/common",
"//python/python-exec-service/execService.python",
],
runtime_deps = ["//python/python-features-trainer:featuresTrainer"],
plugins = ["@lib//:compose-plugin"]
@@ -148,6 +149,8 @@ jvm_library(
"//python/python-sdk-ui:sdk-ui",
"//python/python-poetry/common",
"//python/python-uv/common",
"//python/python-exec-service/execService.python",
"//python/python-exec-service/execService.python:execService.python_test_lib",
],
plugins = ["@lib//:compose-plugin"]
)
@@ -85,5 +85,6 @@
<orderEntry type="module" module-name="intellij.python.sdk.ui" />
<orderEntry type="module" module-name="intellij.python.community.impl.poetry.common" />
<orderEntry type="module" module-name="intellij.python.community.impl.uv.common" />
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
</component>
</module>
@@ -10,6 +10,7 @@
<module name="intellij.python.sdk.ui"/>
<module name="intellij.python.community.impl.poetry.common"/>
<module name="intellij.python.community.impl.uv.common"/>
<module name="intellij.python.community.execService.python"/>
</dependencies>
<projectListeners>
@@ -16,13 +16,13 @@ import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationC
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.InputData
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
import com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewCondaEnvFromFilePanel
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.execService.BinOnEel
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.PyBundle
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.emit
import com.jetbrains.python.getOrNull
import com.jetbrains.python.onSuccess
import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils
@@ -36,6 +36,7 @@ import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
import com.jetbrains.python.sdk.conda.createCondaSdkAlongWithNewEnv
import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
import com.jetbrains.python.sdk.conda.suggestCondaPath
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.findAmongRoots
@@ -77,10 +78,16 @@ class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
}
val canManage = condaPath != null
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.condaenv.suggestion")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
when {
canManage && checkExistence && getCondaEnvIdentity(module, condaPath.path) != null -> EnvCheckerResult.EnvFound("", intentionName)
canManage -> EnvCheckerResult.EnvNotFound(intentionName)
canManage && checkExistence -> {
getCondaEnvIdentity(module, condaPath.path)?.let { env ->
val binaryToExec = BinOnEel(Path.of(condaPath.path))
CondaExecutor.getPythonInfo(binaryToExec, env).findEnvOrNull(intentionName)
} ?: envNotFound
}
canManage -> envNotFound
else -> EnvCheckerResult.CannotConfigure
}
}
@@ -50,7 +50,7 @@ class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
canManage && checkExistence -> {
val defaultEnv = hatchService.findDefaultVirtualEnvironmentOrNull().getOrLogException(LOGGER)?.pythonVirtualEnvironment
when (defaultEnv) {
is PythonVirtualEnvironment.Existing -> EnvCheckerResult.EnvFound("", intentionName)
is PythonVirtualEnvironment.Existing -> EnvCheckerResult.EnvFound(defaultEnv.pythonInfo, intentionName)
is PythonVirtualEnvironment.NotExisting, null -> envNotFound
}
}
@@ -16,7 +16,9 @@ import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBund
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.InputData
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.PipEnvResult
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.execService.ZeroCodeStdoutParserTransformer
import com.intellij.python.community.impl.pipenv.pipenvPath
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.components.JBLabel
@@ -71,10 +73,19 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
when {
canManage && checkExistence -> {
PropertiesComponent.getInstance().pipenvPath = pipEnvExecutable.pathString
val envPath = runPipEnv(module.basePath?.toNioPathOrNull(), "--venv").mapSuccess { Path.of(it) }.successOrNull
val envPath = runPipEnv(
module.basePath?.toNioPathOrNull(),
"--venv",
transformer = ZeroCodeStdoutParserTransformer { PyResult.success(Path.of(it)) }
).successOrNull
val path = envPath?.resolvePythonBinary()
val envExists = path?.let { LocalFileSystem.getInstance().refreshAndFindFileByPath(it.pathString) != null } ?: false
if (envExists) EnvCheckerResult.EnvFound("", intentionName) else envNotFound
val envExists = path?.let {
LocalFileSystem.getInstance().refreshAndFindFileByPath(it.pathString) != null
} ?: false
if (envExists) {
path.findEnvOrNull(intentionName) ?: envNotFound
}
else envNotFound
}
canManage -> envNotFound
else -> EnvCheckerResult.CannotConfigure
@@ -11,6 +11,7 @@ import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.platform.util.progress.reportRawProgress
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
import com.intellij.python.pyproject.PyProjectToml
@@ -73,7 +74,7 @@ class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
val envPath = runPoetry(basePath, "env", "info", "-p")
.mapSuccess { it.toNioPathOrNull() }
.getOr { return@reportRawProgress envNotFound }
envPath?.resolvePythonBinary()?.let { EnvCheckerResult.EnvFound("", intentionName) } ?: return@reportRawProgress envNotFound
envPath?.resolvePythonBinary()?.findEnvOrNull(intentionName) ?: return@reportRawProgress envNotFound
}
canManage -> envNotFound
else -> EnvCheckerResult.CannotConfigure
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.readText
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.uv.common.UV_TOOL_ID
import com.intellij.python.pyproject.PyProjectToml
@@ -85,10 +86,14 @@ class PyUvSdkConfiguration : PyProjectTomlConfigurationExtension {
else true to module.name
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.uv.environment", projectName)
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
return when {
checkExistence && getUvEnv(if (checkToml) module else module.getSdkAssociatedModule()) != null -> EnvCheckerResult.EnvFound("", intentionName)
canManage -> EnvCheckerResult.EnvNotFound(intentionName)
checkExistence -> {
val detectedSdk = getUvEnv(if (checkToml) module else module.getSdkAssociatedModule())
detectedSdk?.findEnvOrNull(intentionName) ?: if (canManage) envNotFound else EnvCheckerResult.CannotConfigure
}
canManage -> envNotFound
else -> EnvCheckerResult.CannotConfigure
}
}
@@ -115,8 +120,8 @@ class PyUvSdkConfiguration : PyProjectTomlConfigurationExtension {
getUvEnv(sdkAssociatedModule)?.homePath?.toNioPathOrNull()?.let {
setupExistingEnvAndSdk(it, workingDir, false, workingDir, existingSdks)
} ?: run {
logger.error("Can't find existing uv environment in project, but it was expected. " +
"Probably it was deleted. New environment will be created")
logger.warn("Can't find existing uv environment in project, but it was expected. " +
"Probably it was deleted. New environment will be created")
setupNewUvSdkAndEnv(workingDir, existingSdks, null)
}
}
@@ -6,6 +6,7 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.MessageError
@@ -28,7 +29,7 @@ class PyVenvSdkConfiguration : PyProjectSdkConfigurationExtension {
override val toolId: ToolId = ToolId("Venv")
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
{ checkManageableEnv(module) }
{ checkManageableEnv(module) }
) { { setupVenv(module) } }
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension? = null
@@ -38,7 +39,7 @@ class PyVenvSdkConfiguration : PyProjectSdkConfigurationExtension {
): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
withContext(Dispatchers.IO) {
getVirtualEnv(module)?.let {
EnvCheckerResult.EnvFound("", PyCharmCommunityCustomizationBundle.message("sdk.use.existing.venv", it.name))
it.findEnvOrNull(PyCharmCommunityCustomizationBundle.message("sdk.use.existing.venv", it.name))
} ?: EnvCheckerResult.CannotConfigure
}
}
@@ -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.intellij.pycharm.community.ide.impl
import com.intellij.codeInspection.util.IntentionName
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.orLogExceptionAsWarn
import com.jetbrains.python.sdk.asBinToExecute
import com.jetbrains.python.sdk.configuration.EnvCheckerResult
internal suspend fun PythonBinary.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? =
validatePythonAndGetInfo().findEnvOrNull(intentionName)
internal suspend fun Sdk.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? =
asBinToExecute().validatePythonAndGetInfo().findEnvOrNull(intentionName)
internal fun PyResult<PythonInfo>.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? =
orLogExceptionAsWarn(fileLogger())?.let { EnvCheckerResult.EnvFound(it, intentionName) }
@@ -128,15 +128,18 @@ fun <SUCC, NEW_S, ERR> Result<SUCC, ERR>.mapResult(map: (SUCC) -> Result<NEW_S,
is Failure -> this
}
fun <T> Result<T, *>.orLogException(logger: Logger): T? =
fun <T> Result<T, *>.orLogException(logger: Logger): T? = orLogExceptionImpl(logger, false)
fun <T> Result<T, *>.orLogExceptionAsWarn(logger: Logger): T? = orLogExceptionImpl(logger, true)
private fun <T> Result<T, *>.orLogExceptionImpl(logger: Logger, asWarn: Boolean): T? =
when (val r = this) {
is Failure -> {
when (val err = r.error) {
is Throwable -> {
logger.error(err)
if (asWarn) logger.warn(err) else logger.error(err)
}
else -> {
logger.error(err.toString())
if (asWarn) logger.warn(err.toString()) else logger.error(err.toString())
}
}
null
@@ -177,6 +180,7 @@ inline fun <S, E, E2> Result<S, E>.mapError(code: (E) -> E2): Result<S, E2> =
// aliases to drop-in replace for kotlin Result
fun <S, E> Result<S, E>.getOrLogException(logger: Logger): S? = this.orLogException(logger)
fun <S, E> Result<S, E>.getOrLogExceptionAsWarn(logger: Logger): S? = this.orLogExceptionAsWarn(logger)
fun <S, E> Result<S, E>.getOrNull(): S? = this.successOrNull
val <S, E> Result<S, E>.isFailure: Boolean get() = this is Failure
val <S, E> Result<S, E>.isSuccess: Boolean get() = this is Success
@@ -1,4 +1,4 @@
<idea-plugin>
<idea-plugin visibility="internal">
<dependencies>
<module name="intellij.python.sdk"/>
<module name="intellij.python.community.execService"/>
@@ -1,7 +1,6 @@
// 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
@@ -16,8 +15,6 @@ 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
import kotlinx.coroutines.withContext
@@ -25,13 +22,24 @@ 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 gilCheckOutput = executePythonAdvanced(
python,
Args("-c", GIL_CHECK_CMD),
processInteractiveHandler = transformerToHandler(null, ZeroCodeStdoutTransformer),
options = options
).getOr(message("python.cannot.exec", python.userReadableName)) { return@withContext it }.trim()
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()
if (smokeTestOutput != "1") {
return@withContext PyResult.localizedError(message("python.get.version.error", python.userReadableName, smokeTestOutput))
val freeThreaded = when (gilCheckOutput) {
"True" -> false
"False" -> true
else -> return@withContext PyResult.localizedError(message("python.get.version.error", python.userReadableName, gilCheckOutput))
}
val versionOutput: EelProcessExecutionResult = executePythonAdvanced(python, options = options, args = Args(PYTHON_VERSION_ARG), processInteractiveHandler = transformerToHandler<EelProcessExecutionResult>(null) { r ->
@@ -44,17 +52,6 @@ internal suspend fun ExecService.validatePythonAndGetInfoImpl(python: Executable
return@withContext PyResult.localizedError(message("python.get.version.wrong.version", python.userReadableName, versionString))
}
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))
}
+1
View File
@@ -38,6 +38,7 @@ jvm_library(
"@lib//:io-github-z4kn4fein-semver-jvm-provided",
"//python/python-sdk-ui:sdk-ui",
"//python/common",
"//python/python-exec-service/execService.python",
]
)
### auto-generated section `build intellij.python.hatch` end
@@ -47,5 +47,6 @@
<orderEntry type="library" scope="PROVIDED" name="io.github.z4kn4fein.semver.jvm" level="project" />
<orderEntry type="module" module-name="intellij.python.sdk.ui" />
<orderEntry type="module" module-name="intellij.python.common" />
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
</component>
</module>
@@ -6,6 +6,7 @@
<module name="intellij.python.sdk.ui"/>
<module name="intellij.python.pyproject"/>
<module name="intellij.python.common"/>
<module name="intellij.python.community.execService.python"/>
</dependencies>
<extensions defaultExtensionNs="Pythonid">
@@ -9,6 +9,7 @@ import com.intellij.python.hatch.cli.HatchEnvironment
import com.intellij.python.hatch.service.CliBasedHatchService
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
@@ -65,7 +66,7 @@ sealed interface PythonVirtualEnvironment {
* Represents an existing Python virtual environment.
* The environment was verified and the Python version was already discovered.
*/
data class Existing(override val pythonHomePath: PythonHomePath, val pythonVersion: String) : PythonVirtualEnvironment
data class Existing(override val pythonHomePath: PythonHomePath, val pythonInfo: PythonInfo) : PythonVirtualEnvironment
/**
* Represents a non-existing Python virtual environment.
@@ -3,6 +3,7 @@ package com.intellij.python.hatch.runtime
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.validatePythonAndGetInfo
import com.intellij.python.hatch.*
import com.intellij.python.hatch.cli.HatchCli
import com.jetbrains.python.PythonBinary
@@ -13,7 +14,6 @@ import com.jetbrains.python.sdk.impl.resolvePythonBinary
import java.nio.file.Path
import kotlin.io.path.isDirectory
import kotlin.io.path.isExecutable
import kotlin.time.Duration.Companion.minutes
class HatchRuntime(
val hatchBinary: BinOnEel,
@@ -63,15 +63,13 @@ class HatchRuntime(
}
internal suspend fun resolvePythonVirtualEnvironment(pythonHomePath: PythonHomePath): PyResult<PythonVirtualEnvironment> {
val pythonVersion = pythonHomePath.takeIf { it.isDirectory() }?.resolvePythonBinary()?.let { pythonBinaryPath ->
execService.execGetStdout(pythonBinaryPath, Args("--version"),
ExecOptions(timeout = 20.minutes),
procListener = null).getOr { return it }.trim()
val pythonInfo = pythonHomePath.takeIf { it.isDirectory() }?.resolvePythonBinary()?.let { pythonBinaryPath ->
pythonBinaryPath.validatePythonAndGetInfo().getOr { return it }
}
val pythonVirtualEnvironment = when {
pythonVersion == null -> PythonVirtualEnvironment.NotExisting(pythonHomePath)
else -> PythonVirtualEnvironment.Existing(pythonHomePath, pythonVersion)
pythonInfo == null -> PythonVirtualEnvironment.NotExisting(pythonHomePath)
else -> PythonVirtualEnvironment.Existing(pythonHomePath, pythonInfo)
}
return Result.success(pythonVirtualEnvironment)
}
@@ -65,7 +65,7 @@ internal class ModulesSdkConfigurator private constructor(
when (createInfo) {
is ModuleCreateInfo.CreateSdkInfoWrapper -> {
val version = when (val r = createInfo.createSdkInfo) {
is CreateSdkInfo.ExistingEnv -> r.version
is CreateSdkInfo.ExistingEnv -> r.pythonInfo.languageLevel.toPythonVersion()
is CreateSdkInfo.WillCreateEnv -> null
}
ModuleDTO(moduleName, createInfo.toolId.id, version, children[moduleName]!!.toList().sorted())
@@ -165,12 +165,12 @@ internal class ModulesSdkConfigurator private constructor(
val createInfo = (modules[module.name] ?: error("No create info for module $module, caller broke the contract"))
when (createInfo) {
is ModuleCreateInfo.CreateSdkInfoWrapper -> {
when (val r = createInfo.createSdkInfo.sdkCreator(false)) {
when (val r = createInfo.createSdkInfo.createSdkWithoutConfirmation()) {
is Result.Failure -> { //TODO: Show SDK creation error?
logger.warn("Failed to create SDK for ${module.name}: ${r.error}")
}
is Result.Success -> {
val sdk = r.result!! // can't be `null` and will be non-null soon
val sdk = r.result
ModuleRootModificationUtil.setModuleSdk(module, sdk)
}
}
@@ -2,6 +2,7 @@ package com.jetbrains.python.sdk.configuration
import com.intellij.codeInspection.util.IntentionName
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import org.jetbrains.annotations.ApiStatus
@@ -16,6 +17,15 @@ sealed interface CreateSdkInfo : Comparable<CreateSdkInfo> {
val intentionName: String
val sdkCreator: suspend (NeedsConfirmation) -> PyResult<Sdk?>
/**
* Nullable SDK is only possible when we requested user confirmation but didn't get it. The idea behind this function is to provide
* non-nullable SDK when no confirmation from the user is needed.
*
* It's a temporary solution until we'll be able to remove all custom user dialogs and enable [enableSDKAutoConfigurator] by default.
* After that lands, we'll get rid of nullable SDK.
*/
suspend fun createSdkWithoutConfirmation(): PyResult<Sdk> = sdkCreator(false).mapSuccess { it!! }
/**
* We want to preserve the initial order, but at the same time existing environment should have a higher priority by default
*/
@@ -26,7 +36,7 @@ sealed interface CreateSdkInfo : Comparable<CreateSdkInfo> {
}
data class ExistingEnv(
val version: String,
val pythonInfo: PythonInfo,
override val intentionName: String,
override val sdkCreator: suspend (NeedsConfirmation) -> PyResult<Sdk?>,
) : CreateSdkInfo
@@ -39,7 +49,7 @@ sealed interface CreateSdkInfo : Comparable<CreateSdkInfo> {
@ApiStatus.Internal
sealed interface EnvCheckerResult {
data class EnvFound(val version: String, val intentionName: @IntentionName String) : EnvCheckerResult
data class EnvFound(val pythonInfo: PythonInfo, val intentionName: @IntentionName String) : EnvCheckerResult
data class EnvNotFound(val intentionName: @IntentionName String) : EnvCheckerResult
object CannotConfigure : EnvCheckerResult
}
@@ -53,14 +63,14 @@ suspend fun prepareSdkCreator(
var res = envChecker(true)
return when (res) {
is EnvCheckerResult.EnvFound -> CreateSdkInfo.ExistingEnv(
res.version,
res.pythonInfo,
res.intentionName,
sdkCreator(true)
)
is EnvCheckerResult.EnvNotFound -> {
res = envChecker(false)
when (res) {
is EnvCheckerResult.EnvNotFound -> CreateSdkInfo.WillCreateEnv(res.intentionName, sdkCreator(false))
is EnvCheckerResult.EnvNotFound -> CreateSdkInfo.WillCreateEnv(res.intentionName, sdkCreator(false))
is EnvCheckerResult.EnvFound -> throw AssertionError("Env shouldn't exist if we didn't check for it")
is EnvCheckerResult.CannotConfigure -> null
}
@@ -10,10 +10,10 @@ import com.intellij.python.hatch.PythonVirtualEnvironment
import com.intellij.python.hatch.getHatchEnvVirtualProjectPath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.impl.resolvePythonBinary
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.add.v2.PathHolder
import com.jetbrains.python.sdk.createSdk
import com.jetbrains.python.sdk.impl.resolvePythonBinary
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.persist
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -51,6 +51,6 @@ private fun PythonVirtualEnvironment.Existing.suggestHatchSdkName(): @NlsSafe St
val nonDefaultEnvName = pythonHomePath.name.takeIf { it != normalizedProjectName }
val envNamePrefix = nonDefaultEnvName?.let { "$it@" } ?: ""
val sdkName = "Hatch ($envNamePrefix$normalizedProjectName) [$pythonVersion]"
val sdkName = "Hatch ($envNamePrefix$normalizedProjectName) [${pythonInfo.languageLevel}]"
return sdkName
}
@@ -176,12 +176,12 @@ fun detectVirtualEnvs(module: Module?, existingSdks: List<Sdk>, context: UserDat
@Internal
fun filterSharedCondaEnvs(module: Module?, existingSdks: List<Sdk>): List<Sdk> {
return existingSdks.filter { PythonSdkUtil.isPythonSdk(it) && it.isCondaVirtualEnv && !it.isAssociatedWithAnotherModule(module) }
return existingSdks.filter { isPythonSdk(it) && it.isCondaVirtualEnv && !it.isAssociatedWithAnotherModule(module) }
}
@Internal
fun filterAssociatedSdks(module: Module, existingSdks: List<Sdk>): List<Sdk> {
return existingSdks.filter { PythonSdkUtil.isPythonSdk(it) && it.isAssociatedWithModule(module) }
return existingSdks.filter { isPythonSdk(it) && it.isAssociatedWithModule(module) }
}
@Internal
@@ -5,26 +5,21 @@ import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.platform.eel.isWindows
import com.intellij.platform.eel.provider.getEelDescriptor
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.ProcessOutputTransformer
import com.intellij.python.community.execService.ZeroCodeJsonParserTransformer
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
import com.intellij.python.community.execService.*
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetInfo
import com.intellij.util.ShellEnvironmentReader
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
import com.jetbrains.python.packaging.common.PythonPackage
import com.jetbrains.python.sdk.conda.execution.models.CondaEnvInfo
import com.jetbrains.python.sdk.configureBuilderToRunPythonOnTarget
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import com.jetbrains.python.sdk.runExecutableWithProgress
import com.jetbrains.python.sdk.targetEnvConfiguration
import com.jetbrains.python.target.PyTargetAwareAdditionalData
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
import java.nio.file.Path
@@ -105,17 +100,18 @@ object CondaExecutor {
)
}
private suspend fun <T> runConda(
binaryToExec: BinaryToExec,
args: List<String>,
condaEnvIdentity: PyCondaEnvIdentity?,
timeout: Duration = 15.minutes,
transformer: ProcessOutputTransformer<T>,
): PyResult<T> {
suspend fun getPythonInfo(binaryToExec: BinaryToExec, envIdentity: PyCondaEnvIdentity): PyResult<PythonInfo> {
val runArgs = prepareCondaRunArgs(listOf("run"), listOf("python"), envIdentity)
return ExecService().validatePythonAndGetInfo(ExecutablePython(binaryToExec, runArgs, emptyMap()))
}
private fun prepareCondaRunArgs(
argsBeforeEnv: List<String>, argsAfterEnv: List<String>, condaEnvIdentity: PyCondaEnvIdentity?,
): List<String> {
val condaEnv = when (condaEnvIdentity) {
is PyCondaEnvIdentity.UnnamedEnv -> {
if (condaEnvIdentity.isBase)
listOf()
emptyList()
else
listOf("-p", condaEnvIdentity.envPath)
}
@@ -125,11 +121,18 @@ object CondaExecutor {
null -> emptyList()
}
val envs = getFixedEnvs(binaryToExec).getOr {
return it
}
return argsBeforeEnv + condaEnv + argsAfterEnv
}
val runArgs = (args + condaEnv).toTypedArray()
private suspend fun <T> runConda(
binaryToExec: BinaryToExec,
args: List<String>,
condaEnvIdentity: PyCondaEnvIdentity?,
timeout: Duration = 15.minutes,
transformer: ProcessOutputTransformer<T>,
): PyResult<T> {
val envs = getFixedEnvs(binaryToExec).getOr { return it }
val runArgs = prepareCondaRunArgs(args, emptyList(), condaEnvIdentity).toTypedArray()
return runExecutableWithProgress(binaryToExec, timeout, env = envs, *runArgs, transformer = transformer)
}
@@ -8,6 +8,8 @@ import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.localEel
import com.intellij.platform.eel.where
import com.intellij.python.community.execService.ProcessOutputTransformer
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
import com.intellij.python.community.impl.pipenv.pipenvPath
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
@@ -26,9 +28,13 @@ import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.minutes
@Internal
suspend fun runPipEnv(dirPath: Path?, vararg args: String): PyResult<String> {
suspend fun runPipEnv(dirPath: Path?, vararg args: String): PyResult<String> =
runPipEnv(dirPath, args = args, transformer = ZeroCodeStdoutTransformer)
@Internal
suspend fun <T> runPipEnv(dirPath: Path?, vararg args: String, transformer: ProcessOutputTransformer<T>): PyResult<T> {
val executable = getPipEnvExecutable().getOr { return it }
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args)
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args, transformer = transformer)
}
/**