Restore outputs when Kotlin task fails
#KT-12295 Fixed Gradle forces non-incremetnal builds, when a task failed, but its outputs were changed. This is an issue for Kotlin incremental compilation, because it also forces all dependent modules to be rebuilt. This change fixes the issue by reading all output and restoring it on a compile error/exception. This change does not affect non-incremental builds.
This commit is contained in:
+10
@@ -451,6 +451,16 @@ abstract class BaseGradleIT {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CompiledProject.assertTasksFailed(vararg tasks: String) {
|
||||||
|
assertTasksFailed(tasks.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CompiledProject.assertTasksFailed(tasks: Iterable<String>) {
|
||||||
|
for (task in tasks) {
|
||||||
|
assertContains("$task FAILED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun CompiledProject.assertTasksUpToDate(vararg tasks: String) {
|
fun CompiledProject.assertTasksUpToDate(vararg tasks: String) {
|
||||||
assertTasksUpToDate(tasks.toList())
|
assertTasksUpToDate(tasks.toList())
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-8
@@ -27,12 +27,18 @@ class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiPr
|
|||||||
|
|
||||||
override val additionalLibDependencies: String =
|
override val additionalLibDependencies: String =
|
||||||
"implementation \"org.jetbrains.kotlin:kotlin-test-js:${'$'}kotlin_version\""
|
"implementation \"org.jetbrains.kotlin:kotlin-test-js:${'$'}kotlin_version\""
|
||||||
|
|
||||||
|
override val compileKotlinTaskName: String
|
||||||
|
get() = "compileKotlin2Js"
|
||||||
}
|
}
|
||||||
|
|
||||||
class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
|
class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
|
||||||
override val additionalLibDependencies: String =
|
override val additionalLibDependencies: String =
|
||||||
"implementation \"org.jetbrains.kotlin:kotlin-stdlib:${'$'}kotlin_version\""
|
"implementation \"org.jetbrains.kotlin:kotlin-stdlib:${'$'}kotlin_version\""
|
||||||
|
|
||||||
|
override val compileKotlinTaskName: String
|
||||||
|
get() = "compileKotlin"
|
||||||
|
|
||||||
override fun defaultProject(): Project =
|
override fun defaultProject(): Project =
|
||||||
Project("incrementalMultiproject")
|
Project("incrementalMultiproject")
|
||||||
|
|
||||||
@@ -203,6 +209,7 @@ open class A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract val additionalLibDependencies: String
|
protected abstract val additionalLibDependencies: String
|
||||||
|
protected abstract val compileKotlinTaskName: String
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAddDependencyToLib() {
|
fun testAddDependencyToLib() {
|
||||||
@@ -251,20 +258,31 @@ open class A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val bKt = project.projectDir.getFileByName("B.kt")
|
val bKt = project.projectDir.getFileByName("B.kt")
|
||||||
|
val bKtContent = bKt.readText()
|
||||||
bKt.delete()
|
bKt.delete()
|
||||||
|
|
||||||
project.build("build") {
|
fun runFailingBuild() {
|
||||||
assertFailed()
|
project.build("build") {
|
||||||
|
assertFailed()
|
||||||
|
assertContains("B.kt has been removed")
|
||||||
|
assertTasksFailed(":lib:$compileKotlinTaskName")
|
||||||
|
val affectedFiles = project.projectDir.getFilesByNames("barUseAB.kt", "barUseB.kt")
|
||||||
|
assertCompiledKotlinSources(project.relativize(affectedFiles))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project.projectDir.getFileByName("barUseB.kt").delete()
|
runFailingBuild()
|
||||||
project.projectDir.getFileByName("barUseAB.kt").delete()
|
runFailingBuild()
|
||||||
|
|
||||||
|
bKt.writeText(bKtContent.replace("fun b", "open fun b"))
|
||||||
|
|
||||||
project.build("build") {
|
project.build("build") {
|
||||||
assertFailed()
|
assertSuccessful()
|
||||||
val affectedSources = project.projectDir.allKotlinFiles()
|
val affectedFiles = project.projectDir.getFilesByNames(
|
||||||
val relativePaths = project.relativize(affectedSources)
|
"B.kt", "barUseAB.kt", "barUseB.kt",
|
||||||
assertCompiledKotlinSources(relativePaths)
|
"BB.kt", "fooUseB.kt"
|
||||||
|
)
|
||||||
|
assertCompiledKotlinSources(project.relativize(affectedFiles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
@@ -71,6 +71,32 @@ class KaptIncrementalIT : BaseGradleIT() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCompileError() {
|
||||||
|
val project = getProject()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val bKt = project.projectDir.getFileByName("B.kt")
|
||||||
|
val errorKt = bKt.resolveSibling("error.kt")
|
||||||
|
errorKt.writeText("<COMPILE_ERROR_MARKER>")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertFailed()
|
||||||
|
assertTasksFailed(":kaptGenerateStubsKotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
errorKt.delete()
|
||||||
|
bKt.modify { "$it\n" }
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertCompiledKotlinSources(project.relativize(bKt))
|
||||||
|
assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChangeFunctionBodyWithoutChangingSignature() {
|
fun testChangeFunctionBodyWithoutChangingSignature() {
|
||||||
val project = getProject()
|
val project = getProject()
|
||||||
|
|||||||
+2
-15
@@ -91,30 +91,17 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
|||||||
get() = incrementalCompilationEnvironment != null
|
get() = incrementalCompilationEnvironment != null
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (!isIncremental) {
|
|
||||||
clearLocalStateDirectories(log, localStateDirectories, "IC is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
val messageCollector = GradlePrintingMessageCollector(log)
|
val messageCollector = GradlePrintingMessageCollector(log)
|
||||||
val exitCode = try {
|
val exitCode = try {
|
||||||
compileWithDaemonOrFallbackImpl(messageCollector)
|
compileWithDaemonOrFallbackImpl(messageCollector)
|
||||||
} catch (e: Throwable) {
|
|
||||||
clearLocalStateDirectories(log, localStateDirectories, "exception when running compiler")
|
|
||||||
throw e
|
|
||||||
} finally {
|
} finally {
|
||||||
if (buildFile != null && System.getProperty(DELETE_MODULE_FILE_PROPERTY) != "false") {
|
if (buildFile != null && System.getProperty(DELETE_MODULE_FILE_PROPERTY) != "false") {
|
||||||
buildFile.delete()
|
buildFile.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incrementalCompilationEnvironment != null) {
|
if (incrementalCompilationEnvironment?.disableMultiModuleIC == true) {
|
||||||
if (incrementalCompilationEnvironment.disableMultiModuleIC) {
|
incrementalCompilationEnvironment.multiModuleICSettings.buildHistoryFile.delete()
|
||||||
incrementalCompilationEnvironment.multiModuleICSettings.buildHistoryFile.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exitCode != ExitCode.OK) {
|
|
||||||
clearLocalStateDirectories(log, localStateDirectories, "exit code: $exitCode")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throwGradleExceptionIfError(exitCode)
|
throwGradleExceptionIfError(exitCode)
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
|
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
|
||||||
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
|
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
|
||||||
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
|
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.clearLocalStateDirectories
|
||||||
import org.jetbrains.kotlin.gradle.tasks.localStateDirectories
|
import org.jetbrains.kotlin.gradle.tasks.localStateDirectories
|
||||||
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
|
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
|
|||||||
fun compile() {
|
fun compile() {
|
||||||
logger.debug("Running kapt annotation processing using the Kotlin compiler")
|
logger.debug("Running kapt annotation processing using the Kotlin compiler")
|
||||||
checkAnnotationProcessorClasspath()
|
checkAnnotationProcessorClasspath()
|
||||||
|
clearLocalStateDirectories()
|
||||||
|
|
||||||
val args = prepareCompilerArguments()
|
val args = prepareCompilerArguments()
|
||||||
|
|
||||||
|
|||||||
+50
-4
@@ -30,14 +30,12 @@ import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
|
|||||||
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
||||||
import org.jetbrains.kotlin.gradle.logging.kotlinWarn
|
import org.jetbrains.kotlin.gradle.logging.kotlinWarn
|
||||||
import org.jetbrains.kotlin.gradle.plugin.*
|
import org.jetbrains.kotlin.gradle.plugin.*
|
||||||
import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion
|
import org.jetbrains.kotlin.gradle.utils.*
|
||||||
import org.jetbrains.kotlin.gradle.utils.isParentOf
|
|
||||||
import org.jetbrains.kotlin.gradle.utils.pathsAsStringRelativeTo
|
|
||||||
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
|
|
||||||
import org.jetbrains.kotlin.incremental.ChangedFiles
|
import org.jetbrains.kotlin.incremental.ChangedFiles
|
||||||
import org.jetbrains.kotlin.incremental.classpathAsList
|
import org.jetbrains.kotlin.incremental.classpathAsList
|
||||||
import org.jetbrains.kotlin.incremental.destinationAsFile
|
import org.jetbrains.kotlin.incremental.destinationAsFile
|
||||||
import org.jetbrains.kotlin.utils.LibraryUtils
|
import org.jetbrains.kotlin.utils.LibraryUtils
|
||||||
|
import org.jetbrains.kotlin.utils.keysToMap
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -257,6 +255,54 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
|
|||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
fun execute(inputs: IncrementalTaskInputs) {
|
fun execute(inputs: IncrementalTaskInputs) {
|
||||||
|
// If task throws exception, but its outputs are changed during execution,
|
||||||
|
// then Gradle forces next build to be non-incremental (see Gradle's DefaultTaskArtifactStateRepository#persistNewOutputs)
|
||||||
|
// To prevent this, we backup outputs before incremental build and restore when exception is thrown
|
||||||
|
val prevOutputs: Map<File, ByteArray>? = if (incremental && inputs.isIncremental) {
|
||||||
|
val outputFiles = HashSet<File>()
|
||||||
|
outputsCompatible.files.forEach {
|
||||||
|
if (it.isDirectory) {
|
||||||
|
it.walk().filterTo(outputFiles, File::isFile)
|
||||||
|
} else if (it.isFile) {
|
||||||
|
outputFiles.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles.keysToMap { it.readBytes() }
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (!incremental) {
|
||||||
|
clearLocalStateDirectories("IC is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
executeImpl(inputs)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
outputsCompatible.files.forEach {
|
||||||
|
if (it.isDirectory) {
|
||||||
|
it.deleteRecursively()
|
||||||
|
} else if (it.isFile) {
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevOutputs != null) {
|
||||||
|
val dirs = HashSet<File>()
|
||||||
|
|
||||||
|
for ((file, bytes) in prevOutputs) {
|
||||||
|
val dir = file.parentFile
|
||||||
|
if (dirs.add(dir)) {
|
||||||
|
dir.mkdirs()
|
||||||
|
}
|
||||||
|
file.writeBytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeImpl(inputs: IncrementalTaskInputs) {
|
||||||
// Check that the JDK tools are available in Gradle (fail-fast, instead of a fail during the compiler run):
|
// Check that the JDK tools are available in Gradle (fail-fast, instead of a fail during the compiler run):
|
||||||
findToolsJar()
|
findToolsJar()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user