mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] PY-83881 Provide python info during environment detection
GitOrigin-RevId: 4e424c8fa9c08ce3cd497586f5f13e5a5816f52a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8ae63f6126
commit
ea82fe5cc2
@@ -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>
|
||||
|
||||
+10
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -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
|
||||
|
||||
+2
-1
@@ -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
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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
-1
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user