[eel] RUBY-34864 Keep WSL remote interpreters for local projects when per-environment separation is OFF

- Honor `ide.workspace.model.per.environment.model.separation` being OFF by attributing SDK entities to the local environment (`LocalEelDescriptor`) and by not filtering out SDK bridge events across environments.
- `getEffectiveWorkspaceEelDescriptorOfHomePath(...)`: returns `LocalEelDescriptor` when the separation flag is disabled; falls back to local on invalid paths.
- `SdkEntity.getAssociatedEffectiveWorkspaceEelDescriptor()`: mirrors the same OFF-mode behavior so SDKs aren’t partitioned away from RubyMine’s shared global model.
- `GlobalSdkBridgesLoader`: initializes bridges and processes SDK changes even when entities originate from different environments if separation is OFF; only filters by environment when the flag is ON.

(cherry picked from commit 7d2f82e4f3a64a8b6bbcad1d59037ccfbbe0449f)

IJ-MR-182760

GitOrigin-RevId: 3cfc15a689e2a69d1c7815da62009e12369eee90
This commit is contained in:
Alexander Koshevoy
2025-11-19 16:50:46 +00:00
committed by intellij-monorepo-bot
parent 3173ef4fdd
commit 5e937826db
3 changed files with 69 additions and 9 deletions
@@ -71,7 +71,7 @@ private class ProjectJdkTableProjectView(val descriptor: EelDescriptor, val dele
}
private fun validateDescriptor(sdk: Sdk): Boolean {
val sdkDescriptor = sdk.homePath?.let { getEelDescriptorOfHomePath(it) }
val sdkDescriptor = sdk.homePath?.let { getEffectiveWorkspaceEelDescriptorOfHomePath(it) }
return if (sdkDescriptor == null) {
true
}
@@ -7,6 +7,7 @@ package com.intellij.openapi.projectRoots.impl
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkTypeId
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.provider.LocalEelDescriptor
import com.intellij.platform.eel.provider.LocalEelMachine
@@ -23,13 +24,46 @@ fun findClashingSdk(sdkName: String, sdk: Sdk): SdkEntity? {
return relevantSnapshot.entities(SdkEntity::class.java).find { it.name == sdkName }
}
fun getEelDescriptorOfHomePath(homePath: String): EelDescriptor =
try {
/**
* Returns the [EelDescriptor] that the Workspace Model should use for SDK entities
* associated with the given `homePath`.
*
* This helper abstracts the current "per-environment" separation of the global
* workspace model by EEL descriptors:
*
* - If the registry key `ide.workspace.model.per.environment.model.separation` is ON,
* the descriptor is inferred from `homePath` via `Path.getEelDescriptor()` and thus
* points to the machine that actually owns the path (e.g., local OS, a specific WSL
* distribution, a remote/container environment).
* - If the registry key is OFF, the local machine descriptor ([LocalEelDescriptor]) is
* always returned so that SDKs from different environments are kept together in a
* single, shared model. This escape hatch is required by IDEs that expect certain
* remote interpreters (e.g., WSL) to be visible from both local and remote projects,
* where strict separation would otherwise break discovery or reuse.
* - If `homePath` cannot be parsed into a valid [Path], the method falls back to
* [LocalEelDescriptor].
*
* The returned descriptor determines which instance of [GlobalWorkspaceModel] will hold
* the corresponding [SdkEntity] records, preventing cross-environment linkage issues
* when separation is enabled, while preserving legacy behavior when it is disabled.
*
* @param homePath a string path used solely to infer the owning environment; it does not
* need to exist locally and is not validated beyond basic path parsing
* @return the effective environment ([EelDescriptor]) to use for workspace model operations
* @see com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
* @see com.intellij.platform.eel.provider.getEelDescriptor
*/
fun getEffectiveWorkspaceEelDescriptorOfHomePath(homePath: String): EelDescriptor {
if (!Registry.`is`("ide.workspace.model.per.environment.model.separation")) {
return LocalEelDescriptor
}
return try {
Path.of(homePath).getEelDescriptor()
}
catch (_: InvalidPathException) {
LocalEelDescriptor
}
}
/**
* Creates an SDK in the [GlobalWorkspaceModel] that matches the eek environment inferred
@@ -61,7 +95,7 @@ fun ProjectJdkTable.createSdkForEnvironment(
homePathForEnvironmentDetection: String,
): Sdk =
if (this is EnvironmentScopedSdkTableOps) {
val eelDescriptor = getEelDescriptorOfHomePath(homePathForEnvironmentDetection)
val eelDescriptor = getEffectiveWorkspaceEelDescriptorOfHomePath(homePathForEnvironmentDetection)
createSdk(name, sdkType, eelDescriptor)
}
else {
@@ -26,13 +26,39 @@ import com.intellij.workspaceModel.ide.toPath
import java.nio.file.InvalidPathException
import java.util.concurrent.ConcurrentHashMap
private fun SdkEntity.getAssociatedEelDescriptor(): EelDescriptor =
try {
/**
* Resolves the effective [EelDescriptor] (environment) that the Global Workspace Model
* should associate with this [SdkEntity].
*
* Behavior depends on the per-environment model separation flag
* (`ide.workspace.model.per.environment.model.separation`):
*
* - When separation is ON, the environment is inferred from `homePath`, so the SDK is attributed
* to the machine that actually owns the path (e.g., local OS, a particular WSL distribution,
* or a remote/container environment).
* - When separation is OFF, [LocalEelDescriptor] is always returned so SDKs from different
* environments are kept together in a single, shared global model (compatibility mode for IDEs
* that expect cross-environment SDK visibility).
* - If `homePath` is `null`, cannot be converted to a path, or throws [InvalidPathException],
* the method falls back to [LocalEelDescriptor].
*
* The resolved descriptor is used to:
* - decide which [com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel] instance
* (per environment) should store the SDK entity;
* - filter which per-environment bridges should create/load SDK bridges, preventing unrelated
* environments from processing changes.
*/
private fun SdkEntity.getAssociatedEffectiveWorkspaceEelDescriptor(): EelDescriptor {
if (!Registry.`is`("ide.workspace.model.per.environment.model.separation")) {
return LocalEelDescriptor
}
return try {
homePath?.toPath()?.getEelDescriptor() ?: LocalEelDescriptor
}
catch (_: InvalidPathException) {
LocalEelDescriptor
}
}
private class GlobalSdkBridgeInitializer : BridgeInitializer {
override fun isEnabled(): Boolean = true
@@ -45,7 +71,7 @@ private class GlobalSdkBridgeInitializer : BridgeInitializer {
val environmentName = projectEelDescriptor.machine.getInternalEnvironmentName()
for (addChange in addChanges) {
if (addChange.newEntity.getAssociatedEelDescriptor() != projectEelDescriptor) continue
if (addChange.newEntity.getAssociatedEffectiveWorkspaceEelDescriptor() != projectEelDescriptor) continue
// Will initialize the bridge if missing
builder.mutableSdkMap.getOrPutDataByEntity(addChange.newEntity) {
val sdkEntityCopy = SdkBridgeImpl.createEmptySdkEntity("", "", environmentName = environmentName)
@@ -63,7 +89,7 @@ private class GlobalSdkBridgesLoader(private val eelMachine: EelMachine) : Globa
val sdks = mutableStorage
.entities(SdkEntity::class.java)
.filter { mutableStorage.sdkMap.getDataByEntity(it) == null }
.filter { it.getAssociatedEelDescriptor().machine == eelMachine }
.filter { it.getAssociatedEffectiveWorkspaceEelDescriptor().machine == eelMachine }
.map { sdkEntity ->
val sdkEntityBuilder = sdkEntity.createEntityTreeCopy(false) as SdkEntityBuilder
sdkEntity to ProjectJdkImpl(SdkBridgeImpl(sdkEntityBuilder, environmentName))
@@ -85,7 +111,7 @@ private class GlobalSdkBridgesLoader(private val eelMachine: EelMachine) : Globa
val environmentName = eelMachine.getInternalEnvironmentName()
for (addChange in addChanges) {
if (addChange.newEntity.getAssociatedEelDescriptor().machine != eelMachine) continue
if (addChange.newEntity.getAssociatedEffectiveWorkspaceEelDescriptor().machine != eelMachine) continue
// Will initialize the bridge if missing
builder.mutableSdkMap.getOrPutDataByEntity(addChange.newEntity) {
val sdkEntityCopy = SdkBridgeImpl.createEmptySdkEntity("", "", environmentName = environmentName)