@Override public void buildStarted(CompileContext context) { final JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(project); final String compilerId = config == null ? JavaCompilers.JAVAC_ID : config.getJavaCompilerId(); if (LOG.isDebugEnabled()) { LOG.debug("Java compiler ID: " + compilerId); } final boolean isJavac = JavaCompilers.JAVAC_ID.equalsIgnoreCase(compilerId) || JavaCompilers.JAVAC_API_ID.equalsIgnoreCase(compilerId); final boolean isEclipse = JavaCompilers.ECLIPSE_ID.equalsIgnoreCase(compilerId) || JavaCompilers.ECLIPSE_EMBEDDED_ID.equalsIgnoreCase(compilerId); IS_ENABLED.set(context, isJavac || isEclipse); String messageText = null; if (isJavac) { messageText = "Using javac " + System.getProperty("java.version") + " to compile java sources"; } else if (isEclipse) { messageText = "Using eclipse compiler to compile java sources"; } COMPILER_VERSION_INFO.set(context, new AtomicReference<String>(messageText)); }
/** @author Eugene Zhuravlev Date: 10/25/11 */ public class GroovyBuilder extends ModuleLevelBuilder { private static final int ourOptimizeThreshold = Integer.parseInt(System.getProperty("groovyc.optimized.class.loading.threshold", "10")); private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.groovy.GroovyBuilder"); private static final Key<Boolean> CHUNK_REBUILD_ORDERED = Key.create("CHUNK_REBUILD_ORDERED"); private static final Key<Map<String, String>> STUB_TO_SRC = Key.create("STUB_TO_SRC"); private static final Key<Map<ModuleChunk, GroovycContinuation>> CONTINUATIONS = Key.create("CONTINUATIONS"); private static final Key<Boolean> FILES_MARKED_DIRTY_FOR_NEXT_ROUND = Key.create("SRC_MARKED_DIRTY"); private static final String GROOVY_EXTENSION = "groovy"; private final boolean myForStubs; private final String myBuilderName; public GroovyBuilder(boolean forStubs) { super(forStubs ? BuilderCategory.SOURCE_GENERATOR : BuilderCategory.OVERWRITING_TRANSLATOR); myForStubs = forStubs; myBuilderName = "Groovy " + (forStubs ? "stub generator" : "compiler"); } static { JavaBuilder.registerClassPostProcessor(new RecompileStubSources()); } public ModuleLevelBuilder.ExitCode build( final CompileContext context, final ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException { if (GreclipseBuilder.useGreclipse(context)) return ExitCode.NOTHING_DONE; long start = 0; try { JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject()); Ref<Boolean> hasStubExcludes = Ref.create(false); final List<File> toCompile = collectChangedFiles(context, dirtyFilesHolder, myForStubs, false, hasStubExcludes); if (toCompile.isEmpty()) { return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.NOTHING_DONE; } if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("forStubs=" + myForStubs); } Map<ModuleBuildTarget, String> finalOutputs = getCanonicalModuleOutputs(context, chunk, this); if (finalOutputs == null) { return ExitCode.ABORT; } start = System.currentTimeMillis(); Map<ModuleBuildTarget, String> generationOutputs = myForStubs ? getStubGenerationOutputs(chunk, context) : finalOutputs; String compilerOutput = generationOutputs.get(chunk.representativeTarget()); GroovycOutputParser parser = runGroovycOrContinuation( context, chunk, settings, finalOutputs, compilerOutput, toCompile, hasStubExcludes.get()); Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> compiled = processCompiledFiles( context, chunk, generationOutputs, compilerOutput, parser.getSuccessfullyCompiled()); if (checkChunkRebuildNeeded(context, parser)) { clearContinuation(context, chunk); return ExitCode.CHUNK_REBUILD_REQUIRED; } if (myForStubs) { addStubRootsToJavacSourcePath(context, generationOutputs); rememberStubSources(context, compiled); } for (CompilerMessage message : parser.getCompilerMessages()) { context.processMessage(message); } if (!myForStubs) { updateDependencies(context, toCompile, compiled, outputConsumer, this); } return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.OK; } catch (Exception e) { throw new ProjectBuildException(e); } finally { if (start > 0 && LOG.isDebugEnabled()) { LOG.debug( myBuilderName + " took " + (System.currentTimeMillis() - start) + " on " + chunk.getName()); } if (!myForStubs) { FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, null); } } } @NotNull private GroovycOutputParser runGroovycOrContinuation( CompileContext context, ModuleChunk chunk, JpsGroovySettings settings, Map<ModuleBuildTarget, String> finalOutputs, String compilerOutput, List<File> toCompile, boolean hasStubExcludes) throws Exception { GroovycContinuation continuation = takeContinuation(context, chunk); if (continuation != null) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("using continuation for " + chunk); } return continuation.continueCompilation(); } final Set<String> toCompilePaths = getPathsToCompile(toCompile); JpsSdk<JpsDummyElement> jdk = getJdk(chunk); String version = jdk == null ? SystemInfo.JAVA_RUNTIME_VERSION : jdk.getVersionString(); boolean inProcess = "true".equals(System.getProperty("groovyc.in.process", "true")); boolean mayDependOnUtilJar = version != null && StringUtil.compareVersionNumbers(version, "1.6") >= 0; boolean optimizeClassLoading = !inProcess && mayDependOnUtilJar && ourOptimizeThreshold != 0 && toCompilePaths.size() >= ourOptimizeThreshold; Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs); final String encoding = context .getProjectDescriptor() .getEncodingConfiguration() .getPreferredModuleChunkEncoding(chunk); List<String> patchers = new ArrayList<String>(); for (GroovyBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) { patchers.addAll(extension.getCompilationUnitPatchers(context, chunk)); } Collection<String> classpath = generateClasspath(context, chunk); if (LOG.isDebugEnabled()) { LOG.debug("Optimized class loading: " + optimizeClassLoading); LOG.debug("Groovyc classpath: " + classpath); } final File tempFile = GroovycOutputParser.fillFileWithGroovycParameters( compilerOutput, toCompilePaths, finalOutputs.values(), class2Src, encoding, patchers, optimizeClassLoading ? StringUtil.join(classpath, File.pathSeparator) : ""); GroovycFlavor groovyc = inProcess ? new InProcessGroovyc(finalOutputs.values(), hasStubExcludes) : new ForkedGroovyc(optimizeClassLoading, chunk); GroovycOutputParser parser = new GroovycOutputParser(chunk, context); continuation = groovyc.runGroovyc(classpath, myForStubs, settings, tempFile, parser); setContinuation(context, chunk, continuation); return parser; } private static void clearContinuation(CompileContext context, ModuleChunk chunk) { GroovycContinuation continuation = takeContinuation(context, chunk); if (continuation != null) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("clearing continuation for " + chunk); } continuation.buildAborted(); } } @Nullable private static GroovycContinuation takeContinuation(CompileContext context, ModuleChunk chunk) { Map<ModuleChunk, GroovycContinuation> map = CONTINUATIONS.get(context); return map == null ? null : map.remove(chunk); } private static void setContinuation( CompileContext context, ModuleChunk chunk, @Nullable GroovycContinuation continuation) { clearContinuation(context, chunk); if (continuation != null) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("registering continuation for " + chunk); } Map<ModuleChunk, GroovycContinuation> map = CONTINUATIONS.get(context); if (map == null) CONTINUATIONS.set(context, map = ContainerUtil.newConcurrentMap()); map.put(chunk, continuation); } } private Boolean hasFilesToCompileForNextRound(CompileContext context) { return !myForStubs && FILES_MARKED_DIRTY_FOR_NEXT_ROUND.get(context, Boolean.FALSE); } private static Set<String> getPathsToCompile(List<File> toCompile) { final Set<String> toCompilePaths = new LinkedHashSet<String>(); for (File file : toCompile) { if (LOG.isDebugEnabled()) { LOG.debug("Path to compile: " + file.getPath()); } toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath())); } return toCompilePaths; } private static boolean checkChunkRebuildNeeded( CompileContext context, GroovycOutputParser parser) { if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context) || !parser.shouldRetry()) { return false; } if (CHUNK_REBUILD_ORDERED.get(context) != null) { CHUNK_REBUILD_ORDERED.set(context, null); return false; } CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE); LOG.info("Order chunk rebuild"); return true; } private static void rememberStubSources( CompileContext context, Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> compiled) { Map<String, String> stubToSrc = STUB_TO_SRC.get(context); if (stubToSrc == null) { STUB_TO_SRC.set(context, stubToSrc = new HashMap<String, String>()); } for (Collection<GroovycOutputParser.OutputItem> items : compiled.values()) { for (GroovycOutputParser.OutputItem item : items) { stubToSrc.put(FileUtil.toSystemIndependentName(item.outputPath), item.sourcePath); } } } private static void addStubRootsToJavacSourcePath( CompileContext context, Map<ModuleBuildTarget, String> generationOutputs) { final BuildRootIndex rootsIndex = context.getProjectDescriptor().getBuildRootIndex(); for (ModuleBuildTarget target : generationOutputs.keySet()) { File root = new File(generationOutputs.get(target)); rootsIndex.associateTempRoot( context, target, new JavaSourceRootDescriptor(root, target, true, true, "", Collections.<File>emptySet())); } } public static Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> processCompiledFiles( CompileContext context, ModuleChunk chunk, Map<ModuleBuildTarget, String> generationOutputs, String compilerOutput, List<GroovycOutputParser.OutputItem> successfullyCompiled) throws IOException { ProjectDescriptor pd = context.getProjectDescriptor(); final Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> compiled = new THashMap<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>>(); for (final GroovycOutputParser.OutputItem item : successfullyCompiled) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("compiled=" + item); } final JavaSourceRootDescriptor rd = pd.getBuildRootIndex().findJavaRootDescriptor(context, new File(item.sourcePath)); if (rd != null) { final String outputPath = ensureCorrectOutput(chunk, item, generationOutputs, compilerOutput, rd.target); Collection<GroovycOutputParser.OutputItem> items = compiled.get(rd.target); if (items == null) { items = new ArrayList<GroovycOutputParser.OutputItem>(); compiled.put(rd.target, items); } items.add(new GroovycOutputParser.OutputItem(outputPath, item.sourcePath)); } else { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("No java source root descriptor for the item found =" + item); } } } if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("Chunk " + chunk + " compilation finished"); } return compiled; } @Override public void buildStarted(CompileContext context) { if (myForStubs) { File stubRoot = getStubRoot(context); if (stubRoot.exists() && !FileUtil.deleteWithRenaming(stubRoot)) { context.processMessage( new CompilerMessage( myBuilderName, BuildMessage.Kind.ERROR, "External make cannot clean " + stubRoot.getPath())); } } } @Override public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) { JavaBuilderUtil.cleanupChunkResources(context); clearContinuation(context, chunk); STUB_TO_SRC.set(context, null); } private static Map<ModuleBuildTarget, String> getStubGenerationOutputs( ModuleChunk chunk, CompileContext context) throws IOException { Map<ModuleBuildTarget, String> generationOutputs = new HashMap<ModuleBuildTarget, String>(); File commonRoot = getStubRoot(context); for (ModuleBuildTarget target : chunk.getTargets()) { File targetRoot = new File( commonRoot, target.getModule().getName() + File.separator + target.getTargetType().getTypeId()); if (targetRoot.exists() && !FileUtil.deleteWithRenaming(targetRoot)) { throw new IOException("External make cannot clean " + targetRoot.getPath()); } if (!targetRoot.mkdirs()) { throw new IOException("External make cannot create " + targetRoot.getPath()); } generationOutputs.put(target, targetRoot.getPath()); } return generationOutputs; } private static File getStubRoot(CompileContext context) { return new File( context.getProjectDescriptor().dataManager.getDataPaths().getDataStorageRoot(), "groovyStubs"); } @Nullable public static Map<ModuleBuildTarget, String> getCanonicalModuleOutputs( CompileContext context, ModuleChunk chunk, Builder builder) { Map<ModuleBuildTarget, String> finalOutputs = new LinkedHashMap<ModuleBuildTarget, String>(); for (ModuleBuildTarget target : chunk.getTargets()) { File moduleOutputDir = target.getOutputDir(); if (moduleOutputDir == null) { context.processMessage( new CompilerMessage( builder.getPresentableName(), BuildMessage.Kind.ERROR, "Output directory not specified for module " + target.getModule().getName())); return null; } //noinspection ResultOfMethodCallIgnored moduleOutputDir.mkdirs(); String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath()); assert moduleOutputPath != null; finalOutputs.put( target, moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/"); } return finalOutputs; } private static String ensureCorrectOutput( ModuleChunk chunk, GroovycOutputParser.OutputItem item, Map<ModuleBuildTarget, String> generationOutputs, String compilerOutput, @NotNull ModuleBuildTarget srcTarget) throws IOException { if (chunk.getModules().size() > 1 && !srcTarget.equals(chunk.representativeTarget())) { File output = new File(item.outputPath); String srcTargetOutput = generationOutputs.get(srcTarget); if (srcTargetOutput == null) { LOG.info( "No output for " + srcTarget + "; outputs=" + generationOutputs + "; targets = " + chunk.getTargets()); return item.outputPath; } // todo honor package prefixes File correctRoot = new File(srcTargetOutput); File correctOutput = new File(correctRoot, FileUtil.getRelativePath(new File(compilerOutput), output)); FileUtil.rename(output, correctOutput); return correctOutput.getPath(); } return item.outputPath; } static JpsSdk<JpsDummyElement> getJdk(ModuleChunk chunk) { return chunk.getModules().iterator().next().getSdk(JpsJavaSdkType.INSTANCE); } static List<File> collectChangedFiles( CompileContext context, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, final boolean forStubs, final boolean forEclipse, final Ref<Boolean> hasExcludes) throws IOException { final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance() .getCompilerConfiguration(context.getProjectDescriptor().getProject()); assert configuration != null; final JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject()); final List<File> toCompile = new ArrayList<File>(); dirtyFilesHolder.processDirtyFiles( new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() { public boolean apply( ModuleBuildTarget target, File file, JavaSourceRootDescriptor sourceRoot) throws IOException { final String path = file.getPath(); // todo file type check if ((isGroovyFile(path) || forEclipse && path.endsWith(".java")) && !configuration.isResourceFile(file, sourceRoot.root)) { if (forStubs && settings.isExcludedFromStubGeneration(file)) { hasExcludes.set(true); return true; } toCompile.add(file); } return true; } }); return toCompile; } public static void updateDependencies( CompileContext context, List<File> toCompile, Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> successfullyCompiled, OutputConsumer outputConsumer, Builder builder) throws IOException { JavaBuilderUtil.registerFilesToCompile(context, toCompile); if (!successfullyCompiled.isEmpty()) { final Callbacks.Backend callback = JavaBuilderUtil.getDependenciesRegistrar(context); for (Map.Entry<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> entry : successfullyCompiled.entrySet()) { final ModuleBuildTarget target = entry.getKey(); final Collection<GroovycOutputParser.OutputItem> compiled = entry.getValue(); for (GroovycOutputParser.OutputItem item : compiled) { final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath); final String outputPath = FileUtil.toSystemIndependentName(item.outputPath); final File outputFile = new File(outputPath); final File srcFile = new File(sourcePath); try { final byte[] bytes = FileUtil.loadFileBytes(outputFile); if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("registerCompiledClass " + outputFile + " from " + srcFile); } outputConsumer.registerCompiledClass( target, new CompiledClass( outputFile, srcFile, readClassName(bytes), new BinaryContent(bytes))); callback.associate(outputPath, sourcePath, new FailSafeClassReader(bytes)); } catch (Throwable e) { // need this to make sure that unexpected errors in, for example, ASM will not ruin the // compilation final String message = "Class dependency information may be incomplete! Error parsing generated class " + item.outputPath; LOG.info(message, e); context.processMessage( new CompilerMessage( builder.getPresentableName(), BuildMessage.Kind.WARNING, message + "\n" + CompilerMessage.getTextFromThrowable(e), sourcePath)); } JavaBuilderUtil.registerSuccessfullyCompiled(context, srcFile); } } } } private static String readClassName(byte[] classBytes) throws IOException { final Ref<String> nameRef = Ref.create(null); new ClassReader(classBytes) .accept( new ClassVisitor(Opcodes.API_VERSION) { public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { nameRef.set(name.replace('/', '.')); } }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); return nameRef.get(); } private static Collection<String> generateClasspath(CompileContext context, ModuleChunk chunk) { final Set<String> cp = new LinkedHashSet<String>(); // groovy_rt.jar // IMPORTANT! must be the first in classpath cp.addAll(getGroovyRtRoots()); for (File file : ProjectPaths.getCompilationClasspathFiles(chunk, chunk.containsTests(), false, false)) { cp.add(FileUtil.toCanonicalPath(file.getPath())); } for (GroovyBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) { cp.addAll(extension.getCompilationClassPath(context, chunk)); } return cp; } static List<String> getGroovyRtRoots() { File rt = ClasspathBootstrap.getResourceFile(GroovyBuilder.class); File constants = ClasspathBootstrap.getResourceFile(GroovyRtConstants.class); return Arrays.asList( new File(rt.getParentFile(), rt.isFile() ? "groovy_rt.jar" : "groovy_rt").getPath(), new File( constants.getParentFile(), constants.isFile() ? "groovy-rt-constants.jar" : "groovy-rt-constants") .getPath()); } public static boolean isGroovyFile(String path) { return path.endsWith("." + GROOVY_EXTENSION); } @Override public List<String> getCompilableFileExtensions() { return Collections.singletonList(GROOVY_EXTENSION); } private static Map<String, String> buildClassToSourceMap( ModuleChunk chunk, CompileContext context, Set<String> toCompilePaths, Map<ModuleBuildTarget, String> finalOutputs) throws IOException { final Map<String, String> class2Src = new HashMap<String, String>(); JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance() .getOrCreateCompilerConfiguration(context.getProjectDescriptor().getProject()); for (ModuleBuildTarget target : chunk.getTargets()) { String moduleOutputPath = finalOutputs.get(target); final SourceToOutputMapping srcToOut = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target); for (String src : srcToOut.getSources()) { if (!toCompilePaths.contains(src) && isGroovyFile(src) && !configuration.getCompilerExcludes().isExcluded(new File(src))) { final Collection<String> outs = srcToOut.getOutputs(src); if (outs != null) { for (String out : outs) { if (out.endsWith(".class") && out.startsWith(moduleOutputPath)) { final String className = out.substring(moduleOutputPath.length(), out.length() - ".class".length()) .replace('/', '.'); class2Src.put(className, src); } } } } } } return class2Src; } @Override public String toString() { return myBuilderName; } @NotNull public String getPresentableName() { return myBuilderName; } private static class RecompileStubSources implements ClassPostProcessor { public void process(CompileContext context, OutputFileObject out) { Map<String, String> stubToSrc = STUB_TO_SRC.get(context); if (stubToSrc == null) { return; } File src = out.getSourceFile(); if (src == null) { return; } String groovy = stubToSrc.get(FileUtil.toSystemIndependentName(src.getPath())); if (groovy == null) { return; } try { final File groovyFile = new File(groovy); if (!FSOperations.isMarkedDirty(context, CompilationRound.CURRENT, groovyFile)) { FSOperations.markDirty(context, CompilationRound.NEXT, groovyFile); FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, Boolean.TRUE); } } catch (IOException e) { LOG.error(e); } } } }
@NotNull private GroovycOutputParser runGroovycOrContinuation( CompileContext context, ModuleChunk chunk, JpsGroovySettings settings, Map<ModuleBuildTarget, String> finalOutputs, String compilerOutput, List<File> toCompile, boolean hasStubExcludes) throws Exception { GroovycContinuation continuation = takeContinuation(context, chunk); if (continuation != null) { if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) { LOG.info("using continuation for " + chunk); } return continuation.continueCompilation(); } final Set<String> toCompilePaths = getPathsToCompile(toCompile); JpsSdk<JpsDummyElement> jdk = getJdk(chunk); String version = jdk == null ? SystemInfo.JAVA_RUNTIME_VERSION : jdk.getVersionString(); boolean inProcess = "true".equals(System.getProperty("groovyc.in.process", "true")); boolean mayDependOnUtilJar = version != null && StringUtil.compareVersionNumbers(version, "1.6") >= 0; boolean optimizeClassLoading = !inProcess && mayDependOnUtilJar && ourOptimizeThreshold != 0 && toCompilePaths.size() >= ourOptimizeThreshold; Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs); final String encoding = context .getProjectDescriptor() .getEncodingConfiguration() .getPreferredModuleChunkEncoding(chunk); List<String> patchers = new ArrayList<String>(); for (GroovyBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) { patchers.addAll(extension.getCompilationUnitPatchers(context, chunk)); } Collection<String> classpath = generateClasspath(context, chunk); if (LOG.isDebugEnabled()) { LOG.debug("Optimized class loading: " + optimizeClassLoading); LOG.debug("Groovyc classpath: " + classpath); } final File tempFile = GroovycOutputParser.fillFileWithGroovycParameters( compilerOutput, toCompilePaths, finalOutputs.values(), class2Src, encoding, patchers, optimizeClassLoading ? StringUtil.join(classpath, File.pathSeparator) : ""); GroovycFlavor groovyc = inProcess ? new InProcessGroovyc(finalOutputs.values(), hasStubExcludes) : new ForkedGroovyc(optimizeClassLoading, chunk); GroovycOutputParser parser = new GroovycOutputParser(chunk, context); continuation = groovyc.runGroovyc(classpath, myForStubs, settings, tempFile, parser); setContinuation(context, chunk, continuation); return parser; }
/** @author Eugene Zhuravlev Date: 9/21/11 */ public class JavaBuilder extends ModuleLevelBuilder { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.java.JavaBuilder"); public static final String BUILDER_NAME = "java"; private static final String JAVA_EXTENSION = ".java"; public static final boolean USE_EMBEDDED_JAVAC = System.getProperty(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION) == null; private static final Key<Integer> JAVA_COMPILER_VERSION_KEY = Key.create("_java_compiler_version_"); private static final Key<Boolean> IS_ENABLED = Key.create("_java_compiler_enabled_"); private static final Key<AtomicReference<String>> COMPILER_VERSION_INFO = Key.create("_java_compiler_version_info_"); private static final Set<String> FILTERED_OPTIONS = new HashSet<String>(Arrays.<String>asList("-target")); private static final Set<String> FILTERED_SINGLE_OPTIONS = new HashSet<String>( Arrays.<String>asList( "-g", "-deprecation", "-nowarn", "-verbose", "-proc:none", "-proc:only", "-proceedOnError")); private static final FileFilter JAVA_SOURCES_FILTER = SystemInfo.isFileSystemCaseSensitive ? new FileFilter() { public boolean accept(File file) { return file.getPath().endsWith(JAVA_EXTENSION); } } : new FileFilter() { public boolean accept(File file) { return StringUtil.endsWithIgnoreCase(file.getPath(), JAVA_EXTENSION); } }; private final Executor myTaskRunner; private static final List<ClassPostProcessor> ourClassProcessors = new ArrayList<ClassPostProcessor>(); public static void registerClassPostProcessor(ClassPostProcessor processor) { ourClassProcessors.add(processor); } public JavaBuilder(Executor tasksExecutor) { super(BuilderCategory.TRANSLATOR); myTaskRunner = new SequentialTaskExecutor(tasksExecutor); // add here class processors in the sequence they should be executed } @NotNull public String getPresentableName() { return BUILDER_NAME; } @Override public void buildStarted(CompileContext context) { final JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(project); final String compilerId = config == null ? JavaCompilers.JAVAC_ID : config.getJavaCompilerId(); if (LOG.isDebugEnabled()) { LOG.debug("Java compiler ID: " + compilerId); } final boolean isJavac = JavaCompilers.JAVAC_ID.equalsIgnoreCase(compilerId) || JavaCompilers.JAVAC_API_ID.equalsIgnoreCase(compilerId); final boolean isEclipse = JavaCompilers.ECLIPSE_ID.equalsIgnoreCase(compilerId) || JavaCompilers.ECLIPSE_EMBEDDED_ID.equalsIgnoreCase(compilerId); IS_ENABLED.set(context, isJavac || isEclipse); String messageText = null; if (isJavac) { messageText = "Using javac " + System.getProperty("java.version") + " to compile java sources"; } else if (isEclipse) { messageText = "Using eclipse compiler to compile java sources"; } COMPILER_VERSION_INFO.set(context, new AtomicReference<String>(messageText)); } public ExitCode build( final CompileContext context, final ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException { if (!IS_ENABLED.get(context, Boolean.TRUE)) { return ExitCode.NOTHING_DONE; } try { final Map<File, ModuleBuildTarget> filesToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY); dirtyFilesHolder.processDirtyFiles( new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() { public boolean apply( ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor) throws IOException { if (JAVA_SOURCES_FILTER.accept(file)) { filesToCompile.put(file, target); } return true; } }); if (context.isMake()) { final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger(); if (logger.isEnabled()) { if (filesToCompile.size() > 0) { logger.logCompiledFiles(filesToCompile.keySet(), BUILDER_NAME, "Compiling files:"); } } } return compile(context, chunk, dirtyFilesHolder, filesToCompile.keySet(), outputConsumer); } catch (ProjectBuildException e) { throw e; } catch (Exception e) { String message = e.getMessage(); if (message == null) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream stream = new PrintStream(out); try { e.printStackTrace(stream); } finally { stream.close(); } message = "Internal error: \n" + out.toString(); } context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message)); throw new ProjectBuildException(message, e); } } @Override public boolean shouldHonorFileEncodingForCompilation(File file) { return JAVA_SOURCES_FILTER.accept(file); } private ExitCode compile( final CompileContext context, ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, Collection<File> files, OutputConsumer outputConsumer) throws Exception { ExitCode exitCode = ExitCode.NOTHING_DONE; final boolean hasSourcesToCompile = !files.isEmpty(); if (!hasSourcesToCompile && !dirtyFilesHolder.hasRemovedFiles()) { return exitCode; } final ProjectDescriptor pd = context.getProjectDescriptor(); JavaBuilderUtil.ensureModuleHasJdk( chunk.representativeTarget().getModule(), context, BUILDER_NAME); final Collection<File> classpath = ProjectPaths.getCompilationClasspath(chunk, false /*context.isProjectRebuild()*/); final Collection<File> platformCp = ProjectPaths.getPlatformCompilationClasspath(chunk, false /*context.isProjectRebuild()*/); // begin compilation round final DiagnosticSink diagnosticSink = new DiagnosticSink(context); final Mappings delta = pd.dataManager.getMappings().createDelta(); final Callbacks.Backend mappingsCallback = delta.getCallback(); final OutputFilesSink outputSink = new OutputFilesSink(context, outputConsumer, mappingsCallback, chunk.getName()); try { if (hasSourcesToCompile) { final AtomicReference<String> ref = COMPILER_VERSION_INFO.get(context); final String versionInfo = ref.getAndSet(null); // display compiler version info only once per compile session if (versionInfo != null) { LOG.info(versionInfo); context.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, versionInfo)); } exitCode = ExitCode.OK; final Set<File> srcPath = new HashSet<File>(); final BuildRootIndex index = pd.getBuildRootIndex(); for (ModuleBuildTarget target : chunk.getTargets()) { for (JavaSourceRootDescriptor rd : index.getTempTargetRoots(target, context)) { srcPath.add(rd.root); } } final String chunkName = chunk.getName(); context.processMessage(new ProgressMessage("Parsing java... [" + chunkName + "]")); final int filesCount = files.size(); boolean compiledOk = true; if (filesCount > 0) { LOG.info( "Compiling " + filesCount + " java files; module: " + chunkName + (chunk.containsTests() ? " (tests)" : "")); if (LOG.isDebugEnabled()) { for (File file : files) { LOG.debug("Compiling " + file.getPath()); } LOG.debug(" classpath for " + chunkName + ":"); for (File file : classpath) { LOG.debug(" " + file.getAbsolutePath()); } LOG.debug(" platform classpath for " + chunkName + ":"); for (File file : platformCp) { LOG.debug(" " + file.getAbsolutePath()); } } compiledOk = compileJava( context, chunk, files, classpath, platformCp, srcPath, diagnosticSink, outputSink); } context.checkCanceled(); if (!compiledOk && diagnosticSink.getErrorCount() == 0) { diagnosticSink.report( new PlainMessageDiagnostic( Diagnostic.Kind.ERROR, "Compilation failed: internal java compiler error")); } if (!Utils.PROCEED_ON_ERROR_KEY.get(context, Boolean.FALSE) && diagnosticSink.getErrorCount() > 0) { if (!compiledOk) { diagnosticSink.report( new PlainMessageDiagnostic( Diagnostic.Kind.OTHER, "Errors occurred while compiling module '" + chunkName + "'")); } throw new ProjectBuildException( "Compilation failed: errors: " + diagnosticSink.getErrorCount() + "; warnings: " + diagnosticSink.getWarningCount()); } } } finally { if (JavaBuilderUtil.updateMappings( context, delta, dirtyFilesHolder, chunk, files, outputSink.getSuccessfullyCompiled())) { exitCode = ExitCode.ADDITIONAL_PASS_REQUIRED; } } return exitCode; } private boolean compileJava( final CompileContext context, ModuleChunk chunk, Collection<File> files, Collection<File> classpath, Collection<File> platformCp, Collection<File> sourcePath, DiagnosticOutputConsumer diagnosticSink, final OutputFileConsumer outputSink) throws Exception { final TasksCounter counter = new TasksCounter(); COUNTER_KEY.set(context, counter); final JpsJavaExtensionService javaExt = JpsJavaExtensionService.getInstance(); final JpsJavaCompilerConfiguration compilerConfig = javaExt.getCompilerConfiguration(context.getProjectDescriptor().getProject()); assert compilerConfig != null; final Set<JpsModule> modules = chunk.getModules(); ProcessorConfigProfile profile = null; if (modules.size() == 1) { final JpsModule module = modules.iterator().next(); profile = compilerConfig.getAnnotationProcessingProfile(module); } else { // perform cycle-related validations Pair<String, LanguageLevel> pair = null; for (JpsModule module : modules) { final LanguageLevel moduleLevel = javaExt.getLanguageLevel(module); if (pair == null) { pair = Pair.create(module.getName(), moduleLevel); // first value } else { if (!Comparing.equal(pair.getSecond(), moduleLevel)) { final String message = "Modules " + pair.getFirst() + " and " + module.getName() + " must have the same language level because of cyclic dependencies between them"; diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message)); return true; } } } // check that all chunk modules are excluded from annotation processing for (JpsModule module : modules) { final ProcessorConfigProfile prof = compilerConfig.getAnnotationProcessingProfile(module); if (prof.isEnabled()) { final String message = "Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [" + chunk.getName() + "] are excluded from annotation processing"; diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message)); return true; } } } final Map<File, Set<File>> outs = buildOutputDirectoriesMap(context, chunk); final List<String> options = getCompilationOptions(context, chunk, profile); final ClassProcessingConsumer classesConsumer = new ClassProcessingConsumer(context, outputSink); if (LOG.isDebugEnabled()) { LOG.debug( "Compiling chunk [" + chunk.getName() + "] with options: \"" + StringUtil.join(options, " ") + "\""); } try { final boolean rc; if (USE_EMBEDDED_JAVAC) { final boolean useEclipse = useEclipseCompiler(context); rc = JavacMain.compile( options, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer, context.getCancelStatus(), useEclipse); } else { final JavacServerClient client = ensureJavacServerLaunched(context); final RequestFuture<JavacServerResponseHandler> future = client.sendCompileRequest( options, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer); while (!future.waitFor(100L, TimeUnit.MILLISECONDS)) { if (context.getCancelStatus().isCanceled()) { future.cancel(false); } } rc = future.getMessageHandler().isTerminatedSuccessfully(); } return rc; } finally { counter.await(); } } private static boolean useEclipseCompiler(CompileContext context) { if (!USE_EMBEDDED_JAVAC) { return false; } JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getCompilerConfiguration(project); final String compilerId = configuration != null ? configuration.getJavaCompilerId() : null; return JavaCompilers.ECLIPSE_ID.equalsIgnoreCase(compilerId) || JavaCompilers.ECLIPSE_EMBEDDED_ID.equalsIgnoreCase(compilerId); } private void submitAsyncTask(CompileContext context, final Runnable taskRunnable) { final TasksCounter counter = COUNTER_KEY.get(context); assert counter != null; counter.incTaskCount(); myTaskRunner.execute( new Runnable() { public void run() { try { taskRunnable.run(); } finally { counter.decTaskCounter(); } } }); } private static synchronized JavacServerClient ensureJavacServerLaunched(CompileContext context) throws Exception { final ExternalJavacDescriptor descriptor = ExternalJavacDescriptor.KEY.get(context); if (descriptor != null) { return descriptor.client; } // start server here final int port = findFreePort(); final int heapSize = getJavacServerHeapSize(context); // defaulting to the same jdk that used to run the build process String javaHome = SystemProperties.getJavaHome(); int javaVersion = convertToNumber(SystemProperties.getJavaVersion()); for (JpsSdk<?> sdk : context.getProjectDescriptor().getProjectJavaSdks()) { final String version = sdk.getVersionString(); final int ver = convertToNumber(version); if (ver > javaVersion) { javaVersion = ver; javaHome = sdk.getHomePath(); } } final BaseOSProcessHandler processHandler = JavacServerBootstrap.launchJavacServer( javaHome, heapSize, port, Utils.getSystemRoot(), getCompilationVMOptions(context)); final JavacServerClient client = new JavacServerClient(); try { client.connect("127.0.0.1", port); } catch (Throwable ex) { processHandler.destroyProcess(); throw new Exception("Failed to connect to external javac process: ", ex); } ExternalJavacDescriptor.KEY.set(context, new ExternalJavacDescriptor(processHandler, client)); return client; } private static int convertToNumber(String ver) { if (ver == null) { return 0; } final int quoteBegin = ver.indexOf("\""); if (quoteBegin >= 0) { final int quoteEnd = ver.indexOf("\"", quoteBegin + 1); if (quoteEnd > quoteBegin) { ver = ver.substring(quoteBegin + 1, quoteEnd); } } if (ver.isEmpty()) { return 0; } final String prefix = "1."; final int parseBegin = ver.startsWith(prefix) ? prefix.length() : 0; final int parseEnd = ver.indexOf(".", parseBegin); if (parseEnd > 0) { ver = ver.substring(parseBegin, parseEnd); } else { ver = ver.substring(parseBegin); } try { return Integer.parseInt(ver); } catch (NumberFormatException ignored) { } return 0; } private static int findFreePort() { try { final ServerSocket serverSocket = new ServerSocket(0); try { return serverSocket.getLocalPort(); } finally { // workaround for linux : calling close() immediately after opening socket // may result that socket is not closed synchronized (serverSocket) { try { serverSocket.wait(1); } catch (Throwable ignored) { } } serverSocket.close(); } } catch (IOException e) { e.printStackTrace(System.err); return JavacServer.DEFAULT_SERVER_PORT; } } private static int getJavacServerHeapSize(CompileContext context) { final JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project); final JpsJavaCompilerOptions options = config.getCurrentCompilerOptions(); return options.MAXIMUM_HEAP_SIZE; } private static final Key<List<String>> JAVAC_OPTIONS = Key.create("_javac_options_"); private static final Key<List<String>> JAVAC_VM_OPTIONS = Key.create("_javac_vm_options_"); private static List<String> getCompilationVMOptions(CompileContext context) { List<String> cached = JAVAC_VM_OPTIONS.get(context); if (cached == null) { loadCommonJavacOptions(context); cached = JAVAC_VM_OPTIONS.get(context); } return cached; } private static List<String> getCompilationOptions( CompileContext context, ModuleChunk chunk, @Nullable ProcessorConfigProfile profile) { List<String> cached = JAVAC_OPTIONS.get(context); if (cached == null) { loadCommonJavacOptions(context); cached = JAVAC_OPTIONS.get(context); assert cached != null : context; } List<String> options = new ArrayList<String>(cached); addCompilationOptions(options, context, chunk, profile); return options; } public static void addCompilationOptions( List<String> options, CompileContext context, ModuleChunk chunk, @Nullable ProcessorConfigProfile profile) { if (!isEncodingSet(options)) { final CompilerEncodingConfiguration config = context.getProjectDescriptor().getEncodingConfiguration(); final String encoding = config.getPreferredModuleChunkEncoding(chunk); if (config.getAllModuleChunkEncodings(chunk).size() > 1) { final StringBuilder msgBuilder = new StringBuilder(); msgBuilder.append("Multiple encodings set for module chunk ").append(chunk.getName()); if (encoding != null) { msgBuilder.append("\n\"").append(encoding).append("\" will be used by compiler"); } context.processMessage( new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.INFO, msgBuilder.toString())); } if (!StringUtil.isEmpty(encoding)) { options.add("-encoding"); options.add(encoding); } } final String langLevel = getLanguageLevel(chunk.getModules().iterator().next()); if (!StringUtil.isEmpty(langLevel)) { options.add("-source"); options.add(langLevel); } JpsJavaCompilerConfiguration compilerConfiguration = JpsJavaExtensionService.getInstance() .getOrCreateCompilerConfiguration(context.getProjectDescriptor().getProject()); String bytecodeTarget = null; int chunkSdkVersion = -1; for (JpsModule module : chunk.getModules()) { final JpsSdk<JpsDummyElement> sdk = module.getSdk(JpsJavaSdkType.INSTANCE); if (sdk != null) { final int moduleSdkVersion = convertToNumber(sdk.getVersionString()); if (moduleSdkVersion != 0 /*could determine the version*/ && (chunkSdkVersion < 0 || chunkSdkVersion > moduleSdkVersion)) { chunkSdkVersion = moduleSdkVersion; } } final String moduleTarget = compilerConfiguration.getByteCodeTargetLevel(module.getName()); if (moduleTarget == null) { continue; } if (bytecodeTarget == null) { bytecodeTarget = moduleTarget; } else { if (moduleTarget.compareTo(bytecodeTarget) < 0) { bytecodeTarget = moduleTarget; // use the lower possible target among modules that form the chunk } } } if (bytecodeTarget != null) { options.add("-target"); options.add(bytecodeTarget); } else { if (chunkSdkVersion > 0 && getCompilerSdkVersion(context) > chunkSdkVersion) { // force lower bytecode target level to match the version of sdk assigned to this chunk options.add("-target"); options.add("1." + chunkSdkVersion); } } if (profile != null && profile.isEnabled()) { // configuring annotation processing if (!profile.isObtainProcessorsFromClasspath()) { final String processorsPath = profile.getProcessorPath(); options.add("-processorpath"); options.add( processorsPath == null ? "" : FileUtil.toSystemDependentName(processorsPath.trim())); } final Set<String> processors = profile.getProcessors(); if (!processors.isEmpty()) { options.add("-processor"); options.add(StringUtil.join(processors, ",")); } for (Map.Entry<String, String> optionEntry : profile.getProcessorOptions().entrySet()) { options.add("-A" + optionEntry.getKey() + "=" + optionEntry.getValue()); } final File srcOutput = ProjectPaths.getAnnotationProcessorGeneratedSourcesOutputDir( chunk.getModules().iterator().next(), chunk.containsTests(), profile); if (srcOutput != null) { srcOutput.mkdirs(); options.add("-s"); options.add(srcOutput.getPath()); } } else { options.add("-proc:none"); } } private static String getLanguageLevel(JpsModule module) { LanguageLevel level = JpsJavaExtensionService.getInstance().getLanguageLevel(module); if (level == null) return null; switch (level) { case JDK_1_3: return "1.3"; case JDK_1_4: return "1.4"; case JDK_1_5: return "1.5"; case JDK_1_6: return "1.6"; case JDK_1_7: return "1.7"; case JDK_1_8: return "8"; default: return null; } } private static boolean isEncodingSet(List<String> options) { for (String option : options) { if ("-encoding".equals(option)) { return true; } } return false; } private static int getCompilerSdkVersion(CompileContext context) { final Integer cached = JAVA_COMPILER_VERSION_KEY.get(context); if (cached != null) { return cached; } int javaVersion = convertToNumber(SystemProperties.getJavaVersion()); if (!USE_EMBEDDED_JAVAC) { // in case of external javac, run compiler from the newest jdk that is used in the project for (JpsSdk<?> sdk : context.getProjectDescriptor().getProjectJavaSdks()) { final String version = sdk.getVersionString(); final int ver = convertToNumber(version); if (ver > javaVersion) { javaVersion = ver; } } } JAVA_COMPILER_VERSION_KEY.set(context, javaVersion); return javaVersion; } private static void loadCommonJavacOptions(CompileContext context) { final List<String> options = new ArrayList<String>(); final List<String> vmOptions = new ArrayList<String>(); final JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration compilerConfig = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project); final JpsJavaCompilerOptions compilerOptions = compilerConfig.getCurrentCompilerOptions(); if (compilerOptions.DEBUGGING_INFO) { options.add("-g"); } if (compilerOptions.DEPRECATION) { options.add("-deprecation"); } if (compilerOptions.GENERATE_NO_WARNINGS) { options.add("-nowarn"); } if (compilerOptions instanceof EclipseCompilerOptions) { final EclipseCompilerOptions eclipseOptions = (EclipseCompilerOptions) compilerOptions; if (eclipseOptions.PROCEED_ON_ERROR) { options.add("-proceedOnError"); } } final String customArgs = compilerOptions.ADDITIONAL_OPTIONS_STRING; if (customArgs != null) { final StringTokenizer customOptsTokenizer = new StringTokenizer(customArgs, " \t\r\n"); boolean skip = false; while (customOptsTokenizer.hasMoreTokens()) { final String userOption = customOptsTokenizer.nextToken(); if (FILTERED_OPTIONS.contains(userOption)) { skip = true; continue; } if (!skip) { if (!FILTERED_SINGLE_OPTIONS.contains(userOption)) { if (userOption.startsWith("-J-")) { vmOptions.add(userOption.substring("-J".length())); } else { options.add(userOption); } } } } } if (useEclipseCompiler(context)) { for (String option : options) { if (option.startsWith("-proceedOnError")) { Utils.PROCEED_ON_ERROR_KEY.set(context, Boolean.TRUE); break; } } } JAVAC_OPTIONS.set(context, options); JAVAC_VM_OPTIONS.set(context, vmOptions); } @Override public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) { JavaBuilderUtil.cleanupChunkResources(context); } private static Map<File, Set<File>> buildOutputDirectoriesMap( CompileContext context, ModuleChunk chunk) { final Map<File, Set<File>> map = new THashMap<File, Set<File>>(FileUtil.FILE_HASHING_STRATEGY); for (ModuleBuildTarget target : chunk.getTargets()) { final File outputDir = target.getOutputDir(); if (outputDir == null) { continue; } final Set<File> roots = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); for (JavaSourceRootDescriptor descriptor : context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context)) { roots.add(descriptor.root); } map.put(outputDir, roots); } return map; } private class DiagnosticSink implements DiagnosticOutputConsumer { private final CompileContext myContext; private volatile int myErrorCount = 0; private volatile int myWarningCount = 0; public DiagnosticSink(CompileContext context) { myContext = context; } public void registerImports( final String className, final Collection<String> imports, final Collection<String> staticImports) { // submitAsyncTask(myContext, new Runnable() { // public void run() { // final Callbacks.Backend callback = DELTA_MAPPINGS_CALLBACK_KEY.get(myContext); // if (callback != null) { // callback.registerImports(className, imports, staticImports); // } // } // }); } public void outputLineAvailable(String line) { if (!StringUtil.isEmpty(line)) { if (line.contains("java.lang.OutOfMemoryError")) { myContext.processMessage( new CompilerMessage( BUILDER_NAME, BuildMessage.Kind.ERROR, "OutOfMemoryError: insufficient memory")); myErrorCount++; } else { final BuildMessage.Kind kind = getKindByMessageText(line); if (kind == BuildMessage.Kind.ERROR) { myErrorCount++; } else if (kind == BuildMessage.Kind.WARNING) { myWarningCount++; } myContext.processMessage(new CompilerMessage(BUILDER_NAME, kind, line)); } } } private BuildMessage.Kind getKindByMessageText(String line) { final String lowercasedLine = line.toLowerCase(Locale.US); if (lowercasedLine.contains("error") || lowercasedLine.contains("requires target release")) { return BuildMessage.Kind.ERROR; } return BuildMessage.Kind.INFO; } public void report(Diagnostic<? extends JavaFileObject> diagnostic) { final CompilerMessage.Kind kind; switch (diagnostic.getKind()) { case ERROR: kind = BuildMessage.Kind.ERROR; myErrorCount++; break; case MANDATORY_WARNING: case WARNING: case NOTE: kind = BuildMessage.Kind.WARNING; myWarningCount++; break; default: kind = BuildMessage.Kind.INFO; } File sourceFile = null; try { // for eclipse compiler just an attempt to call getSource() may lead to an NPE, // so calling this method under try/catch to avoid induced compiler errors final JavaFileObject source = diagnostic.getSource(); sourceFile = source != null ? Utils.convertToFile(source.toUri()) : null; } catch (Exception e) { LOG.info(e); } final String srcPath = sourceFile != null ? FileUtil.toSystemIndependentName(sourceFile.getPath()) : null; String message = diagnostic.getMessage(Locale.US); if (Utils.IS_TEST_MODE) { LOG.info(message); } myContext.processMessage( new CompilerMessage( BUILDER_NAME, kind, message, srcPath, diagnostic.getStartPosition(), diagnostic.getEndPosition(), diagnostic.getPosition(), diagnostic.getLineNumber(), diagnostic.getColumnNumber())); } public int getErrorCount() { return myErrorCount; } public int getWarningCount() { return myWarningCount; } } private class ClassProcessingConsumer implements OutputFileConsumer { private final CompileContext myContext; private final OutputFileConsumer myDelegateOutputFileSink; public ClassProcessingConsumer(CompileContext context, OutputFileConsumer sink) { myContext = context; myDelegateOutputFileSink = sink != null ? sink : new OutputFileConsumer() { public void save(@NotNull OutputFileObject fileObject) { throw new RuntimeException("Output sink for compiler was not specified"); } }; } public void save(@NotNull final OutputFileObject fileObject) { if (JavaFileObject.Kind.CLASS != fileObject.getKind()) { // generated sources or resources must be saved synchronously, because some compilers (e.g. // eclipse) // may want to read generated text for further compilation try { final BinaryContent content = fileObject.getContent(); if (content != null) { content.saveToFile(fileObject.getFile()); } } catch (IOException e) { myContext.processMessage( new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage())); } } submitAsyncTask( myContext, new Runnable() { public void run() { try { for (ClassPostProcessor processor : ourClassProcessors) { processor.process(myContext, fileObject); } } finally { myDelegateOutputFileSink.save(fileObject); } } }); } } private static final Key<TasksCounter> COUNTER_KEY = Key.create("_async_task_counter_"); private static final class TasksCounter { private int myCounter = 0; public synchronized void incTaskCount() { myCounter++; } public synchronized void decTaskCounter() { myCounter = Math.max(0, myCounter - 1); if (myCounter == 0) { notifyAll(); } } public synchronized void await() { while (myCounter > 0) { try { wait(); } catch (InterruptedException e) { } } } } }