2 * Copyright 2000-2013 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.jetbrains.jps.incremental.java;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Comparing;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.util.SystemProperties;
26 import com.intellij.util.concurrency.SequentialTaskExecutor;
27 import com.intellij.util.io.PersistentEnumeratorBase;
28 import gnu.trove.THashMap;
29 import gnu.trove.THashSet;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32 import org.jetbrains.jps.ModuleChunk;
33 import org.jetbrains.jps.ProjectPaths;
34 import org.jetbrains.jps.builders.BuildRootIndex;
35 import org.jetbrains.jps.builders.DirtyFilesHolder;
36 import org.jetbrains.jps.builders.FileProcessor;
37 import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
38 import org.jetbrains.jps.builders.java.JavaBuilderExtension;
39 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
40 import org.jetbrains.jps.builders.java.JavaCompilingTool;
41 import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
42 import org.jetbrains.jps.builders.java.dependencyView.Callbacks;
43 import org.jetbrains.jps.builders.java.dependencyView.Mappings;
44 import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
45 import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
46 import org.jetbrains.jps.cmdline.ProjectDescriptor;
47 import org.jetbrains.jps.incremental.*;
48 import org.jetbrains.jps.incremental.messages.BuildMessage;
49 import org.jetbrains.jps.incremental.messages.CompilerMessage;
50 import org.jetbrains.jps.incremental.messages.ProgressMessage;
51 import org.jetbrains.jps.javac.*;
52 import org.jetbrains.jps.model.JpsDummyElement;
53 import org.jetbrains.jps.model.JpsProject;
54 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
55 import org.jetbrains.jps.model.java.JpsJavaSdkType;
56 import org.jetbrains.jps.model.java.LanguageLevel;
57 import org.jetbrains.jps.model.java.compiler.*;
58 import org.jetbrains.jps.model.library.sdk.JpsSdk;
59 import org.jetbrains.jps.model.module.JpsModule;
60 import org.jetbrains.jps.model.module.JpsModuleType;
61 import org.jetbrains.jps.service.JpsServiceManager;
65 import java.net.ServerSocket;
67 import java.util.concurrent.Executor;
68 import java.util.concurrent.atomic.AtomicReference;
71 * @author Eugene Zhuravlev
74 public class JavaBuilder extends ModuleLevelBuilder {
75 private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.java.JavaBuilder");
76 public static final String BUILDER_NAME = "java";
77 private static final String JAVA_EXTENSION = "java";
78 private static final String DOT_JAVA_EXTENSION = "." + JAVA_EXTENSION;
79 private static final Key<Integer> JAVA_COMPILER_VERSION_KEY = Key.create("_java_compiler_version_");
80 public static final Key<Boolean> IS_ENABLED = Key.create("_java_compiler_enabled_");
81 private static final Key<JavaCompilingTool> COMPILING_TOOL = Key.create("_java_compiling_tool_");
82 private static final Key<AtomicReference<String>> COMPILER_VERSION_INFO = Key.create("_java_compiler_version_info_");
84 private static final Set<String> FILTERED_OPTIONS = new HashSet<String>(Arrays.<String>asList(
87 private static final Set<String> FILTERED_SINGLE_OPTIONS = new HashSet<String>(Arrays.<String>asList(
88 "-g", "-deprecation", "-nowarn", "-verbose", "-proc:none", "-proc:only", "-proceedOnError"
91 public static final FileFilter JAVA_SOURCES_FILTER =
92 SystemInfo.isFileSystemCaseSensitive?
94 public boolean accept(File file) {
95 return file.getPath().endsWith(DOT_JAVA_EXTENSION);
99 public boolean accept(File file) {
100 return StringUtil.endsWithIgnoreCase(file.getPath(), DOT_JAVA_EXTENSION);
103 private static final String RT_JAR_PATH_SUFFIX = File.separator + "rt.jar";
105 private final Executor myTaskRunner;
106 private static final List<ClassPostProcessor> ourClassProcessors = new ArrayList<ClassPostProcessor>();
107 private static final Set<JpsModuleType<?>> ourCompilableModuleTypes;
109 private static final File ourDefaultRtJar;
111 ourCompilableModuleTypes = new HashSet<JpsModuleType<?>>();
112 for (JavaBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(JavaBuilderExtension.class)) {
113 ourCompilableModuleTypes.addAll(extension.getCompilableModuleTypes());
116 StringTokenizer tokenizer = new StringTokenizer(System.getProperty("sun.boot.class.path", ""), File.pathSeparator, false);
117 while (tokenizer.hasMoreTokens()) {
118 final String path = tokenizer.nextToken();
119 if (isRtJarPath(path)) {
120 rtJar = new File(path);
124 ourDefaultRtJar = rtJar;
127 private static boolean isRtJarPath(String path) {
128 if (StringUtil.endsWithIgnoreCase(path, RT_JAR_PATH_SUFFIX)) {
131 return RT_JAR_PATH_SUFFIX.charAt(0) != '/' && StringUtil.endsWithIgnoreCase(path, "/rt.jar");
134 public static void registerClassPostProcessor(ClassPostProcessor processor) {
135 ourClassProcessors.add(processor);
138 public JavaBuilder(Executor tasksExecutor) {
139 super(BuilderCategory.TRANSLATOR);
140 myTaskRunner = new SequentialTaskExecutor(tasksExecutor);
141 //add here class processors in the sequence they should be executed
145 public String getPresentableName() {
150 public void buildStarted(CompileContext context) {
151 final JpsProject project = context.getProjectDescriptor().getProject();
152 final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(project);
153 final String compilerId = config == null? JavaCompilers.JAVAC_ID : config.getJavaCompilerId();
154 if (LOG.isDebugEnabled()) {
155 LOG.debug("Java compiler ID: " + compilerId);
157 JavaCompilingTool compilingTool = JavaBuilderUtil.findCompilingTool(compilerId);
158 COMPILING_TOOL.set(context, compilingTool);
159 String messageText = compilingTool != null ? "Using " + compilingTool.getDescription() + " to compile java sources" : null;
160 COMPILER_VERSION_INFO.set(context, new AtomicReference<String>(messageText));
164 public List<String> getCompilableFileExtensions() {
165 return Collections.singletonList(JAVA_EXTENSION);
168 public ExitCode build(@NotNull CompileContext context,
169 @NotNull ModuleChunk chunk,
170 @NotNull DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
171 @NotNull OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
172 JavaCompilingTool compilingTool = COMPILING_TOOL.get(context);
173 if (!IS_ENABLED.get(context, Boolean.TRUE) || compilingTool == null) {
174 return ExitCode.NOTHING_DONE;
176 return doBuild(context, chunk, dirtyFilesHolder, outputConsumer, compilingTool);
179 public ExitCode doBuild(@NotNull CompileContext context,
180 @NotNull ModuleChunk chunk,
181 @NotNull DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
182 @NotNull OutputConsumer outputConsumer, JavaCompilingTool compilingTool) throws ProjectBuildException, IOException {
184 final Set<File> filesToCompile = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
186 dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
187 public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor) throws IOException {
188 if (JAVA_SOURCES_FILTER.accept(file) && ourCompilableModuleTypes.contains(target.getModule().getModuleType())) {
189 filesToCompile.add(file);
195 if (JavaBuilderUtil.isCompileJavaIncrementally(context)) {
196 final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
197 if (logger.isEnabled()) {
198 if (filesToCompile.size() > 0) {
199 logger.logCompiledFiles(filesToCompile, BUILDER_NAME, "Compiling files:");
204 return compile(context, chunk, dirtyFilesHolder, filesToCompile, outputConsumer, compilingTool);
206 catch (BuildDataCorruptedException e) {
209 catch (ProjectBuildException e) {
212 catch (PersistentEnumeratorBase.CorruptedException e) {
215 catch (Exception e) {
217 String message = e.getMessage();
218 if (message == null) {
219 final ByteArrayOutputStream out = new ByteArrayOutputStream();
220 final PrintStream stream = new PrintStream(out);
222 e.printStackTrace(stream);
227 message = "Internal error: \n" + out.toString();
229 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message));
230 throw new StopBuildException();
234 private ExitCode compile(final CompileContext context,
236 DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
237 Collection<File> files,
238 OutputConsumer outputConsumer, @NotNull JavaCompilingTool compilingTool)
240 ExitCode exitCode = ExitCode.NOTHING_DONE;
242 final boolean hasSourcesToCompile = !files.isEmpty();
244 if (!hasSourcesToCompile && !dirtyFilesHolder.hasRemovedFiles()) {
248 final ProjectDescriptor pd = context.getProjectDescriptor();
250 JavaBuilderUtil.ensureModuleHasJdk(chunk.representativeTarget().getModule(), context, BUILDER_NAME);
251 final Collection<File> classpath = ProjectPaths.getCompilationClasspath(chunk, false/*context.isProjectRebuild()*/);
252 final Collection<File> platformCp = ProjectPaths.getPlatformCompilationClasspath(chunk, false/*context.isProjectRebuild()*/);
254 // begin compilation round
255 final Mappings delta = pd.dataManager.getMappings().createDelta();
256 final Callbacks.Backend mappingsCallback = delta.getCallback();
257 final OutputFilesSink outputSink = new OutputFilesSink(context, outputConsumer, mappingsCallback, chunk.getPresentableShortName());
259 if (hasSourcesToCompile) {
260 final AtomicReference<String> ref = COMPILER_VERSION_INFO.get(context);
261 final String versionInfo = ref.getAndSet(null); // display compiler version info only once per compile session
262 if (versionInfo != null) {
263 LOG.info(versionInfo);
264 context.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, versionInfo));
266 exitCode = ExitCode.OK;
268 final Set<File> srcPath = new HashSet<File>();
269 final BuildRootIndex index = pd.getBuildRootIndex();
270 for (ModuleBuildTarget target : chunk.getTargets()) {
271 for (JavaSourceRootDescriptor rd : index.getTempTargetRoots(target, context)) {
272 srcPath.add(rd.root);
275 final DiagnosticSink diagnosticSink = new DiagnosticSink(context);
277 final String chunkName = chunk.getName();
278 context.processMessage(new ProgressMessage("Parsing java... [" + chunk.getPresentableShortName() + "]"));
280 final int filesCount = files.size();
281 boolean compiledOk = true;
282 if (filesCount > 0) {
283 LOG.info("Compiling " + filesCount + " java files; module: " + chunkName + (chunk.containsTests() ? " (tests)" : ""));
284 if (LOG.isDebugEnabled()) {
285 for (File file : files) {
286 LOG.debug("Compiling " + file.getPath());
288 LOG.debug(" classpath for " + chunkName + ":");
289 for (File file : classpath) {
290 LOG.debug(" " + file.getAbsolutePath());
292 LOG.debug(" platform classpath for " + chunkName + ":");
293 for (File file : platformCp) {
294 LOG.debug(" " + file.getAbsolutePath());
298 compiledOk = compileJava(context, chunk, files, classpath, platformCp, srcPath, diagnosticSink, outputSink, compilingTool);
301 // heuristic: incorrect paths data recovery, so that the next make should not contain non-existing sources in 'recompile' list
302 for (File file : diagnosticSink.getFilesWithErrors()) {
303 if (!file.exists()) {
304 FSOperations.markDeleted(context, file);
310 context.checkCanceled();
312 if (!compiledOk && diagnosticSink.getErrorCount() == 0) {
313 diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, "Compilation failed: internal java compiler error"));
315 if (!Utils.PROCEED_ON_ERROR_KEY.get(context, Boolean.FALSE) && diagnosticSink.getErrorCount() > 0) {
317 diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.OTHER, "Errors occurred while compiling module '" + chunkName + "'"));
319 throw new StopBuildException(
320 "Compilation failed: errors: " + diagnosticSink.getErrorCount() + "; warnings: " + diagnosticSink.getWarningCount()
326 if (JavaBuilderUtil.updateMappings(context, delta, dirtyFilesHolder, chunk, files, outputSink.getSuccessfullyCompiled())) {
327 exitCode = ExitCode.ADDITIONAL_PASS_REQUIRED;
334 private boolean compileJava(
335 final CompileContext context,
337 Collection<File> files,
338 Collection<File> classpath,
339 Collection<File> platformCp,
340 Collection<File> sourcePath,
341 DiagnosticOutputConsumer diagnosticSink,
342 final OutputFileConsumer outputSink, JavaCompilingTool compilingTool) throws Exception {
344 final TasksCounter counter = new TasksCounter();
345 COUNTER_KEY.set(context, counter);
347 final JpsJavaExtensionService javaExt = JpsJavaExtensionService.getInstance();
348 final JpsJavaCompilerConfiguration compilerConfig = javaExt.getCompilerConfiguration(context.getProjectDescriptor().getProject());
349 assert compilerConfig != null;
351 final Set<JpsModule> modules = chunk.getModules();
352 ProcessorConfigProfile profile = null;
353 if (modules.size() == 1) {
354 profile = compilerConfig.getAnnotationProcessingProfile(modules.iterator().next());
357 String message = validateCycle(chunk, javaExt, compilerConfig, modules);
358 if (message != null) {
359 diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
364 final Map<File, Set<File>> outs = buildOutputDirectoriesMap(context, chunk);
365 final List<String> options = getCompilationOptions(context, chunk, profile, compilingTool);
366 final ClassProcessingConsumer classesConsumer = new ClassProcessingConsumer(context, outputSink);
367 if (LOG.isDebugEnabled()) {
368 LOG.debug("Compiling chunk [" + chunk.getName() + "] with options: \"" + StringUtil.join(options, " ") + "\"");
372 if (!shouldForkCompilerProcess(context, chunk, compilingTool)) {
373 final Collection<File> _platformCp = calcEffectivePlatformCp(platformCp, options, compilingTool);
374 if (_platformCp == null) {
375 diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR,
376 "Compact compilation profile was requested, but target platform for module \"" + chunk.getName() + "\" differs from javac's platform (" + System.getProperty("java.version") + ")\nCompilation profiles are not supported for such configuration"
380 rc = JavacMain.compile(
381 options, files, classpath, _platformCp, sourcePath, outs, diagnosticSink, classesConsumer, context.getCancelStatus(), compilingTool
385 // fork external javac
386 final String sdkHome = getChunkSdkHome(chunk);
387 if (sdkHome == null) {
388 diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, "Cannot start javac process for " + chunk.getName() + ": unknown JDK home path.\nPlease check project configuration."));
392 final List<String> vmOptions = getCompilationVMOptions(context, compilingTool);
393 final ExternalJavacServer server = ensureJavacServerStarted(context);
394 rc = server.forkJavac(
395 context, options, vmOptions, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer, sdkHome, compilingTool
406 public static String validateCycle(ModuleChunk chunk,
407 JpsJavaExtensionService javaExt,
408 JpsJavaCompilerConfiguration compilerConfig, Set<JpsModule> modules) {
409 Pair<String, LanguageLevel> pair = null;
410 for (JpsModule module : modules) {
411 final LanguageLevel moduleLevel = javaExt.getLanguageLevel(module);
413 pair = Pair.create(module.getName(), moduleLevel); // first value
416 if (!Comparing.equal(pair.getSecond(), moduleLevel)) {
421 " must have the same language level because of cyclic dependencies between them";
426 // check that all chunk modules are excluded from annotation processing
427 for (JpsModule module : modules) {
428 final ProcessorConfigProfile prof = compilerConfig.getAnnotationProcessingProfile(module);
429 if (prof.isEnabled()) {
430 return "Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [" +
432 "] are excluded from annotation processing";
438 private static boolean shouldForkCompilerProcess(CompileContext context, ModuleChunk chunk, JavaCompilingTool tool) {
439 final int compilerSdkVersion = getCompilerSdkVersion(context);
440 if (compilerSdkVersion < 9) {
441 // javac up to version 9 supports all previous releases
444 final int chunkSdkVersion = getChunkSdkVersion(chunk);
445 if (chunkSdkVersion < 0) {
446 return false; // was not able to determine jdk version, assuming in-process compiler
448 // according to JEP 182: Retiring javac "one plus three back" policy
449 return Math.abs(compilerSdkVersion - chunkSdkVersion) > 3;
452 // If platformCp of the build process is the same as the target plafform, do not specify platformCp explicitly
453 // this will allow javac to resolve against ct.sym file, which is required for the "compilation profiles" feature
455 private static Collection<File> calcEffectivePlatformCp(Collection<File> platformCp, List<String> options, JavaCompilingTool compilingTool) {
456 if (ourDefaultRtJar == null || !(compilingTool instanceof JavacCompilerTool)) {
459 boolean profileFeatureRequested = false;
460 for (String option : options) {
461 if ("-profile".equalsIgnoreCase(option)) {
462 profileFeatureRequested = true;
466 if (!profileFeatureRequested) {
469 boolean isTargetPlatformSameAsBuildRuntime = false;
470 for (File file : platformCp) {
471 if (FileUtil.filesEqual(file, ourDefaultRtJar)) {
472 isTargetPlatformSameAsBuildRuntime = true;
476 if (!isTargetPlatformSameAsBuildRuntime) {
477 // compact profile was requested, but we have to use alternative platform classpath to meet project settings
478 // consider this a compile error and let user re-configure the project
481 // returning empty list will force default behaviour for platform classpath calculation
482 // javac will resolve against its own bootclasspath and use ct.sym file when available
483 return Collections.emptyList();
486 private void submitAsyncTask(final CompileContext context, final Runnable taskRunnable) {
487 final TasksCounter counter = COUNTER_KEY.get(context);
489 assert counter != null;
491 counter.incTaskCount();
492 myTaskRunner.execute(new Runnable() {
497 catch (Throwable e) {
498 context.processMessage(new CompilerMessage(BUILDER_NAME, e));
501 counter.decTaskCounter();
507 private static synchronized ExternalJavacServer ensureJavacServerStarted(@NotNull CompileContext context) throws Exception {
508 ExternalJavacServer server = ExternalJavacServer.KEY.get(context);
509 if (server != null) {
512 final int listenPort = findFreePort();
513 server = new ExternalJavacServer();
514 server.start(listenPort);
515 ExternalJavacServer.KEY.set(context, server);
519 private static int convertToNumber(String ver) {
523 final int quoteBegin = ver.indexOf("\"");
524 if (quoteBegin >= 0) {
525 final int quoteEnd = ver.indexOf("\"", quoteBegin + 1);
526 if (quoteEnd > quoteBegin) {
527 ver = ver.substring(quoteBegin + 1, quoteEnd);
534 final String prefix = "1.";
535 final int parseBegin = ver.startsWith(prefix)? prefix.length() : 0;
537 final int parseEnd = ver.indexOf(".", parseBegin);
539 ver = ver.substring(parseBegin, parseEnd);
542 ver = ver.substring(parseBegin);
546 return Integer.parseInt(ver);
548 catch (NumberFormatException ignored) {
553 private static int findFreePort() {
555 final ServerSocket serverSocket = new ServerSocket(0);
557 return serverSocket.getLocalPort();
560 //workaround for linux : calling close() immediately after opening socket
561 //may result that socket is not closed
562 synchronized (serverSocket) {
564 serverSocket.wait(1);
566 catch (Throwable ignored) {
569 serverSocket.close();
572 catch (IOException e) {
573 e.printStackTrace(System.err);
574 return ExternalJavacServer.DEFAULT_SERVER_PORT;
578 private static final Key<List<String>> JAVAC_OPTIONS = Key.create("_javac_options_");
579 private static final Key<List<String>> JAVAC_VM_OPTIONS = Key.create("_javac_vm_options_");
580 private static final Key<String> USER_DEFINED_BYTECODE_TARGET = Key.create("_user_defined_bytecode_target_");
582 private static List<String> getCompilationVMOptions(CompileContext context, JavaCompilingTool compilingTool) {
583 List<String> cached = JAVAC_VM_OPTIONS.get(context);
584 if (cached == null) {
585 loadCommonJavacOptions(context, compilingTool);
586 cached = JAVAC_VM_OPTIONS.get(context);
591 private static List<String> getCompilationOptions(CompileContext context,
593 @Nullable ProcessorConfigProfile profile,
594 @NotNull JavaCompilingTool compilingTool) {
595 List<String> cached = JAVAC_OPTIONS.get(context);
596 if (cached == null) {
597 loadCommonJavacOptions(context, compilingTool);
598 cached = JAVAC_OPTIONS.get(context);
599 assert cached != null : context;
602 List<String> options = new ArrayList<String>(cached);
603 addCompilationOptions(options, context, chunk, profile);
607 public static void addCompilationOptions(List<String> options, CompileContext context, ModuleChunk chunk, @Nullable ProcessorConfigProfile profile) {
608 if (!isEncodingSet(options)) {
609 final CompilerEncodingConfiguration config = context.getProjectDescriptor().getEncodingConfiguration();
610 final String encoding = config.getPreferredModuleChunkEncoding(chunk);
611 if (config.getAllModuleChunkEncodings(chunk).size() > 1) {
612 final StringBuilder msgBuilder = new StringBuilder();
613 msgBuilder.append("Multiple encodings set for module chunk ").append(chunk.getName());
614 if (encoding != null) {
615 msgBuilder.append("\n\"").append(encoding).append("\" will be used by compiler");
617 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.INFO, msgBuilder.toString()));
619 if (!StringUtil.isEmpty(encoding)) {
620 options.add("-encoding");
621 options.add(encoding);
625 final String langLevel = getLanguageLevel(chunk.getModules().iterator().next());
626 if (!StringUtil.isEmpty(langLevel)) {
627 options.add("-source");
628 options.add(langLevel);
631 final JpsJavaCompilerConfiguration compilerConfiguration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(
632 context.getProjectDescriptor().getProject()
635 String bytecodeTarget = null;
636 for (JpsModule module : chunk.getModules()) {
637 final String moduleTarget = compilerConfiguration.getByteCodeTargetLevel(module.getName());
638 if (moduleTarget == null) {
641 if (bytecodeTarget == null) {
642 bytecodeTarget = moduleTarget;
645 if (moduleTarget.compareTo(bytecodeTarget) < 0) {
646 bytecodeTarget = moduleTarget; // use the lower possible target among modules that form the chunk
651 if (bytecodeTarget == null) {
652 // last resort and backward compatibility:
653 // check if user explicitly defined bytecode target in additional compiler options
654 bytecodeTarget = USER_DEFINED_BYTECODE_TARGET.get(context);
657 final int compilerSdkVersion = getCompilerSdkVersion(context);
658 final int chunkSdkVersion = getChunkSdkVersion(chunk);
660 if (bytecodeTarget != null) {
661 options.add("-target");
662 if (chunkSdkVersion > 0 && compilerSdkVersion > chunkSdkVersion) {
663 // if compiler is newer than module JDK
664 final int userSpecifiedTargetVersion = convertToNumber(bytecodeTarget);
665 if (userSpecifiedTargetVersion > 0 && userSpecifiedTargetVersion <= compilerSdkVersion) {
666 // if user-specified bytecode version can be determined and is supported by compiler
667 if (userSpecifiedTargetVersion > chunkSdkVersion) {
668 // and user-specified bytecode target level is higher than the highest one supported by the target JDK,
669 // force compiler to use highest-available bytecode target version that is supported by the chunk JDK.
670 bytecodeTarget = "1." + chunkSdkVersion;
673 // otherwise let compiler display compilation error about incorrectly set bytecode target version
675 options.add(bytecodeTarget);
678 if (chunkSdkVersion > 0 && compilerSdkVersion > chunkSdkVersion) {
679 // force lower bytecode target level to match the version of sdk assigned to this chunk
680 options.add("-target");
681 options.add("1." + chunkSdkVersion);
685 if (profile != null && profile.isEnabled()) {
686 // configuring annotation processing
687 if (!profile.isObtainProcessorsFromClasspath()) {
688 final String processorsPath = profile.getProcessorPath();
689 options.add("-processorpath");
690 options.add(processorsPath == null? "" : FileUtil.toSystemDependentName(processorsPath.trim()));
693 final Set<String> processors = profile.getProcessors();
694 if (!processors.isEmpty()) {
695 options.add("-processor");
696 options.add(StringUtil.join(processors, ","));
699 for (Map.Entry<String, String> optionEntry : profile.getProcessorOptions().entrySet()) {
700 options.add("-A" + optionEntry.getKey() + "=" + optionEntry.getValue());
703 final File srcOutput = ProjectPaths.getAnnotationProcessorGeneratedSourcesOutputDir(
704 chunk.getModules().iterator().next(), chunk.containsTests(), profile
706 if (srcOutput != null) {
709 options.add(srcOutput.getPath());
713 options.add("-proc:none");
717 private static String getLanguageLevel(JpsModule module) {
718 final LanguageLevel level = JpsJavaExtensionService.getInstance().getLanguageLevel(module);
721 case JDK_1_3: return "1.3";
722 case JDK_1_4: return "1.4";
723 case JDK_1_5: return "1.5";
724 case JDK_1_6: return "1.6";
725 case JDK_1_7: return "1.7";
726 case JDK_1_8: return "8";
727 case JDK_1_9: return "9";
733 private static boolean isEncodingSet(List<String> options) {
734 for (String option : options) {
735 if ("-encoding".equals(option)) {
742 private static int getCompilerSdkVersion(CompileContext context) {
743 final Integer cached = JAVA_COMPILER_VERSION_KEY.get(context);
744 if (cached != null) {
747 int javaVersion = convertToNumber(SystemProperties.getJavaVersion());
748 JAVA_COMPILER_VERSION_KEY.set(context, javaVersion);
752 private static int getChunkSdkVersion(ModuleChunk chunk) {
753 int chunkSdkVersion = -1;
754 for (JpsModule module : chunk.getModules()) {
755 final JpsSdk<JpsDummyElement> sdk = module.getSdk(JpsJavaSdkType.INSTANCE);
757 final int moduleSdkVersion = convertToNumber(sdk.getVersionString());
758 if (moduleSdkVersion != 0 /*could determine the version*/&& (chunkSdkVersion < 0 || chunkSdkVersion > moduleSdkVersion)) {
759 chunkSdkVersion = moduleSdkVersion;
763 return chunkSdkVersion;
766 private static String getChunkSdkHome(ModuleChunk chunk) {
767 for (JpsModule module : chunk.getModules()) {
768 final JpsSdk<JpsDummyElement> sdk = module.getSdk(JpsJavaSdkType.INSTANCE);
770 return sdk.getHomePath();
776 private static void loadCommonJavacOptions(@NotNull CompileContext context, @NotNull JavaCompilingTool compilingTool) {
777 final List<String> options = new ArrayList<String>();
778 final List<String> vmOptions = new ArrayList<String>();
780 final JpsProject project = context.getProjectDescriptor().getProject();
781 final JpsJavaCompilerConfiguration compilerConfig = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
782 final JpsJavaCompilerOptions compilerOptions = compilerConfig.getCurrentCompilerOptions();
783 if (compilerOptions.DEBUGGING_INFO) {
786 if (compilerOptions.DEPRECATION) {
787 options.add("-deprecation");
789 if (compilerOptions.GENERATE_NO_WARNINGS) {
790 options.add("-nowarn");
792 if (compilerOptions instanceof EclipseCompilerOptions) {
793 final EclipseCompilerOptions eclipseOptions = (EclipseCompilerOptions)compilerOptions;
794 if (eclipseOptions.PROCEED_ON_ERROR) {
795 options.add("-proceedOnError");
798 final String customArgs = compilerOptions.ADDITIONAL_OPTIONS_STRING;
799 if (customArgs != null) {
800 final StringTokenizer customOptsTokenizer = new StringTokenizer(customArgs, " \t\r\n");
801 boolean skip = false;
802 boolean targetOptionFound = false;
803 while (customOptsTokenizer.hasMoreTokens()) {
804 final String userOption = customOptsTokenizer.nextToken();
805 if (FILTERED_OPTIONS.contains(userOption)) {
807 targetOptionFound = "-target".equals(userOption);
812 if (targetOptionFound) {
813 targetOptionFound = false;
814 USER_DEFINED_BYTECODE_TARGET.set(context, userOption);
818 if (!FILTERED_SINGLE_OPTIONS.contains(userOption)) {
819 if (userOption.startsWith("-J-")) {
820 vmOptions.add(userOption.substring("-J".length()));
823 options.add(userOption);
830 compilingTool.processCompilerOptions(context, options);
832 JAVAC_OPTIONS.set(context, options);
833 JAVAC_VM_OPTIONS.set(context, vmOptions);
837 public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
838 JavaBuilderUtil.cleanupChunkResources(context);
841 private static Map<File, Set<File>> buildOutputDirectoriesMap(CompileContext context, ModuleChunk chunk) {
842 final Map<File, Set<File>> map = new THashMap<File, Set<File>>(FileUtil.FILE_HASHING_STRATEGY);
843 for (ModuleBuildTarget target : chunk.getTargets()) {
844 final File outputDir = target.getOutputDir();
845 if (outputDir == null) {
848 final Set<File> roots = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
849 for (JavaSourceRootDescriptor descriptor : context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context)) {
850 roots.add(descriptor.root);
852 map.put(outputDir, roots);
857 private static class DiagnosticSink implements DiagnosticOutputConsumer {
858 private final CompileContext myContext;
859 private volatile int myErrorCount = 0;
860 private volatile int myWarningCount = 0;
861 private final Set<File> myFilesWithErrors = new HashSet<File>();
863 public DiagnosticSink(CompileContext context) {
868 public void javaFileLoaded(File file) {
871 public void registerImports(final String className, final Collection<String> imports, final Collection<String> staticImports) {
872 //submitAsyncTask(myContext, new Runnable() {
873 // public void run() {
874 // final Callbacks.Backend callback = DELTA_MAPPINGS_CALLBACK_KEY.get(myContext);
875 // if (callback != null) {
876 // callback.registerImports(className, imports, staticImports);
882 public void outputLineAvailable(String line) {
883 if (!StringUtil.isEmpty(line)) {
884 if (line.contains("java.lang.OutOfMemoryError")) {
885 myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "OutOfMemoryError: insufficient memory"));
889 final BuildMessage.Kind kind = getKindByMessageText(line);
890 if (kind == BuildMessage.Kind.ERROR) {
893 else if (kind == BuildMessage.Kind.WARNING) {
896 myContext.processMessage(new CompilerMessage(BUILDER_NAME, kind, line));
901 private static BuildMessage.Kind getKindByMessageText(String line) {
902 final String lowercasedLine = line.toLowerCase(Locale.US);
903 if (lowercasedLine.contains("error") || lowercasedLine.contains("requires target release")) {
904 return BuildMessage.Kind.ERROR;
906 return BuildMessage.Kind.INFO;
909 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
910 final CompilerMessage.Kind kind;
911 switch (diagnostic.getKind()) {
913 kind = BuildMessage.Kind.ERROR;
916 case MANDATORY_WARNING:
918 kind = BuildMessage.Kind.WARNING;
923 kind = BuildMessage.Kind.INFO;
925 File sourceFile = null;
927 // for eclipse compiler just an attempt to call getSource() may lead to an NPE,
928 // so calling this method under try/catch to avoid induced compiler errors
929 final JavaFileObject source = diagnostic.getSource();
930 sourceFile = source != null ? Utils.convertToFile(source.toUri()) : null;
932 catch (Exception e) {
935 final String srcPath;
936 if (sourceFile != null) {
937 myFilesWithErrors.add(sourceFile);
938 srcPath = FileUtil.toSystemIndependentName(sourceFile.getPath());
943 String message = diagnostic.getMessage(Locale.US);
944 if (Utils.IS_TEST_MODE) {
947 myContext.processMessage(new CompilerMessage(
948 BUILDER_NAME, kind, message, srcPath, diagnostic.getStartPosition(),
949 diagnostic.getEndPosition(), diagnostic.getPosition(), diagnostic.getLineNumber(),
950 diagnostic.getColumnNumber()
954 public int getErrorCount() {
958 public int getWarningCount() {
959 return myWarningCount;
962 public Collection<File> getFilesWithErrors() {
963 return myFilesWithErrors;
967 private class ClassProcessingConsumer implements OutputFileConsumer {
968 private final CompileContext myContext;
969 private final OutputFileConsumer myDelegateOutputFileSink;
971 public ClassProcessingConsumer(CompileContext context, OutputFileConsumer sink) {
973 myDelegateOutputFileSink = sink != null ? sink : new OutputFileConsumer() {
974 public void save(@NotNull OutputFileObject fileObject) {
975 throw new RuntimeException("Output sink for compiler was not specified");
980 public void save(@NotNull final OutputFileObject fileObject) {
981 // generated files must be saved synchronously, because some compilers (e.g. eclipse)
982 // may want to read them for further compilation
984 final BinaryContent content = fileObject.getContent();
985 final File file = fileObject.getFile();
986 if (content != null) {
987 content.saveToFile(file);
990 myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, "Missing content for file " + file.getPath()));
993 catch (IOException e) {
994 myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage()));
997 submitAsyncTask(myContext, new Runnable() {
1000 for (ClassPostProcessor processor : ourClassProcessors) {
1001 processor.process(myContext, fileObject);
1005 myDelegateOutputFileSink.save(fileObject);
1013 private static final Key<TasksCounter> COUNTER_KEY = Key.create("_async_task_counter_");
1015 private static final class TasksCounter {
1016 private int myCounter = 0;
1018 public synchronized void incTaskCount() {
1022 public synchronized void decTaskCounter() {
1023 myCounter = Math.max(0, myCounter - 1);
1024 if (myCounter == 0) {
1029 public synchronized void await() {
1030 while (myCounter > 0) {
1034 catch (InterruptedException e) {