From 27f189e47ee61a377b96eb21e443c192568131d2 Mon Sep 17 00:00:00 2001 From: Alexander Koshevoy Date: Wed, 11 Mar 2026 12:07:30 +0100 Subject: [PATCH] IJPL-222935 Fix Settings Sync writing WSL JDK table to wrong location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Settings Sync is enabled, it acts as an exclusive `com.intellij.configurationStore.StreamProvider`, so all settings writes go through `SettingsSyncIdeMediatorImpl.write()`, which calls `getFileRelativeToRootConfig()` to resolve the final disk path. In 2025.3 the per-environment workspace model separation feature stores WSL JDK settings under an environment-specific subdirectory, e.g. "WSL-Ubuntu/jdk.table.xml". `getFileRelativeToRootConfig()` classified any fileSpec containing "/" as a "schema" path and returned it unchanged, causing the file to be written to "{configRoot}/WSL-Ubuntu/jdk.table.xml" instead of "{configRoot}/options/WSL-Ubuntu/jdk.table.xml". Because the `StreamProvider` is exclusive, the correct "options/" path was never written, and JPS couldn't find the JDK table → "Cannot find JDK for module". Fix: extend the condition in `getFileRelativeToRootConfig()` to recognise env-prefixed paths whose filename is one of the known options-directory files ("jdk.table.xml", "applicationLibraries.xml") and prepend "options/" for them, just like for plain single-component specs. (cherry picked from commit 5b62fc11ad09b637fcdba001d97846f1a30753ae) IJ-MR-195770 GitOrigin-RevId: 49f0e95c83f3a3d19a2c56f5dad5e2a06545cff6 --- .../intellij/configurationStore/storeUtil.kt | 14 +++++++++++++- .../core/SettingsSyncIdeMediatorTest.kt | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/configurationStore/storeUtil.kt b/platform/platform-impl/src/com/intellij/configurationStore/storeUtil.kt index 59ae956eb882..dc2f40117e5e 100644 --- a/platform/platform-impl/src/com/intellij/configurationStore/storeUtil.kt +++ b/platform/platform-impl/src/com/intellij/configurationStore/storeUtil.kt @@ -190,6 +190,16 @@ fun getPerOsSettingsStorageFolderName(): String = when { else -> if (SystemInfoRt.isUnix) "unix" else "other_os" } +/** + * File names that are stored inside the options directory but may arrive with an environment-specific + * subdirectory prefix (e.g. "WSL-Ubuntu/jdk.table.xml"). Despite containing a path separator, they + * still need the "options/" prefix — unlike schema files which already carry their full relative path. + * + * Note: an identical set exists privately in OptionsDirectoryProcessor (intellij.java.compiler.impl), + * kept separate to avoid a cross-module dependency. + */ +private val ENVIRONMENT_SPECIFIC_OPTIONS_FILENAMES = setOf("jdk.table.xml", "applicationLibraries.xml") + /** * Converts fileSpec passed to [StreamProvider]'s methods to a relative path from the root config directory. */ @@ -197,7 +207,9 @@ fun getPerOsSettingsStorageFolderName(): String = when { fun getFileRelativeToRootConfig(fileSpecPassedToProvider: String): String = // For PersistentStateComponents the fileSpec is passed without the 'options' folder, e.g. 'editor.xml' or 'mac/keymaps.xml' // OTOH for schemas it is passed together with the containing folder, e.g. 'keymaps/my_keymap.xml' - if (!fileSpecPassedToProvider.contains("/") || fileSpecPassedToProvider.startsWith(getPerOsSettingsStorageFolderName() + "/")) { + if (!fileSpecPassedToProvider.contains("/") + || fileSpecPassedToProvider.startsWith(getPerOsSettingsStorageFolderName() + "/") + || ENVIRONMENT_SPECIFIC_OPTIONS_FILENAMES.any { fileSpecPassedToProvider.endsWith("/$it") }) { "${PathManager.OPTIONS_DIRECTORY}/${fileSpecPassedToProvider}" } else fileSpecPassedToProvider diff --git a/platform/settings-sync-core/tests/com/intellij/settingsSync/core/SettingsSyncIdeMediatorTest.kt b/platform/settings-sync-core/tests/com/intellij/settingsSync/core/SettingsSyncIdeMediatorTest.kt index e5596e462055..15e9d0b1fec9 100644 --- a/platform/settings-sync-core/tests/com/intellij/settingsSync/core/SettingsSyncIdeMediatorTest.kt +++ b/platform/settings-sync-core/tests/com/intellij/settingsSync/core/SettingsSyncIdeMediatorTest.kt @@ -2,6 +2,7 @@ package com.intellij.settingsSync.core import com.intellij.configurationStore.ComponentStoreImpl import com.intellij.configurationStore.StateStorageManager +import com.intellij.configurationStore.getFileRelativeToRootConfig import com.intellij.configurationStore.getStateSpec import com.intellij.ide.plugins.PluginManagerCore import com.intellij.idea.TestFor @@ -236,6 +237,22 @@ class SettingsSyncIdeMediatorTest : BasePlatformTestCase() { } } + @TestFor(issues = ["IJPL-222935"]) + @Test + fun `getFileRelativeToRootConfig prepends options for environment-specific file specs`() { + // Plain option files (no slash) → options/ prefix added + assertThat(getFileRelativeToRootConfig("jdk.table.xml")).isEqualTo("options/jdk.table.xml") + assertThat(getFileRelativeToRootConfig("applicationLibraries.xml")).isEqualTo("options/applicationLibraries.xml") + assertThat(getFileRelativeToRootConfig("editor.xml")).isEqualTo("options/editor.xml") + + // Environment-specific subdirectory prefix → options/ prefix must be added too + assertThat(getFileRelativeToRootConfig("WSL-Ubuntu/jdk.table.xml")).isEqualTo("options/WSL-Ubuntu/jdk.table.xml") + assertThat(getFileRelativeToRootConfig("WSL-Ubuntu/applicationLibraries.xml")).isEqualTo("options/WSL-Ubuntu/applicationLibraries.xml") + + // Schema paths (folder/file that is not an env-specific options file) → returned as-is + assertThat(getFileRelativeToRootConfig("keymaps/my_keymap.xml")).isEqualTo("keymaps/my_keymap.xml") + } + @State( name = "SecondComponent", storages = [Storage("second.xml")],