boolean (List<String> vmOptions, List<String> args, StringWriter out, StringWriter err, Map<String, List<String>> outputs, CompileContext context, ModuleChunk chunk) { final ClassLoader jpsLoader = Thread.currentThread().getContextClassLoader(); try { // We have to set context class loader in order because greclipse will create child GroovyClassLoader, // and will use context class loader as parent. // // Here's what happens if we leave jpsLoader: // 1. org.codehaus.groovy.transform.ASTTransformationCollectorCodeVisitor // is loaded with GreclipseMain's class loader, i.e. myGreclipseLoader; // 2. org.codehaus.groovy.transform.ASTTransformation inside ASTTransformationCollectorCodeVisitor.verifyClass // is loaded with ASTTransformationCollectorCodeVisitor' loader, i.e. myGreclipseLoader; // 3. transformation GroovyClassLoader is created with context class loader (jpsLoader) as a parent; // 4. some CoolTransform implements ASTTransformation is loaded with GroovyClassLoader; // 5. ASTTransformation supertype of CoolTransform is loaded with GroovyClassLoader too; // 6. GroovyClassLoader asks its parent, which is jpsLoader, it doesn't know about ASTTransformation // => GroovyClassLoader loads ASTTransformation by itself; // 7. there are two different ASTTransformation class instances // => we get ASTTransformation.class.isAssignableFrom(klass) = false // => compilation fails with error. // // If we set context classloader here, then in the 6th step parent loader will be myGreclipseLoader, // and ASTTransformation class will be returned from myGreclipseLoader, and the compilation won't fail. Thread.currentThread().setContextClassLoader(myGreclipseLoader); if (vmOptions.isEmpty()) { Class<?> mainClass = Class.forName(GreclipseMain.class.getName(), true, myGreclipseLoader); Constructor<?> constructor = mainClass.getConstructor(PrintWriter.class, PrintWriter.class, Map.class); Method compileMethod = mainClass.getMethod("compile", String[].class); Object main = constructor.newInstance(new PrintWriter(out), new PrintWriter(err), outputs); return (Boolean)compileMethod.invoke(main, new Object[]{ArrayUtilRt.toStringArray(args)}); } else { final List<String> cmd = ExternalProcessUtil.buildJavaCommandLine( ForkedGroovyc.getJavaExecutable(chunk), "org.jetbrains.jps.incremental.groovy.GreclipseMain", Collections.emptyList(), Arrays.asList(myGreclipseJar, Objects.requireNonNull(PathManager.getJarForClass(GreclipseMain.class)).toAbsolutePath() .toString()), vmOptions, args ); final Process process = Runtime.getRuntime().exec(ArrayUtilRt.toStringArray(cmd)); ProcessHandler handler = new BaseOSProcessHandler(process, StringUtil.join(cmd, " "), null) { @NotNull @Override public Future<?> executeTask(@NotNull Runnable task) { return SharedThreadPool.getInstance().submit(task); } @Override public void notifyTextAvailable(@NotNull String text, @NotNull Key outputType) { if (outputType == ProcessOutputType.STDERR) { err.append(text); } if (outputType == ProcessOutputType.STDOUT) { out.append(text); } } }; handler.startNotify(); handler.waitFor(); return true; } } catch (Exception e) { context.processMessage(CompilerMessage.createInternalBuilderError(getPresentableName(), e)); return false; } finally { Thread.currentThread().setContextClassLoader(jpsLoader); } }