PY-79486: (WIP) Replace old SDK configuration process with new one if enabled.

When registry key is enabled, we replace all other SDK configurations (done by inspection and open processors) with a new one.

We also have an action to start this process

GitOrigin-RevId: b6e624cc0390c6f668625fffdb6b308a1c903455
This commit is contained in:
Ilya.Kazakevich
2025-10-14 03:43:56 +00:00
committed by intellij-monorepo-bot
parent 3fa6653800
commit 9bd4dea5d7
26 changed files with 195 additions and 56 deletions
+1
View File
@@ -353,6 +353,7 @@ jvm_library(
"//python/impl.helperLocator:community-helpersLocator",
"//python/python-exec-service/execService.python",
"//python/interpreters",
"//python/python-sdk-configurator/common",
],
exports = [
"//python/openapi:community",
+2
View File
@@ -66,6 +66,7 @@ jvm_library(
"//python/common",
"//platform/non-modal-welcome-screen/backend",
"//python/python-exec-service:community-execService",
"//python/python-sdk-configurator/common",
],
runtime_deps = ["//python/python-features-trainer:featuresTrainer"],
plugins = ["@lib//:compose-plugin"]
@@ -138,6 +139,7 @@ jvm_library(
"//platform/non-modal-welcome-screen/backend",
"//python/python-exec-service:community-execService",
"//python/python-exec-service:community-execService_test_lib",
"//python/python-sdk-configurator/common",
],
plugins = ["@lib//:compose-plugin"]
)
@@ -80,5 +80,6 @@
<orderEntry type="module" module-name="intellij.python.common" />
<orderEntry type="module" module-name="intellij.platform.ide.nonModalWelcomeScreen.backend" />
<orderEntry type="module" module-name="intellij.python.community.execService" />
<orderEntry type="module" module-name="intellij.python.sdkConfigurator.common" />
</component>
</module>
@@ -6,6 +6,7 @@
<module name="intellij.platform.whatsNew"/>
<module name="intellij.platform.ide.nonModalWelcomeScreen"/>
<module name="intellij.platform.ide.nonModalWelcomeScreen.backend"/>
<module name="intellij.python.sdkConfigurator.common"/>
</dependencies>
<projectListeners>
@@ -6,6 +6,7 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
@@ -25,6 +26,7 @@ import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.platform.util.progress.reportRawProgress
import com.intellij.python.community.services.systemPython.SystemPython
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.sdkConfigurator.common.enableSDKAutoConfigurator
import com.jetbrains.python.PyBundle
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import com.jetbrains.python.sdk.*
@@ -41,6 +43,13 @@ import java.nio.file.Path
class PythonSdkConfigurator : DirectoryProjectConfigurator {
init {
// new SDK configurator obsoletes this engine
if (enableSDKAutoConfigurator) {
throw ExtensionNotApplicableException.create()
}
}
override fun configureProject(project: Project, baseDir: VirtualFile, moduleRef: Ref<Module>, isProjectCreatedWithWizard: Boolean) {
val sdk = project.pythonSdk
thisLogger().debug { "Input: $sdk, $isProjectCreatedWithWizard" }
@@ -188,5 +188,6 @@
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" />
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
<orderEntry type="module" module-name="intellij.python.community.interpreters" />
<orderEntry type="module" module-name="intellij.python.sdkConfigurator.common" />
</component>
</module>
@@ -19,6 +19,7 @@
<module name="intellij.python.community.impl.installer"/>
<module name="intellij.python.community.impl.venv"/>
<module name="intellij.python.community.services.systemPython"/>
<module name="intellij.python.sdkConfigurator.common"/>
</dependencies>
<resource-bundle>messages.PyBundle</resource-bundle>
@@ -7,6 +7,7 @@
<module name="intellij.platform.backend"/>
<module name="intellij.platform.rpc.backend"/>
</dependencies>
<resource-bundle>messages.PySdkConfiguratorBundle</resource-bundle>
<projectListeners>
<listener class="com.intellij.python.sdkConfigurator.backend.impl.platformBridge.ModelRebuiltListenerImpl"
topic="com.intellij.python.pyproject.model.api.ModelRebuiltListener"/>
@@ -14,4 +15,12 @@
<extensions defaultExtensionNs="com.intellij.platform">
<rpc.backend.remoteApiProvider implementation="com.intellij.python.sdkConfigurator.backend.impl.rpcBridge.ApiProvider"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<platform.rpc.projectRemoteTopicListener
implementation="com.intellij.python.sdkConfigurator.backend.impl.rpcBridge.BackendTopicListener"/>
</extensions>
<actions>
<action class="com.intellij.python.sdkConfigurator.backend.impl.platformBridge.ConfigureSDKAction" id="ConfigureSDKAction"/>
</actions>
</idea-plugin>
@@ -1 +1,3 @@
intellij.python.sdk.configuring.module=Configuring SDK for module {0}
intellij.python.sdk.configuring.module=Configuring SDK for module {0}
action.ConfigureSDKAction.text=Configure SDK Automatically
action.ConfigureSDKAction.description=Configure SDK for all modules in project
@@ -1,5 +1,8 @@
package com.intellij.python.sdkConfigurator.backend.impl
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
@@ -7,16 +10,38 @@ import com.intellij.openapi.project.modules
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.platform.rpc.topics.sendToClient
import com.intellij.python.pyproject.model.api.SuggestedSdk
import com.intellij.python.pyproject.model.api.suggestSdk
import com.intellij.python.sdkConfigurator.common.ModuleName
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
import com.intellij.python.sdkConfigurator.common.impl.SHOW_SDK_CONFIG_UI_TOPIC
import com.jetbrains.python.Result
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import com.jetbrains.python.sdk.setAssociationToPath
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
private val askUserMutex = Mutex()
// TODO: DOC
internal fun configureSdkAskingUser(project: Project) {
project.service<MyService>().scope.launch(Dispatchers.Default) {
askUserMutex.withLock {
val moduleToSuggestedSdk = getModulesWithoutSDK(project)
if (moduleToSuggestedSdk.modules.isNotEmpty()) {
// No need to send empty list
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, moduleToSuggestedSdk)
}
}
}
}
/**
* Configures SDK for modules without SDK in automatic manner trying to fix as many modules as possible.
* Errors are logged.
@@ -85,6 +110,15 @@ internal suspend fun configureSdkAutomatically(project: Project, modulesOnly: Se
}
}
private suspend fun getModulesWithoutSDK(project: Project): ModulesDTO =
ModulesDTO(project.modules.filter { ModuleRootManager.getInstance(it).sdk == null }.associate { module ->
val parent = when (val r = module.suggestSdk()) {
null, is SuggestedSdk.PyProjectIndependent -> null
is SuggestedSdk.SameAs -> r.parentModule
}
Pair(module.name, parent?.name)
})
private suspend fun configureSdkForModule(module: Module, configurators: List<PyProjectSdkConfigurationExtension>, checkForIntention: Boolean): Boolean {
for (extension in configurators) {
if (checkForIntention && extension.getIntention(module) == null) {
@@ -107,4 +141,8 @@ private suspend fun configureSdkForModule(module: Module, configurators: List<Py
return false
}
private val logger = fileLogger()
private val logger = fileLogger()
@Service(Level.PROJECT)
private class MyService(val scope: CoroutineScope)
@@ -0,0 +1,26 @@
package com.intellij.python.sdkConfigurator.backend.impl.platformBridge
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAskingUser
import com.intellij.python.sdkConfigurator.common.enableSDKAutoConfigurator
internal class ConfigureSDKAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
if (!enableSDKAutoConfigurator) {
return
}
configureSdkAskingUser(project)
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null && enableSDKAutoConfigurator
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
}
@@ -1,43 +1,21 @@
package com.intellij.python.sdkConfigurator.backend.impl.platformBridge
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.platform.rpc.topics.sendToClient
import com.intellij.python.pyproject.model.api.ModelRebuiltListener
import com.intellij.python.pyproject.model.api.SuggestedSdk
import com.intellij.python.pyproject.model.api.suggestSdk
import com.intellij.python.sdkConfigurator.common.ModulesDTO
import com.intellij.python.sdkConfigurator.common.SHOW_SDK_CONFIG_UI_TOPIC
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAskingUser
import com.intellij.python.sdkConfigurator.common.enableSDKAutoConfigurator
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
internal class ModelRebuiltListenerImpl : ModelRebuiltListener {
private val mutex = Mutex()
override fun modelRebuilt(project: Project) {
project.service<MyService>().scope.launch(Dispatchers.Default) {
mutex.withLock {
// TODO: Extract to lib
val moduleToSuggestedSdk = project.modules.filter { ModuleRootManager.getInstance(it).sdk == null }.associate { module ->
val parent = when (val r = module.suggestSdk()) {
null, is SuggestedSdk.PyProjectIndependent -> null
is SuggestedSdk.SameAs -> r.parentModule
}
Pair(module.name, parent?.name)
}
if (moduleToSuggestedSdk.isNotEmpty()) {
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, ModulesDTO(moduleToSuggestedSdk))
}
}
init {
if (!enableSDKAutoConfigurator) {
throw ExtensionNotApplicableException.create()
}
}
override fun modelRebuilt(project: Project) {
configureSdkAskingUser(project)
}
}
@Service(Level.PROJECT)
private class MyService(val scope: CoroutineScope)
@@ -1,13 +1,13 @@
package com.intellij.python.sdkConfigurator.backend.impl.rpcBridge
import com.intellij.platform.rpc.backend.RemoteApiProvider
import com.intellij.python.sdkConfigurator.common.SdkConfiguratorApi
import com.intellij.python.sdkConfigurator.common.impl.SdkConfiguratorBackEndApi
import fleet.rpc.remoteApiDescriptor
internal class ApiProvider : RemoteApiProvider {
override fun RemoteApiProvider.Sink.remoteApis() {
remoteApi(remoteApiDescriptor<SdkConfiguratorApi>()) {
remoteApi(remoteApiDescriptor<SdkConfiguratorBackEndApi>()) {
SdkConfiguratorApiImpl
}
}
@@ -0,0 +1,15 @@
package com.intellij.python.sdkConfigurator.backend.impl.rpcBridge
import com.intellij.openapi.project.Project
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
import com.intellij.platform.rpc.topics.ProjectRemoteTopicListener
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAskingUser
import com.intellij.python.sdkConfigurator.common.impl.DETECT_SDK_FOR_MODULES
internal class BackendTopicListener : ProjectRemoteTopicListener<Unit> {
override val topic: ProjectRemoteTopic<Unit> = DETECT_SDK_FOR_MODULES
override fun handleEvent(project: Project, event: Unit) {
configureSdkAskingUser(project)
}
}
@@ -3,10 +3,10 @@ package com.intellij.python.sdkConfigurator.backend.impl.rpcBridge
import com.intellij.platform.project.ProjectId
import com.intellij.platform.project.findProject
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAutomatically
import com.intellij.python.sdkConfigurator.common.ModuleName
import com.intellij.python.sdkConfigurator.common.SdkConfiguratorApi
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
import com.intellij.python.sdkConfigurator.common.impl.SdkConfiguratorBackEndApi
internal object SdkConfiguratorApiImpl : SdkConfiguratorApi {
internal object SdkConfiguratorApiImpl : SdkConfiguratorBackEndApi {
override suspend fun configureSdkAutomatically(projectId: ProjectId, onlyModules: Set<ModuleName>) {
configureSdkAutomatically(projectId.findProject(), onlyModules)
}
@@ -1,2 +1,5 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<registryKey defaultValue="false" description="Configure SDK for modules which lack thereof in automatic manner" key="intellij.python.sdkConfigurator.auto" restartRequired="false"/>
</extensions>
</idea-plugin>
@@ -1,4 +1,4 @@
package com.intellij.python.sdkConfigurator.common
package com.intellij.python.sdkConfigurator.common.impl
import com.intellij.platform.project.ProjectId
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
@@ -6,8 +6,11 @@ import fleet.rpc.RemoteApi
import fleet.rpc.Rpc
import kotlinx.serialization.Serializable
/**
* Front calls back
*/
@Rpc
interface SdkConfiguratorApi : RemoteApi<Unit> {
interface SdkConfiguratorBackEndApi : RemoteApi<Unit> {
/***
* Configure SDK for all modules in [projectId] if their names in [onlyModules]
*/
@@ -17,7 +20,7 @@ interface SdkConfiguratorApi : RemoteApi<Unit> {
typealias ModuleName = String
/**
* Ask user to choose from [ModulesDTO] and then call [SdkConfiguratorApi.configureSdkAutomatically]
* Ask user to choose from [ModulesDTO] and then call [SdkConfiguratorBackEndApi.configureSdkAutomatically]
*/
val SHOW_SDK_CONFIG_UI_TOPIC: ProjectRemoteTopic<ModulesDTO> = ProjectRemoteTopic("PySDKConfigurationUITopic", ModulesDTO.serializer())
@@ -0,0 +1,10 @@
package com.intellij.python.sdkConfigurator.common.impl
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
import kotlinx.serialization.builtins.serializer
/**
* Backend listens for this topic to start SDK detection process
*/
val DETECT_SDK_FOR_MODULES: ProjectRemoteTopic<Unit> = ProjectRemoteTopic("PySDKConfigurationDetectSDKTopic", Unit.serializer())
@@ -0,0 +1,7 @@
/**
* Front and back shared classes, do not use outside of "sdkConfigurator"
*/
@ApiStatus.Internal
package com.intellij.python.sdkConfigurator.common.impl;
import org.jetbrains.annotations.ApiStatus;
@@ -0,0 +1,24 @@
package com.intellij.python.sdkConfigurator.common
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.rpc.topics.sendToClient
import com.intellij.python.sdkConfigurator.common.impl.DETECT_SDK_FOR_MODULES
/**
* New SDK configurator enabled
*/
val enableSDKAutoConfigurator: Boolean get() = Registry.`is`("intellij.python.sdkConfigurator.auto")
/**
* Check if [enableSDKAutoConfigurator] (return `false` if not) and start detecting process.
* it might ask user for list of modules and then detect them
*/
fun detectSdkForModulesIn(project: Project): Boolean {
if (!enableSDKAutoConfigurator) {
return false
}
DETECT_SDK_FOR_MODULES.sendToClient(project, Unit)
return true
}
@@ -12,6 +12,6 @@
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<platform.rpc.projectRemoteTopicListener
implementation="com.intellij.python.sdkConfigurator.frontend.rpcBridge.TopicListener"/>
implementation="com.intellij.python.sdkConfigurator.frontend.rpcBridge.FrontendTopicListener"/>
</extensions>
</idea-plugin>
@@ -2,8 +2,8 @@ package com.intellij.python.sdkConfigurator.frontend
import androidx.compose.runtime.mutableStateSetOf
import androidx.compose.runtime.snapshots.SnapshotStateSet
import com.intellij.python.sdkConfigurator.common.ModuleName
import com.intellij.python.sdkConfigurator.common.ModulesDTO
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentListOf
@@ -7,13 +7,13 @@ import com.intellij.openapi.project.Project
import com.intellij.platform.project.projectId
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
import com.intellij.platform.rpc.topics.ProjectRemoteTopicListener
import com.intellij.python.sdkConfigurator.common.ModulesDTO
import com.intellij.python.sdkConfigurator.common.SHOW_SDK_CONFIG_UI_TOPIC
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
import com.intellij.python.sdkConfigurator.common.impl.SHOW_SDK_CONFIG_UI_TOPIC
import com.intellij.python.sdkConfigurator.frontend.askUser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
internal class TopicListener : ProjectRemoteTopicListener<ModulesDTO> {
internal class FrontendTopicListener : ProjectRemoteTopicListener<ModulesDTO> {
override val topic: ProjectRemoteTopic<ModulesDTO> = SHOW_SDK_CONFIG_UI_TOPIC
override fun handleEvent(project: Project, event: ModulesDTO) {
@@ -1,9 +1,9 @@
package com.intellij.python.sdkConfigurator.frontend.rpcBridge
import com.intellij.platform.rpc.RemoteApiProviderService
import com.intellij.python.sdkConfigurator.common.SdkConfiguratorApi
import com.intellij.python.sdkConfigurator.common.impl.SdkConfiguratorBackEndApi
import fleet.rpc.remoteApiDescriptor
internal suspend fun getBackendApi() = RemoteApiProviderService.resolve(remoteApiDescriptor<SdkConfiguratorApi>())
internal suspend fun getBackendApi() = RemoteApiProviderService.resolve(remoteApiDescriptor<SdkConfiguratorBackEndApi>())
@@ -3,8 +3,8 @@ package com.intellij.python.sdkConfigurator.frontend
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.python.sdkConfigurator.common.ModuleName
import com.intellij.python.sdkConfigurator.common.ModulesDTO
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
import com.intellij.python.sdkConfigurator.frontend.components.ModuleList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -45,7 +45,10 @@ import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.sdk.*;
import com.jetbrains.python.sdk.PyDetectedSdk;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PySdkPopupFactory;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer;
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration;
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension;
@@ -69,6 +72,9 @@ import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.python.sdkConfigurator.common.PublicApiKt.detectSdkForModulesIn;
public final class PyInterpreterInspection extends PyInspection {
private static final @NotNull Logger LOGGER = Logger.getInstance(PyInterpreterInspection.class);
@@ -332,7 +338,7 @@ public final class PyInterpreterInspection extends PyInspection {
private final @Nullable Module myModule;
public InterpreterSettingsQuickFix(@Nullable Module module) {
public InterpreterSettingsQuickFix(@Nullable Module module) {
myModule = module;
}
@@ -429,7 +435,9 @@ public final class PyInterpreterInspection extends PyInspection {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension);
if (! detectSdkForModulesIn(project)) {
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension);
}
}
@Override