From 30d0cc3a34fdfc15debcd5c9eb4b009d7c1df624 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Sun, 18 Mar 2018 22:15:15 +0300 Subject: [PATCH] Ensure all output directories are cleared on IC rebuild In some cases IC needs to perform a rebuild. Before this change IC was not clearing output directories besides destination dir for classes, so for example kapt stubs were not cleared. Stalled stubs might lead to compile errors. For example: 1. foo/XGen.java is generated from annotated class foo/X (XGen also references X). 2. foo/X is moved to bar/X and some other change forces IC to rebuild. 3. kapt generates bar/X stub, but foo/X stub was not removed because stubs dir is not cleared. 4. kapt runs annotation processors, foo/XGen.java is generated from foo/X stub, bar/XGen.java is generated from bar/X stub. 5. kotlinc rebuilds. Since destination dir is cleared properly, only bar/X.class exists. 6. javac tries to compile foo/XGen and fails, because it compiles against actual Kotlin classes, not stubs. This commit fixes the issue by passing all output directories of a task from Gradle to Kotlin IC. #KT-21735 fixed --- .../daemon/common/CompilationOptions.kt | 7 ++- .../kotlin/daemon/CompileServiceImpl.kt | 3 +- .../incremental/IncrementalCompilerRunner.kt | 13 ++++- .../IncrementalJvmCompilerRunner.kt | 9 ++-- .../org/jetbrains/kotlin/gradle/Kapt3IT.kt | 48 +++++++++++++++++++ .../kapt2/incrementalRebuild/build.gradle | 24 ++++++++++ .../src/main/java/bar/UseBar.kt | 14 ++++++ .../src/main/java/foo/Bar.java | 16 +++++++ .../src/main/java/foo/Foo.java | 16 +++++++ .../GradleCompilerEnvironment.kt | 3 +- .../GradleKotlinCompilerRunner.kt | 3 +- .../gradle/internal/KaptGenerateStubsTask.kt | 10 ---- .../kotlin/gradle/internal/KaptTask.kt | 9 +--- .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 12 ++--- .../kotlin/gradle/tasks/tasksUtils.kt | 29 +++++++++++ 15 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/bar/UseBar.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Bar.java create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Foo.java diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt index 01f3e0ced60..72438ef864e 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt @@ -62,7 +62,11 @@ class IncrementalCompilationOptions( requestedCompilationResults: Array, val resultDifferenceFile: File? = null, val friendDifferenceFile: File? = null, - val usePreciseJavaTracking: Boolean + val usePreciseJavaTracking: Boolean, + /** + * Directories that should be cleared when IC decides to rebuild + */ + val localStateDirs: List ) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) { companion object { const val serialVersionUID: Long = 0 @@ -80,6 +84,7 @@ class IncrementalCompilationOptions( "resultDifferenceFile=$resultDifferenceFile, " + "friendDifferenceFile=$friendDifferenceFile, " + "usePreciseJavaTracking=$usePreciseJavaTracking" + + "localStateDirs=$localStateDirs" + ")" } } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index 99749b82246..9f4854de86f 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -522,7 +522,8 @@ class CompileServiceImpl( artifactChanges, changesRegistry, buildHistoryFile = incrementalCompilationOptions.resultDifferenceFile, friendBuildHistoryFile = incrementalCompilationOptions.friendDifferenceFile, - usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking + usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking, + localStateDirs = incrementalCompilationOptions.localStateDirs ) return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles) } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index 55dd0797956..c74b1302761 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -43,7 +43,8 @@ abstract class IncrementalCompilerRunner< protected val cacheVersions: List, protected val reporter: ICReporter, protected val artifactChangesProvider: ArtifactChangesProvider?, - protected val changesRegistry: ChangesRegistry? + protected val changesRegistry: ChangesRegistry?, + private val localStateDirs: Collection = emptyList() ) { protected val cacheDirectory = File(workingDir, cacheDirName) @@ -70,7 +71,15 @@ abstract class IncrementalCompilerRunner< caches.clean() dirtySourcesSinceLastTimeFile.delete() - destinationDir(args).deleteRecursively() + + reporter.report { "Deleting output directories on rebuild:" } + for (dir in sequenceOf(destinationDir(args)) + localStateDirs.asSequence()) { + if (!dir.isDirectory) continue + + dir.deleteRecursively() + dir.mkdirs() + reporter.report { "deleted $dir" } + } caches = createCacheManager(args) if (providedChangedFiles == null) { diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index 52490df6f78..d8ccce53fff 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -71,7 +71,8 @@ fun makeIncrementally( sourceRoots.map { JvmSourceRoot(it, null) }.toSet(), versions, reporter, // Use precise setting in case of non-Gradle build - usePreciseJavaTracking = true + usePreciseJavaTracking = true, + localStateDirs = emptyList() ) compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null) } @@ -104,14 +105,16 @@ class IncrementalJvmCompilerRunner( changesRegistry: ChangesRegistry? = null, private val buildHistoryFile: File? = null, private val friendBuildHistoryFile: File? = null, - private val usePreciseJavaTracking: Boolean + private val usePreciseJavaTracking: Boolean, + localStateDirs: Collection ) : IncrementalCompilerRunner( workingDir, "caches-jvm", cacheVersions, reporter, artifactChangesProvider, - changesRegistry + changesRegistry, + localStateDirs = localStateDirs ) { override fun isICEnabled(): Boolean = IncrementalCompilation.isEnabled() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kapt3IT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kapt3IT.kt index 930b9813294..73dea124b71 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kapt3IT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kapt3IT.kt @@ -193,6 +193,54 @@ open class Kapt3IT : Kapt3BaseIT() { } } + @Test + fun testRemoveJavaClassICRebuild() { + testICRebuild { project -> + project.projectFile("Foo.java").delete() + } + } + + @Test + fun testChangeClasspathICRebuild() { + testICRebuild { project -> + project.projectFile("build.gradle").modify { + "$it\ndependencies { compile 'org.jetbrains.kotlin:kotlin-reflect:' + kotlin_version }" + } + } + } + + // tests all output directories are cleared when IC rebuilds + private fun testICRebuild(performChange: (Project) -> Unit) { + val project = Project("incrementalRebuild", directoryPrefix = "kapt2") + val options = defaultBuildOptions().copy(incremental = true) + val generatedSrc = "build/generated/source/kapt/main" + + project.build("build", options = options) { + assertSuccessful() + + // generated sources + assertFileExists("$generatedSrc/bar/UseBar_MembersInjector.java") + } + + performChange(project) + project.projectFile("UseBar.kt").modify { it.replace("package bar", "package foo.bar") } + + project.build("build", options = options) { + assertSuccessful() + assertTasksExecuted(listOf(":kaptGenerateStubsKotlin", ":kaptKotlin", ":compileKotlin", ":compileJava")) + + // generated sources + assertFileExists("$generatedSrc/foo/bar/UseBar_MembersInjector.java") + assertNoSuchFile("$generatedSrc/bar/UseBar_MembersInjector.java") + + // classes + assertFileExists(kotlinClassesDir() + "foo/bar/UseBar.class") + assertNoSuchFile(kotlinClassesDir() + "bar/UseBar.class") + assertFileExists(javaClassesDir() + "foo/bar/UseBar_MembersInjector.class") + assertNoSuchFile(javaClassesDir() + "bar/UseBar_MembersInjector.class") + } + } + @Test fun testRemoveAnnotationIC() { val project = Project("simple", directoryPrefix = "kapt2") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/build.gradle new file mode 100644 index 00000000000..042cd12474a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/build.gradle @@ -0,0 +1,24 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: "java" +apply plugin: "kotlin" +apply plugin: "kotlin-kapt" + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile 'com.google.dagger:dagger:2.14.1' + kapt 'com.google.dagger:dagger-compiler:2.14.1' +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/bar/UseBar.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/bar/UseBar.kt new file mode 100644 index 00000000000..79830a10b03 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/bar/UseBar.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-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 bar + +import javax.inject.Inject +import foo.* + +class UseBar { + @Inject + lateinit var bar: Bar +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Bar.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Bar.java new file mode 100644 index 00000000000..b3be5762e3c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Bar.java @@ -0,0 +1,16 @@ +/* + * Copyright 2010-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 foo; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class Bar { + @Inject + public Bar(){ + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Foo.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Foo.java new file mode 100644 index 00000000000..53d5f2d2230 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/incrementalRebuild/src/main/java/foo/Foo.java @@ -0,0 +1,16 @@ +/* + * Copyright 2010-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 foo; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class Foo { + @Inject + public Foo(){ + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt index db0245b49e8..0007a3bf615 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt @@ -39,5 +39,6 @@ internal class GradleIncrementalCompilerEnvironment( val artifactFile: File? = null, val buildHistoryFile: File? = null, val friendBuildHistoryFile: File? = null, - val usePreciseJavaTracking: Boolean = false + val usePreciseJavaTracking: Boolean = false, + val localStateDirs: List = emptyList() ) : GradleCompilerEnvironment(compilerClasspath, messageCollector, outputItemsCollector, compilerArgs) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt index 8cf153d7d49..a28d8c8612b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt @@ -269,7 +269,8 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil targetPlatform = targetPlatform, resultDifferenceFile = environment.buildHistoryFile, friendDifferenceFile = environment.friendBuildHistoryFile, - usePreciseJavaTracking = environment.usePreciseJavaTracking + usePreciseJavaTracking = environment.usePreciseJavaTracking, + localStateDirs = environment.localStateDirs ) log.info("Options for KOTLIN DAEMON: $compilationOptions") diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt index 65be5eb088e..48ef00ff0ab 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt @@ -84,16 +84,6 @@ open class KaptGenerateStubsTask : KotlinCompile() { args.destinationAsFile = this.destinationDir } - override fun clearOutputsBeforeNonIncrementalBuild() { - super.clearOutputsBeforeNonIncrementalBuild() - - if (!stubsDir.deleteRecursively()) { - logger.kotlinWarn("Could not delete $stubsDir") - } - - stubsDir.mkdirs() - } - override fun execute(inputs: IncrementalTaskInputs) { val sourceRoots = kotlinCompileTask.getSourceRoots() val allKotlinSources = sourceRoots.kotlinSourceFiles diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt index 2c9787808c9..43071380bd7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt @@ -97,9 +97,7 @@ open class KaptTask : ConventionTask(), CompilerArgumentAwareWithInput= 0 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 8293cab7067..f9249497721 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -341,13 +341,6 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl @Internal override fun getSourceRoots() = SourceRoots.ForJvm.create(getSource(), sourceRootsContainer) - protected open fun clearOutputsBeforeNonIncrementalBuild() { - logger.kotlinDebug { "Removing all kotlin classes in $destinationDir" } - - destinationDir.deleteRecursively() - destinationDir.mkdirs() - } - override fun callCompiler(args: K2JVMCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) { sourceRoots as SourceRoots.ForJvm @@ -371,13 +364,14 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl artifactFile = artifactFile, buildHistoryFile = buildHistoryFile, friendBuildHistoryFile = friendTask?.buildHistoryFile, - usePreciseJavaTracking = usePreciseJavaTracking + usePreciseJavaTracking = usePreciseJavaTracking, + localStateDirs = outputDirectories ) } } if (!incremental) { - clearOutputsBeforeNonIncrementalBuild() + clearOutputDirectories(reason = "IC is disabled for the task") } try { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/tasksUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/tasksUtils.kt index c882aff2913..d2fecfa29d5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/tasksUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/tasksUtils.kt @@ -1,7 +1,15 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.GradleException +import org.gradle.api.Task +import org.gradle.api.tasks.OutputDirectory import org.jetbrains.kotlin.cli.common.ExitCode +import org.jetbrains.kotlin.gradle.plugin.kotlinDebug +import org.jetbrains.kotlin.gradle.utils.outputsCompatible +import java.io.File +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.memberProperties fun throwGradleExceptionIfError(exitCode: ExitCode) { when (exitCode) { @@ -11,4 +19,25 @@ fun throwGradleExceptionIfError(exitCode: ExitCode) { ExitCode.OK -> {} else -> throw IllegalStateException("Unexpected exit code: $exitCode") } +} + +internal val T.outputDirectories: List + get() = outputsCompatible.files.files.filter { it.isDirectory } + +internal fun T.clearOutputDirectories(reason: String? = null) { + logger.kotlinDebug { + val suffix = reason?.let { " ($it)" }.orEmpty() + "Clearing output directories for task '$path'$suffix:" + } + val outputDirectories = outputDirectories + for (dir in outputDirectories) { + when { + dir.isDirectory -> { + dir.deleteRecursively() + dir.mkdirs() + logger.kotlinDebug { " deleted $dir" } + } + else -> logger.kotlinDebug { " skipping $dir (not a directory)" } + } + } } \ No newline at end of file