diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/IncrementalCompilationIT.java b/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/IncrementalCompilationIT.java index 9fb55d6b5fe..31c189588e4 100644 --- a/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/IncrementalCompilationIT.java +++ b/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/IncrementalCompilationIT.java @@ -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 } diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/MavenExecutionResult.java b/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/MavenExecutionResult.java index 5975a5c912e..4bd997f7edc 100644 --- a/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/MavenExecutionResult.java +++ b/libraries/tools/kotlin-maven-plugin-test/src/test/java/org.jetbrains.kotlin.maven/MavenExecutionResult.java @@ -110,4 +110,14 @@ class MavenExecutionResult { } }); } + + MavenExecutionResult fileExists(@NotNull final String path) throws Exception { + return check(new Action() { + @Override + public void run(MavenExecutionResult execResult) { + File file = new File(workingDir, path); + Assert.assertTrue(file + " does not exist", file.exists()); + } + }); + } } diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/pom.xml b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/pom.xml index 9dbae229971..92aea01d0fa 100644 --- a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/pom.xml +++ b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/pom.xml @@ -21,6 +21,8 @@ + ${project.basedir}/src/main/kotlin + @@ -40,7 +42,6 @@ - ${project.basedir}/src kotlin-maven-plugin diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/A.kt b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/A.kt similarity index 100% rename from libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/A.kt rename to libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/A.kt diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/Dummy.kt b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/Dummy.kt similarity index 100% rename from libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/Dummy.kt rename to libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/Dummy.kt diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/useA.kt b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/useA.kt similarity index 100% rename from libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/useA.kt rename to libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/kotlin/useA.kt diff --git a/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/resources/test.properties b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/resources/test.properties new file mode 100644 index 00000000000..c918280b7e5 --- /dev/null +++ b/libraries/tools/kotlin-maven-plugin-test/src/test/resources/kotlinSimple/src/main/resources/test.properties @@ -0,0 +1 @@ +example.property=101 \ No newline at end of file diff --git a/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JVMCompileMojo.java b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JVMCompileMojo.java index 62939f1ba61..8c556df1b6c 100644 --- a/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JVMCompileMojo.java +++ b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JVMCompileMojo.java @@ -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 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; diff --git a/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/incremental/FileCopier.java b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/incremental/FileCopier.java new file mode 100644 index 00000000000..9e9363a8773 --- /dev/null +++ b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/incremental/FileCopier.java @@ -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 previousSnapshots = readFileSnapshots(snapshotsStorageFile); + + Path sourceBase = Paths.get(sourceBaseFile.getPath()).normalize(); + Path targetBase = Paths.get(targetBaseFile.getPath()).normalize(); + Map 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 snapshots, @NotNull File outputFile) { + try (ObjectOutputStream output = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)))) { + output.writeInt(VERSION); + output.writeInt(snapshots.size()); + for (Map.Entry 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 readFileSnapshots(@NotNull File inputFile) { + Map 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; + } + } +}