Avoid removing target/classes dir during IC in Maven

#KT-21581 fixed
This commit is contained in:
Alexey Tsvetkov
2018-02-12 20:34:02 +03:00
parent fa3ca0cddb
commit 42c433f950
9 changed files with 198 additions and 12 deletions
@@ -9,7 +9,8 @@ public class IncrementalCompilationIT extends MavenITBase {
MavenProject project = new MavenProject("kotlinSimple");
project.exec("package")
.succeeded()
.compiledKotlin("src/A.kt", "src/useA.kt", "src/Dummy.kt");
.fileExists("target/classes/test.properties")
.compiledKotlin("src/main/kotlin/A.kt", "src/main/kotlin/useA.kt", "src/main/kotlin/Dummy.kt");
}
@Test
@@ -27,7 +28,7 @@ public class IncrementalCompilationIT extends MavenITBase {
MavenProject project = new MavenProject("kotlinSimple");
project.exec("package");
File aKt = project.file("src/A.kt");
File aKt = project.file("src/main/kotlin/A.kt");
String original = "class A";
String replacement = "private class A";
MavenTestUtils.replaceFirstInFile(aKt, original, replacement);
@@ -39,7 +40,7 @@ public class IncrementalCompilationIT extends MavenITBase {
MavenTestUtils.replaceFirstInFile(aKt, replacement, original);
project.exec("package")
.succeeded()
.compiledKotlin("src/A.kt", "src/useA.kt");
.compiledKotlin("src/main/kotlin/A.kt", "src/main/kotlin/useA.kt");
}
@@ -48,12 +49,12 @@ public class IncrementalCompilationIT extends MavenITBase {
MavenProject project = new MavenProject("kotlinSimple");
project.exec("package");
File aKt = project.file("src/A.kt");
File aKt = project.file("src/main/kotlin/A.kt");
MavenTestUtils.replaceFirstInFile(aKt, "fun foo", "internal fun foo");
project.exec("package")
.succeeded()
.compiledKotlin("src/A.kt", "src/useA.kt");
.compiledKotlin("src/main/kotlin/A.kt", "src/main/kotlin/useA.kt");
// todo rebuild and compare output
}
@@ -110,4 +110,14 @@ class MavenExecutionResult {
}
});
}
MavenExecutionResult fileExists(@NotNull final String path) throws Exception {
return check(new Action<MavenExecutionResult>() {
@Override
public void run(MavenExecutionResult execResult) {
File file = new File(workingDir, path);
Assert.assertTrue(file + " does not exist", file.exists());
}
});
}
}
@@ -21,6 +21,8 @@
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<pluginManagement>
<plugins>
<plugin>
@@ -40,7 +42,6 @@
</plugins>
</pluginManagement>
<sourceDirectory>${project.basedir}/src</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
@@ -16,7 +16,9 @@
package org.jetbrains.kotlin.maven;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiJavaModule;
import kotlin.collections.MapsKt;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
@@ -32,15 +34,15 @@ 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.FileCopier;
import org.jetbrains.kotlin.maven.incremental.MavenICReporter;
import org.jetbrains.kotlin.maven.kapt.AnnotationProcessingManager;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
import static com.intellij.openapi.util.text.StringUtil.join;
import static org.jetbrains.kotlin.maven.Util.filterClassPath;
@@ -241,19 +243,43 @@ public class K2JVMCompileMojo extends KotlinCompileMojoBase<K2JVMCompilerArgumen
File cachesDir = getCachesDir();
//noinspection ResultOfMethodCallIgnored
cachesDir.mkdirs();
String destination = arguments.getDestination();
assert destination != null : "output is not specified!";
File classesDir = new File(destination);
File kotlinClassesDir = new File(cachesDir, "classes");
File snapshotsFile = new File(cachesDir, "snapshots.bin");
String classpath = arguments.getClasspath();
MavenICReporter icReporter = MavenICReporter.get(getLog());
try {
arguments.setDestination(kotlinClassesDir.getAbsolutePath());
if (classpath != null) {
List<String> filteredClasspath = new ArrayList<>();
for (String path : classpath.split(File.pathSeparator)) {
if (!classesDir.equals(new File(path))) {
filteredClasspath.add(path);
}
}
arguments.setClasspath(StringUtil.join(filteredClasspath, File.pathSeparator));
}
IncrementalJvmCompilerRunnerKt.makeIncrementally(cachesDir, sourceRoots, arguments, messageCollector, icReporter);
int compiledKtFilesCount = icReporter.getCompiledKotlinFiles().size();
getLog().info("Compiled " + icReporter.getCompiledKotlinFiles().size() + " Kotlin files using incremental compiler");
if (!messageCollector.hasErrors()) {
(new FileCopier(getLog())).syncDirs(kotlinClassesDir, classesDir, snapshotsFile);
}
}
catch (Throwable t) {
t.printStackTrace();
return ExitCode.INTERNAL_ERROR;
}
finally {
arguments.setDestination(destination);
arguments.setClasspath(classpath);
}
if (messageCollector.hasErrors()) {
return ExitCode.COMPILATION_ERROR;
@@ -0,0 +1,147 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.maven.incremental;
import org.apache.maven.plugin.logging.Log;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class FileCopier {
private final static int VERSION = 0;
private final Log log;
public FileCopier(Log log) {
this.log = log;
}
public void syncDirs(@NotNull File from, @NotNull File to, @NotNull File snapshotsStorageFile) {
try {
syncDirsImpl(from, to, snapshotsStorageFile);
} catch (IOException e) {
log.warn("Could not copy Kotlin files from " + from + " to " + to, e);
}
}
private void syncDirsImpl(
@NotNull File sourceBaseFile,
@NotNull File targetBaseFile,
@NotNull File snapshotsStorageFile
) throws IOException {
// snapshots are stored as relative paths (to source or target base)
Map<String, FileSnapshot> previousSnapshots = readFileSnapshots(snapshotsStorageFile);
Path sourceBase = Paths.get(sourceBaseFile.getPath()).normalize();
Path targetBase = Paths.get(targetBaseFile.getPath()).normalize();
Map<String, FileSnapshot> newSnapshots = new HashMap<>();
for (Path path : Files.walk(sourceBase).collect(Collectors.toList())) {
if (!Files.isRegularFile(path)) continue;
String relativePath = sourceBase.relativize(path).toString();
File file = path.toFile();
FileSnapshot snapshot = new FileSnapshot(file.lastModified(), file.length());
// remove current files from previousSnapshots map so only removed files would remain in previousSnapshots
FileSnapshot prevSnapshot = previousSnapshots.remove(relativePath);
if (!snapshot.equals(prevSnapshot)) {
Path target = targetBase.resolve(relativePath);
if (!Files.isDirectory(target)) {
Files.deleteIfExists(target);
Files.createDirectories(target.getParent());
Files.copy(path, target);
}
log.debug("Copied " + path + " to " + target);
}
newSnapshots.put(relativePath, snapshot);
}
for (String removedPath : previousSnapshots.keySet()) {
Path target = targetBase.resolve(removedPath);
log.debug("Deleted " + target + " as it is no longer present in " + sourceBase);
if (Files.isRegularFile(target)) {
Files.delete(target);
}
}
writeFileSnapshots(newSnapshots, snapshotsStorageFile);
}
private void writeFileSnapshots(@NotNull Map<String, FileSnapshot> snapshots, @NotNull File outputFile) {
try (ObjectOutputStream output = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)))) {
output.writeInt(VERSION);
output.writeInt(snapshots.size());
for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
String path = entry.getKey();
output.writeUTF(path);
FileSnapshot snapshot = entry.getValue();
output.writeLong(snapshot.lastModified);
output.writeLong(snapshot.size);
}
} catch (Exception e) {
log.debug("Couldn't write copied files list to " + outputFile, e);
}
}
@NotNull
private Map<String, FileSnapshot> readFileSnapshots(@NotNull File inputFile) {
Map<String, FileSnapshot> snapshots = new HashMap<>();
if (!inputFile.isFile()) return snapshots;
try (ObjectInputStream input = new ObjectInputStream(new BufferedInputStream(new FileInputStream(inputFile)))) {
int version = input.readInt();
if (version != VERSION) return snapshots;
int size = input.readInt();
for (int i = 0; i < size; i++) {
String path = input.readUTF();
long lastModified = input.readLong();
long fileSize = input.readLong();
snapshots.put(path, new FileSnapshot(lastModified, fileSize));
}
} catch (Exception e) {
log.debug("Couldn't read copied files list from " + inputFile, e);
}
return snapshots;
}
private static class FileSnapshot {
final long lastModified;
final long size;
private FileSnapshot(long lastModified, long size) {
this.lastModified = lastModified;
this.size = size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileSnapshot that = (FileSnapshot) o;
if (lastModified != that.lastModified) return false;
return size == that.size;
}
@Override
public int hashCode() {
int result = (int) (lastModified ^ (lastModified >>> 32));
result = 31 * result + (int) (size ^ (size >>> 32));
return result;
}
}
}