[python] PY-83881 Detect existing environments when creating SDK

Before the changes, there wasn't any mechanism to detect that
environment was already created (for example, .venv exists in the
project). In these situations, during SDK creation we could've created
another environment which was not expected by users.

With these changes, it's now possible to detect in the configurator that
environment already exists, and use it when creating SDK.

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

GitOrigin-RevId: dd0cf0c02b18e90022e9ec828b7f9ad2282cd5b3
This commit is contained in:
Alexey Katsman
2025-10-21 21:47:11 +00:00
committed by intellij-monorepo-bot
parent d511ce0919
commit cc191a617f
54 changed files with 886 additions and 418 deletions
+1
View File
@@ -56,6 +56,7 @@ jvm_library(
"//python/openapi:community",
"//python/openapi:community_test_lib",
"//python/poetry",
"//python/pipenv",
"//python/setup-test-environment:community-testFramework-testEnv",
"//python/python-sdk:sdk",
"//python/python-sdk:sdk_test_lib",
@@ -39,6 +39,7 @@
<orderEntry type="module" module-name="intellij.platform.util.coroutines" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.impl.poetry" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.impl.pipenv" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.plugin" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.python.community.testFramework.testEnv" exported="" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.sdk" scope="TEST" />
@@ -5,6 +5,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessNotCreatedException
import com.intellij.ide.util.PropertiesComponent
import com.intellij.python.community.impl.pipenv.pipenvPath
import com.intellij.python.community.impl.poetry.poetryPath
import com.intellij.python.community.testFramework.testEnv.PythonType
import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3
@@ -27,32 +28,13 @@ internal class VanillaPythonEnvExtension : PythonEnvExtensionBase<PythonBinary,
additionalTags = arrayOf("poetry")
) {
private companion object {
val checkedPoetries = mutableMapOf<Path, Unit>()
val checkedTools = mutableMapOf<String, MutableSet<Path>>()
}
override fun onEnvFound(env: PythonBinary) {
val poetry = env.resolvePythonHome().resolvePythonTool("poetry")
if (poetry !in checkedPoetries) {
val output = try {
CapturingProcessHandler(GeneralCommandLine(poetry.toString(), "--version")).runProcess(60_000, true)
}
catch (e: ProcessNotCreatedException) {
val customPythonMessage = buildString {
PythonType.customPythonMessage?.let {
append(it)
append(" install poetry there, i.e: 'python -m pip install poetry' ")
}
append(" or run/rerun ")
append(PythonType.BUILD_KTS_MESSAGE)
}
throw AssertionError(customPythonMessage, e)
}
assert(output.exitCode == 0) { "$poetry seems to be broken, output: $output. For Windows check `fix_path.cmd`" }
LOG.info("Poetry found at $poetry")
checkedPoetries[poetry] = Unit
}
// There is no API that accepts path to poetry: only this global object is used
PropertiesComponent.getInstance().poetryPath = poetry.toString()
// There is no API that accepts path to poetry or pipenv: only this global object is used
PropertiesComponent.getInstance().poetryPath = checkAndGetToolPath(env, "poetry", true)
PropertiesComponent.getInstance().pipenvPath = checkAndGetToolPath(env, "pipenv", false)
val uv = env.resolvePythonHome().resolvePythonTool("uv")
PropertiesComponent.getInstance().setValue(
@@ -60,4 +42,35 @@ internal class VanillaPythonEnvExtension : PythonEnvExtensionBase<PythonBinary,
uv.toString()
)
}
private fun checkAndGetToolPath(env: PythonBinary, toolName: String, toThrow: Boolean): String? {
val tool = env.resolvePythonHome().resolvePythonTool(toolName)
if (checkedTools[toolName]?.contains(tool) != true) {
val output = try {
CapturingProcessHandler(GeneralCommandLine(tool.toString(), "--version")).runProcess(60_000, true)
}
catch (e: ProcessNotCreatedException) {
val customPythonMessage = buildString {
PythonType.customPythonMessage?.let {
append(it)
append(" install ${toolName} there, i.e: 'python -m pip install ${toolName}' ")
}
append(" or run/rerun ")
append(PythonType.BUILD_KTS_MESSAGE)
}
if (toThrow) {
throw AssertionError(customPythonMessage, e)
}
else {
LOG.error(customPythonMessage)
return null
}
}
assert(output.exitCode == 0) { "$tool seems to be broken, output: $output. For Windows check `fix_path.cmd`" }
LOG.info("${toolName} found at $tool")
checkedTools.compute(toolName) { _, v -> (v ?: mutableSetOf()).also { it.add(tool) } }
}
return tool.toString()
}
}