diff --git a/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt b/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt index a3c595c2add..de20d5ebc55 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.incremental import java.io.File +import java.io.IOException fun File.isJavaFile() = extension.equals("java", ignoreCase = true) @@ -26,3 +27,43 @@ fun File.isKotlinFile(sourceFilesExtensions: List): Boolean = fun File.isClassFile(): Boolean = extension.equals("class", ignoreCase = true) + +/** + * Deletes the contents of this directory (not the directory itself) if it exists, or creates the directory if it does not yet exist. + * + * If this is a regular file, this method will throw an exception. + */ +fun File.cleanDirectoryContents() { + when { + isDirectory -> listFiles()!!.forEach { it.forceDeleteRecursively() } + isFile -> error("File.cleanDirectoryContents does not accept a regular file: $path") + else -> forceMkdirs() + } +} + +/** Deletes this file or directory recursively (if it exists). */ +fun File.forceDeleteRecursively() { + if (!deleteRecursively()) { + throw IOException("Could not delete '$path'") + } +} + +/** + * Creates this directory (if it does not yet exist). + * + * If this is a regular file, this method will throw an exception. + */ +@Suppress("SpellCheckingInspection") +fun File.forceMkdirs() { + when { + this.isDirectory -> { /* Do nothing */ } + this.isFile -> error("File.forceMkdirs does not accept a regular file: $path") + else -> { + // Note that if the directory already exists, mkdirs() will return `false`, but here we ensure that the directory does not exist + // before calling mkdirs(), so it's safe to check the returned result of mkdirs() below. + if (!mkdirs()) { + throw IOException("Could not create directory '$path'") + } + } + } +} 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 c071a5cc5e6..5d61da8cf5f 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 @@ -38,7 +38,6 @@ import org.jetbrains.kotlin.incremental.util.BufferingMessageCollector import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.progress.CompilationCanceledStatus import java.io.File -import java.io.IOException abstract class IncrementalCompilerRunner< Args : CommonCompilerArguments, @@ -50,7 +49,7 @@ abstract class IncrementalCompilerRunner< private val buildHistoryFile: File, // there might be some additional output directories (e.g. for generated java in kapt) // to remove them correctly on rebuild, we pass them as additional argument - private val outputFiles: Collection = emptyList() + private val additionalOutputFiles: Collection = emptyList() ) { protected val cacheDirectory = File(workingDir, cacheDirName) @@ -106,7 +105,7 @@ abstract class IncrementalCompilerRunner< caches.close(false) // todo: we can recompile all files incrementally (not cleaning caches), so rebuild won't propagate reporter.measure(BuildTime.CLEAR_OUTPUT_ON_REBUILD) { - clearOutputsOnRebuild(args) + cleanOutputsAndLocalStateOnRebuild(args) } caches = createCacheManager(args, projectDir) if (providedChangedFiles == null) { @@ -168,7 +167,9 @@ abstract class IncrementalCompilerRunner< } } - performWorkAfterCompilation(caches) + if (exitCode == ExitCode.OK) { + performWorkAfterSuccessfulCompilation(caches) + } if (!caches.close(flush = true)) throw RuntimeException("Could not flush caches") // Here we should analyze exit code of compiler. E.g. compiler failure should lead to caches rebuild, @@ -193,41 +194,32 @@ abstract class IncrementalCompilerRunner< rebuild(BuildAttribute.CACHE_CORRUPTION) } finally { if (cachesMayBeCorrupted) { - clearOutputsOnRebuild(args) + cleanOutputsAndLocalStateOnRebuild(args) } } } /** - * Deletes output files and contents of output directories on rebuild (including `@LocalState` files/directories). + * Deletes output files and contents of output directories on rebuild, including `@LocalState` files/directories. * - * If the output directories do not yet exist, they will be created. + * If the directories do not yet exist, they will be created. */ - private fun clearOutputsOnRebuild(args: Args) { - val destinationDir = destinationDir(args) - val (outputDirsThatExist, regularOrNonExistentOutputFiles) = outputFiles.partition { it.isDirectory } - val regularOutputFiles = regularOrNonExistentOutputFiles.filter { it.exists() } + private fun cleanOutputsAndLocalStateOnRebuild(args: Args) { + // Use Set as additionalOutputFiles may already contain destinationDir and workingDir + val outputFiles = setOf(destinationDir(args), workingDir) + additionalOutputFiles - // outputDirsThatExist may or may not contain destinationDir and workingDir. - // Collect all of them so that we don't miss any output directories. - // Use Set to avoid duplication. - val allOutputDirs = setOf(destinationDir, workingDir) + outputDirsThatExist - - reporter.reportVerbose { "Clearing outputs on rebuild" } - allOutputDirs.forEach { dir -> - reporter.reportVerbose { " Deleting contents of directory '${dir.path}'" } - dir.listFiles()?.forEach { - it.deleteRecursively() - if (it.exists()) throw IOException("Could not delete '${it.path}'") + reporter.reportVerbose { "Cleaning outputs on rebuild" } + outputFiles.forEach { + when { + it.isDirectory -> { + reporter.reportVerbose { " Deleting contents of directory '${it.path}'" } + it.cleanDirectoryContents() + } + it.isFile -> { + reporter.reportVerbose { " Deleting file '${it.path}'" } + it.forceDeleteRecursively() + } } - - dir.mkdirs() - if (!dir.exists()) throw IOException("Could not create directory '${dir.path}'") - } - regularOutputFiles.forEach { file -> - reporter.reportVerbose { " Deleting file '${file.path}'" } - file.delete() - if (file.exists()) throw IOException("Could not delete file '${file.path}'") } } @@ -502,7 +494,12 @@ abstract class IncrementalCompilerRunner< BuildDiffsStorage.writeToFile(buildHistoryFile, BuildDiffsStorage(prevDiffs + newDiff), reporter) } - protected open fun performWorkAfterCompilation(caches: CacheManager) {} + /** + * Performs some work after a compilation if the compilation completed successfully. + * + * This method MUST NOT be called when the compilation failed because the results produced by the work here would be incorrect. + */ + protected open fun performWorkAfterSuccessfulCompilation(caches: CacheManager) {} companion object { const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt" 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 074f4500711..baedbdf7d24 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 @@ -129,7 +129,7 @@ class IncrementalJvmCompilerRunner( workingDir, "caches-jvm", reporter, - outputFiles = outputFiles, + additionalOutputFiles = outputFiles, buildHistoryFile = buildHistoryFile ) { override fun isICEnabled(): Boolean = @@ -454,7 +454,7 @@ class IncrementalJvmCompilerRunner( return exitCode } - override fun performWorkAfterCompilation(caches: IncrementalJvmCachesManager) { + override fun performWorkAfterSuccessfulCompilation(caches: IncrementalJvmCachesManager) { if (classpathChanges is ClasspathChanges.ClasspathSnapshotEnabled) { reporter.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) { shrinkAndSaveClasspathSnapshot(classpathChanges, caches.lookupCache) 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 8b81bb84c0f..5f6be178f96 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 @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.compilerRunner -import org.gradle.api.file.FileCollection import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector import org.jetbrains.kotlin.gradle.report.ReportingSettings @@ -15,7 +14,7 @@ internal class GradleCompilerEnvironment( val compilerClasspath: Iterable, messageCollector: GradlePrintingMessageCollector, outputItemsCollector: OutputItemsCollector, - val outputFiles: FileCollection, + val outputFiles: List, val reportingSettings: ReportingSettings, val incrementalCompilationEnvironment: IncrementalCompilationEnvironment? = null, val kotlinScriptExtensions: Array = emptyArray() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerRunnerWithWorkers.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerRunnerWithWorkers.kt index 65466a08eb9..b4ef11297c3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerRunnerWithWorkers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerRunnerWithWorkers.kt @@ -6,8 +6,11 @@ package org.jetbrains.kotlin.compilerRunner import org.gradle.api.GradleException -import org.gradle.api.file.* +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations import org.gradle.api.logging.Logging +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters @@ -63,7 +66,8 @@ internal class GradleCompilerRunnerWithWorkers( fileSystemOperations, parameters.buildDir, parameters.snapshotsDir, - parameters.taskOutputs, + parameters.taskOutputs.files.toList(), + emptyList(), logger ) } else { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt index 5864afb1e6e..99141549193 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.gradle.report.TaskExecutionInfo import org.jetbrains.kotlin.gradle.report.TaskExecutionProperties.ABI_SNAPSHOT import org.jetbrains.kotlin.gradle.report.TaskExecutionResult import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy -import org.jetbrains.kotlin.gradle.tasks.clearLocalState +import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError import org.jetbrains.kotlin.gradle.utils.stackTraceAsString import org.jetbrains.kotlin.incremental.ChangedFiles @@ -313,7 +313,7 @@ internal class GradleKotlinCompilerWork @Inject constructor( private fun compileOutOfProcess(): ExitCode { metrics.addAttribute(BuildAttribute.OUT_OF_PROCESS_EXECUTION) - clearLocalState(outputFiles, log, metrics, reason = "out-of-process execution strategy is non-incremental") + cleanOutputsAndLocalState(outputFiles, log, metrics, reason = "out-of-process execution strategy is non-incremental") return metrics.measure(BuildTime.NON_INCREMENTAL_COMPILATION_OUT_OF_PROCESS) { runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log, buildDir) @@ -322,7 +322,7 @@ internal class GradleKotlinCompilerWork @Inject constructor( private fun compileInProcess(messageCollector: MessageCollector): ExitCode { metrics.addAttribute(BuildAttribute.IN_PROCESS_EXECUTION) - clearLocalState(outputFiles, log, metrics, reason = "in-process execution strategy is non-incremental") + cleanOutputsAndLocalState(outputFiles, log, metrics, reason = "in-process execution strategy is non-incremental") metrics.startMeasure(BuildTime.NON_INCREMENTAL_COMPILATION_IN_PROCESS, System.nanoTime()) // in-process compiler should always be run in a different thread diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt index 2f74da58b00..4c257f50321 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt @@ -21,13 +21,7 @@ import org.jetbrains.kotlin.gradle.internal.kapt.incremental.UnknownSnapshot import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.tasks.* -import org.jetbrains.kotlin.gradle.tasks.cacheOnlyIfEnabledForKotlin -import org.jetbrains.kotlin.gradle.tasks.clearLocalState import org.jetbrains.kotlin.gradle.utils.* -import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable -import org.jetbrains.kotlin.gradle.utils.property -import org.jetbrains.kotlin.gradle.utils.propertyWithConvention -import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance import org.jetbrains.kotlin.utils.addToStdlib.cast import java.io.File import java.util.concurrent.Callable @@ -251,7 +245,7 @@ abstract class KaptTask @Inject constructor( return if (isIncremental) { findClasspathChanges(inputChanges) } else { - clearLocalState() + cleanOutputsAndLocalState() KaptIncrementalChanges.Unknown } } @@ -286,7 +280,7 @@ abstract class KaptTask @Inject constructor( val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) if (classpathChanges == KaptClasspathChanges.Unknown) { // We are unable to determine classpath changes, so clean the local state as we will run non-incrementally - clearLocalState() + cleanOutputsAndLocalState() } currentSnapshot.writeToCache() @@ -311,7 +305,7 @@ abstract class KaptTask @Inject constructor( ) } } else { - clearLocalState("Kapt is running non-incrementally") + cleanOutputsAndLocalState("Kapt is running non-incrementally") ClasspathSnapshot.ClasspathSnapshotFactory .createCurrent( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/tasks/TaskWithLocalState.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/tasks/TaskWithLocalState.kt index c39485d5950..df1d059f3d2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/tasks/TaskWithLocalState.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/tasks/TaskWithLocalState.kt @@ -7,10 +7,10 @@ package org.jetbrains.kotlin.gradle.internal.tasks import org.gradle.api.Task import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.FileCollection import org.gradle.api.provider.Property import org.gradle.api.tasks.Internal import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter +import java.io.File internal interface TaskWithLocalState : Task { @get:Internal @@ -20,5 +20,5 @@ internal interface TaskWithLocalState : Task { val metrics: Property } -internal fun TaskWithLocalState.allOutputFiles(): FileCollection = - outputs.files + localStateDirectories +internal fun TaskWithLocalState.allOutputFiles(): List = + (outputs.files.files + localStateDirectories.files).toList() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt index 6a5e705b1f1..8d4ab219e40 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt @@ -348,7 +348,7 @@ internal class CacheBuilder( computedCompilerClasspath, messageCollector, outputItemCollector, - outputFiles = objectFiles, + outputFiles = objectFiles.files.toList(), reportingSettings = reportingSettings ) 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 301c6a5c996..14ce845d132 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 @@ -60,7 +60,6 @@ import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnable import org.jetbrains.kotlin.incremental.ClasspathSnapshotFiles import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner import org.jetbrains.kotlin.library.impl.isKotlinLibrary -import org.jetbrains.kotlin.statistics.BuildSessionLogger import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.utils.JsLibraryUtils import org.jetbrains.kotlin.utils.addToStdlib.cast @@ -333,6 +332,10 @@ abstract class AbstractKotlinCompile : AbstractKotl private val systemPropertiesService = CompilerSystemPropertiesService.registerIfAbsent(project.gradle) + /** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup]'s kdoc for more info). */ + @get:Internal + protected open val taskOutputsBackupExcludes: List = emptyList() + @TaskAction fun execute(inputChanges: InputChanges) { KotlinBuildStatsService.applyIfInitialised { @@ -357,6 +360,7 @@ abstract class AbstractKotlinCompile : AbstractKotl layout.buildDirectory, layout.buildDirectory.dir("snapshot/kotlin/$name"), allOutputFiles(), + taskOutputsBackupExcludes, logger ).also { it.createSnapshot() @@ -365,9 +369,9 @@ abstract class AbstractKotlinCompile : AbstractKotl else null if (!isIncrementalCompilationEnabled()) { - clearLocalState("IC is disabled") + cleanOutputsAndLocalState("IC is disabled") } else if (!inputChanges.isIncremental) { - clearLocalState("Task cannot run incrementally") + cleanOutputsAndLocalState("Task cannot run incrementally") } executeImpl(inputChanges, outputsBackup) @@ -648,6 +652,13 @@ abstract class KotlinCompile @Inject constructor( abstract val classpathSnapshotDir: DirectoryProperty } + override val incrementalProps: List + get() = listOf(stableSources, commonSourceSet, classpathSnapshotProperties.classpath, classpathSnapshotProperties.classpathSnapshot) + + // Exclude classpathSnapshotDir from TaskOutputsBackup (see TaskOutputsBackup's kdoc for more info). */ + override val taskOutputsBackupExcludes: List + get() = classpathSnapshotProperties.classpathSnapshotDir.orNull?.asFile?.let { listOf(it) } ?: emptyList() + @get:Internal internal val defaultKotlinJavaToolchain: Provider = objects .propertyWithNewInstance( @@ -712,9 +723,6 @@ abstract class KotlinCompile @Inject constructor( KotlinJvmCompilerArgumentsContributor(KotlinJvmCompilerArgumentsProvider(this)) } - override val incrementalProps: List - get() = listOf(stableSources, commonSourceSet, classpathSnapshotProperties.classpath, classpathSnapshotProperties.classpathSnapshot) - override fun getSourceRoots(): SourceRoots.ForJvm = jvmSourceRoots override fun validateCompilerArguments(args: K2JVMCompilerArguments) { @@ -745,9 +753,14 @@ abstract class KotlinCompile @Inject constructor( ) } else null + @Suppress("ConvertArgumentToSet") val environment = GradleCompilerEnvironment( defaultCompilerClasspath, messageCollector, outputItemCollector, - outputFiles = allOutputFiles(), + // In the incremental compiler, outputFiles will be cleaned on rebuild. However, because classpathSnapshotDir is not included in + // TaskOutputsBackup, we don't want classpathSnapshotDir to be cleaned immediately on rebuild, and therefore we exclude it from + // outputFiles here. (See TaskOutputsBackup's kdoc for more info.) + outputFiles = allOutputFiles() + - (classpathSnapshotProperties.classpathSnapshotDir.orNull?.asFile?.let { setOf(it) } ?: emptySet()), reportingSettings = reportingSettings, incrementalCompilationEnvironment = icEnv, kotlinScriptExtensions = sourceFilesExtensions.get().toTypedArray() @@ -1149,4 +1162,3 @@ data class KotlinCompilerPluginData( val outputFiles: Set ) } - diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksOutputsBackup.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksOutputsBackup.kt index 7f4b7676b3b..89b41c32601 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksOutputsBackup.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksOutputsBackup.kt @@ -20,13 +20,29 @@ internal class TaskOutputsBackup( val fileSystemOperations: FileSystemOperations, val buildDirectory: DirectoryProperty, val snapshotsDir: Provider, - val outputs: FileCollection, + + allOutputs: List, + + /** + * Task outputs that we don't want to back up for performance reasons (e.g., if (1) they are too big, and (2) they are usually updated + * only at the end of the task execution--in a failed task run, they are usually unchanged and therefore don't need to be restored). + * + * NOTE: In `IncrementalCompilerRunner`, if incremental compilation fails, it will try again by cleaning all the outputs and perform + * non-incremental compilation. It is important that `IncrementalCompilerRunner` do not clean [outputsToExclude] immediately but only + * right before [outputsToExclude] are updated (which is usually at the end of the task execution). This is so that if the fallback + * compilation fails, [outputsToExclude] will remain unchanged and the other outputs will be restored, and the next task run can be + * incremental. + */ + outputsToExclude: List = emptyList(), val logger: Logger ) { + /** The outputs to back up and restore. Note that this may be a subset of all the outputs of a task (see `outputsToExclude`). */ + val outputs: List = allOutputs - outputsToExclude.toSet() + fun createSnapshot() { // Kotlin JS compilation task declares one file from 'destinationDirectory' output as task `@OutputFile' // property. To avoid snapshot sync collisions, each snapshot output directory has also 'index' as prefix. - outputs.files.toSortedSet().forEachIndexed { index, outputPath -> + outputs.toSortedSet().forEachIndexed { index, outputPath -> val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}" if (outputPath.isDirectory) { snapshotsDir @@ -53,7 +69,7 @@ internal class TaskOutputsBackup( it.delete(outputs) } - outputs.files.toSortedSet().forEachIndexed { index, outputPath -> + outputs.toSortedSet().forEachIndexed { index, outputPath -> val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}" val fileInSnapshot = snapshotsDir.get().file(pathInSnapshot).asFile if (fileInSnapshot.isDirectory) { 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 ad3a895b58b..d202141f6e3 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 @@ -6,10 +6,12 @@ import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.build.report.metrics.measure import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.compilerRunner.KotlinLogger -import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles +import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.logging.kotlinDebug +import org.jetbrains.kotlin.incremental.cleanDirectoryContents +import org.jetbrains.kotlin.incremental.forceDeleteRecursively import java.io.File fun throwGradleExceptionIfError(exitCode: ExitCode) { @@ -23,12 +25,12 @@ fun throwGradleExceptionIfError(exitCode: ExitCode) { } } -internal fun TaskWithLocalState.clearLocalState(reason: String? = null) { +internal fun TaskWithLocalState.cleanOutputsAndLocalState(reason: String? = null) { val log = GradleKotlinLogger(logger) - clearLocalState(allOutputFiles(), log, metrics.get(), reason) + cleanOutputsAndLocalState(allOutputFiles(), log, metrics.get(), reason) } -internal fun clearLocalState( +internal fun cleanOutputsAndLocalState( outputFiles: Iterable, log: KotlinLogger, metrics: BuildMetricsReporter, @@ -36,21 +38,19 @@ internal fun clearLocalState( ) { log.kotlinDebug { val suffix = reason?.let { " ($it)" }.orEmpty() - "Clearing output$suffix:" + "Cleaning output$suffix:" } metrics.measure(BuildTime.CLEAR_OUTPUT) { for (file in outputFiles) { - if (!file.exists()) continue when { file.isDirectory -> { - log.debug("Deleting output directory: $file") - file.deleteRecursively() - file.mkdirs() + log.debug("Deleting contents of output directory: $file") + file.cleanDirectoryContents() } file.isFile -> { log.debug("Deleting output file: $file") - file.delete() + file.forceDeleteRecursively() } } }