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
This commit is contained in:
+6
-1
@@ -62,7 +62,11 @@ class IncrementalCompilationOptions(
|
|||||||
requestedCompilationResults: Array<Int>,
|
requestedCompilationResults: Array<Int>,
|
||||||
val resultDifferenceFile: File? = null,
|
val resultDifferenceFile: File? = null,
|
||||||
val friendDifferenceFile: 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<File>
|
||||||
) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) {
|
) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) {
|
||||||
companion object {
|
companion object {
|
||||||
const val serialVersionUID: Long = 0
|
const val serialVersionUID: Long = 0
|
||||||
@@ -80,6 +84,7 @@ class IncrementalCompilationOptions(
|
|||||||
"resultDifferenceFile=$resultDifferenceFile, " +
|
"resultDifferenceFile=$resultDifferenceFile, " +
|
||||||
"friendDifferenceFile=$friendDifferenceFile, " +
|
"friendDifferenceFile=$friendDifferenceFile, " +
|
||||||
"usePreciseJavaTracking=$usePreciseJavaTracking" +
|
"usePreciseJavaTracking=$usePreciseJavaTracking" +
|
||||||
|
"localStateDirs=$localStateDirs" +
|
||||||
")"
|
")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -522,7 +522,8 @@ class CompileServiceImpl(
|
|||||||
artifactChanges, changesRegistry,
|
artifactChanges, changesRegistry,
|
||||||
buildHistoryFile = incrementalCompilationOptions.resultDifferenceFile,
|
buildHistoryFile = incrementalCompilationOptions.resultDifferenceFile,
|
||||||
friendBuildHistoryFile = incrementalCompilationOptions.friendDifferenceFile,
|
friendBuildHistoryFile = incrementalCompilationOptions.friendDifferenceFile,
|
||||||
usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking
|
usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking,
|
||||||
|
localStateDirs = incrementalCompilationOptions.localStateDirs
|
||||||
)
|
)
|
||||||
return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
|
return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -43,7 +43,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
protected val cacheVersions: List<CacheVersion>,
|
protected val cacheVersions: List<CacheVersion>,
|
||||||
protected val reporter: ICReporter,
|
protected val reporter: ICReporter,
|
||||||
protected val artifactChangesProvider: ArtifactChangesProvider?,
|
protected val artifactChangesProvider: ArtifactChangesProvider?,
|
||||||
protected val changesRegistry: ChangesRegistry?
|
protected val changesRegistry: ChangesRegistry?,
|
||||||
|
private val localStateDirs: Collection<File> = emptyList()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
protected val cacheDirectory = File(workingDir, cacheDirName)
|
protected val cacheDirectory = File(workingDir, cacheDirName)
|
||||||
@@ -70,7 +71,15 @@ abstract class IncrementalCompilerRunner<
|
|||||||
|
|
||||||
caches.clean()
|
caches.clean()
|
||||||
dirtySourcesSinceLastTimeFile.delete()
|
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)
|
caches = createCacheManager(args)
|
||||||
if (providedChangedFiles == null) {
|
if (providedChangedFiles == null) {
|
||||||
|
|||||||
+6
-3
@@ -71,7 +71,8 @@ fun makeIncrementally(
|
|||||||
sourceRoots.map { JvmSourceRoot(it, null) }.toSet(),
|
sourceRoots.map { JvmSourceRoot(it, null) }.toSet(),
|
||||||
versions, reporter,
|
versions, reporter,
|
||||||
// Use precise setting in case of non-Gradle build
|
// Use precise setting in case of non-Gradle build
|
||||||
usePreciseJavaTracking = true
|
usePreciseJavaTracking = true,
|
||||||
|
localStateDirs = emptyList()
|
||||||
)
|
)
|
||||||
compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null)
|
compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null)
|
||||||
}
|
}
|
||||||
@@ -104,14 +105,16 @@ class IncrementalJvmCompilerRunner(
|
|||||||
changesRegistry: ChangesRegistry? = null,
|
changesRegistry: ChangesRegistry? = null,
|
||||||
private val buildHistoryFile: File? = null,
|
private val buildHistoryFile: File? = null,
|
||||||
private val friendBuildHistoryFile: File? = null,
|
private val friendBuildHistoryFile: File? = null,
|
||||||
private val usePreciseJavaTracking: Boolean
|
private val usePreciseJavaTracking: Boolean,
|
||||||
|
localStateDirs: Collection<File>
|
||||||
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
|
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
|
||||||
workingDir,
|
workingDir,
|
||||||
"caches-jvm",
|
"caches-jvm",
|
||||||
cacheVersions,
|
cacheVersions,
|
||||||
reporter,
|
reporter,
|
||||||
artifactChangesProvider,
|
artifactChangesProvider,
|
||||||
changesRegistry
|
changesRegistry,
|
||||||
|
localStateDirs = localStateDirs
|
||||||
) {
|
) {
|
||||||
override fun isICEnabled(): Boolean =
|
override fun isICEnabled(): Boolean =
|
||||||
IncrementalCompilation.isEnabled()
|
IncrementalCompilation.isEnabled()
|
||||||
|
|||||||
+48
@@ -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
|
@Test
|
||||||
fun testRemoveAnnotationIC() {
|
fun testRemoveAnnotationIC() {
|
||||||
val project = Project("simple", directoryPrefix = "kapt2")
|
val project = Project("simple", directoryPrefix = "kapt2")
|
||||||
|
|||||||
+24
@@ -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'
|
||||||
|
}
|
||||||
+14
@@ -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
|
||||||
|
}
|
||||||
+16
@@ -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(){
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -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(){
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -39,5 +39,6 @@ internal class GradleIncrementalCompilerEnvironment(
|
|||||||
val artifactFile: File? = null,
|
val artifactFile: File? = null,
|
||||||
val buildHistoryFile: File? = null,
|
val buildHistoryFile: File? = null,
|
||||||
val friendBuildHistoryFile: File? = null,
|
val friendBuildHistoryFile: File? = null,
|
||||||
val usePreciseJavaTracking: Boolean = false
|
val usePreciseJavaTracking: Boolean = false,
|
||||||
|
val localStateDirs: List<File> = emptyList()
|
||||||
) : GradleCompilerEnvironment(compilerClasspath, messageCollector, outputItemsCollector, compilerArgs)
|
) : GradleCompilerEnvironment(compilerClasspath, messageCollector, outputItemsCollector, compilerArgs)
|
||||||
|
|||||||
+2
-1
@@ -269,7 +269,8 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
|||||||
targetPlatform = targetPlatform,
|
targetPlatform = targetPlatform,
|
||||||
resultDifferenceFile = environment.buildHistoryFile,
|
resultDifferenceFile = environment.buildHistoryFile,
|
||||||
friendDifferenceFile = environment.friendBuildHistoryFile,
|
friendDifferenceFile = environment.friendBuildHistoryFile,
|
||||||
usePreciseJavaTracking = environment.usePreciseJavaTracking
|
usePreciseJavaTracking = environment.usePreciseJavaTracking,
|
||||||
|
localStateDirs = environment.localStateDirs
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info("Options for KOTLIN DAEMON: $compilationOptions")
|
log.info("Options for KOTLIN DAEMON: $compilationOptions")
|
||||||
|
|||||||
-10
@@ -84,16 +84,6 @@ open class KaptGenerateStubsTask : KotlinCompile() {
|
|||||||
args.destinationAsFile = this.destinationDir
|
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) {
|
override fun execute(inputs: IncrementalTaskInputs) {
|
||||||
val sourceRoots = kotlinCompileTask.getSourceRoots()
|
val sourceRoots = kotlinCompileTask.getSourceRoots()
|
||||||
val allKotlinSources = sourceRoots.kotlinSourceFiles
|
val allKotlinSources = sourceRoots.kotlinSourceFiles
|
||||||
|
|||||||
+1
-8
@@ -97,9 +97,7 @@ open class KaptTask : ConventionTask(), CompilerArgumentAwareWithInput<K2JVMComp
|
|||||||
fun compile() {
|
fun compile() {
|
||||||
/** Delete everything inside generated sources and classes output directory
|
/** Delete everything inside generated sources and classes output directory
|
||||||
* (annotation processing is not incremental) */
|
* (annotation processing is not incremental) */
|
||||||
destinationDir.clearDirectory()
|
clearOutputDirectories()
|
||||||
classesDir.clearDirectory()
|
|
||||||
kotlinSourcesDestinationDir.clearDirectory()
|
|
||||||
|
|
||||||
val sourceRootsFromKotlin = kotlinCompileTask.sourceRootsContainer.sourceRoots
|
val sourceRootsFromKotlin = kotlinCompileTask.sourceRootsContainer.sourceRoots
|
||||||
val rawSourceRoots = FilteringSourceRootsContainer(sourceRootsFromKotlin, { !isInsideDestinationDirs(it) })
|
val rawSourceRoots = FilteringSourceRootsContainer(sourceRootsFromKotlin, { !isInsideDestinationDirs(it) })
|
||||||
@@ -124,11 +122,6 @@ open class KaptTask : ConventionTask(), CompilerArgumentAwareWithInput<K2JVMComp
|
|||||||
throwGradleExceptionIfError(exitCode)
|
throwGradleExceptionIfError(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun File.clearDirectory() {
|
|
||||||
deleteRecursively()
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val isAtLeastJava9: Boolean
|
private val isAtLeastJava9: Boolean
|
||||||
get() = compareVersionNumbers(getJavaRuntimeVersion(), "9") >= 0
|
get() = compareVersionNumbers(getJavaRuntimeVersion(), "9") >= 0
|
||||||
|
|
||||||
|
|||||||
+3
-9
@@ -341,13 +341,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
|
|||||||
@Internal
|
@Internal
|
||||||
override fun getSourceRoots() = SourceRoots.ForJvm.create(getSource(), sourceRootsContainer)
|
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) {
|
override fun callCompiler(args: K2JVMCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
|
||||||
sourceRoots as SourceRoots.ForJvm
|
sourceRoots as SourceRoots.ForJvm
|
||||||
|
|
||||||
@@ -371,13 +364,14 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
|
|||||||
artifactFile = artifactFile,
|
artifactFile = artifactFile,
|
||||||
buildHistoryFile = buildHistoryFile,
|
buildHistoryFile = buildHistoryFile,
|
||||||
friendBuildHistoryFile = friendTask?.buildHistoryFile,
|
friendBuildHistoryFile = friendTask?.buildHistoryFile,
|
||||||
usePreciseJavaTracking = usePreciseJavaTracking
|
usePreciseJavaTracking = usePreciseJavaTracking,
|
||||||
|
localStateDirs = outputDirectories
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!incremental) {
|
if (!incremental) {
|
||||||
clearOutputsBeforeNonIncrementalBuild()
|
clearOutputDirectories(reason = "IC is disabled for the task")
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
+29
@@ -1,7 +1,15 @@
|
|||||||
package org.jetbrains.kotlin.gradle.tasks
|
package org.jetbrains.kotlin.gradle.tasks
|
||||||
|
|
||||||
import org.gradle.api.GradleException
|
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.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) {
|
fun throwGradleExceptionIfError(exitCode: ExitCode) {
|
||||||
when (exitCode) {
|
when (exitCode) {
|
||||||
@@ -12,3 +20,24 @@ fun throwGradleExceptionIfError(exitCode: ExitCode) {
|
|||||||
else -> throw IllegalStateException("Unexpected exit code: $exitCode")
|
else -> throw IllegalStateException("Unexpected exit code: $exitCode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val <T : Task> T.outputDirectories: List<File>
|
||||||
|
get() = outputsCompatible.files.files.filter { it.isDirectory }
|
||||||
|
|
||||||
|
internal fun <T : Task> 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)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user