[eel][sdk] IJPL-214464 Resolve SDKs via environment‑scoped lookup interface

Introduce `EnvironmentScopedProjectJdkLookup` interface for environment‑aware SDK resolution and make `ProjectJdkTableImpl` implement it. Add `findJdk(String, InternalEnvironmentName)` and `findJdk(String, String, InternalEnvironmentName)` methods that delegate to `SdkTableImplementationDelegate#findSdkByName(String, InternalEnvironmentName)`. Update `ProjectJdkTableProjectView.findJdk(...)` (both overloads) to use the interface methods with environment from the project descriptor. This enables per‑environment SDK separation, reduces unnecessary cross‑environment lookups, and improves correctness without changing external behavior.


(cherry picked from commit 73fefa0c54c2156e824da9f081f70efea262b933)

IJ-CR-180956

GitOrigin-RevId: 160cfcffeea6209e974d137a0cc144d52170c62b
This commit is contained in:
Alexander Koshevoy
2025-10-28 17:54:20 +01:00
committed by intellij-monorepo-bot
parent 21a271d2a1
commit be70b2c8c7
10 changed files with 91 additions and 16 deletions

View File

@@ -58,6 +58,7 @@ jvm_library(
"//platform/xdebugger-testFramework:debugger-testFramework",
"//xml/xml-psi-api:psi",
"//java/java-syntax:syntax",
"//platform/eel",
],
exports = [
"//java/testFramework/shared",

View File

@@ -55,6 +55,7 @@
<orderEntry type="module" module-name="intellij.platform.debugger.testFramework" />
<orderEntry type="module" module-name="intellij.xml.psi" />
<orderEntry type="module" module-name="intellij.java.syntax" />
<orderEntry type="module" module-name="intellij.platform.eel" />
</component>
<component name="copyright">
<Base>

View File

@@ -0,0 +1,28 @@
// 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.openapi.projectRoots.impl
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.platform.eel.EelDescriptor
import org.jetbrains.annotations.ApiStatus
/**
* Provides environment-aware SDK lookup operations for optimized SDK resolution across isolated environments.
*
* This interface enables efficient SDK lookups that are scoped to specific environments (such as local, WSL,
* Docker containers, etc.) without requiring retrieval and filtering of all available SDKs. Implementations
* should leverage environment-specific SDK storage to avoid unnecessary cross-environment operations.
*
* The primary benefit over using [com.intellij.openapi.projectRoots.ProjectJdkTable.getAllJdks] and manual
* filtering is performance optimization: instead of retrieving all SDKs from all environments and then
* filtering by environment, this interface allows direct lookup within the target environment's SDK
* namespace.
*
* @see com.intellij.openapi.projectRoots.ProjectJdkTable
* @see EelDescriptor
*/
@ApiStatus.Internal
interface EnvironmentScopedProjectJdkLookup {
fun findJdk(name: String, eelDescriptor: EelDescriptor): Sdk?
fun findJdk(name: String, type: String, eelDescriptor: EelDescriptor): Sdk?
}

View File

@@ -16,17 +16,21 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkType
import com.intellij.openapi.projectRoots.SdkTypeId
import com.intellij.openapi.util.Disposer
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.workspace.jps.serialization.impl.JpsGlobalEntitiesSerializers
import com.intellij.platform.workspace.storage.InternalEnvironmentName
import com.intellij.serviceContainer.ComponentManagerImpl
import com.intellij.workspaceModel.ide.impl.getInternalEnvironmentName
import com.intellij.workspaceModel.ide.impl.legacyBridge.sdk.SdkTableBridgeImpl
import com.intellij.workspaceModel.ide.legacyBridge.sdk.SdkTableImplementationDelegate
import org.jdom.Element
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.TestOnly
// This annotation is needed only for support of the "export settings" action
@State(name = "ProjectJdkTable", storages = [Storage(value = JpsGlobalEntitiesSerializers.SDK_FILE_NAME + DEFAULT_EXT)], presentableName = ProjectJdkTableImpl.PresentableNameGetter::class)
open class ProjectJdkTableImpl: ProjectJdkTable() {
open class ProjectJdkTableImpl: ProjectJdkTable(), EnvironmentScopedProjectJdkLookup {
private val delegate: SdkTableImplementationDelegate
@@ -52,24 +56,44 @@ open class ProjectJdkTableImpl: ProjectJdkTable() {
override fun findJdk(name: String): Sdk? = delegate.findSdkByName(name)
override fun findJdk(name: String, type: String): Sdk? {
var sdk = findJdk(name)
val sdk = findJdk(name)
if (sdk != null) return sdk
return getCachedJdkOrTryCreateJdkUsingSystemProperty(name, type, InternalEnvironmentName.Local)
}
@ApiStatus.Internal
override fun findJdk(name: String, eelDescriptor: EelDescriptor): Sdk? {
return delegate.findSdkByName(name, environmentName = eelDescriptor.machine.getInternalEnvironmentName())
}
@ApiStatus.Internal
override fun findJdk(name: String, type: String, eelDescriptor: EelDescriptor): Sdk? {
val environmentName = eelDescriptor.machine.getInternalEnvironmentName()
val sdk = delegate.findSdkByName(name, environmentName)
if (sdk != null) return sdk
return getCachedJdkOrTryCreateJdkUsingSystemProperty(name, type, environmentName)
}
private fun getCachedJdkOrTryCreateJdkUsingSystemProperty(name: String, type: String, environmentName: InternalEnvironmentName): Sdk? {
val uniqueName = "$type.$name"
sdk = cachedProjectJdks[uniqueName]
val sdk = cachedProjectJdks[uniqueName]
if (sdk != null) return sdk
return when (environmentName) {
InternalEnvironmentName.Local -> {
val sdkPath = System.getProperty("jdk.$name")
if (sdkPath == null) return null
val sdkPath = System.getProperty("jdk.$name")
if (sdkPath == null) return null
val sdkType = SdkType.findByName(type)
if (sdkType != null && sdkType.isValidSdkHome(sdkPath)) {
val createdSdk = delegate.createSdk(name, sdkType, sdkPath)
sdkType.setupSdkPaths(createdSdk)
cachedProjectJdks[uniqueName] = createdSdk
return createdSdk
val sdkType = SdkType.findByName(type)
if (sdkType != null && sdkType.isValidSdkHome(sdkPath)) {
val createdSdk = delegate.createSdk(name, sdkType, sdkPath)
sdkType.setupSdkPaths(createdSdk)
cachedProjectJdks[uniqueName] = createdSdk
return createdSdk
}
return null
}
is InternalEnvironmentName.Custom -> null
}
return null
}
override fun getAllJdks(): Array<Sdk> = delegate.getAllSdks().toTypedArray()

View File

@@ -51,12 +51,18 @@ private class SdkTableProjectViewProviderImpl(project: Project) : SdkTableProjec
private class ProjectJdkTableProjectView(val descriptor: EelDescriptor, val delegate: ProjectJdkTable) : ProjectJdkTable() {
override fun findJdk(name: String): Sdk? {
if (delegate is EnvironmentScopedProjectJdkLookup) {
return delegate.findJdk(name, descriptor)
}
return delegate.allJdks.find {
it.name == name && validateDescriptor(it)
}
}
override fun findJdk(name: String, type: String): Sdk? {
if (delegate is EnvironmentScopedProjectJdkLookup) {
return delegate.findJdk(name, type, descriptor)
}
// sometimes delegate.findJdk can do mutating operations, like in the case of ProjectJdkTableImpl
return delegate.allJdks.find { it.name == name && it.sdkType.name == type && validateDescriptor(it) } ?: delegate.findJdk(name, type)
}

View File

@@ -14,6 +14,7 @@ import com.intellij.platform.workspace.jps.entities.SdkEntity
import com.intellij.platform.workspace.jps.entities.SdkRoot
import com.intellij.platform.workspace.jps.entities.SdkRootTypeId
import com.intellij.platform.workspace.jps.entities.modifySdkEntity
import com.intellij.platform.workspace.storage.InternalEnvironmentName
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.workspaceModel.ide.JpsGlobalModelSynchronizer
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
@@ -48,6 +49,14 @@ class SdkTableBridgeImpl: SdkTableImplementationDelegate {
return null
}
override fun findSdkByName(name: String, environmentName: InternalEnvironmentName): Sdk? {
val globalWorkspaceModel = GlobalWorkspaceModel.getInstanceByEnvironmentName(environmentName)
val currentSnapshot = globalWorkspaceModel.currentSnapshot
val sdkEntity = currentSnapshot.entities(SdkEntity::class.java)
.firstOrNull { Comparing.strEqual(name, it.name) } ?: return null
return currentSnapshot.sdkMap.getDataByEntity(sdkEntity)
}
override fun getAllSdks(): List<Sdk> {
val globalWorkspaceModels = GlobalWorkspaceModel.getInstancesBlocking()
return globalWorkspaceModels.flatMap {

View File

@@ -79,7 +79,7 @@ internal class GlobalWorkspaceModelCacheImpl(coroutineScope: CoroutineScope) : G
private suspend fun doCacheSaving() {
cacheFiles.entries.forEachConcurrent { (id, cacheFile) ->
val storage = GlobalWorkspaceModel.getInstanceByEnvironmentName(InternalEnvironmentName.of(id)).currentSnapshot
val storage = GlobalWorkspaceModel.getInstanceByEnvironmentNameAsync(InternalEnvironmentName.of(id)).currentSnapshot
if (!storage.isConsistent) {
invalidateCaches()
}

View File

@@ -221,7 +221,7 @@ open class JpsGlobalModelSynchronizerImpl(private val coroutineScope: CoroutineS
@VisibleForTesting
protected open suspend fun delayLoadGlobalWorkspaceModel(environmentName: InternalEnvironmentName) {
val globalWorkspaceModel = GlobalWorkspaceModel.getInstanceByEnvironmentName(environmentName)
val globalWorkspaceModel = GlobalWorkspaceModel.getInstanceByEnvironmentNameAsync(environmentName)
if (loadedFromDisk[environmentName] == true || !globalWorkspaceModel.loadedFromCache) {
return
}

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkTypeId
import com.intellij.platform.workspace.storage.InternalEnvironmentName
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
@@ -19,6 +20,7 @@ interface SdkTableImplementationDelegate {
fun getAllSdks(): List<Sdk>
fun findSdkByName(name: String): Sdk?
fun findSdkByName(name: String, environmentName: InternalEnvironmentName): Sdk?
@TestOnly
fun saveOnDisk()

View File

@@ -362,7 +362,11 @@ class GlobalWorkspaceModel internal constructor(
return ApplicationManager.getApplication().serviceAsync<GlobalWorkspaceModelRegistry>().getGlobalModel(eelMachine)
}
suspend fun getInstanceByEnvironmentName(environmentName: InternalEnvironmentName): GlobalWorkspaceModel {
fun getInstanceByEnvironmentName(environmentName: InternalEnvironmentName): GlobalWorkspaceModel {
return ApplicationManager.getApplication().service<GlobalWorkspaceModelRegistry>().getGlobalModelByEnvironmentName(environmentName)
}
suspend fun getInstanceByEnvironmentNameAsync(environmentName: InternalEnvironmentName): GlobalWorkspaceModel {
return ApplicationManager.getApplication().serviceAsync<GlobalWorkspaceModelRegistry>().getGlobalModelByEnvironmentName(environmentName)
}