From fd95f3324b63ebb9106c61359b860713fa19a2ca Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 13 Sep 2026 14:49:49 +0200 Subject: [PATCH] build(verifier): stop failing verifyPlugin on optional dependencies a target IDE lacks The develop-to-main release PR went red on Plugin verifier with all eight targets Compatible: PyCharm has no com.intellij.modules.java, the optional dependency behind the uast domain, and the IntelliJ Platform Gradle plugin (2.16.0, 2.18.1 alike) parses the verifier's output by the "Missing dependencies" heading without telling an optional gap from a mandatory one. With FailureLevel.MISSING_DEPENDENCIES on, every optional dependency a target lacks is a failed verification. The protection that level gave, a mandatory dependency the IDE cannot satisfy means the plugin never loads, moves to PluginDependenciesContractTest: every non-optional must be a platform module every IntelliJ-based IDE ships, every optional one names an existing config-file, and the level stays out of failureLevel until the Gradle plugin ships its "missing mandatory" verdict marker. verifyPlugin now passes locally with the Java dependency declared: 8 of 8 Compatible, 42 experimental API usages, no deprecated or internal API. --- build.gradle.kts | 14 ++--- .../PluginDependenciesContractTest.kt | 62 +++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 src/test/kotlin/dev/lain/claudejb/PluginDependenciesContractTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3069aa3b..916c56c9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -469,19 +469,19 @@ intellijPlatform { // experimental API is acceptable with a reason; a deprecated one is not acceptable at all, because // it has an announced removal date and the plugin has to keep working across the IDE range. // - // MISSING_DEPENDENCIES is here for a reason found the hard way, and it is the most load-bearing entry - // in this list: a mandatory `` that the target IDE cannot satisfy means **the plugin does not - // load at all** — not a degraded feature, not a warning, nothing. The verifier detects it perfectly - // (pointed at 253.28294.334 it says "1 missing mandatory dependency" in as many words) and, without - // this line, still finished with BUILD SUCCESSFUL. A gate that finds the fault and passes anyway is - // worse than no gate: it is a green tick over a plugin that cannot start. + // MISSING_DEPENDENCIES is deliberately NOT here either. A mandatory `` the target IDE cannot + // satisfy means the plugin does not load at all, and the verifier does detect it — but the Gradle plugin + // (2.16.0, and 2.18.1 alike) parses the verifier's stdout by the "Missing dependencies" heading and + // cannot tell `(optional): Unavailable` from a mandatory gap, so with that level on, every PyCharm + // target fails on the optional com.intellij.modules.java dependency that PyCharm lacks by design. + // The protection that level gave lives in PluginDependenciesContractTest instead: every non-optional + // must be a platform module every IntelliJ-based IDE ships. failureLevel = listOf( VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS, VerifyPluginTask.FailureLevel.INTERNAL_API_USAGES, VerifyPluginTask.FailureLevel.OVERRIDE_ONLY_API_USAGES, VerifyPluginTask.FailureLevel.DEPRECATED_API_USAGES, - VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES, ) ides { // No hardcoded path in the repo: a developer can point the verifier at local IDE installs to skip the diff --git a/src/test/kotlin/dev/lain/claudejb/PluginDependenciesContractTest.kt b/src/test/kotlin/dev/lain/claudejb/PluginDependenciesContractTest.kt new file mode 100644 index 00000000..5f38f06b --- /dev/null +++ b/src/test/kotlin/dev/lain/claudejb/PluginDependenciesContractTest.kt @@ -0,0 +1,62 @@ +package dev.lain.claudejb + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.io.File + +class PluginDependenciesContractTest { + + private val metaInf = File("src/main/resources/META-INF") + private val descriptor = metaInf.resolve("plugin.xml").readText() + private val depends = DEPENDS.findAll(descriptor).map { Dependency(it.groupValues[1], it.groupValues[2].trim()) }.toList() + + @Test + fun `every mandatory dependency is a platform module that every IntelliJ-based IDE ships`() { + val mandatory = depends.filterNot { it.optional }.map { it.id } + assertEquals( + emptyList(), + mandatory - MODULES_IN_EVERY_IDE, + "A mandatory the target IDE cannot satisfy means the plugin does not load at all, and the " + + "verifier no longer fails the build over missing dependencies because it cannot tell an optional " + + "one from a mandatory one. Make it optional with a config-file, or add it here with the proof that " + + "every IDE in the verified range ships it.", + ) + } + + @Test + fun `every optional dependency names a config-file that exists`() { + depends.filter { it.optional }.forEach { dependency -> + val configFile = dependency.configFile + assertTrue(configFile != null, "${dependency.id} is optional but names no config-file") + assertTrue( + metaInf.resolve(configFile!!).isFile, + "META-INF/$configFile is referenced by plugin.xml for ${dependency.id} but does not exist", + ) + } + } + + @Test + fun `the verifier is not asked to fail on missing dependencies, because it fails the optional ones too`() { + val failureLevels = Regex("""failureLevel\s*=\s*listOf\(([^)]*)\)""").find(File("build.gradle.kts").readText()) + assertTrue(failureLevels != null, "No failureLevel list found in build.gradle.kts") + assertFalse( + "MISSING_DEPENDENCIES" in failureLevels!!.groupValues[1], + "FailureLevel.MISSING_DEPENDENCIES turns every optional dependency a target IDE lacks into a failed " + + "verification: PyCharm has no com.intellij.modules.java. The mandatory case is guarded by this test.", + ) + } + + private class Dependency(attributes: String, val id: String) { + val optional = OPTIONAL.containsMatchIn(attributes) + val configFile = CONFIG_FILE.find(attributes)?.groupValues?.get(1) + } + + private companion object { + val DEPENDS = Regex("""]*)>([^<]+)""") + val OPTIONAL = Regex("""optional\s*=\s*"true"""") + val CONFIG_FILE = Regex("""config-file\s*=\s*"([^"]+)"""") + val MODULES_IN_EVERY_IDE = setOf("com.intellij.modules.platform", "com.intellij.modules.jcef") + } +}