Converted KotlinBuilder from Java to Kotlin
This commit is contained in:
@@ -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>)'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.openapi.util.Key com.intellij.openapi.util.Key<T> create(java.lang.String)'>
|
||||
<item name='com.intellij.openapi.util.Key T get(com.intellij.openapi.util.UserDataHolder, T)'>
|
||||
<annotation name='kotlin.jvm.KotlinSignature'>
|
||||
<val name="value" val=""fun get(holder: UserDataHolder?, defaultValue: T): T""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='com.intellij.openapi.util.Key com.intellij.openapi.util.Key<T> create(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.openapi.util.Pair A getFirst()'>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<root>
|
||||
<item name='org.jetbrains.jps.ModuleChunk java.util.Set<org.jetbrains.jps.incremental.ModuleBuildTarget> getTargets()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.jetbrains.jps.ModuleChunk java.util.Set<org.jetbrains.jps.model.module.JpsModule> getModules()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.jetbrains.jps.ModuleChunk org.jetbrains.jps.incremental.ModuleBuildTarget representativeTarget()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -0,0 +1,5 @@
|
||||
<root>
|
||||
<item name='org.jetbrains.jps.cmdline.ProjectDescriptor dataManager'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -0,0 +1,25 @@
|
||||
<root>
|
||||
<item name='org.jetbrains.jps.incremental.CompileContext org.jetbrains.jps.cmdline.ProjectDescriptor getProjectDescriptor()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='org.jetbrains.jps.incremental.ModuleLevelBuilder org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode build(org.jetbrains.jps.incremental.CompileContext, org.jetbrains.jps.ModuleChunk, org.jetbrains.jps.builders.DirtyFilesHolder<org.jetbrains.jps.builders.java.JavaSourceRootDescriptor,org.jetbrains.jps.incremental.ModuleBuildTarget>, org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer)'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='org.jetbrains.jps.incremental.ModuleLevelBuilder org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode build(org.jetbrains.jps.incremental.CompileContext, org.jetbrains.jps.ModuleChunk, org.jetbrains.jps.builders.DirtyFilesHolder<org.jetbrains.jps.builders.java.JavaSourceRootDescriptor,org.jetbrains.jps.incremental.ModuleBuildTarget>, org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='org.jetbrains.jps.incremental.ModuleLevelBuilder org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode build(org.jetbrains.jps.incremental.CompileContext, org.jetbrains.jps.ModuleChunk, org.jetbrains.jps.builders.DirtyFilesHolder<org.jetbrains.jps.builders.java.JavaSourceRootDescriptor,org.jetbrains.jps.incremental.ModuleBuildTarget>, org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='org.jetbrains.jps.incremental.ModuleLevelBuilder org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode build(org.jetbrains.jps.incremental.CompileContext, org.jetbrains.jps.ModuleChunk, org.jetbrains.jps.builders.DirtyFilesHolder<org.jetbrains.jps.builders.java.JavaSourceRootDescriptor,org.jetbrains.jps.incremental.ModuleBuildTarget>, org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer) 2'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='org.jetbrains.jps.incremental.ModuleLevelBuilder org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode build(org.jetbrains.jps.incremental.CompileContext, org.jetbrains.jps.ModuleChunk, org.jetbrains.jps.builders.DirtyFilesHolder<org.jetbrains.jps.builders.java.JavaSourceRootDescriptor,org.jetbrains.jps.incremental.ModuleBuildTarget>, org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer) 3'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -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,
|
||||
|
||||
@@ -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<File> sourceFiles;
|
||||
private final File outputFile;
|
||||
|
||||
public SimpleOutputItem(Collection<File> sourceFiles, File outputFile) {
|
||||
public SimpleOutputItem(@NotNull Collection<File> sourceFiles, @NotNull File outputFile) {
|
||||
this.sourceFiles = sourceFiles;
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getSourceFiles() {
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getOutputFile() {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
@@ -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<Set<File>> ALL_COMPILED_FILES_KEY = Key.create("_all_kotlin_compiled_files_");
|
||||
private static final Key<Set<ModuleBuildTarget>> 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<String> COMPILABLE_FILE_EXTENSIONS = Collections.singletonList("kt");
|
||||
|
||||
private static final Function<JpsModule,String> MODULE_NAME = new Function<JpsModule, String>() {
|
||||
@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<JavaSourceRootDescriptor, ModuleBuildTarget> 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<ModuleBuildTarget, IncrementalCacheImpl> 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<File> 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<File> sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget);
|
||||
//List<File> sourceFiles = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder);
|
||||
|
||||
if (sourceFiles.isEmpty()) {
|
||||
return ExitCode.NOTHING_DONE;
|
||||
}
|
||||
|
||||
File outputFile = new File(outputDir, representativeTarget.getModule().getName() + ".js");
|
||||
List<String> 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<ModuleBuildTarget, File> filesToCompile = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder);
|
||||
for (ModuleBuildTarget target : filesToCompile.keySet()) {
|
||||
filesToCompile.getModifiable(target).removeAll(allCompiledFiles);
|
||||
}
|
||||
allCompiledFiles.addAll(filesToCompile.values());
|
||||
|
||||
Set<ModuleBuildTarget> 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<File, ModuleBuildTarget> sourceToTarget = new HashMap<File, ModuleBuildTarget>();
|
||||
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<File> 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<File> getAllCompiledFilesContainer(CompileContext context) {
|
||||
Set<File> allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context);
|
||||
if (allCompiledFiles == null) {
|
||||
allCompiledFiles = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
|
||||
ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles);
|
||||
}
|
||||
return allCompiledFiles;
|
||||
}
|
||||
|
||||
private static Set<ModuleBuildTarget> getProcessedTargetsWithRemovedFilesContainer(CompileContext context) {
|
||||
Set<ModuleBuildTarget> set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context);
|
||||
if (set == null) {
|
||||
set = new HashSet<ModuleBuildTarget>();
|
||||
PROCESSED_TARGETS_WITH_REMOVED_FILES.set(context, set);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private static boolean hasKotlinDirtyOrRemovedFiles(
|
||||
@NotNull DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> 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<String> paths(Collection<File> files) {
|
||||
Collection<String> 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<String> getCompilableFileExtensions() {
|
||||
return COMPILABLE_FILE_EXTENSIONS;
|
||||
}
|
||||
}
|
||||
@@ -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<JavaSourceRootDescriptor, ModuleBuildTarget>,
|
||||
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<IncrementalCacheProvider>(), 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<File> 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<File, ModuleBuildTarget>()
|
||||
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<MutableSet<File>>("_all_kotlin_compiled_files_")
|
||||
private fun getAllCompiledFilesContainer(context: CompileContext): MutableSet<File> {
|
||||
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<MutableSet<ModuleBuildTarget>>("_processed_targets_with_removed_files_")
|
||||
private fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet<ModuleBuildTarget> {
|
||||
var set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context)
|
||||
if (set == null) {
|
||||
set = HashSet<ModuleBuildTarget>()
|
||||
PROCESSED_TARGETS_WITH_REMOVED_FILES.set(context, set)
|
||||
}
|
||||
return set!!
|
||||
}
|
||||
|
||||
private fun hasKotlinDirtyOrRemovedFiles(
|
||||
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
|
||||
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<JavaBuilder>().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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.util.List;
|
||||
|
||||
public class KotlinSourceFileCollector {
|
||||
// For incremental compilation
|
||||
@NotNull
|
||||
public static MultiMap<ModuleBuildTarget, File> getDirtySourceFiles(DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user