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") + } +}