PY-87441 Use existing environment even if another tool fits better

Before the changes, if there's a multi-module project, we used
information about tools from pyproject.toml collected by workspace
tools. That led to an effect when an existing environment wouldn't be
used if it wasn't part of preferred tools.

This change prioritizes existing environment over anything else with the
only exception of uv workspaces.


(cherry picked from commit 5ae899ba4c2541cbb4eab5e393b4f3ca26e3f626)

IJ-MR-192963

GitOrigin-RevId: 001775336af7f155b7ecfc5871ce792afb054258
This commit is contained in:
Alexey Katsman
2026-02-23 17:40:43 +01:00
committed by intellij-monorepo-bot
parent b10d38571b
commit 3a6ae5cccf
2 changed files with 27 additions and 9 deletions

View File

@@ -39,16 +39,22 @@ suspend fun Module.getModuleInfo(
configuratorsByTool: Map<ToolId, PyProjectSdkConfigurationExtension> = PyProjectSdkConfigurationExtension.createMap(),
): ModuleCreateInfo? { // Save on module level
val venvsInModule = findPythonVirtualEnvironments()
val bestProposalFromTools = PyProjectSdkConfigurationExtension.findAllSortedForModule(this, venvsInModule).firstOrNull()
val suggestedByPyProjectToml = when (val suggestedSdk = suggestSdk()) {
is SuggestedSdk.PyProjectIndependent -> {
configuratorsByTool
.filter { it.key in suggestedSdk.preferTools }
.firstNotNullOfOrNull { (toolId, extension) ->
extension.asPyProjectTomlSdkConfigurationExtension()?.createSdkWithoutPyProjectTomlChecks(this, venvsInModule)?.let {
CreateSdkInfoWithTool(it, toolId).asDTO(suggestedSdk.moduleDir)
}
when (bestProposalFromTools?.createSdkInfo) {
is CreateSdkInfo.ExistingEnv -> bestProposalFromTools.asDTO(suggestedSdk.moduleDir)
is CreateSdkInfo.WillCreateEnv, is CreateSdkInfo.WillInstallTool, null -> {
configuratorsByTool
.filter { it.key in suggestedSdk.preferTools }
.firstNotNullOfOrNull { (toolId, extension) ->
extension.asPyProjectTomlSdkConfigurationExtension()?.createSdkWithoutPyProjectTomlChecks(this, venvsInModule)?.let {
CreateSdkInfoWithTool(it, toolId).asDTO(suggestedSdk.moduleDir)
}
}
}
}
}
is SuggestedSdk.SameAs -> {
ModuleCreateInfo.SameAs(suggestedSdk.parentModule)
@@ -58,8 +64,6 @@ suspend fun Module.getModuleInfo(
suggestedByPyProjectToml?.let { return it }
// No tools or not pyproject.toml at all? Use EP as a fallback
val bestProposalFromTools = PyProjectSdkConfigurationExtension.findAllSortedForModule(this, venvsInModule).firstOrNull()
return bestProposalFromTools?.let {
CreateSdkInfoWithTool(it.createSdkInfo, it.toolId).asDTO(guessModuleDir()?.toNioPath())
}

View File

@@ -8,6 +8,11 @@ import com.jetbrains.python.PythonBinary
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CheckReturnValue
@@ -34,6 +39,7 @@ suspend fun Module.findPythonVirtualEnvironments(): List<PythonBinary> {
interface PyProjectSdkConfigurationExtension {
companion object {
private val EP_NAME: ExtensionPointName<PyProjectSdkConfigurationExtension> = ExtensionPointName.create("Pythonid.projectSdkConfigurationExtension")
private val CONCURRENCY_LIMIT = Semaphore(permits = 5)
/**
* EPs associated by tool id
@@ -46,13 +52,21 @@ interface PyProjectSdkConfigurationExtension {
*/
suspend fun findAllSortedForModule(module: Module, venvsInModule: List<PythonBinary>): List<CreateSdkInfoWithTool> {
return EP_NAME.extensionsIfPointIsRegistered
.mapNotNull { e -> e.checkEnvironmentAndPrepareSdkCreator(module, venvsInModule)?.let { CreateSdkInfoWithTool(it, e.toolId) } }
.concurrentMapNotNull { e -> e.checkEnvironmentAndPrepareSdkCreator(module, venvsInModule)?.let { CreateSdkInfoWithTool(it, e.toolId) } }
.sortedBy { it.createSdkInfo }
}
suspend fun findAllSortedForModule(module: Module): List<CreateSdkInfoWithTool> {
return findAllSortedForModule(module, module.findPythonVirtualEnvironments())
}
private suspend fun <A, B> Iterable<A>.concurrentMapNotNull(f: suspend (A) -> B?): List<B> = coroutineScope {
map {
async {
CONCURRENCY_LIMIT.withPermit { f(it) }
}
}.awaitAll().filterNotNull()
}
}
val toolId: ToolId