[IC] Fix fallback logic in IncrementalCompilerRunner
The current logic works as follows: - Try either incremental compilation or non-incremental compilation - If the above (or any of its surrounding work) fails, fall back to non-incremental compilation This means we may perform non-incremental compilation twice. This commit will fix that logic so that we fall back to non-incremental compilation only if *incremental compilation* fails. A nice consequence of this change is that it also resolves the critical bugs described at KT-52669 (which occur because the current logic is flawed). #KT-52669 Fixed
This commit is contained in:
@@ -18,8 +18,9 @@ enum class BuildAttributeKind : Serializable {
|
|||||||
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
|
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
|
||||||
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
|
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
|
||||||
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
|
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
|
||||||
|
INTERNAL_ERROR(BuildAttributeKind.REBUILD_REASON, "Internal error during preparation of IC round"),
|
||||||
CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"),
|
CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"),
|
||||||
CACHE_CORRUPTION(BuildAttributeKind.REBUILD_REASON, "Cache corrupted"),
|
INCREMENTAL_COMPILATION_FAILED(BuildAttributeKind.REBUILD_REASON, "Incremental compilation failed"),
|
||||||
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON, "Unknown Gradle changes"),
|
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON, "Unknown Gradle changes"),
|
||||||
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON, "Untracked Java file is removed"),
|
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON, "Untracked Java file is removed"),
|
||||||
JAVA_CHANGE_UNEXPECTED_PSI(BuildAttributeKind.REBUILD_REASON, "Java PSI file is expected"),
|
JAVA_CHANGE_UNEXPECTED_PSI(BuildAttributeKind.REBUILD_REASON, "Java PSI file is expected"),
|
||||||
|
|||||||
+6
-5
@@ -34,6 +34,7 @@ abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache
|
|||||||
private val caches = arrayListOf<BasicMapsOwner>()
|
private val caches = arrayListOf<BasicMapsOwner>()
|
||||||
|
|
||||||
var isClosed = false
|
var isClosed = false
|
||||||
|
var isSuccessfulyClosed = false
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
protected fun <T : BasicMapsOwner> T.registerCache() {
|
protected fun <T : BasicMapsOwner> T.registerCache() {
|
||||||
@@ -52,15 +53,15 @@ abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun close(flush: Boolean = false): Boolean {
|
fun close(flush: Boolean = false): Boolean {
|
||||||
if (isClosed) {
|
if (isClosed) {
|
||||||
return true
|
return isSuccessfulyClosed
|
||||||
}
|
}
|
||||||
var successful = true
|
isSuccessfulyClosed = true
|
||||||
for (cache in caches) {
|
for (cache in caches) {
|
||||||
if (flush) {
|
if (flush) {
|
||||||
try {
|
try {
|
||||||
cache.flush(false)
|
cache.flush(false)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
successful = false
|
isSuccessfulyClosed = false
|
||||||
reporter.report { "Exception when flushing cache ${cache.javaClass}: $e" }
|
reporter.report { "Exception when flushing cache ${cache.javaClass}: $e" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,13 +69,13 @@ abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache
|
|||||||
try {
|
try {
|
||||||
cache.close()
|
cache.close()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
successful = false
|
isSuccessfulyClosed = false
|
||||||
reporter.report { "Exception when closing cache ${cache.javaClass}: $e" }
|
reporter.report { "Exception when closing cache ${cache.javaClass}: $e" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isClosed = true
|
isClosed = true
|
||||||
return successful
|
return isSuccessfulyClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+85
-82
@@ -71,7 +71,53 @@ abstract class IncrementalCompilerRunner<
|
|||||||
providedChangedFiles: ChangedFiles?,
|
providedChangedFiles: ChangedFiles?,
|
||||||
projectDir: File? = null
|
projectDir: File? = null
|
||||||
): ExitCode = reporter.measure(BuildTime.INCREMENTAL_COMPILATION_DAEMON) {
|
): ExitCode = reporter.measure(BuildTime.INCREMENTAL_COMPILATION_DAEMON) {
|
||||||
compileImpl(allSourceFiles, args, messageCollector, providedChangedFiles, projectDir)
|
try {
|
||||||
|
compileImpl(allSourceFiles, args, messageCollector, providedChangedFiles, projectDir)
|
||||||
|
} finally {
|
||||||
|
reporter.measure(BuildTime.CALCULATE_OUTPUT_SIZE) {
|
||||||
|
reporter.addMetric(
|
||||||
|
BuildPerformanceMetric.SNAPSHOT_SIZE,
|
||||||
|
buildHistoryFile.length() + lastBuildInfoFile.length() + abiSnapshotFile.length()
|
||||||
|
)
|
||||||
|
if (cacheDirectory.exists() && cacheDirectory.isDirectory()) {
|
||||||
|
cacheDirectory.walkTopDown().filter { it.isFile }.map { it.length() }.sum().let {
|
||||||
|
reporter.addMetric(BuildPerformanceMetric.CACHE_DIRECTORY_SIZE, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rebuild(
|
||||||
|
reason: BuildAttribute,
|
||||||
|
allSourceFiles: List<File>,
|
||||||
|
args: Args,
|
||||||
|
messageCollector: MessageCollector,
|
||||||
|
providedChangedFiles: ChangedFiles?,
|
||||||
|
projectDir: File? = null,
|
||||||
|
classpathAbiSnapshot: Map<String, AbiSnapshot>
|
||||||
|
): ExitCode {
|
||||||
|
reporter.report { "Non-incremental compilation will be performed: $reason" }
|
||||||
|
reporter.measure(BuildTime.CLEAR_OUTPUT_ON_REBUILD) {
|
||||||
|
cleanOutputsAndLocalStateOnRebuild(args)
|
||||||
|
}
|
||||||
|
val caches = createCacheManager(args, projectDir)
|
||||||
|
try {
|
||||||
|
if (providedChangedFiles == null) {
|
||||||
|
caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
||||||
|
}
|
||||||
|
val allKotlinFiles = allSourceFiles.filter { it.isKotlinFile(kotlinSourceFilesExtensions) }
|
||||||
|
return compileIncrementally(
|
||||||
|
args, caches, allKotlinFiles, CompilationMode.Rebuild(reason), messageCollector, withAbiSnapshot,
|
||||||
|
classpathAbiSnapshot = classpathAbiSnapshot
|
||||||
|
).also {
|
||||||
|
if (it == ExitCode.OK) {
|
||||||
|
performWorkAfterSuccessfulCompilation(caches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
caches.close(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileImpl(
|
private fun compileImpl(
|
||||||
@@ -82,13 +128,11 @@ abstract class IncrementalCompilerRunner<
|
|||||||
projectDir: File? = null
|
projectDir: File? = null
|
||||||
): ExitCode {
|
): ExitCode {
|
||||||
var caches = createCacheManager(args, projectDir)
|
var caches = createCacheManager(args, projectDir)
|
||||||
|
var rebuildReason = BuildAttribute.INTERNAL_ERROR
|
||||||
|
|
||||||
if (withAbiSnapshot) {
|
|
||||||
reporter.report { "Incremental compilation with ABI snapshot enabled" }
|
|
||||||
}
|
|
||||||
//TODO if abi-snapshot is corrupted unable to rebuild. Should roll back to withSnapshot = false?
|
|
||||||
val classpathAbiSnapshot =
|
val classpathAbiSnapshot =
|
||||||
if (withAbiSnapshot) {
|
if (withAbiSnapshot) {
|
||||||
|
reporter.report { "Incremental compilation with ABI snapshot enabled" }
|
||||||
reporter.measure(BuildTime.SET_UP_ABI_SNAPSHOTS) {
|
reporter.measure(BuildTime.SET_UP_ABI_SNAPSHOTS) {
|
||||||
setupJarDependencies(args, withAbiSnapshot, reporter)
|
setupJarDependencies(args, withAbiSnapshot, reporter)
|
||||||
}
|
}
|
||||||
@@ -96,27 +140,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
emptyMap()
|
emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rebuild(reason: BuildAttribute): ExitCode {
|
try {
|
||||||
reporter.report { "Non-incremental compilation will be performed: $reason" }
|
|
||||||
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) {
|
|
||||||
cleanOutputsAndLocalStateOnRebuild(args)
|
|
||||||
}
|
|
||||||
caches = createCacheManager(args, projectDir)
|
|
||||||
if (providedChangedFiles == null) {
|
|
||||||
caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
|
||||||
}
|
|
||||||
val allKotlinFiles = allSourceFiles.filter { it.isKotlinFile(kotlinSourceFilesExtensions) }
|
|
||||||
return compileIncrementally(
|
|
||||||
args, caches, allKotlinFiles, CompilationMode.Rebuild(reason), messageCollector, withAbiSnapshot,
|
|
||||||
classpathAbiSnapshot = classpathAbiSnapshot
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If compilation has crashed or we failed to close caches we have to clear them
|
|
||||||
var cachesMayBeCorrupted = true
|
|
||||||
return try {
|
|
||||||
val changedFiles = when (providedChangedFiles) {
|
val changedFiles = when (providedChangedFiles) {
|
||||||
is ChangedFiles.Dependencies -> {
|
is ChangedFiles.Dependencies -> {
|
||||||
val changedSources = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
val changedSources = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
||||||
@@ -129,75 +153,51 @@ abstract class IncrementalCompilerRunner<
|
|||||||
else -> providedChangedFiles
|
else -> providedChangedFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MoveVariableDeclarationIntoWhen")
|
var compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
|
||||||
val compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
|
val abiSnapshot = if (compilationMode is CompilationMode.Incremental && withAbiSnapshot) {
|
||||||
|
AbiSnapshotImpl.read(abiSnapshotFile, reporter)
|
||||||
|
} else {
|
||||||
|
if (withAbiSnapshot) {
|
||||||
|
compilationMode = CompilationMode.Rebuild(BuildAttribute.NO_ABI_SNAPSHOT)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val exitCode = when (compilationMode) {
|
when (compilationMode) {
|
||||||
is CompilationMode.Incremental -> {
|
is CompilationMode.Incremental -> {
|
||||||
if (withAbiSnapshot) {
|
try {
|
||||||
val abiSnapshot = AbiSnapshotImpl.read(abiSnapshotFile, reporter)
|
val exitCode = if (withAbiSnapshot) {
|
||||||
if (abiSnapshot != null) {
|
|
||||||
compileIncrementally(
|
compileIncrementally(
|
||||||
args,
|
args, caches, allSourceFiles, compilationMode, messageCollector,
|
||||||
caches,
|
withAbiSnapshot, abiSnapshot!!, classpathAbiSnapshot
|
||||||
allSourceFiles,
|
|
||||||
compilationMode,
|
|
||||||
messageCollector,
|
|
||||||
withAbiSnapshot,
|
|
||||||
abiSnapshot,
|
|
||||||
classpathAbiSnapshot
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
rebuild(BuildAttribute.NO_ABI_SNAPSHOT)
|
compileIncrementally(args, caches, allSourceFiles, compilationMode, messageCollector, withAbiSnapshot)
|
||||||
}
|
}
|
||||||
} else {
|
if (exitCode == ExitCode.OK) {
|
||||||
compileIncrementally(
|
performWorkAfterSuccessfulCompilation(caches)
|
||||||
args,
|
}
|
||||||
caches,
|
return exitCode
|
||||||
allSourceFiles,
|
} catch (e: Throwable) {
|
||||||
compilationMode,
|
reporter.report {
|
||||||
messageCollector,
|
"Incremental compilation failed: ${e.stackTraceToString()}.\nFalling back to non-incremental compilation."
|
||||||
withAbiSnapshot
|
}
|
||||||
)
|
rebuildReason = BuildAttribute.INCREMENTAL_COMPILATION_FAILED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CompilationMode.Rebuild -> {
|
is CompilationMode.Rebuild -> rebuildReason = compilationMode.reason
|
||||||
rebuild(compilationMode.reason)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
if (exitCode == ExitCode.OK) {
|
reporter.report {
|
||||||
performWorkAfterSuccessfulCompilation(caches)
|
"Incremental compilation analysis failed: ${e.stackTraceToString()}.\nFalling back to non-incremental compilation."
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
// but now JsKlib compiler reports invalid exit code.
|
|
||||||
cachesMayBeCorrupted = false
|
|
||||||
|
|
||||||
reporter.measure(BuildTime.CALCULATE_OUTPUT_SIZE) {
|
|
||||||
reporter.addMetric(
|
|
||||||
BuildPerformanceMetric.SNAPSHOT_SIZE,
|
|
||||||
buildHistoryFile.length() + lastBuildInfoFile.length() + abiSnapshotFile.length()
|
|
||||||
)
|
|
||||||
if (cacheDirectory.exists() && cacheDirectory.isDirectory()) {
|
|
||||||
cacheDirectory.walkTopDown().filter { it.isFile }.map { it.length() }.sum().let {
|
|
||||||
reporter.addMetric(BuildPerformanceMetric.CACHE_DIRECTORY_SIZE, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exitCode
|
|
||||||
} catch (e: Exception) { // todo: catch only cache corruption
|
|
||||||
// todo: warn?
|
|
||||||
reporter.report { "Possible caches corruption: $e" }
|
|
||||||
rebuild(BuildAttribute.CACHE_CORRUPTION).also {
|
|
||||||
cachesMayBeCorrupted = false
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (cachesMayBeCorrupted) {
|
if (!caches.close()) {
|
||||||
|
reporter.report { "Unable to close IC caches. Cleaning internal state" }
|
||||||
cleanOutputsAndLocalStateOnRebuild(args)
|
cleanOutputsAndLocalStateOnRebuild(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return rebuild(rebuildReason, allSourceFiles, args, messageCollector, providedChangedFiles, projectDir, classpathAbiSnapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -411,7 +411,10 @@ abstract class IncrementalCompilerRunner<
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val (dirtyLookupSymbols, dirtyClassFqNames, forceRecompile) = changesCollector.getDirtyData(listOf(caches.platformCache), reporter)
|
val (dirtyLookupSymbols, dirtyClassFqNames, forceRecompile) = changesCollector.getDirtyData(
|
||||||
|
listOf(caches.platformCache),
|
||||||
|
reporter
|
||||||
|
)
|
||||||
val compiledInThisIterationSet = sourcesToCompile.toHashSet()
|
val compiledInThisIterationSet = sourcesToCompile.toHashSet()
|
||||||
|
|
||||||
val forceToRecompileFiles = mapClassesFqNamesToFiles(listOf(caches.platformCache), forceRecompile, reporter)
|
val forceToRecompileFiles = mapClassesFqNamesToFiles(listOf(caches.platformCache), forceRecompile, reporter)
|
||||||
|
|||||||
+1
-1
@@ -700,7 +700,7 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
|
|||||||
}
|
}
|
||||||
|
|
||||||
build("assemble") {
|
build("assemble") {
|
||||||
assertOutputContains("Non-incremental compilation will be performed: CACHE_CORRUPTION")
|
assertOutputContains("Non-incremental compilation will be performed: INCREMENTAL_COMPILATION_FAILED")
|
||||||
}
|
}
|
||||||
|
|
||||||
val lookupFile = projectPath.resolve("lib/build/kotlin/${compileKotlinTaskName}/cacheable/${compileCacheFolderName}/lookups/file-to-id.tab")
|
val lookupFile = projectPath.resolve("lib/build/kotlin/${compileKotlinTaskName}/cacheable/${compileCacheFolderName}/lookups/file-to-id.tab")
|
||||||
|
|||||||
Reference in New Issue
Block a user