diff --git a/annotations/com/intellij/openapi/util/annotations.xml b/annotations/com/intellij/openapi/util/annotations.xml index 4ca58460d8c..9a818132493 100644 --- a/annotations/com/intellij/openapi/util/annotations.xml +++ b/annotations/com/intellij/openapi/util/annotations.xml @@ -3,7 +3,12 @@ name='com.intellij.openapi.util.Conditions com.intellij.openapi.util.Condition<T> or(com.intellij.openapi.util.Condition<T>, com.intellij.openapi.util.Condition<T>)'> - + + + + + + diff --git a/annotations/org/jetbrains/jps/annotations.xml b/annotations/org/jetbrains/jps/annotations.xml new file mode 100644 index 00000000000..d2b275b23f6 --- /dev/null +++ b/annotations/org/jetbrains/jps/annotations.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/annotations/org/jetbrains/jps/cmdline/annotations.xml b/annotations/org/jetbrains/jps/cmdline/annotations.xml new file mode 100644 index 00000000000..494db392431 --- /dev/null +++ b/annotations/org/jetbrains/jps/cmdline/annotations.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/annotations/org/jetbrains/jps/incremental/annotations.xml b/annotations/org/jetbrains/jps/incremental/annotations.xml new file mode 100644 index 00000000000..b108d851a95 --- /dev/null +++ b/annotations/org/jetbrains/jps/incremental/annotations.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerEnvironment.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerEnvironment.java index ba3ebc58346..c883ae5ea7c 100644 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerEnvironment.java +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerEnvironment.java @@ -31,6 +31,7 @@ import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERRO public final class CompilerEnvironment { + @NotNull public static CompilerEnvironment getEnvironmentFor( @NotNull KotlinPaths kotlinPaths, @Nullable File outputDir, diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/SimpleOutputItem.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/SimpleOutputItem.java index a89936de4c2..970df11eefe 100644 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/SimpleOutputItem.java +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/SimpleOutputItem.java @@ -16,6 +16,8 @@ package org.jetbrains.jet.compiler.runner; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.util.Collection; @@ -23,15 +25,17 @@ public class SimpleOutputItem { private final Collection sourceFiles; private final File outputFile; - public SimpleOutputItem(Collection sourceFiles, File outputFile) { + public SimpleOutputItem(@NotNull Collection sourceFiles, @NotNull File outputFile) { this.sourceFiles = sourceFiles; this.outputFile = outputFile; } + @NotNull public Collection getSourceFiles() { return sourceFiles; } + @NotNull public File getOutputFile() { return outputFile; } diff --git a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java deleted file mode 100644 index f40738b42ac..00000000000 --- a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright 2010-2013 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.jet.jps.build; - -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.MultiMap; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.cli.common.KotlinVersion; -import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments; -import org.jetbrains.jet.cli.common.arguments.K2JSCompilerArguments; -import org.jetbrains.jet.cli.common.arguments.K2JVMCompilerArguments; -import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation; -import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity; -import org.jetbrains.jet.cli.common.messages.MessageCollector; -import org.jetbrains.jet.compiler.CompilerSettings; -import org.jetbrains.jet.compiler.runner.CompilerEnvironment; -import org.jetbrains.jet.compiler.runner.CompilerRunnerConstants; -import org.jetbrains.jet.compiler.runner.OutputItemsCollectorImpl; -import org.jetbrains.jet.compiler.runner.SimpleOutputItem; -import org.jetbrains.jet.config.IncrementalCompilation; -import org.jetbrains.jet.config.Services; -import org.jetbrains.jet.jps.JpsKotlinCompilerSettings; -import org.jetbrains.jet.jps.incremental.IncrementalCacheImpl; -import org.jetbrains.jet.jps.incremental.IncrementalCacheProviderImpl; -import org.jetbrains.jet.jps.incremental.IncrementalCacheStorageProvider; -import org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.IncrementalCacheProvider; -import org.jetbrains.jet.preloading.ClassCondition; -import org.jetbrains.jet.utils.PathUtil; -import org.jetbrains.jet.utils.UtilsPackage; -import org.jetbrains.jps.ModuleChunk; -import org.jetbrains.jps.builders.DirtyFilesHolder; -import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; -import org.jetbrains.jps.incremental.*; -import org.jetbrains.jps.incremental.java.JavaBuilder; -import org.jetbrains.jps.incremental.messages.BuildMessage; -import org.jetbrains.jps.incremental.messages.CompilerMessage; -import org.jetbrains.jps.incremental.storage.BuildDataManager; -import org.jetbrains.jps.model.JpsProject; -import org.jetbrains.jps.model.module.JpsModule; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.*; - -import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION; -import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.*; -import static org.jetbrains.jet.compiler.runner.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX; -import static org.jetbrains.jet.compiler.runner.KotlinCompilerRunner.runK2JsCompiler; -import static org.jetbrains.jet.compiler.runner.KotlinCompilerRunner.runK2JvmCompiler; - -public class KotlinBuilder extends ModuleLevelBuilder { - private static final Key> ALL_COMPILED_FILES_KEY = Key.create("_all_kotlin_compiled_files_"); - private static final Key> PROCESSED_TARGETS_WITH_REMOVED_FILES = Key.create("_processed_targets_with_removed_files_"); - - public static final String KOTLIN_BUILDER_NAME = "Kotlin Builder"; - private static final List COMPILABLE_FILE_EXTENSIONS = Collections.singletonList("kt"); - - private static final Function MODULE_NAME = new Function() { - @Override - public String fun(JpsModule module) { - return module.getName(); - } - }; - - protected KotlinBuilder() { - super(BuilderCategory.SOURCE_PROCESSOR); - } - - @NotNull - @Override - public String getPresentableName() { - return KOTLIN_BUILDER_NAME; - } - - @Override - public ExitCode build( - CompileContext context, - ModuleChunk chunk, - DirtyFilesHolder dirtyFilesHolder, - OutputConsumer outputConsumer - ) throws ProjectBuildException, IOException { - MessageCollector messageCollector = new MessageCollectorAdapter(context); - // Workaround for Android Studio - if (!isJavaPluginEnabled(context)) { - messageCollector.report(INFO, "Kotlin JPS plugin is disabled", NO_LOCATION); - return ExitCode.NOTHING_DONE; - } - - ModuleBuildTarget representativeTarget = chunk.representativeTarget(); - - // For non-incremental build: take all sources - if (!dirtyFilesHolder.hasDirtyFiles() && !dirtyFilesHolder.hasRemovedFiles()) { - return ExitCode.NOTHING_DONE; - } - - boolean hasKotlinFiles = hasKotlinDirtyOrRemovedFiles(dirtyFilesHolder, chunk); - if (!hasKotlinFiles) { - return ExitCode.NOTHING_DONE; - } - - messageCollector.report(INFO, "Kotlin JPS plugin version " + KotlinVersion.VERSION, NO_LOCATION); - - File outputDir = representativeTarget.getOutputDir(); - - BuildDataManager dataManager = context.getProjectDescriptor().dataManager; - Map incrementalCaches = UtilsPackage.newHashMapWithExpectedSize(chunk.getTargets().size()); - for (ModuleBuildTarget target : chunk.getTargets()) { - incrementalCaches.put(target, dataManager.getStorage(target, IncrementalCacheStorageProvider.INSTANCE$)); - } - - Services compilerServices = new Services.Builder() - .register(IncrementalCacheProvider.class, new IncrementalCacheProviderImpl(incrementalCaches)) - .build(); - - CompilerEnvironment environment = CompilerEnvironment.getEnvironmentFor( - PathUtil.getKotlinPathsForJpsPluginOrJpsTests(), outputDir, getClass().getClassLoader(), new ClassCondition() { - @Override - public boolean accept(String className) { - return className.startsWith("org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.") || - className.equals("org.jetbrains.jet.config.Services"); - } - }, compilerServices); - if (!environment.success()) { - environment.reportErrorsTo(messageCollector); - return ExitCode.ABORT; - } - - assert outputDir != null : "CompilerEnvironment must have checked for outputDir to be not null, but it didn't"; - - OutputItemsCollectorImpl outputItemCollector = new OutputItemsCollectorImpl(); - - JpsProject project = representativeTarget.getModule().getProject(); - CommonCompilerArguments commonArguments = JpsKotlinCompilerSettings.getCommonCompilerArguments(project); - commonArguments.verbose = true; // Make compiler report source to output files mapping - - CompilerSettings compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(project); - - final Set allCompiledFiles = getAllCompiledFilesContainer(context); - - if (JpsUtils.isJsKotlinModule(representativeTarget)) { - if (chunk.getModules().size() > 1) { - // We do not support circular dependencies, but if they are present, we do our best should not break the build, - // so we simply yield a warning and report NOTHING_DONE - messageCollector.report( - WARNING, "Circular dependencies are not supported. " + - "The following JS modules depend on each other: " + StringUtil.join(chunk.getModules(), MODULE_NAME, ", ") + ". " + - "Kotlin is not compiled for these modules", - NO_LOCATION); - return ExitCode.NOTHING_DONE; - } - - Collection sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget); - //List sourceFiles = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder); - - if (sourceFiles.isEmpty()) { - return ExitCode.NOTHING_DONE; - } - - File outputFile = new File(outputDir, representativeTarget.getModule().getName() + ".js"); - List libraryFiles = JpsJsModuleUtils.getLibraryFilesAndDependencies(representativeTarget); - K2JSCompilerArguments k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(project); - - runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, messageCollector, environment, - outputItemCollector, sourceFiles, libraryFiles, outputFile); - } - else { - if (chunk.getModules().size() > 1) { - messageCollector.report( - WARNING, "Circular dependencies are only partially supported. " + - "The following modules depend on each other: " + StringUtil.join(chunk.getModules(), MODULE_NAME, ", ") + ". " + - "Kotlin will compile them, but some strange effect may happen", - NO_LOCATION); - } - - MultiMap filesToCompile = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder); - for (ModuleBuildTarget target : filesToCompile.keySet()) { - filesToCompile.getModifiable(target).removeAll(allCompiledFiles); - } - allCompiledFiles.addAll(filesToCompile.values()); - - Set processedTargetsWithRemoved = getProcessedTargetsWithRemovedFilesContainer(context); - - boolean haveRemovedFiles = false; - for (ModuleBuildTarget target : chunk.getTargets()) { - if (!KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target).isEmpty()) { - if (processedTargetsWithRemoved.add(target)) { - haveRemovedFiles = true; - } - } - } - - File moduleFile = KotlinBuilderModuleScriptGenerator - .generateModuleDescription(context, chunk, filesToCompile, haveRemovedFiles); - if (moduleFile == null) { - // No Kotlin sources found - return ExitCode.NOTHING_DONE; - } - - K2JVMCompilerArguments k2JvmArguments = JpsKotlinCompilerSettings.getK2JvmCompilerArguments(project); - - runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, messageCollector, environment, - moduleFile, outputItemCollector); - moduleFile.delete(); - } - - // If there's only one target, this map is empty: get() always returns null, and the representativeTarget will be used below - Map sourceToTarget = new HashMap(); - if (chunk.getTargets().size() > 1) { - for (ModuleBuildTarget target : chunk.getTargets()) { - for (File file : KotlinSourceFileCollector.getAllKotlinSourceFiles(target)) { - sourceToTarget.put(file, target); - } - } - } - - for (ModuleBuildTarget target : chunk.getTargets()) { - incrementalCaches.get(target).clearCacheForRemovedFiles( - KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target), target.getOutputDir()); - } - - IncrementalCacheImpl.RecompilationDecision recompilationDecision = IncrementalCacheImpl.RecompilationDecision.DO_NOTHING; - - for (SimpleOutputItem outputItem : outputItemCollector.getOutputs()) { - ModuleBuildTarget target = null; - Collection sourceFiles = outputItem.getSourceFiles(); - if (!sourceFiles.isEmpty()) { - target = sourceToTarget.get(sourceFiles.iterator().next()); - } - - if (target == null) { - target = representativeTarget; - } - - File outputFile = outputItem.getOutputFile(); - - if (IncrementalCompilation.ENABLED) { - IncrementalCacheImpl.RecompilationDecision newDecision = incrementalCaches.get(target).saveFileToCache(sourceFiles, outputFile); - recompilationDecision = recompilationDecision.merge(newDecision); - } - - outputConsumer.registerOutputFile(target, outputFile, paths(sourceFiles)); - } - - if (IncrementalCompilation.ENABLED) { - if (recompilationDecision == IncrementalCacheImpl.RecompilationDecision.RECOMPILE_ALL) { - allCompiledFiles.clear(); - return ExitCode.CHUNK_REBUILD_REQUIRED; - } - if (recompilationDecision == IncrementalCacheImpl.RecompilationDecision.COMPILE_OTHERS) { - // TODO should mark dependencies as dirty, as well - FSOperations.markDirty(context, chunk, new FileFilter() { - @Override - public boolean accept(@NotNull File file) { - return !allCompiledFiles.contains(file); - } - }); - } - return ExitCode.ADDITIONAL_PASS_REQUIRED; - } - else { - return ExitCode.OK; - } - } - - private static Set getAllCompiledFilesContainer(CompileContext context) { - Set allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context); - if (allCompiledFiles == null) { - allCompiledFiles = new THashSet(FileUtil.FILE_HASHING_STRATEGY); - ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles); - } - return allCompiledFiles; - } - - private static Set getProcessedTargetsWithRemovedFilesContainer(CompileContext context) { - Set set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context); - if (set == null) { - set = new HashSet(); - PROCESSED_TARGETS_WITH_REMOVED_FILES.set(context, set); - } - return set; - } - - private static boolean hasKotlinDirtyOrRemovedFiles( - @NotNull DirtyFilesHolder dirtyFilesHolder, - @NotNull ModuleChunk chunk - ) - throws IOException { - if (!KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder).isEmpty()) { - return true; - } - - for (ModuleBuildTarget target : chunk.getTargets()) { - if (!KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target).isEmpty()) { - return true; - } - } - - return false; - } - - private static boolean isJavaPluginEnabled(@NotNull CompileContext context) { - try { - // Using reflection for backward compatibility with IDEA 12 - Field javaPluginIsEnabledField = JavaBuilder.class.getDeclaredField("IS_ENABLED"); - return Modifier.isPublic(javaPluginIsEnabledField.getModifiers()) ? JavaBuilder.IS_ENABLED.get(context, Boolean.TRUE) : true; - } - catch (NoSuchFieldException e) { - throw new IllegalArgumentException("Cannot check if Java Jps Plugin is enabled", e); - } - } - - private static Collection paths(Collection files) { - Collection result = ContainerUtil.newArrayList(); - for (File file : files) { - result.add(file.getPath()); - } - return result; - } - - public static class MessageCollectorAdapter implements MessageCollector { - - private final CompileContext context; - - public MessageCollectorAdapter(@NotNull CompileContext context) { - this.context = context; - } - - @Override - public void report( - @NotNull CompilerMessageSeverity severity, - @NotNull String message, - @NotNull CompilerMessageLocation location - ) { - String prefix = ""; - if (severity == EXCEPTION) { - prefix = INTERNAL_ERROR_PREFIX; - } - context.processMessage(new CompilerMessage( - CompilerRunnerConstants.KOTLIN_COMPILER_NAME, - kind(severity), - prefix + message + renderLocationIfNeeded(location), - location.getPath(), - -1, -1, -1, - location.getLine(), - location.getColumn() - )); - } - - private static String renderLocationIfNeeded(@NotNull CompilerMessageLocation location) { - if (location == NO_LOCATION) return ""; - - // Sometimes we report errors in JavaScript library stubs, i.e. files like core/javautil.kt - // IDEA can't find these files, and does not display paths in Messages View, so we add the position information - // to the error message itself: - String pathname = String.valueOf(location.getPath()); - return new File(pathname).exists() ? "" : " (" + location + ")"; - } - - @NotNull - private static BuildMessage.Kind kind(@NotNull CompilerMessageSeverity severity) { - switch (severity) { - case INFO: - return BuildMessage.Kind.INFO; - case ERROR: - case EXCEPTION: - return BuildMessage.Kind.ERROR; - case WARNING: - return BuildMessage.Kind.WARNING; - case LOGGING: - return BuildMessage.Kind.PROGRESS; - default: - throw new IllegalArgumentException("Unsupported severity: " + severity); - } - } - - } - - @Override - public List getCompilableFileExtensions() { - return COMPILABLE_FILE_EXTENSIONS; - } -} diff --git a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.kt b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.kt new file mode 100644 index 00000000000..a322984dea9 --- /dev/null +++ b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.kt @@ -0,0 +1,323 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.jps.build + +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.io.FileUtil +import gnu.trove.THashSet +import org.jetbrains.jet.cli.common.KotlinVersion +import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation +import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.jet.cli.common.messages.MessageCollector +import org.jetbrains.jet.compiler.runner.CompilerEnvironment +import org.jetbrains.jet.compiler.runner.CompilerRunnerConstants +import org.jetbrains.jet.compiler.runner.OutputItemsCollectorImpl +import org.jetbrains.jet.config.Services +import org.jetbrains.jet.config.IncrementalCompilation +import org.jetbrains.jet.jps.JpsKotlinCompilerSettings +import org.jetbrains.jet.jps.incremental.* +import org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.IncrementalCacheProvider +import org.jetbrains.jet.utils.PathUtil +import org.jetbrains.jps.ModuleChunk +import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.incremental.* +import org.jetbrains.jps.incremental.java.JavaBuilder +import org.jetbrains.jps.incremental.messages.BuildMessage +import org.jetbrains.jps.incremental.messages.CompilerMessage +import java.io.File +import java.io.IOException +import java.lang.reflect.Modifier +import java.util.* +import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION +import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.* +import org.jetbrains.jet.compiler.runner.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX +import org.jetbrains.jet.compiler.runner.KotlinCompilerRunner.runK2JsCompiler +import org.jetbrains.jet.compiler.runner.KotlinCompilerRunner.runK2JvmCompiler +import org.jetbrains.jet.utils.keysToMap +import org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode.* + +public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { + + override fun getPresentableName() = "Kotlin Builder" + + override fun getCompilableFileExtensions() = arrayListOf("kt") + + override fun build( + context: CompileContext, + chunk: ModuleChunk, + dirtyFilesHolder: DirtyFilesHolder, + outputConsumer: ModuleLevelBuilder.OutputConsumer + ): ModuleLevelBuilder.ExitCode { + val messageCollector = MessageCollectorAdapter(context) + // Workaround for Android Studio + if (!isJavaPluginEnabled(context)) { + messageCollector.report(INFO, "Kotlin JPS plugin is disabled", NO_LOCATION) + return NOTHING_DONE + } + + val representativeTarget = chunk.representativeTarget() + + // For non-incremental build: take all sources + if (!dirtyFilesHolder.hasDirtyFiles() && !dirtyFilesHolder.hasRemovedFiles()) { + return NOTHING_DONE + } + + if (!hasKotlinDirtyOrRemovedFiles(dirtyFilesHolder, chunk)) { + return NOTHING_DONE + } + + messageCollector.report(INFO, "Kotlin JPS plugin version " + KotlinVersion.VERSION, NO_LOCATION) + + val outputDir = representativeTarget.getOutputDir() + + val dataManager = context.getProjectDescriptor().dataManager + val incrementalCaches = chunk.getTargets().keysToMap { dataManager.getStorage(it, IncrementalCacheStorageProvider) } + + val compilerServices = Services.Builder() + .register(javaClass(), IncrementalCacheProviderImpl(incrementalCaches)) + .build() + + val environment = CompilerEnvironment.getEnvironmentFor( + PathUtil.getKotlinPathsForJpsPluginOrJpsTests(), + outputDir, + javaClass.getClassLoader(), + { className -> + className!!.startsWith("org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.") + || className == "org.jetbrains.jet.config.Services" + }, + compilerServices + ) + + if (!environment.success()) { + environment.reportErrorsTo(messageCollector) + return ABORT + } + + assert(outputDir != null, "CompilerEnvironment must have checked for outputDir to be not null, but it didn't") + + val outputItemCollector = OutputItemsCollectorImpl() + + val project = representativeTarget.getModule().getProject()!! + val commonArguments = JpsKotlinCompilerSettings.getCommonCompilerArguments(project) + commonArguments.verbose = true // Make compiler report source to output files mapping + + val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(project) + + val allCompiledFiles = getAllCompiledFilesContainer(context) + + if (JpsUtils.isJsKotlinModule(representativeTarget)) { + if (chunk.getModules().size() > 1) { + // We do not support circular dependencies, but if they are present, we do our best should not break the build, + // so we simply yield a warning and report NOTHING_DONE + messageCollector.report(WARNING, "Circular dependencies are not supported. " + + "The following JS modules depend on each other: " + + chunk.getModules().map { it.getName() }.joinToString(", ") + + ". " + + "Kotlin is not compiled for these modules", NO_LOCATION) + return NOTHING_DONE + } + + val sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget) + //List sourceFiles = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder); + + if (sourceFiles.isEmpty()) { + return NOTHING_DONE + } + + val outputFile = File(outputDir, representativeTarget.getModule().getName() + ".js") + val libraryFiles = JpsJsModuleUtils.getLibraryFilesAndDependencies(representativeTarget) + val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(project) + + runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, messageCollector, environment, outputItemCollector, sourceFiles, libraryFiles, outputFile) + } + else { + if (chunk.getModules().size() > 1) { + messageCollector.report(WARNING, "Circular dependencies are only partially supported. " + + "The following modules depend on each other: " + + chunk.getModules().map { it.getName() }.joinToString(", ") + + ". " + + "Kotlin will compile them, but some strange effect may happen", NO_LOCATION) + } + + val filesToCompile = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder) + for (target in filesToCompile.keySet()) { + filesToCompile.getModifiable(target).removeAll(allCompiledFiles) + } + allCompiledFiles.addAll(filesToCompile.values()) + + val processedTargetsWithRemoved = getProcessedTargetsWithRemovedFilesContainer(context) + + var haveRemovedFiles = false + for (target in chunk.getTargets()) { + if (!KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target).isEmpty()) { + if (processedTargetsWithRemoved.add(target)) { + haveRemovedFiles = true + } + } + } + + val moduleFile = KotlinBuilderModuleScriptGenerator.generateModuleDescription(context, chunk, filesToCompile, haveRemovedFiles) + if (moduleFile == null) { + // No Kotlin sources found + return NOTHING_DONE + } + + val k2JvmArguments = JpsKotlinCompilerSettings.getK2JvmCompilerArguments(project) + + runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, messageCollector, environment, moduleFile, outputItemCollector) + moduleFile.delete() + } + + // If there's only one target, this map is empty: get() always returns null, and the representativeTarget will be used below + val sourceToTarget = HashMap() + if (chunk.getTargets().size() > 1) { + for (target in chunk.getTargets()) { + for (file in KotlinSourceFileCollector.getAllKotlinSourceFiles(target)) { + sourceToTarget.put(file, target) + } + } + } + + for ((target, cache) in incrementalCaches) { + cache.clearCacheForRemovedFiles( + KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target), + target.getOutputDir()!! + ) + } + + var recompilationDecision = IncrementalCacheImpl.RecompilationDecision.DO_NOTHING + + for (outputItem in outputItemCollector.getOutputs()) { + var target: ModuleBuildTarget? = null + val sourceFiles = outputItem.getSourceFiles() + if (!sourceFiles.isEmpty()) { + target = sourceToTarget[sourceFiles.iterator().next()] + } + + if (target == null) { + target = representativeTarget + } + + val outputFile = outputItem.getOutputFile() + + if (IncrementalCompilation.ENABLED) { + val newDecision = incrementalCaches[target]!!.saveFileToCache(sourceFiles, outputFile) + recompilationDecision = recompilationDecision.merge(newDecision) + } + + outputConsumer.registerOutputFile(target, outputFile, sourceFiles.map { it.getPath() }) + } + + if (IncrementalCompilation.ENABLED) { + if (recompilationDecision == IncrementalCacheImpl.RecompilationDecision.RECOMPILE_ALL) { + allCompiledFiles.clear() + return CHUNK_REBUILD_REQUIRED + } + if (recompilationDecision == IncrementalCacheImpl.RecompilationDecision.COMPILE_OTHERS) { + // TODO should mark dependencies as dirty, as well + FSOperations.markDirty(context, chunk, { file -> !allCompiledFiles.contains(file) }) + } + return ADDITIONAL_PASS_REQUIRED + } + + return OK + } + + public class MessageCollectorAdapter(private val context: CompileContext) : MessageCollector { + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { + var prefix = "" + if (severity == EXCEPTION) { + prefix = INTERNAL_ERROR_PREFIX + } + context.processMessage(CompilerMessage( + CompilerRunnerConstants.KOTLIN_COMPILER_NAME, + kind(severity), + prefix + message + renderLocationIfNeeded(location), + location.getPath(), + -1, -1, -1, + location.getLine().toLong(), location.getColumn().toLong() + )) + } + + private fun renderLocationIfNeeded(location: CompilerMessageLocation): String { + if (location == NO_LOCATION) return "" + + // Sometimes we report errors in JavaScript library stubs, i.e. files like core/javautil.kt + // IDEA can't find these files, and does not display paths in Messages View, so we add the position information + // to the error message itself: + val pathname = "" + location.getPath() + return if (File(pathname).exists()) "" else " (" + location + ")" + } + + private fun kind(severity: CompilerMessageSeverity): BuildMessage.Kind { + return when (severity) { + INFO -> BuildMessage.Kind.INFO + ERROR, EXCEPTION -> BuildMessage.Kind.ERROR + WARNING -> BuildMessage.Kind.WARNING + LOGGING -> BuildMessage.Kind.PROGRESS + else -> throw IllegalArgumentException("Unsupported severity: " + severity) + } + } + + } +} + +private val ALL_COMPILED_FILES_KEY = Key.create>("_all_kotlin_compiled_files_") +private fun getAllCompiledFilesContainer(context: CompileContext): MutableSet { + var allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context) + if (allCompiledFiles == null) { + allCompiledFiles = THashSet(FileUtil.FILE_HASHING_STRATEGY) + ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles) + } + return allCompiledFiles!! +} + +private val PROCESSED_TARGETS_WITH_REMOVED_FILES = Key.create>("_processed_targets_with_removed_files_") +private fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet { + var set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context) + if (set == null) { + set = HashSet() + PROCESSED_TARGETS_WITH_REMOVED_FILES.set(context, set) + } + return set!! +} + +private fun hasKotlinDirtyOrRemovedFiles( + dirtyFilesHolder: DirtyFilesHolder, + chunk: ModuleChunk +): Boolean { + if (!KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder).isEmpty()) { + return true + } + + return chunk.getTargets().any { !KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, it).isEmpty() } +} + +private fun isJavaPluginEnabled(context: CompileContext): Boolean { + try { + // Using reflection for backward compatibility with IDEA 12 + val javaPluginIsEnabledField = javaClass().getDeclaredField("IS_ENABLED") + return if (Modifier.isPublic(javaPluginIsEnabledField.getModifiers())) JavaBuilder.IS_ENABLED[context, true] else true + } + catch (e: NoSuchFieldException) { + throw IllegalArgumentException("Cannot check if Java Jps Plugin is enabled", e) + } + +} + diff --git a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java index 04566c5e5fc..c1e77ab0d3d 100644 --- a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java +++ b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java @@ -40,6 +40,7 @@ import java.util.List; public class KotlinSourceFileCollector { // For incremental compilation + @NotNull public static MultiMap getDirtySourceFiles(DirtyFilesHolder dirtyFilesHolder) throws IOException {