Adding cashes versios handling, refactoring sources to compile computing, minor output refactorings
KT-8487
This commit is contained in:
committed by
Alexey Tsvetkov
parent
db3b6ff10b
commit
9498235407
+119
-46
@@ -216,7 +216,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
logger.kotlinDebug("args.moduleName = ${args.moduleName}")
|
logger.kotlinDebug("args.moduleName = ${args.moduleName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun callCompiler(args: K2JVMCompilerArguments, sources: List<File>, isIncremental: Boolean, modified: List<File>, removed: List<File>, cachesBaseDir: File) {
|
override fun callCompiler(args: K2JVMCompilerArguments, sources: List<File>, isIncrementalRequested: Boolean, modified: List<File>, removed: List<File>, cachesBaseDir: File) {
|
||||||
|
|
||||||
// TODO: consider other ways to pass incremental flag to compiler/builder
|
// TODO: consider other ways to pass incremental flag to compiler/builder
|
||||||
System.setProperty("kotlin.incremental.compilation", "true")
|
System.setProperty("kotlin.incremental.compilation", "true")
|
||||||
@@ -232,6 +232,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
val lookupTracker = LookupTrackerImpl(LookupTracker.DO_NOTHING)
|
val lookupTracker = LookupTrackerImpl(LookupTracker.DO_NOTHING)
|
||||||
var currentRemoved = removed
|
var currentRemoved = removed
|
||||||
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
|
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
|
||||||
|
val compiledSourcesSet = hashSetOf<File>()
|
||||||
|
|
||||||
fun getOrCreateIncrementalCache(target: TargetId): IncrementalCacheImpl<TargetId> {
|
fun getOrCreateIncrementalCache(target: TargetId): IncrementalCacheImpl<TargetId> {
|
||||||
val cacheDir = File(cachesBaseDir, "increCache.${target.name}")
|
val cacheDir = File(cachesBaseDir, "increCache.${target.name}")
|
||||||
@@ -239,6 +240,8 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
return IncrementalCacheImpl(targetDataRoot = cacheDir, targetOutputDir = outputDir, target = target)
|
return IncrementalCacheImpl(targetDataRoot = cacheDir, targetOutputDir = outputDir, target = target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIncrementalCache(it: TargetId) = caches.getOrPut(it, { getOrCreateIncrementalCache(it) })
|
||||||
|
|
||||||
fun PsiClass.findLookupSymbols(): Iterable<LookupSymbol> {
|
fun PsiClass.findLookupSymbols(): Iterable<LookupSymbol> {
|
||||||
val fqn = qualifiedName.orEmpty()
|
val fqn = qualifiedName.orEmpty()
|
||||||
return listOf(LookupSymbol(name.orEmpty(), if (fqn == name) "" else fqn.removeSuffix("." + name!!))) +
|
return listOf(LookupSymbol(name.orEmpty(), if (fqn == name) "" else fqn.removeSuffix("." + name!!))) +
|
||||||
@@ -247,7 +250,18 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
innerClasses.flatMap { it.findLookupSymbols() }
|
innerClasses.flatMap { it.findLookupSymbols() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Iterable<LookupSymbol>.files(filesFilter: (File) -> Boolean = { true }, logAction: (LookupSymbol, Iterable<File>) -> Unit = { l,fs -> }): Iterable<File> =
|
||||||
|
flatMap { lookup ->
|
||||||
|
val files = lookupStorage.get(lookup).map(::File).filter(filesFilter)
|
||||||
|
if (files.any()) {
|
||||||
|
logAction(lookup, files)
|
||||||
|
}
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
fun dirtyKotlinSourcesFromGradle(): List<File> {
|
fun dirtyKotlinSourcesFromGradle(): List<File> {
|
||||||
|
// TODO: take into account deleted files (take their symbols from caches
|
||||||
|
// TODO: handle classpath changes similarly - compare with cashed version (likely a big change, may be costly, some heuristics could be considered)
|
||||||
val kotlinFiles = modified.filter { it.isKotlinFile() }
|
val kotlinFiles = modified.filter { it.isKotlinFile() }
|
||||||
val javaFiles = modified.filter { it.isJavaFile() }
|
val javaFiles = modified.filter { it.isJavaFile() }
|
||||||
if (javaFiles.any()) {
|
if (javaFiles.any()) {
|
||||||
@@ -262,9 +276,15 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
javaFile.classes.flatMap { it.findLookupSymbols() }
|
javaFile.classes.flatMap { it.findLookupSymbols() }
|
||||||
else listOf()
|
else listOf()
|
||||||
}
|
}
|
||||||
logger.kotlinDebug("changed java symbols: ${lookupSymbols.joinToString { "${it.name}:${it.scope}" }}")
|
// logger.kotlinDebug("changed java symbols: ${lookupSymbols.joinToString { "${it.name}:${it.scope}" }}")
|
||||||
if (lookupSymbols.any()) {
|
if (lookupSymbols.any()) {
|
||||||
return kotlinFiles + lookupSymbols.flatMap { lookupStorage.get(it) }.map { File(it) }
|
val kotlinFilesSet = kotlinFiles.toHashSet()
|
||||||
|
return kotlinFiles +
|
||||||
|
lookupSymbols.files(
|
||||||
|
filesFilter = { it !in kotlinFilesSet },
|
||||||
|
logAction = { lookup, files ->
|
||||||
|
logger.kotlinInfo("changes in ${lookup.name} (${lookup.scope}) causes recompilation of ${files.joinToString { projectRelativePath(it) }}")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return kotlinFiles
|
return kotlinFiles
|
||||||
@@ -274,11 +294,57 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
// TODO: that doesn't look to wise - join it first and then split here, consider storing it somewhere in between
|
// TODO: that doesn't look to wise - join it first and then split here, consider storing it somewhere in between
|
||||||
val classpath = args.classpath.split(File.pathSeparator).map { File(it) }.toHashSet()
|
val classpath = args.classpath.split(File.pathSeparator).map { File(it) }.toHashSet()
|
||||||
val changedClasspath = modified.filter { classpath.contains(it) }
|
val changedClasspath = modified.filter { classpath.contains(it) }
|
||||||
if (changedClasspath.any()) {
|
return changedClasspath.any()
|
||||||
logger.kotlinDebug("recompiling project because of classpath changes: $changedClasspath")
|
}
|
||||||
return true
|
|
||||||
|
fun allCachesVersions() = allCachesVersions(cachesBaseDir, listOf(cachesBaseDir))
|
||||||
|
|
||||||
|
fun calculateSourcesToCompile(): Pair<List<File>, Boolean> {
|
||||||
|
|
||||||
|
// TODO: more precise will be not to rebuild unconditionally on classpath changes, but retrieve lookup info and try to find out which sources are affected by cp changes
|
||||||
|
if (!isIncrementalRequested || isClassPathChanged()) {
|
||||||
|
logger.kotlinInfo(if (!isIncrementalRequested) "clean caches on rebuild" else "classpath changed, rebuilding all kotlin files")
|
||||||
|
targets.forEach { getIncrementalCache(it).clean() }
|
||||||
|
lookupStorage.clean()
|
||||||
|
return Pair(sources, false)
|
||||||
}
|
}
|
||||||
return false
|
val actions = if (isIncrementalRequested) allCachesVersions().map { it.checkVersion() }
|
||||||
|
else listOf(CacheVersion.Action.REBUILD_ALL_KOTLIN)
|
||||||
|
// TODO: find out whether these flags should be emulated too
|
||||||
|
// val hasKotlin = HasKotlinMarker(dataManager)
|
||||||
|
// val rebuildAfterCacheVersionChanged = RebuildAfterCacheVersionChangeMarker(dataManager)
|
||||||
|
|
||||||
|
for (status in actions.distinct().sorted()) {
|
||||||
|
when (status) {
|
||||||
|
CacheVersion.Action.REBUILD_ALL_KOTLIN -> {
|
||||||
|
logger.kotlinInfo("Kotlin global lookup map format changed, rebuilding all kotlin files")
|
||||||
|
targets.forEach { getIncrementalCache(it).clean() }
|
||||||
|
lookupStorage.clean()
|
||||||
|
return Pair(sources, false)
|
||||||
|
}
|
||||||
|
CacheVersion.Action.REBUILD_CHUNK -> {
|
||||||
|
logger.kotlinInfo("Clearing caches for " + targets.joinToString { it.name })
|
||||||
|
targets.forEach { getIncrementalCache(it).clean() }
|
||||||
|
}
|
||||||
|
CacheVersion.Action.CLEAN_NORMAL_CACHES -> {
|
||||||
|
logger.kotlinInfo("Clearing caches for all targets")
|
||||||
|
targets.forEach { getIncrementalCache(it).clean() }
|
||||||
|
}
|
||||||
|
CacheVersion.Action.CLEAN_EXPERIMENTAL_CACHES -> {
|
||||||
|
logger.kotlinInfo("Clearing experimental caches for all targets")
|
||||||
|
targets.forEach { getIncrementalCache(it).cleanExperimental() }
|
||||||
|
}
|
||||||
|
CacheVersion.Action.CLEAN_DATA_CONTAINER -> {
|
||||||
|
logger.kotlinInfo("Clearing lookup cache")
|
||||||
|
lookupStorage.clean()
|
||||||
|
dataContainerCacheVersion(cachesBaseDir).clean()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
assert(status == CacheVersion.Action.DO_NOTHING) { "Unknown version status $status" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(dirtyKotlinSourcesFromGradle().distinct(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanupOnError() {
|
fun cleanupOnError() {
|
||||||
@@ -286,34 +352,32 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
|
|
||||||
assert(outputDirFile.exists())
|
assert(outputDirFile.exists())
|
||||||
val generatedRelPaths = allGeneratedFiles.map { it.outputFile.toRelativeString(outputDirFile) }
|
val generatedRelPaths = allGeneratedFiles.map { it.outputFile.toRelativeString(outputDirFile) }
|
||||||
logger.kotlinDebug("deleting output on error: ${generatedRelPaths.joinToString()}")
|
logger.kotlinInfo("deleting output on error: ${generatedRelPaths.joinToString()}")
|
||||||
|
|
||||||
allGeneratedFiles.forEach { it.outputFile.delete() }
|
allGeneratedFiles.forEach { it.outputFile.delete() }
|
||||||
generatedRelPaths.forEach { File(destinationDir, it).delete() }
|
generatedRelPaths.forEach { File(destinationDir, it).delete() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun outputRelativePath(f: File) = f.toRelativeString(outputDir)
|
fun outputRelativePath(f: File) = f.toRelativeString(outputDir)
|
||||||
fun outputRelativePath(p: String) = File(p).toRelativeString(outputDir)
|
|
||||||
|
|
||||||
// TODO: decide what to do if no files are considered dirty - rebuild or skip the module
|
// TODO: decide what to do if no files are considered dirty - rebuild or skip the module
|
||||||
// TODO: more precise will be not to rebuild unconditionally on classpath changes, but retrieve lookup info and try to find out which sources are affected by cp changes
|
var (sourcesToCompile, isIncrementalDecided) = calculateSourcesToCompile()
|
||||||
var sourcesToCompile =
|
|
||||||
if (isIncremental && !isClassPathChanged()) dirtyKotlinSourcesFromGradle().distinct()
|
|
||||||
else sources
|
|
||||||
|
|
||||||
if (isIncremental) {
|
if (isIncrementalDecided) {
|
||||||
|
// TODO: process as list here, merge into string later
|
||||||
args.classpath = args.classpath + File.pathSeparator + outputDir.absolutePath
|
args.classpath = args.classpath + File.pathSeparator + outputDir.absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sourcesToCompile.any()) {
|
while (sourcesToCompile.any()) {
|
||||||
logger.kotlinDebug("compile iteration: ${sourcesToCompile.joinToString(", ")}")
|
logger.kotlinInfo("compile iteration: ${sourcesToCompile.joinToString{ projectRelativePath(it) }}")
|
||||||
|
|
||||||
val (exitCode, generatedFiles) = compileChanged(
|
val (exitCode, generatedFiles) = compileChanged(
|
||||||
targets = targets,
|
targets = targets,
|
||||||
sourcesToCompile = sourcesToCompile,
|
sourcesToCompile = sourcesToCompile,
|
||||||
outputDir = outputDir,
|
outputDir = outputDir,
|
||||||
args = args,
|
args = args,
|
||||||
getIncrementalCache = { caches.getOrPut(it, { getOrCreateIncrementalCache(it) }) },
|
getIncrementalCache = ::getIncrementalCache,
|
||||||
lookupTracker = lookupTracker)
|
lookupTracker = lookupTracker)
|
||||||
|
|
||||||
allGeneratedFiles.addAll(generatedFiles)
|
allGeneratedFiles.addAll(generatedFiles)
|
||||||
@@ -325,12 +389,14 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
compiledWithErrors = exitCode != ExitCode.OK,
|
compiledWithErrors = exitCode != ExitCode.OK,
|
||||||
getIncrementalCache = { caches[it]!! })
|
getIncrementalCache = { caches[it]!! })
|
||||||
|
|
||||||
lookupTracker.lookups.entrySet().forEach {
|
// lookupTracker.lookups.entrySet().forEach {
|
||||||
logger.kotlinDebug("lookups to ${it.key.name}:${it.key.scope} from ${it.value.joinToString { projectRelativePath(it) }}")
|
// logger.kotlinDebug("lookups to ${it.key.name}:${it.key.scope} from ${it.value.joinToString { projectRelativePath(it) }}")
|
||||||
}
|
// }
|
||||||
|
|
||||||
lookupStorage.update(lookupTracker, sourcesToCompile, currentRemoved)
|
lookupStorage.update(lookupTracker, sourcesToCompile, currentRemoved)
|
||||||
|
|
||||||
|
allCachesVersions().forEach { it.saveIfNeeded() }
|
||||||
|
|
||||||
when (exitCode) {
|
when (exitCode) {
|
||||||
ExitCode.COMPILATION_ERROR -> {
|
ExitCode.COMPILATION_ERROR -> {
|
||||||
cleanupOnError()
|
cleanupOnError()
|
||||||
@@ -340,43 +406,46 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
cleanupOnError()
|
cleanupOnError()
|
||||||
throw GradleException("Internal compiler error. See log for more details")
|
throw GradleException("Internal compiler error. See log for more details")
|
||||||
}
|
}
|
||||||
|
ExitCode.SCRIPT_EXECUTION_ERROR -> {
|
||||||
|
cleanupOnError()
|
||||||
|
throw GradleException("Script execution error. See log for more details")
|
||||||
|
}
|
||||||
|
ExitCode.OK -> {
|
||||||
|
logger.kotlinInfo("Compilation succeeded")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.kotlinDebug("generated ${generatedFiles.joinToString { outputRelativePath(it.outputFile) }}")
|
if (!isIncrementalDecided) break;
|
||||||
logger.kotlinDebug("changes: ${changes.changes.joinToString { "${it.fqName}: ${it.javaClass.simpleName}" }}")
|
|
||||||
|
|
||||||
logger.kotlinLazyDebug({
|
// logger.kotlinDebug("generated ${generatedFiles.joinToString { outputRelativePath(it.outputFile) }}")
|
||||||
"known lookups:\n${lookupStorage.dump(changes.changes.flatMap {
|
// logger.kotlinDebug("changes: ${changes.changes.joinToString { "${it.fqName}: ${it.javaClass.simpleName}" }}")
|
||||||
change ->
|
//
|
||||||
if (change is ChangeInfo.MembersChanged)
|
// logger.kotlinLazyDebug({
|
||||||
change.names.asSequence().map { LookupSymbol(it, change.fqName.asString()) }
|
// "known lookups:\n${lookupStorage.dump(changes.changes.flatMap {
|
||||||
else
|
// change ->
|
||||||
sequenceOf<LookupSymbol>()
|
// if (change is ChangeInfo.MembersChanged)
|
||||||
}.toSet(), project.projectDir)}" })
|
// change.names.asSequence().map { LookupSymbol(it, change.fqName.asString()) }
|
||||||
|
// else
|
||||||
|
// sequenceOf<LookupSymbol>()
|
||||||
|
// }.toSet(), project.projectDir)}" })
|
||||||
|
|
||||||
if (!isIncremental) break;
|
compiledSourcesSet.addAll(sourcesToCompile)
|
||||||
|
|
||||||
// TODO: consider using some order-preserving set for sourcesToCompile instead
|
|
||||||
val compiledSourcesSet = sourcesToCompile.toHashSet()
|
|
||||||
|
|
||||||
val dirtyLookups = changes.dirtyLookups<TargetId>(caches.values.asSequence())
|
val dirtyLookups = changes.dirtyLookups<TargetId>(caches.values.asSequence())
|
||||||
|
|
||||||
logger.kotlinDebug("dirty lookups: ${dirtyLookups.joinToString { "${it.name}:${it.scope}" }}")
|
// logger.kotlinDebug("dirty lookups: ${dirtyLookups.joinToString { "${it.name}:${it.scope}" }}")
|
||||||
|
|
||||||
val dirty = dirtyLookups.flatMap { lookup ->
|
val dirty = dirtyLookups.files(
|
||||||
val files = lookupStorage.get(lookup).map(::File).filter { it !in compiledSourcesSet }
|
filesFilter = { it !in compiledSourcesSet },
|
||||||
if (files.any()) {
|
logAction = { lookup, files ->
|
||||||
logger.kotlinDebug("changes in $lookup causes recompilation of ${files.joinToString { projectRelativePath(it) }}")
|
logger.kotlinInfo("changes in ${lookup.name} (${lookup.scope}) causes recompilation of ${files.joinToString { projectRelativePath(it) }}")
|
||||||
}
|
})
|
||||||
files
|
|
||||||
}
|
|
||||||
//val dirty = changes.dirtyFiles(lookupStorage).filter { it !in compiledSourcesSet }
|
|
||||||
sourcesToCompile = dirty.filter { it in sources }.toList()
|
sourcesToCompile = dirty.filter { it in sources }.toList()
|
||||||
if (currentRemoved.any()) {
|
if (currentRemoved.any()) {
|
||||||
currentRemoved = listOf()
|
currentRemoved = listOf()
|
||||||
}
|
}
|
||||||
logger.kotlinDebug("dirty: ${dirty.joinToString { projectRelativePath(it) }}")
|
// logger.kotlinDebug("dirty: ${dirty.joinToString { projectRelativePath(it) }}")
|
||||||
logger.kotlinDebug("to compile: ${sourcesToCompile.joinToString { projectRelativePath(it) }}")
|
// logger.kotlinDebug("to compile: ${sourcesToCompile.joinToString { projectRelativePath(it) }}")
|
||||||
}
|
}
|
||||||
lookupStorage.flush(false)
|
lookupStorage.flush(false)
|
||||||
lookupStorage.close()
|
lookupStorage.close()
|
||||||
@@ -622,11 +691,15 @@ class GradleMessageCollector(val logger: Logger, val outputCollector: OutputItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Logger.kotlinDebug(message: String) {
|
internal fun Logger.kotlinInfo(message: String) {
|
||||||
|
this.info("[KOTLIN] $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Logger.kotlinDebug(message: String) {
|
||||||
this.debug("[KOTLIN] $message")
|
this.debug("[KOTLIN] $message")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Logger.kotlinLazyDebug(makeMessage: () -> String) {
|
internal fun Logger.kotlinLazyDebug(makeMessage: () -> String) {
|
||||||
if (this.isInfoEnabled)
|
if (this.isInfoEnabled)
|
||||||
kotlinDebug(makeMessage())
|
kotlinDebug(makeMessage())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user