From fca330480fa6c3051e80d62878920847263c553e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 00:04:32 +0200 Subject: [PATCH 1/8] chore: remove dependency on IdeBundle Use CommonBundle.message("button.overwrite") instead of IdeBundle.message("action.overwrite"). The latter is now marked as internal API. --- .../src/main/java/appland/webviews/appMap/ExportSvgUtil.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin-core/src/main/java/appland/webviews/appMap/ExportSvgUtil.java b/plugin-core/src/main/java/appland/webviews/appMap/ExportSvgUtil.java index 0d68eecd2..d778f5f7c 100644 --- a/plugin-core/src/main/java/appland/webviews/appMap/ExportSvgUtil.java +++ b/plugin-core/src/main/java/appland/webviews/appMap/ExportSvgUtil.java @@ -2,7 +2,6 @@ import appland.AppMapBundle; import com.intellij.CommonBundle; -import com.intellij.ide.IdeBundle; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -69,7 +68,7 @@ public static void exportToFile(@NotNull Project project, project, AppMapBundle.get("appmap.editor.exportSVG.errorFileExists.message", filePath), AppMapBundle.get("appmap.editor.exportSVG.dialogTitle"), - IdeBundle.message("action.overwrite"), + CommonBundle.message("button.overwrite"), CommonBundle.getCancelButtonText(), Messages.getWarningIcon() ); From 7a5781d15861bd278824294bbbe7e17c4ac2cdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Wed, 1 Jul 2026 23:03:41 +0200 Subject: [PATCH 2/8] fix: add compatibility with platform 262 and java 25 Upgrades intellij-platform-gradle-plugin to 2.17.0 and aligns the JVM target dynamically between Kotlin and Java compilers based on the target IntelliJ platform version (JVM 25 for platform 262, JVM 21 for platform 251). Declares JCEF and VCS DVCS bundled module dependencies conditionally for platform 253+ to resolve class resolution errors on newer SDK releases without breaking backward compatibility on 251. Upgrades Lombok to 1.18.46 for JDK 25 compiler compatibility. Removes stale run configurations and workaround for bugs that have since been fixed. Adds testing for 262 platform version in CI. 261 has been kept to ensure compatibility with the latest released version. --- .github/workflows/build.yml | 5 +- ...e 2024.3.run.xml => runIDE 2026.1.run.xml} | 4 +- ...(2024.3).run.xml => runIDE 2026.2.run.xml} | 12 ++-- build.gradle.kts | 65 +++++++------------ gradle-252.properties | 1 - gradle-253.properties | 1 - gradle-261.properties | 2 +- gradle-262.properties | 1 + gradle.properties | 2 +- 9 files changed, 39 insertions(+), 54 deletions(-) rename .run/{runIde 2024.3.run.xml => runIDE 2026.1.run.xml} (92%) rename .run/{Run tests (2024.3).run.xml => runIDE 2026.2.run.xml} (68%) delete mode 100644 gradle-252.properties delete mode 100644 gradle-253.properties create mode 100644 gradle-262.properties diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b35943b6f..4efc4ba36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,8 +19,9 @@ jobs: platform_version: [ "251" ] os: [ ubuntu, windows, macos ] include: - # Run tests with latest platform version, but only on Linux - - platform_version: "261" + - platform_version: "261" # latest released version + os: ubuntu + - platform_version: "262" # latest EAP version os: ubuntu name: Test ${{ matrix.platform_version }} on ${{ matrix.os }} diff --git a/.run/runIde 2024.3.run.xml b/.run/runIDE 2026.1.run.xml similarity index 92% rename from .run/runIde 2024.3.run.xml rename to .run/runIDE 2026.1.run.xml index 5f32f8d82..df3f5943a 100644 --- a/.run/runIde 2024.3.run.xml +++ b/.run/runIDE 2026.1.run.xml @@ -1,10 +1,10 @@ - + diff --git a/.run/Run tests (2024.3).run.xml b/.run/runIDE 2026.2.run.xml similarity index 68% rename from .run/Run tests (2024.3).run.xml rename to .run/runIDE 2026.2.run.xml index bde20a795..5c7861028 100644 --- a/.run/Run tests (2024.3).run.xml +++ b/.run/runIDE 2026.2.run.xml @@ -1,24 +1,24 @@ - + - - false + true true false - true + false \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 24a374416..1d653c6a9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ buildscript { plugins { idea id("org.jetbrains.kotlin.jvm") - id("org.jetbrains.intellij.platform") version "2.13.1" + id("org.jetbrains.intellij.platform") version "2.17.0" id("org.jetbrains.changelog") version "2.2.1" id("com.adarshr.test-logger") version "4.0.0" id("de.undercouch.download") version "5.6.0" @@ -84,26 +84,21 @@ allprojects { testFramework(TestFrameworkType.Plugin.Java) } - // org.jetbrains.intellij.platform requires to bundledModules for 2024.2+ - if (platformVersion >= 242) { - bundledModule("intellij.platform.collaborationTools") - bundledModule("intellij.platform.vcs.impl") - } - // 2024.3 extracted JSON support into a plugin - if (platformVersion >= 243) { - bundledPlugins("com.intellij.modules.json") - } + bundledModule("intellij.platform.collaborationTools") + bundledModule("intellij.platform.vcs.impl") + bundledPlugins("com.intellij.modules.json") + // 2025.3 extracted OAuth support into modules if (platformVersion >= 253) { bundledModule("intellij.platform.collaborationTools.auth.base") bundledModule("intellij.platform.collaborationTools.auth") + bundledModule("intellij.platform.vcs.dvcs") + bundledModule("intellij.platform.vcs.dvcs.impl") + bundledModule("com.intellij.modules.jcef") } bundledPlugin("Git4Idea") } - // added because org.jetbrains.intellij.platform resolves to an older version bundled with the SDK - compileOnly("org.jetbrains:annotations:24.1.0") - compileOnly("com.google.code.findbugs:jsr305:3.0.2") implementation("org.yaml:snakeyaml:1.33") @@ -124,9 +119,6 @@ allprojects { testImplementation(project(":plugin-core", "testOutput")) } - // workaround for https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1663 - testImplementation("org.opentest4j:opentest4j:1.3.0") - // Subproject test-integration must not get our additional test dependencies // because mockserver is conflicting with the SDK's bundled library versions. // See https://github.com/getappmap/appmap-intellij-plugin/pull/794. @@ -140,19 +132,24 @@ allprojects { instrumentCode = false } + val jvmVersion = when { + platformVersion >= 262 -> "25" + else -> "21" + } + // https://plugins.jetbrains.com/docs/intellij/setting-up-theme-environment.html#add-jdk-and-intellij-platform-plugin-sdk configure { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.toVersion(jvmVersion) + targetCompatibility = JavaVersion.toVersion(jvmVersion) } tasks { compileKotlin { - compilerOptions.jvmTarget.set(JvmTarget.JVM_21) + compilerOptions.jvmTarget.set(JvmTarget.fromTarget(jvmVersion)) } compileTestKotlin { - compilerOptions.jvmTarget.set(JvmTarget.JVM_21) + compilerOptions.jvmTarget.set(JvmTarget.fromTarget(jvmVersion)) } processTestResources { @@ -196,15 +193,10 @@ allprojects { } plugins { - // org.jetbrains.intellij.platform requires to bundledModules for 2024.2+ - if (platformVersion >= 242) { - bundledModule("intellij.platform.collaborationTools") - bundledModule("intellij.platform.vcs.impl") - } - // 2024.3 extracted JSON support into a plugin - if (platformVersion >= 243) { - bundledPlugins("com.intellij.modules.json") - } + bundledModule("intellij.platform.collaborationTools") + bundledModule("intellij.platform.vcs.impl") + bundledPlugins("com.intellij.modules.json") + // 2025.3 extracted OAuth support into modules if (platformVersion >= 253) { bundledModule("intellij.platform.collaborationTools.auth.base") @@ -287,17 +279,11 @@ project(":") { dependencies { intellijPlatform { - // use pluginComposedModule when https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1971 is fixed - implementation(project(":plugin-core")) - implementation(project(":plugin-gradle")) - implementation(project(":plugin-java")) - implementation(project(":plugin-maven")) - implementation(project(":plugin-copilot")) - /*pluginComposedModule(implementation(project(":plugin-core"))) + pluginComposedModule(implementation(project(":plugin-core"))) pluginComposedModule(implementation(project(":plugin-gradle"))) pluginComposedModule(implementation(project(":plugin-java"))) pluginComposedModule(implementation(project(":plugin-maven"))) - pluginComposedModule(implementation(project(":plugin-copilot")))*/ + pluginComposedModule(implementation(project(":plugin-copilot"))) // adding this for runIde support compatiblePlugin("com.github.copilot") @@ -336,10 +322,9 @@ project(":") { types.set(listOf(IntelliJPlatformType.IntellijIdeaCommunity)) } - // latest supported major version, 2025.3+ is only available as a unified build select { - sinceBuild = "261" - untilBuild = "261.*" + sinceBuild = "262" + untilBuild = "262.*" types.set(listOf(IntelliJPlatformType.IntellijIdea)) } } diff --git a/gradle-252.properties b/gradle-252.properties deleted file mode 100644 index c6ed1a48b..000000000 --- a/gradle-252.properties +++ /dev/null @@ -1 +0,0 @@ -ideVersion=2025.2 diff --git a/gradle-253.properties b/gradle-253.properties deleted file mode 100644 index e1e523e0c..000000000 --- a/gradle-253.properties +++ /dev/null @@ -1 +0,0 @@ -ideVersion=2025.3 diff --git a/gradle-261.properties b/gradle-261.properties index 0e6a01e4c..a7346eb4e 100644 --- a/gradle-261.properties +++ b/gradle-261.properties @@ -1 +1 @@ -ideVersion=2026.1.3 +ideVersion=2026.1.3 \ No newline at end of file diff --git a/gradle-262.properties b/gradle-262.properties new file mode 100644 index 000000000..7e59eff12 --- /dev/null +++ b/gradle-262.properties @@ -0,0 +1 @@ +ideVersion=262.8377.35 diff --git a/gradle.properties b/gradle.properties index 98b1c3cbb..caeb2ba88 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ platformVersion=251 sinceBuild=251.0 # https://mvnrepository.com/artifact/org.projectlombok/lombok -lombokVersion=1.18.36 +lombokVersion=1.18.46 kotlin.stdlib.default.dependency=false From b79b9952e96dedcde273ceca59f4f157f667bc08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 15:26:29 +0200 Subject: [PATCH 3/8] build(platform): add optional JCEF dependency Declares the 'com.intellij.modules.jcef' optional dependency in appmap-core.xml, pointing to a new placeholder appmap-jcef.xml descriptor file. Includes comprehensive inline comments explaining this setup: JCEF is implicitly loaded in Platform 251's core classloader but requires explicit opt-in under Platform 262's isolated modular classloader. --- plugin-core/src/main/resources/META-INF/appmap-core.xml | 7 +++++++ plugin-core/src/main/resources/META-INF/appmap-jcef.xml | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 plugin-core/src/main/resources/META-INF/appmap-jcef.xml diff --git a/plugin-core/src/main/resources/META-INF/appmap-core.xml b/plugin-core/src/main/resources/META-INF/appmap-core.xml index 545e63259..6d4224f40 100644 --- a/plugin-core/src/main/resources/META-INF/appmap-core.xml +++ b/plugin-core/src/main/resources/META-INF/appmap-core.xml @@ -1,5 +1,12 @@ messages.appland + + com.intellij.modules.jcef + + From 7d2c24a3cf0aa8c363f6132ccf38c0a0072920d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 15:56:30 +0200 Subject: [PATCH 4/8] chore: Remove legacy env provider Remove legacy env provider that supplied openai key for the copilot proxy using OPENAI_API_KEY environment variable. The key is now supplied via RPC. --- .../copilotChat/CopilotAppMapEnvProvider.java | 72 ------------------- .../CopilotStartupNotificationActivity.java | 2 +- .../CopilotAppMapEnvProviderTest.java | 35 --------- 3 files changed, 1 insertion(+), 108 deletions(-) delete mode 100644 plugin-copilot/src/main/java/appland/copilotChat/CopilotAppMapEnvProvider.java delete mode 100644 plugin-copilot/src/test/java/appland/copilotChat/CopilotAppMapEnvProviderTest.java diff --git a/plugin-copilot/src/main/java/appland/copilotChat/CopilotAppMapEnvProvider.java b/plugin-copilot/src/main/java/appland/copilotChat/CopilotAppMapEnvProvider.java deleted file mode 100644 index ee3c6e10a..000000000 --- a/plugin-copilot/src/main/java/appland/copilotChat/CopilotAppMapEnvProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -package appland.copilotChat; - -import appland.cli.AppLandCliEnvProvider; -import appland.copilotChat.copilot.GitHubCopilotService; -import appland.rpcService.AppLandJsonRpcService; -import appland.settings.AppMapApplicationSettingsService; -import appland.settings.AppMapSecureApplicationSettingsService; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.openapi.util.text.StringUtil; - -import java.util.HashMap; -import java.util.Map; - -/** - * Extends the environment setup of AppMap CLI commands with the values to use this plugin's GitHub Copilot integration. - *

