@@ -14,7 +14,6 @@ import java.io.File
1414import java.io.PrintStream
1515import java.net.URLClassLoader
1616import java.nio.file.Files
17- import java.nio.file.Path
1817import java.util.concurrent.Callable
1918import java.util.concurrent.Executors
2019import java.util.concurrent.Future
@@ -141,10 +140,14 @@ object KotlinExecutor {
141140 elapsedMs(startNs), warnings)
142141 }
143142 } catch (t: Throwable ) {
143+ // Unwrap InvocationTargetException + chained causes — Method.invoke wraps every
144+ // failure, often with an inner cause whose message is the real diagnostic.
145+ val root = generateSequence<Throwable >(t) { it.cause }.lastOrNull() ? : t
146+ val rootMessage = root.message ? : root.javaClass.simpleName
144147 return ExecutionResult (false , null , null ,
145148 stdoutBuf.toString().ifEmpty { null },
146149 stderrBuf.toString().ifEmpty { null },
147- " Execution setup failed: ${t .javaClass.simpleName} : ${t.message } " ,
150+ " Execution setup failed: ${root .javaClass.simpleName} : $rootMessage \n ${root.stackTraceToString().take( 3000 ) }" ,
148151 elapsedMs(startNs), warnings)
149152 } finally {
150153 if (args.captureStdout) System .setOut(originalOut)
@@ -179,74 +182,112 @@ object KotlinExecutor {
179182 ) as List <String >
180183 }
181184
182- /* * Isolated classloader for the embedded Kotlin compiler — must NOT chain into the IDE. */
185+ /* *
186+ * Classloader for `EmbeddedCompilerKt.compile()`. We build a **fresh, isolated**
187+ * UrlClassLoader over (a) the IDE Kotlin plugin's `kotlinc/lib` jars (full
188+ * kotlin-compiler + scripting + stdlib) plus (b) our 8-KB wrapper jar plus (c) the
189+ * JDK class roots. Parent is the JDK bootstrap loader — NOT the IDE's PluginClassLoader.
190+ *
191+ * Why isolated and not just `kotlinPlugin.classLoader` as parent: the Kotlin plugin
192+ * exposes its public API via PluginClassLoader but **does NOT export CLI compiler
193+ * internals** (`org.jetbrains.kotlin.cli.common.messages.MessageRenderer`, etc.) — they
194+ * live in the Kotlin plugin's separate `kotlinc/lib/kotlin-compiler.jar` and are
195+ * loaded only when the plugin internally needs them.
196+ *
197+ * Why parent = null and not IDE classloader: kotlin-compiler.jar carries duplicate
198+ * `com.intellij.openapi.*` classes (un-shaded). Mixing them with the IDE's own
199+ * com.intellij.* in one classloader chain leads to "class X seen from two loaders"
200+ * errors and `ApplicationManager.ourApplication` being initialised twice.
201+ *
202+ * Trade-off: requires the org.jetbrains.kotlin plugin to be installed. Most IntelliJ
203+ * IDEs ship it (IDEA, Android Studio, PyCharm Pro, GoLand, WebStorm, RubyMine);
204+ * DataGrip, Rider, RustRover, CLion don't. Phase 2 adds a Maven-download fallback.
205+ */
183206 private val compilerClassLoader: ClassLoader by lazy {
184- val jars = pluginKotlinCompilerJars()
185- check(jars.isNotEmpty()) {
186- " No kotlin-compiler jars found in <plugin>/kotlin-compiler — was the prepareSandbox relocate task run?"
207+ val wrapperJar = findWrapperJar()
208+ ? : error(" kotlin-compiler-wrapper jar not found in plugin's lib/ — the build is broken." )
209+ val kotlincJars = kotlincJars()
210+ check(kotlincJars.isNotEmpty()) {
211+ " Couldn't find the Kotlin plugin's kotlinc/lib jars in this IDE. " +
212+ " exec.execute_kotlin_in_ide requires the org.jetbrains.kotlin plugin. " +
213+ " Install it from Settings → Plugins → Marketplace and restart."
187214 }
188215 UrlClassLoader .build()
189- .files(jdkClassRoots( ) + jars .map(File ::toPath))
216+ .files(( listOf (wrapperJar ) + kotlincJars) .map(File ::toPath) + jdkClassRoots( ))
190217 .noPreload()
191218 .allowBootstrapResources()
192219 .useCache()
193220 .get()
194221 }
195222
196- private fun pluginKotlinCompilerJars (): List < File > {
223+ private fun findWrapperJar (): File ? {
197224 val ourDescriptor = PluginLookup .findPlugin(PluginId .getId(" com.github.xepozz.ide.introspector" ))
198- ? : return emptyList()
199- val dir = ourDescriptor.pluginPath?.toFile()?.resolve(" kotlin-compiler" ) ? : return emptyList()
200- if (! dir.isDirectory) return emptyList()
201- return dir.listFiles { f -> f.isFile && f.name.endsWith(" .jar" ) }?.toList().orEmpty()
225+ ? : return null
226+ val libDir = ourDescriptor.pluginPath?.toFile()?.resolve(" lib" ) ? : return null
227+ return libDir.listFiles { f -> f.isFile && f.name.startsWith(" kotlin-compiler-wrapper" ) && f.name.endsWith(" .jar" ) }
228+ ?.firstOrNull()
229+ }
230+
231+ /* *
232+ * The Kotlin IDE plugin ships its full compiler under `<plugin>/kotlinc/lib/`. We pull
233+ * every jar from there except the `*-sources.jar` files (no need at runtime) and the
234+ * Kotlin **compiler plugins** (which target IntelliJ APIs and clash with our isolated
235+ * loader). LivePlugin uses the same filter rule.
236+ */
237+ private fun kotlincJars (): List <File > {
238+ val kotlinPlugin = PluginLookup .findPlugin(PluginId .getId(" org.jetbrains.kotlin" )) ? : return emptyList()
239+ val kotlincLib = kotlinPlugin.pluginPath?.toFile()?.resolve(" kotlinc/lib" ) ? : return emptyList()
240+ if (! kotlincLib.isDirectory) return emptyList()
241+ return kotlincLib.listFiles { f ->
242+ f.isFile && f.name.endsWith(" .jar" ) &&
243+ ! f.name.endsWith(" -sources.jar" ) &&
244+ ! f.name.contains(" compiler-plugin" )
245+ }?.toList().orEmpty()
202246 }
203247
204248 /* *
205- * Best-effort discovery of JDK class roots (rt.jar / java.base modules) that the
206- * embedded compiler needs to resolve `java.*` types. Falls back to scanning
207- * `${java.home}/lib/modules` and a few well-known directories .
249+ * Best-effort discovery of JDK class roots that the compiler needs to resolve `java.*`.
250+ * Tries IntelliJ's `JavaSdkUtil.getJdkClassesRoots` reflectively first, falls back to
251+ * the `${java.home}/lib/modules` jimage file (works on JDK 9+) .
208252 */
209- private fun jdkClassRoots (): List <Path > {
253+ private fun jdkClassRoots (): List <java.nio.file. Path > {
210254 val jdkRoot = File (System .getProperty(" java.home" ))
211- // Try IntelliJ's JavaSdkUtil reflectively — present in jps-model.jar in modern IDEs.
212255 try {
213256 val cls = Class .forName(" org.jetbrains.jps.model.java.impl.JavaSdkUtil" )
214257 val m = cls.methods.firstOrNull { it.name == " getJdkClassesRoots" && it.parameterCount == 2 }
215258 if (m != null ) {
216259 @Suppress(" UNCHECKED_CAST" )
217- return m.invoke(null , jdkRoot.toPath(), true ) as List <Path >
260+ return m.invoke(null , jdkRoot.toPath(), true ) as List < java.nio.file. Path >
218261 }
219262 } catch (_: Throwable ) { /* fall through */ }
220- // Fallback: just the jrt-fs JDK image — works on JDK 9+.
221263 val jrtModules = jdkRoot.resolve(" lib/modules" )
222264 return if (jrtModules.isFile) listOf (jrtModules.toPath()) else emptyList()
223265 }
224266
225267 private fun buildCompilerClasspath (): List <File > {
226268 val out = LinkedHashSet <File >()
227269
228- // 1) Every jar in the IDE's main lib/ — gives us com.intellij.* platform classes.
270+ // 1) Every jar in the IDE's main lib/ — com.intellij.* platform classes.
229271 val ideLib = File (PathManager .getLibPath())
230272 if (ideLib.isDirectory) {
231273 ideLib.listFiles { f -> f.isFile && (f.name.endsWith(" .jar" ) || f.name.endsWith(" .zip" )) }
232274 ?.let { out .addAll(it) }
233275 }
234276
235- // 2) Our own plugin's lib/ — so user code can reference com.github.xepozz.* types.
277+ // 2) Our own plugin's lib/ — user code can reference com.github.xepozz.* types.
236278 val ourDescriptor = PluginLookup .findPlugin(PluginId .getId(" com.github.xepozz.ide.introspector" ))
237279 ourDescriptor?.pluginPath?.toFile()?.resolve(" lib" )?.listFiles { f ->
238280 f.isFile && f.name.endsWith(" .jar" )
239281 }?.let { out .addAll(it) }
240282
241- // 3) kotlin-stdlib + kotlin-reflect from OUR isolated kotlin-compiler/ — provides
242- // kotlin.* / kotlin.collections.* symbols at compile time. We deliberately do
243- // NOT pull in the IDE Kotlin plugin's kotlinc/lib/kotlin-compiler.jar — that's
244- // a 156 MB "fat" jar with duplicate copies of many compiler-internal classes
245- // that confuse K2's codegen ("Exception while generating code for run …").
246- // At runtime the JVM resolves kotlin.* via the IDE classloader chain (Kotlin
247- // plugin's classloader), which sees its own bundled stdlib — versions match in
248- // practice because we pin our bundled stdlib (2.3.21) close to the IDE's.
249- ourDescriptor?.pluginPath?.toFile()?.resolve(" kotlin-compiler" )?.listFiles { f ->
283+ // 3) Kotlin plugin's bundled `kotlinc/lib/kotlin-stdlib*.jar` + `kotlin-reflect.jar` +
284+ // coroutines — kotlin.* / kotlin.collections.* symbols at compile time. Skip the
285+ // bigger `kotlin-compiler.jar` (156 MB) — it carries duplicate IntelliJ classes
286+ // that confuse K2 codegen (the user code's compile classpath should NOT include
287+ // the compiler itself).
288+ val kotlinPlugin = PluginLookup .findPlugin(PluginId .getId(" org.jetbrains.kotlin" ))
289+ val kotlinPluginRoot = kotlinPlugin?.pluginPath?.toFile()
290+ kotlinPluginRoot?.resolve(" kotlinc/lib" )?.listFiles { f ->
250291 f.isFile && f.name.endsWith(" .jar" ) && (
251292 f.name.startsWith(" kotlin-stdlib" ) ||
252293 f.name.startsWith(" kotlin-reflect" ) ||
0 commit comments