Provide incremental compilation for Maven

#KT-11916 fixed

To use the IC either:
1. set the `kotlin.compiler.incremental` property to `true` in a pom.xml:
```
<properties>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
</properties>
```
2. pass the `kotlin.compiler.incremental` property in a command line:
```
mvn install -Dkotlin.compiler.incremental=true
```

When IC is on Kotlin plugin is expected to print the warning in the log:
```
Using experimental Kotlin incremental compilation
```

After each call an incremental compiler will also log how many files it has compiled:
```
Compiled %SOME_NUMBER% Kotlin files using incremental compiler
```

Note that the first build will be non-incremental.

For more diagnostic information (such as an exact list of compiled files) use the `kotlin.compiler.incremental.log.level` system property:
```
mvn install -Dkotlin.compiler.incremental=true -Dkotlin.compiler.incremental.log.level=info
```

To force the rebuild just run the 'clean' goal:
```
mvn clean install
```
This commit is contained in:
Alexey Tsvetkov
2017-03-30 16:25:31 +03:00
parent 21da11fe18
commit 30d6af7aae
14 changed files with 665 additions and 21 deletions
@@ -20,13 +20,17 @@ import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.MavenProject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.CLICompiler;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerKt;
import org.jetbrains.kotlin.maven.incremental.MavenICReporter;
import org.jetbrains.kotlin.maven.kapt.AnnotationProcessingManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import static com.intellij.openapi.util.text.StringUtil.join;
import static org.jetbrains.kotlin.maven.Util.filterClassPath;
@@ -65,6 +69,26 @@ public class K2JVMCompileMojo extends KotlinCompileMojoBase<K2JVMCompilerArgumen
@Parameter(property = "kotlin.compiler.scriptTemplates", required = false, readonly = false)
protected List<String> scriptTemplates;
@Parameter(property = "kotlin.compiler.incremental", defaultValue = "false", required = false, readonly = false)
private boolean myIncremental;
@Parameter(property = "kotlin.compiler.incremental.cache.root", defaultValue = "${project.build.directory}/kotlin-ic", required = false, readonly = false)
public String incrementalCachesRoot;
@NotNull
private File getCachesDir() {
return new File(incrementalCachesRoot, getSourceSetName());
}
protected boolean isIncremental() {
return myIncremental;
}
private boolean isIncrementalSystemProperty() {
String value = System.getProperty("kotlin.incremental");
return value != null && value.equals("true");
}
@Override
protected List<String> getRelatedSourceRoots(MavenProject project) {
return project.getCompileSourceRoots();
@@ -141,4 +165,50 @@ public class K2JVMCompileMojo extends KotlinCompileMojoBase<K2JVMCompilerArgumen
arguments.scriptTemplates = scriptTemplates.toArray(new String[0]);
}
}
@Override
protected ExitCode execCompiler(
CLICompiler<K2JVMCompilerArguments> compiler,
MessageCollector messageCollector,
K2JVMCompilerArguments arguments,
List<File> sourceRoots
) throws MojoExecutionException {
if (isIncremental()) {
return runIncrementalCompiler(messageCollector, arguments, sourceRoots);
}
return super.execCompiler(compiler, messageCollector, arguments, sourceRoots);
}
@NotNull
private ExitCode runIncrementalCompiler(
MessageCollector messageCollector,
K2JVMCompilerArguments arguments,
List<File> sourceRoots
) throws MojoExecutionException {
getLog().warn("Using experimental Kotlin incremental compilation");
File cachesDir = getCachesDir();
//noinspection ResultOfMethodCallIgnored
cachesDir.mkdirs();
MavenICReporter icReporter = MavenICReporter.get(getLog());
try {
IncrementalJvmCompilerRunnerKt.makeIncrementally(cachesDir, sourceRoots, arguments, messageCollector, icReporter);
int compiledKtFilesCount = icReporter.getCompiledKotlinFiles().size();
getLog().info("Compiled " + icReporter.getCompiledKotlinFiles().size() + " Kotlin files using incremental compiler");
}
catch (Throwable t) {
t.printStackTrace();
return ExitCode.INTERNAL_ERROR;
}
if (messageCollector.hasErrors()) {
return ExitCode.COMPILATION_ERROR;
}
else {
return ExitCode.OK;
}
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.CLICompiler;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.config.KotlinCompilerVersion;
import org.jetbrains.kotlin.config.Services;
@@ -192,18 +193,33 @@ public abstract class KotlinCompileMojoBase<A extends CommonCompilerArguments> e
A arguments = createCompilerArguments();
CLICompiler<A> compiler = createCompiler();
List<File> sourceRoots = getSourceRoots();
configureCompilerArguments(arguments, compiler);
printCompilerArgumentsIfDebugEnabled(arguments, compiler);
MavenPluginLogMessageCollector messageCollector = new MavenPluginLogMessageCollector(getLog());
ExitCode exitCode = compiler.exec(messageCollector, Services.EMPTY, arguments);
ExitCode exitCode = execCompiler(compiler, messageCollector, arguments, sourceRoots);
if (exitCode != ExitCode.OK) {
messageCollector.throwKotlinCompilerException();
}
}
@NotNull
protected ExitCode execCompiler(
CLICompiler<A> compiler,
MessageCollector messageCollector,
A arguments,
List<File> sourceRoots
) throws MojoExecutionException {
for (File sourceRoot : sourceRoots) {
arguments.freeArgs.add(sourceRoot.getPath());
}
return compiler.exec(messageCollector, Services.EMPTY, arguments);
}
private boolean hasKotlinFilesInSources() throws MojoExecutionException {
List<File> sources = getSourceDirs();
if (sources == null || sources.isEmpty()) return false;
@@ -400,32 +416,34 @@ public abstract class KotlinCompileMojoBase<A extends CommonCompilerArguments> e
return pluginOptions;
}
@NotNull
private List<File> getSourceRoots() throws MojoExecutionException {
List<File> sourceRoots = new ArrayList<File>();
for (File sourceDir : getSourceDirs()) {
if (sourceDir.exists()) {
sourceRoots.add(sourceDir);
}
else {
getLog().warn("Source root doesn't exist: " + sourceDir);
}
}
if (sourceRoots.isEmpty()) {
throw new MojoExecutionException("No source roots to compile");
}
getLog().info("Compiling Kotlin sources from " + sourceRoots);
return sourceRoots;
}
private void configureCompilerArguments(@NotNull A arguments, @NotNull CLICompiler<A> compiler) throws MojoExecutionException {
if (getLog().isDebugEnabled()) {
arguments.verbose = true;
}
List<String> sources = new ArrayList<String>();
for (File source : getSourceDirs()) {
if (source.exists()) {
sources.add(source.getPath());
}
else {
getLog().warn("Source root doesn't exist: " + source);
}
}
if (sources.isEmpty()) {
throw new MojoExecutionException("No source roots to compile");
}
arguments.suppressWarnings = nowarn;
arguments.languageVersion = languageVersion;
arguments.apiVersion = apiVersion;
arguments.multiPlatform = multiPlatform;
getLog().info("Compiling Kotlin sources from " + sources);
configureSpecificCompilerArguments(arguments);
try {
@@ -435,8 +453,6 @@ public abstract class KotlinCompileMojoBase<A extends CommonCompilerArguments> e
throw new MojoExecutionException(e.getMessage());
}
arguments.freeArgs.addAll(sources);
if (arguments.noInline) {
getLog().info("Method inlining is turned off");
}
@@ -0,0 +1,124 @@
package org.jetbrains.kotlin.maven.incremental;
import kotlin.jvm.functions.Function0;
import org.apache.maven.plugin.logging.Log;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.incremental.ICReporter;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class MavenICReporter implements ICReporter {
private static final String IC_LOG_LEVEL_PROPERTY_NAME = "kotlin.compiler.incremental.log.level";
private static final String IC_LOG_LEVEL_NONE = "none";
private static final String IC_LOG_LEVEL_INFO = "info";
private static final String IC_LOG_LEVEL_DEBUG = "debug";
public static MavenICReporter get(@NotNull final Log log) {
String logLevel = System.getProperty(IC_LOG_LEVEL_PROPERTY_NAME);
if (logLevel == null) {
if (log.isDebugEnabled()) {
logLevel = IC_LOG_LEVEL_DEBUG;
}
else {
logLevel = IC_LOG_LEVEL_NONE;
}
}
if (logLevel.equalsIgnoreCase(IC_LOG_LEVEL_INFO)) {
return MavenICReporter.info(log);
}
else if (logLevel.equalsIgnoreCase(IC_LOG_LEVEL_DEBUG)) {
return MavenICReporter.debug(log);
}
else {
if (!logLevel.equalsIgnoreCase(IC_LOG_LEVEL_NONE)) {
log.warn("Unknown incremental compilation log level '" + logLevel + "'," +
"possible values: " + IC_LOG_LEVEL_NONE + ", " + IC_LOG_LEVEL_INFO + ", " + IC_LOG_LEVEL_DEBUG);
}
return MavenICReporter.noLog();
}
}
private static MavenICReporter info(@NotNull final Log log) {
return new MavenICReporter() {
@Override
protected boolean isLogEnabled() {
return log.isInfoEnabled();
}
@Override
protected void log(String str) {
log.info(str);
}
};
}
private static MavenICReporter debug(@NotNull final Log log) {
return new MavenICReporter() {
@Override
protected boolean isLogEnabled() {
return log.isDebugEnabled();
}
@Override
protected void log(String str) {
log.debug(str);
}
};
}
private static MavenICReporter noLog() {
return new MavenICReporter();
}
@NotNull
private final Set<File> compiledKotlinFiles = new HashSet<File>();
protected boolean isLogEnabled() {
return false;
}
protected void log(String str) {
}
private MavenICReporter() {
}
@Override
public void report(Function0<String> getMessage) {
if (isLogEnabled()) {
log(getMessage.invoke());
}
}
@Override
public void reportCompileIteration(Collection<? extends File> sourceFiles, ExitCode exitCode) {
compiledKotlinFiles.addAll(sourceFiles);
if (isLogEnabled()) {
log("Kotlin compile iteration: " + pathsAsString(sourceFiles));
log("Exit code: " + exitCode.toString());
}
}
@NotNull
@Override
public String pathsAsString(Iterable<? extends File> files) {
return ICReporter.DefaultImpls.pathsAsString(this, files);
}
@NotNull
@Override
public String pathsAsString(File... files) {
return ICReporter.DefaultImpls.pathsAsString(this, files);
}
@NotNull
public Set<File> getCompiledKotlinFiles() {
return compiledKotlinFiles;
}
}
@@ -180,4 +180,9 @@ public class KaptJVMCompilerMojo extends K2JVMCompileMojo {
return result;
}
@Override
protected boolean isIncremental() {
return false;
}
}