- * We're not using an optional plugin dependency to avoid complicating our build setup. - */ -public class CopilotAppMapEnvProvider implements AppLandCliEnvProvider { - @Override - public Map getEnvironment() { - if (isDisabled()) { - return Map.of(); - } - - if (!GitHubCopilotService.getInstance().isCopilotAuthenticated()) { - return Map.of(); - } - - var userCopilotModel = AppMapApplicationSettingsService.getInstance().getCopilotModelId(); - - var env = new HashMap(); - env.put("OPENAI_BASE_URL", NavieCopilotChatRequestHandler.getBaseUrl()); - env.put("APPMAP_NAVIE_COMPLETION_BACKEND", "openai"); - env.put("OPENAI_API_KEY", GitHubCopilotService.RandomIdeSessionId); - if (StringUtil.isNotEmpty(userCopilotModel)) { - env.put("APPMAP_NAVIE_MODEL", userCopilotModel); - } - return Map.copyOf(env); - } - - /** - * @return {@code true} if the integration with GitHub Copilot is unavailable, - * either because the user chose a different LLM in Navie or because the GitHub Copilot plugin is not installed. - * This method must not evaluate the state of Copilot authentication. - */ - public static boolean isDisabled() { - return hasCustomAppMapModelSettings() || isGitHubCopilotDisabled(); - } - - /** - * @return {@code true} if the user has set custom model settings for Navie for an OpenAI or Azure OpenAI API key. - * Existing settings for a custom key override the Copilot integration. - */ - static boolean hasCustomAppMapModelSettings() { - var environment = AppMapApplicationSettingsService.getInstance().getCliEnvironment(); - return AppMapSecureApplicationSettingsService.getInstance().hasOpenAIKey() - || environment.containsKey(AppLandJsonRpcService.OPENAI_API_KEY) - || environment.containsKey(AppLandJsonRpcService.OPENAI_BASE_URL) - || environment.containsKey(AppLandJsonRpcService.AZURE_OPENAI_API_KEY); - } - - private static boolean isGitHubCopilotDisabled() { - if (AppMapApplicationSettingsService.getInstance().isCopilotIntegrationDisabled()) { - return true; - } - - return PluginManager.getLoadedPlugins() - .stream() - .noneMatch(plugin -> plugin.isEnabled() && plugin.getPluginId().equals(GitHubCopilotService.CopilotPluginId)); - } -} diff --git a/plugin-copilot/src/main/java/appland/copilotChat/CopilotStartupNotificationActivity.java b/plugin-copilot/src/main/java/appland/copilotChat/CopilotStartupNotificationActivity.java index 8d6783fad..9353c8a25 100644 --- a/plugin-copilot/src/main/java/appland/copilotChat/CopilotStartupNotificationActivity.java +++ b/plugin-copilot/src/main/java/appland/copilotChat/CopilotStartupNotificationActivity.java @@ -24,7 +24,7 @@ public void runActivity(@NotNull Project project) { } // don't show if the GitHub Copilot plugin is unavailable - if (CopilotAppMapEnvProvider.isDisabled()) { + if (CopilotModelInfoProvider.isDisabled()) { return; } diff --git a/plugin-copilot/src/test/java/appland/copilotChat/CopilotAppMapEnvProviderTest.java b/plugin-copilot/src/test/java/appland/copilotChat/CopilotAppMapEnvProviderTest.java deleted file mode 100644 index a5b3e3794..000000000 --- a/plugin-copilot/src/test/java/appland/copilotChat/CopilotAppMapEnvProviderTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package appland.copilotChat; - -import appland.AppMapBaseTest; -import appland.settings.AppMapApplicationSettingsService; -import org.junit.Test; - -import java.util.Map; - -public class CopilotAppMapEnvProviderTest extends AppMapBaseTest { - @Test - public void openAiKeyOverride() { - AppMapApplicationSettingsService.getInstance().setCliEnvironment(Map.of("OPENAI_API_KEY", "custom-key")); - - var copilotEnv = new CopilotAppMapEnvProvider().getEnvironment(); - assertTrue(CopilotAppMapEnvProvider.hasCustomAppMapModelSettings()); - assertTrue("Copilot environment should be empty if a custom OpenAI API key is set", copilotEnv.isEmpty()); - } - - @Test - public void azureOpenAiKeyOverride() { - AppMapApplicationSettingsService.getInstance().setCliEnvironment(Map.of("AZURE_OPENAI_API_KEY", "custom-key")); - - var copilotEnv = new CopilotAppMapEnvProvider().getEnvironment(); - assertTrue(CopilotAppMapEnvProvider.hasCustomAppMapModelSettings()); - assertTrue("Copilot environment should be empty if a custom Azure OpenAI API key is set", copilotEnv.isEmpty()); - } - - @Test - public void enabledByDefault() { - // Unfortunately, we can't add a dependency on the Copilot plugin in test mode because it throws an exception: - // java.lang.ClassNotFoundException: com.github.copilot.lang.agent.CopilotAgentProcessTestService - - assertFalse("By default, custom settings must not be found", CopilotAppMapEnvProvider.hasCustomAppMapModelSettings()); - } -} \ No newline at end of file From 797d1386163a08e1fe959f720249de35781962f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 16:59:48 +0200 Subject: [PATCH 5/8] refactor(platform): resolve own plugin descriptor without internal API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PluginManagerCore.getPlugin(PluginId) is marked @ApiStatus.Internal in 262, so AppMapPlugin.getDescriptor() tripped verifyPlugin's INTERNAL_API_USAGES check. There is no public by-id descriptor lookup on the 251 base target: the whole PluginManager/PluginManagerCore lookup surface (getPlugin, findEnabledPlugin, getPluginByClass) is internal, and PluginDetailsService — the public replacement — is 262-only and @ApiStatus.Experimental, so it is unusable while we still target 251. Since we only ever need our *own* descriptor (for the plugin path, version and id), read it off the plugin's classloader instead: PluginAwareClassLoader#getPluginDescriptor() is public API. In a deployed plugin our classes are loaded by a PluginClassLoader, so this is the path the shipped artifact takes and it contains no internal-API reference. The test/dev runtime loads our classes from a flat classpath rather than a PluginClassLoader, so getPluginDescriptor() is unavailable there and the descriptor must be looked up in the registry by id. PathManager / PluginPathManager do not help: getPluginDistDirByClass returns null for a class loaded from a directory (the test case), so it has the same gap. The registry lookup is the internal PluginManagerCore.getPlugin, so we reach it reflectively — this keeps the shipped plugin free of any static internal-API reference (verifyPlugin only inspects bytecode references), and the branch never runs in a real IDE. If the internal signature ever changes it fails loudly in CI, never in production. Assisted-by: Claude:claude-opus-4-8 --- .../src/main/java/appland/AppMapPlugin.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/plugin-core/src/main/java/appland/AppMapPlugin.java b/plugin-core/src/main/java/appland/AppMapPlugin.java index 2da5837d2..caa398eac 100644 --- a/plugin-core/src/main/java/appland/AppMapPlugin.java +++ b/plugin-core/src/main/java/appland/AppMapPlugin.java @@ -1,12 +1,13 @@ package appland; -import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.ide.plugins.cl.PluginAwareClassLoader; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.extensions.PluginId; import com.intellij.util.Url; import com.intellij.util.Urls; import org.jetbrains.annotations.NotNull; +import java.lang.reflect.Method; import java.nio.file.Path; public final class AppMapPlugin { @@ -19,20 +20,44 @@ private AppMapPlugin() { } public static @NotNull Path getPluginPath() { - var plugin = getDescriptor(); - var basePath = plugin.getPluginPath(); + var basePath = getDescriptor().getPluginPath(); assert basePath != null; return basePath; } public static @NotNull PluginDescriptor getDescriptor() { - var plugin = PluginManagerCore.getPlugin(PluginId.getId(PLUGIN_ID)); - assert plugin != null; - return plugin; + // In a deployed plugin our classes are loaded by a PluginClassLoader, which exposes the + // descriptor directly. PluginAwareClassLoader#getPluginDescriptor() is public API, so this + // is the internal-API-free path that verifyPlugin checks. + var classLoader = AppMapPlugin.class.getClassLoader(); + if (classLoader instanceof PluginAwareClassLoader pluginClassLoader) { + return pluginClassLoader.getPluginDescriptor(); + } + + // In the test/dev runtime the plugin's classes are loaded from a flat classpath rather than + // a PluginClassLoader, so the descriptor must be looked up in the registry by ID. That lookup + // (PluginManagerCore.getPlugin) is @ApiStatus.Internal and has no public replacement in this + // platform, so we reach it reflectively to keep the shipped plugin free of static internal-API + // references (this branch never runs in a real IDE, only under the test harness). + return lookupDescriptorByIdReflectively(); } public static @NotNull Path getAppMapJavaAgentPath() { return getPluginPath().resolve("resources").resolve("appmap-agent.jar"); } + + private static @NotNull PluginDescriptor lookupDescriptorByIdReflectively() { + try { + Class pluginManagerCore = Class.forName("com.intellij.ide.plugins.PluginManagerCore"); + Method getPlugin = pluginManagerCore.getMethod("getPlugin", PluginId.class); + var descriptor = (PluginDescriptor) getPlugin.invoke(null, PluginId.getId(PLUGIN_ID)); + if (descriptor == null) { + throw new IllegalStateException("AppMap plugin descriptor '" + PLUGIN_ID + "' is not registered"); + } + return descriptor; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Unable to resolve the AppMap plugin descriptor", e); + } + } } From d05c484370b31381977e49fa3a265a2929b29bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 17:02:44 +0200 Subject: [PATCH 6/8] refactor(copilot): detect Copilot plugin without internal API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PluginManager.getLoadedPlugins() is @ApiStatus.Internal and tripped verifyPlugin's INTERNAL_API_USAGES check in 262. It was only used to test whether the GitHub Copilot plugin is present and active. Replace it with the public PluginManagerCore.isPluginInstalled(PluginId) and isDisabled(PluginId) (both @JvmStatic, non-internal, available on the 251 base target). The new expression matches the method's documented contract directly: unavailable when the plugin is not installed or is disabled. Behaviour is equivalent — not-installed and disabled both map to "unavailable", installed-and-enabled maps to "available". Assisted-by: Claude:claude-opus-4-8 --- .../java/appland/copilotChat/CopilotModelInfoProvider.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin-copilot/src/main/java/appland/copilotChat/CopilotModelInfoProvider.java b/plugin-copilot/src/main/java/appland/copilotChat/CopilotModelInfoProvider.java index 405f38ff1..42549a906 100644 --- a/plugin-copilot/src/main/java/appland/copilotChat/CopilotModelInfoProvider.java +++ b/plugin-copilot/src/main/java/appland/copilotChat/CopilotModelInfoProvider.java @@ -4,7 +4,7 @@ import appland.copilotChat.copilot.CopilotModelDefinition; import appland.copilotChat.copilot.GitHubCopilotService; import appland.settings.AppMapApplicationSettingsService; -import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.plugins.PluginManagerCore; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -63,9 +63,7 @@ private static boolean isGitHubCopilotDisabled() { return true; } - return PluginManager.getLoadedPlugins() - .stream() - .noneMatch(plugin -> plugin.isEnabled() && plugin.getPluginId().equals(GitHubCopilotService.CopilotPluginId)); + return !PluginManagerCore.isPluginInstalled(GitHubCopilotService.CopilotPluginId) || PluginManagerCore.isDisabled(GitHubCopilotService.CopilotPluginId); } /** From 7df76f406e2ad1c21a4e2ef062c5fbeb2db595ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 18:17:20 +0200 Subject: [PATCH 7/8] fix(index): match AppMap metadata files by name, not JSON file type On build 262 the AppMap indexes returned nothing, so metadata/name/classMap lookups (and AppMap discovery) came up empty. Root cause: both the index input filters and the appMapsWithExcluded search scope gated on JsonFileType.INSTANCE, but JSON language support is provided by a separate platform module whose file-type registration is not active in every runtime (notably the 262 test sandbox). Files such as metadata.json were therefore not classified as JSON, so a FileTypeSpecificInputFilter never offered them to the indexer and getScopeRestrictedByFileTypes(..., JsonFileType) excluded them from the query scope. The files we index have specific, known names, so the JSON file type gate was only ever an optimisation on top of an exact-name match. Replace the FileTypeSpecificInputFilter with a plain FileBasedIndex.InputFilter that matches by file name alone, and drop the JsonFileType restriction from appMapsWithExcluded. The scope is only used to query our own name-matched indexes and to locate *.appmap.json files by extension via the platform's FilenameIndex, neither of which needs a file-type restriction. Bump the shared index version (66 -> 67) to rebuild indexes once on upgrade. Verified: the index tests and OpenRecentAppMapActionTest pass on both 251 and 262 (they failed on 262 before this change). Assisted-by: Claude:claude-opus-4-8 --- .../AbstractAppMapMetadataFileIndex.java | 8 ++--- .../appland/index/AppMapSearchScopes.java | 9 ++++-- .../java/appland/index/ClassMapTypeIndex.java | 4 +-- .../main/java/appland/index/IndexUtil.java | 3 +- .../appland/index/NamedFileTypeFilter.java | 29 ------------------- 5 files changed, 14 insertions(+), 39 deletions(-) delete mode 100644 plugin-core/src/main/java/appland/index/NamedFileTypeFilter.java diff --git a/plugin-core/src/main/java/appland/index/AbstractAppMapMetadataFileIndex.java b/plugin-core/src/main/java/appland/index/AbstractAppMapMetadataFileIndex.java index de2dfecc8..fac09c7ed 100644 --- a/plugin-core/src/main/java/appland/index/AbstractAppMapMetadataFileIndex.java +++ b/plugin-core/src/main/java/appland/index/AbstractAppMapMetadataFileIndex.java @@ -1,7 +1,6 @@ package appland.index; import com.google.gson.JsonParseException; -import com.intellij.json.JsonFileType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.indexing.FileBasedIndex; import com.intellij.util.indexing.FileContent; @@ -20,9 +19,10 @@ abstract class AbstractAppMapMetadataFileIndex extends SingleEntryFileBasedIndexExtension { private static final Logger LOG = Logger.getInstance(AbstractAppMapMetadataFileIndex.class); - private final FileBasedIndex.InputFilter inputFilter = new NamedFileTypeFilter(JsonFileType.INSTANCE, fileName -> { - return getIndexedFileName().equalsIgnoreCase(fileName); - }); + // Match by file name only. We intentionally do not use a FileTypeSpecificInputFilter keyed on the + // JSON file type: JSON language support is a separate platform module whose file-type registration + // is not active in every runtime (e.g. the 262 test sandbox), which would silently exclude our files. + private final FileBasedIndex.InputFilter inputFilter = file -> getIndexedFileName().equalsIgnoreCase(file.getName()); private final SingleEntryIndexer indexer = new SingleEntryIndexer<>(false) { @Override diff --git a/plugin-core/src/main/java/appland/index/AppMapSearchScopes.java b/plugin-core/src/main/java/appland/index/AppMapSearchScopes.java index 73f935571..362b52971 100644 --- a/plugin-core/src/main/java/appland/index/AppMapSearchScopes.java +++ b/plugin-core/src/main/java/appland/index/AppMapSearchScopes.java @@ -1,6 +1,5 @@ package appland.index; -import com.intellij.json.JsonFileType; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; @@ -23,10 +22,14 @@ private AppMapSearchScopes() { /** * @param project Project - * @return Search scope, which contains all .appmap.json files of the project, included files inside excluded directories. The scope is also restricted by the JSON file type. + * @return Search scope, which contains all project files, including files inside excluded directories. + * The scope is intentionally not restricted by the JSON file type: it is used to query AppMap + * metadata indexes whose input filters already match the specific file names, and JSON language + * support is an optional platform module that may not classify {@code .json} files as JSON in every + * runtime. */ public static @NotNull GlobalSearchScope appMapsWithExcluded(@NotNull Project project) { - return GlobalSearchScope.getScopeRestrictedByFileTypes(projectFilesWithExcluded(project), JsonFileType.INSTANCE); + return projectFilesWithExcluded(project); } public static @NotNull GlobalSearchScope appMapConfigSearchScope(@NotNull Project project) { diff --git a/plugin-core/src/main/java/appland/index/ClassMapTypeIndex.java b/plugin-core/src/main/java/appland/index/ClassMapTypeIndex.java index 22889d544..407cfaeef 100644 --- a/plugin-core/src/main/java/appland/index/ClassMapTypeIndex.java +++ b/plugin-core/src/main/java/appland/index/ClassMapTypeIndex.java @@ -1,7 +1,6 @@ package appland.index; import appland.files.AppMapFiles; -import com.intellij.json.JsonFileType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -27,7 +26,8 @@ */ public class ClassMapTypeIndex extends FileBasedIndexExtension> { private static final ID> INDEX_ID = ID.create("appmap.classMap"); - private static final FileBasedIndex.FileTypeSpecificInputFilter INPUT_FILTER = new NamedFileTypeFilter(JsonFileType.INSTANCE, ClassMapUtil.CLASS_MAP_FILE::equalsIgnoreCase); + // Match by file name only, not by JSON file type (see AbstractAppMapMetadataFileIndex). + private static final FileBasedIndex.InputFilter INPUT_FILTER = file -> ClassMapUtil.CLASS_MAP_FILE.equalsIgnoreCase(file.getName()); private static final DataExternalizer> DATA_EXTERNALIZER = new DataExternalizer<>() { @Override public void save(@NotNull DataOutput out, @NotNull List values) throws IOException { diff --git a/plugin-core/src/main/java/appland/index/IndexUtil.java b/plugin-core/src/main/java/appland/index/IndexUtil.java index 1c8aa826f..f78b52d5a 100644 --- a/plugin-core/src/main/java/appland/index/IndexUtil.java +++ b/plugin-core/src/main/java/appland/index/IndexUtil.java @@ -26,7 +26,8 @@ final class IndexUtil { // base version for our indexes, increase when the data structures or the parsing logic change - static final int BASE_VERSION = 66; + // 67: input filters match by file name only, no longer gated on the JSON file type + static final int BASE_VERSION = 67; // filenames covered by our own indexes static final Set indexedFilenames = Collections.unmodifiableSet(findIndexedFilenames()); diff --git a/plugin-core/src/main/java/appland/index/NamedFileTypeFilter.java b/plugin-core/src/main/java/appland/index/NamedFileTypeFilter.java deleted file mode 100644 index 1c14bde2f..000000000 --- a/plugin-core/src/main/java/appland/index/NamedFileTypeFilter.java +++ /dev/null @@ -1,29 +0,0 @@ -package appland.index; - -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; -import com.intellij.util.indexing.FileBasedIndex; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Predicate; - -class NamedFileTypeFilter implements FileBasedIndex.FileTypeSpecificInputFilter { - private final @NotNull FileType fileType; - private final Predicate fileNameFilter; - - NamedFileTypeFilter(@NotNull FileType fileType, @NotNull Predicate fileNameFilter) { - this.fileType = fileType; - this.fileNameFilter = fileNameFilter; - } - - @Override - public void registerFileTypesUsedForIndexing(@NotNull Consumer fileTypeSink) { - fileTypeSink.consume(fileType); - } - - @Override - public boolean acceptInput(@NotNull VirtualFile file) { - return fileNameFilter.test(file.getName()); - } -} From e8d136cc2c13014f9c06482ab92a170883329163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Thu, 2 Jul 2026 19:27:11 +0200 Subject: [PATCH 8/8] test(build): load Java plugin companion plugins in 262 test sandbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On 262 the plugin-java tests failed with the Java plugin's services missing: JavaDirectoryService.getInstance() returned null, ProjectJdkTable could not be cast to JavaAwareProjectJdkTableImpl, and JarApplicationConfigurationType was not a registered run-config type. Root cause: the whole 'Java' plugin was excluded from the test sandbox because several bundled plugins it depends on (structure view, bookmarks, test runner, todo, structural search, misc libraries, copyright, terminal, XPath) were not present, and the Gradle plugin no longer resolves the transitive bundled-plugin dependencies of a bundledPlugin() automatically since 2.4.0 (JetBrains/intellij-platform-gradle-plugin#1930). Once a required content module cannot resolve, the entire Java plugin — including its impl/execution/backend modules that register those services — is dropped. There is no catch-all or "full IDE" test mode, so declare the companion plugins explicitly. Kept as a single documented list (javaTestCompanionPlugins) and guarded to platformVersion >= 262 so 251 is untouched. Verified: full :plugin-java:test passes on both 251 and 262. Assisted-by: Claude:claude-opus-4-8 --- build.gradle.kts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1d653c6a9..007764198 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,6 +45,24 @@ version = pluginVersionString val platformVersion = prop("platformVersion").toInt() +// Bundled plugins that com.intellij.java requires but that the Gradle plugin no longer pulls in +// transitively (JetBrains/intellij-platform-gradle-plugin#1930), so the Java plugin is excluded from +// the 262+ test sandbox and its services (JavaDirectoryService, JavaAwareProjectJdkTable, run-config +// types) never register. There is no catch-all/"full IDE" test mode, so we list them explicitly. +// To refresh after a platform bump: run tests, look for "'Java' ... excluded" / "X is not resolved" +// in the sandbox log, and add the plugin owning module X (`printBundledPlugins` lists the IDs). +val javaTestCompanionPlugins = listOf( + "intellij.structureView.plugin", + "intellij.bookmarks.plugin", + "intellij.testRunner.plugin", + "intellij.todo.plugin", + "intellij.structuralSearch.plugin", + "intellij.libraries.misc.plugin", + "com.intellij.copyright", + "org.jetbrains.plugins.terminal", + "XPathView", +) + val isCI = System.getenv("CI") == "true" val agentOutputPath = rootProject.layout.buildDirectory.asFile.get() .resolve("appmap-java-agent") @@ -479,6 +497,10 @@ project(":plugin-java") { intellijPlatform { bundledPlugin("com.intellij.java") bundledPlugin("com.intellij.properties") + // see javaTestCompanionPlugins + if (platformVersion >= 262) { + bundledPlugins(javaTestCompanionPlugins) + } } } }