Refactoring: extract parts of IC

This commit is contained in:
Alexey Tsvetkov
2016-10-03 16:11:47 +03:00
parent 26373d4952
commit e9d4d85af2
2 changed files with 101 additions and 79 deletions
@@ -1,11 +1,9 @@
package org.jetbrains.kotlin.gradle.tasks package org.jetbrains.kotlin.gradle.tasks
import org.apache.commons.io.FilenameUtils
import org.codehaus.groovy.runtime.MethodClosure import org.codehaus.groovy.runtime.MethodClosure
import org.gradle.api.GradleException import org.gradle.api.GradleException
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceTask import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.api.tasks.compile.AbstractCompile
@@ -24,7 +22,6 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
import org.jetbrains.kotlin.cli.js.K2JSCompiler import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.com.intellij.ide.highlighter.JavaFileType
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollector import org.jetbrains.kotlin.compilerRunner.OutputItemsCollector
@@ -103,10 +100,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
private fun getKotlinSources(): List<File> = (getSource() as Iterable<File>).filter { it.isKotlinFile() } private fun getKotlinSources(): List<File> = (getSource() as Iterable<File>).filter { it.isKotlinFile() }
protected fun File.isKotlinFile(): Boolean = protected abstract fun callCompiler(args: T, allKotlinSources: List<File>, isIncremental: Boolean, modified: List<File>, removed: List<File>)
FilenameUtils.isExtension(name.toLowerCase(), listOf("kt", "kts"))
protected abstract fun callCompiler(args: T, sources: List<File>, isIncremental: Boolean, modified: List<File>, removed: List<File>)
} }
@@ -181,18 +175,11 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
return args return args
} }
override fun callCompiler(args: K2JVMCompilerArguments, sources: List<File>, isIncrementalRequested: Boolean, modified: List<File>, removed: List<File>) { override fun callCompiler(args: K2JVMCompilerArguments, allKotlinSources: List<File>, isIncrementalRequested: Boolean, modified: List<File>, removed: List<File>) {
fun relativePathOrCanonical(file: File): String =
file.relativeToOrNull(project.rootProject.projectDir)?.path
?: file.canonicalPath
fun filesToString(files: Iterable<File>) =
"[" + files.map(::relativePathOrCanonical).sorted().joinToString(separator = ", \n") + "]"
val outputDir = destinationDir val outputDir = destinationDir
var currentRemoved = removed.filter { it.isKotlinFile() } var currentRemoved = removed.filter { it.isKotlinFile() }
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
val logAction = { logStr: String -> logger.kotlinInfo(logStr) } val logAction = { logStr: String -> logger.kotlinInfo(logStr) }
val relativePathOrCanonical = { file: File -> relativePathOrCanonical(file) }
fun getClasspathChanges(modifiedClasspath: List<File>, lastBuildInfo: BuildInfo?): ChangesEither { fun getClasspathChanges(modifiedClasspath: List<File>, lastBuildInfo: BuildInfo?): ChangesEither {
if (modifiedClasspath.isEmpty()) { if (modifiedClasspath.isEmpty()) {
@@ -239,16 +226,16 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
logger.kotlinInfo("Non-incremental compilation will be performed: $reason") logger.kotlinInfo("Non-incremental compilation will be performed: $reason")
caches.clean() caches.clean()
dirtySourcesSinceLastTimeFile.delete() dirtySourcesSinceLastTimeFile.delete()
return Pair(sources.toSet(), false) return Pair(allKotlinSources.toSet(), false)
} }
if (!incremental) return rebuild("incremental compilation is not enabled") if (!incremental) return rebuild("incremental compilation is not enabled")
if (!isIncrementalRequested) return rebuild("inputs' changes are unknown (first or clean build)") if (!isIncrementalRequested) return rebuild("inputs' changes are unknown (first or clean build)")
val removedClassFiles = removed.filter(File::hasClassFileExtension) val removedClassFiles = removed.filter(File::isClassFile)
if (removedClassFiles.any()) return rebuild("Removed class files: ${filesToString(removedClassFiles)}") if (removedClassFiles.any()) return rebuild("Removed class files: ${filesToString(removedClassFiles)}")
val modifiedClassFiles = modified.filter(File::hasClassFileExtension) val modifiedClassFiles = modified.filter(File::isClassFile)
if (modifiedClassFiles.any()) return rebuild("Modified class files: ${filesToString(modifiedClassFiles)}") if (modifiedClassFiles.any()) return rebuild("Modified class files: ${filesToString(modifiedClassFiles)}")
val modifiedClasspathEntries = modified.filter { it in classpath } val modifiedClasspathEntries = modified.filter { it in classpath }
@@ -274,13 +261,13 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
lookupSymbols.addAll(classpathChanges.lookupSymbols) lookupSymbols.addAll(classpathChanges.lookupSymbols)
if (lookupSymbols.any()) { if (lookupSymbols.any()) {
val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, logAction, ::relativePathOrCanonical) val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, logAction, relativePathOrCanonical)
dirtyFiles.addAll(dirtyFilesFromLookups) dirtyFiles.addAll(dirtyFilesFromLookups)
} }
val dirtyClassesFqNames = classpathChanges.fqNames.flatMap { withSubtypes(it, listOf(caches.incrementalCache)) } val dirtyClassesFqNames = classpathChanges.fqNames.flatMap { withSubtypes(it, listOf(caches.incrementalCache)) }
if (dirtyClassesFqNames.any()) { if (dirtyClassesFqNames.any()) {
val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassesFqNames, logAction, ::relativePathOrCanonical) val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassesFqNames, logAction, relativePathOrCanonical)
dirtyFiles.addAll(dirtyFilesFromFqNames) dirtyFiles.addAll(dirtyFilesFromFqNames)
} }
@@ -296,31 +283,9 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
return Pair(dirtyFiles, true) return Pair(dirtyFiles, true)
} }
fun cleanupOnError() {
logger.kotlinInfo("deleting $destinationDir on error")
destinationDir.deleteRecursively()
}
fun processCompilerExitCode(exitCode: ExitCode) {
if (exitCode != ExitCode.OK) {
cleanupOnError()
}
when (exitCode) {
ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details")
ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details")
ExitCode.SCRIPT_EXECUTION_ERROR -> throw GradleException("Script execution error. See log for more details")
ExitCode.OK -> {
sourceAnnotationsRegistry?.flush()
cacheVersions.forEach { it.saveIfNeeded() }
logger.kotlinInfo("Compilation succeeded")
}
}
}
if (!incremental) { if (!incremental) {
anyClassesCompiled = true anyClassesCompiled = true
processCompilerExitCode(compileNotIncremental(sources, outputDir, args)) processCompilerExitCode(compileNotIncremental(allKotlinSources, outputDir, args))
return return
} }
@@ -335,11 +300,30 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
// 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
var (sourcesToCompile, isIncrementalDecided) = calculateSourcesToCompile(javaFilesProcessor, caches, lastBuildInfo) var (sourcesToCompile, isIncrementalDecided) = calculateSourcesToCompile(javaFilesProcessor, caches, lastBuildInfo)
compileIncrementally(allGeneratedFiles, args, caches, currentRemoved, isIncrementalDecided, javaFilesProcessor, logAction, lookupTracker, outputDir, relativePathOrCanonical, allKotlinSources, sourcesToCompile, targetId)
}
if (isIncrementalDecided) { private fun compileIncrementally(
args: K2JVMCompilerArguments,
caches: IncrementalCachesManager,
currentRemoved: List<File>,
isIncrementalDecided: Boolean,
javaFilesProcessor: ChangedJavaFilesProcessor,
logAction: (String) -> Unit,
lookupTracker: LookupTrackerImpl,
outputDir: File,
relativePathOrCanonical: (File) -> String,
allKotlinSources: List<File>,
sourcesToCompile: Set<File>,
targetId: TargetId
) {
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
var currentRemoved1 = currentRemoved
var isIncrementalDecided1 = isIncrementalDecided
var sourcesToCompile1 = sourcesToCompile
if (isIncrementalDecided1) {
additionalClasspath.add(destinationDir) additionalClasspath.add(destinationDir)
} } else {
else {
// there is no point in updating annotation file since all files will be compiled anyway // there is no point in updating annotation file since all files will be compiled anyway
kaptAnnotationsFileUpdater = null kaptAnnotationsFileUpdater = null
} }
@@ -351,19 +335,19 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val buildDirtyFqNames = HashSet<FqName>() val buildDirtyFqNames = HashSet<FqName>()
var exitCode = ExitCode.OK var exitCode = ExitCode.OK
while (sourcesToCompile.any() || currentRemoved.any()) { while (sourcesToCompile1.any() || currentRemoved1.any()) {
val removedAndModified = (sourcesToCompile + currentRemoved).toList() val removedAndModified = (sourcesToCompile1 + currentRemoved1).toList()
val outdatedClasses = caches.incrementalCache.classesBySources(removedAndModified) val outdatedClasses = caches.incrementalCache.classesBySources(removedAndModified)
caches.incrementalCache.markOutputClassesDirty(removedAndModified) caches.incrementalCache.markOutputClassesDirty(removedAndModified)
caches.incrementalCache.removeClassfilesBySources(removedAndModified) caches.incrementalCache.removeClassfilesBySources(removedAndModified)
// can be empty if only removed sources are present // can be empty if only removed sources are present
if (sourcesToCompile.isNotEmpty()) { if (sourcesToCompile1.isNotEmpty()) {
logger.kotlinInfo("compile iteration: ${sourcesToCompile.joinToString(transform = ::relativePathOrCanonical)}") logger.kotlinInfo("compile iteration: ${sourcesToCompile1.joinToString(transform = relativePathOrCanonical)}")
} }
val (existingSource, nonExistingSource) = sourcesToCompile.partition { it.isFile } val (existingSource, nonExistingSource) = sourcesToCompile1.partition { it.isFile }
assert(nonExistingSource.isEmpty()) { "Trying to compile removed files: ${nonExistingSource.map(::relativePathOrCanonical)}" } assert(nonExistingSource.isEmpty()) { "Trying to compile removed files: ${nonExistingSource.map(relativePathOrCanonical)}" }
val text = existingSource.map { it.canonicalPath }.joinToString(separator = System.getProperty("line.separator")) val text = existingSource.map { it.canonicalPath }.joinToString(separator = System.getProperty("line.separator"))
dirtySourcesSinceLastTimeFile.writeText(text) dirtySourcesSinceLastTimeFile.writeText(text)
@@ -374,23 +358,22 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
if (exitCode == ExitCode.OK) { if (exitCode == ExitCode.OK) {
dirtySourcesSinceLastTimeFile.delete() dirtySourcesSinceLastTimeFile.delete()
kaptAnnotationsFileUpdater?.updateAnnotations(outdatedClasses) kaptAnnotationsFileUpdater?.updateAnnotations(outdatedClasses)
} } else {
else {
kaptAnnotationsFileUpdater?.revert() kaptAnnotationsFileUpdater?.revert()
break break
} }
allGeneratedFiles.addAll(compilerOutput.generatedFiles) allGeneratedFiles.addAll(compilerOutput.generatedFiles)
val compilationResult = updateIncrementalCaches(listOf(targetId), compilerOutput.generatedFiles, val compilationResult = updateIncrementalCaches(listOf(targetId), compilerOutput.generatedFiles,
compiledWithErrors = exitCode != ExitCode.OK, compiledWithErrors = exitCode != ExitCode.OK,
getIncrementalCache = { caches.incrementalCache }) getIncrementalCache = { caches.incrementalCache })
caches.lookupCache.update(lookupTracker, sourcesToCompile, currentRemoved) caches.lookupCache.update(lookupTracker, sourcesToCompile1, currentRemoved1)
val generatedJavaFiles = kapt2GeneratedSourcesDir.walk().filter { it.isJavaFile() }.toList() val generatedJavaFiles = kapt2GeneratedSourcesDir.walk().filter { it.isJavaFile() }.toList()
val generatedJavaFilesDiff = caches.incrementalCache.compareAndUpdateFileSnapshots(generatedJavaFiles) val generatedJavaFilesDiff = caches.incrementalCache.compareAndUpdateFileSnapshots(generatedJavaFiles)
if (!isIncrementalDecided) { if (!isIncrementalDecided1) {
artifactFile?.let { artifactDifferenceRegistry?.remove(it) } artifactFile?.let { artifactDifferenceRegistry?.remove(it) }
break break
} }
@@ -400,28 +383,28 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val dirtyKotlinFilesFromJava = when (generatedJavaFilesChanges) { val dirtyKotlinFilesFromJava = when (generatedJavaFilesChanges) {
is ChangesEither.Unknown -> { is ChangesEither.Unknown -> {
logger.kotlinDebug { "Could not get changes for generated java files, recompiling all kotlin" } logger.kotlinDebug { "Could not get changes for generated java files, recompiling all kotlin" }
isIncrementalDecided = false isIncrementalDecided1 = false
sources.toSet() allKotlinSources.toSet()
} }
is ChangesEither.Known -> { is ChangesEither.Known -> {
mapLookupSymbolsToFiles(caches.lookupCache, generatedJavaFilesChanges.lookupSymbols, logAction, ::relativePathOrCanonical, excludes = sourcesToCompile) mapLookupSymbolsToFiles(caches.lookupCache, generatedJavaFilesChanges.lookupSymbols, logAction, relativePathOrCanonical, excludes = sourcesToCompile1)
} }
else -> throw IllegalStateException("Unknown ChangesEither implementation: $generatedJavaFiles") else -> throw IllegalStateException("Unknown ChangesEither implementation: $generatedJavaFiles")
} }
sourcesToCompile = dirtyKotlinFilesFromJava + sourcesToCompile1 = dirtyKotlinFilesFromJava +
mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, logAction, ::relativePathOrCanonical, excludes = sourcesToCompile) + mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, logAction, relativePathOrCanonical, excludes = sourcesToCompile1) +
mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassFqNames, logAction, ::relativePathOrCanonical, excludes = sourcesToCompile) mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassFqNames, logAction, relativePathOrCanonical, excludes = sourcesToCompile1)
buildDirtyLookupSymbols.addAll(dirtyLookupSymbols) buildDirtyLookupSymbols.addAll(dirtyLookupSymbols)
buildDirtyFqNames.addAll(dirtyClassFqNames) buildDirtyFqNames.addAll(dirtyClassFqNames)
if (currentRemoved.any()) { if (currentRemoved1.any()) {
anyClassesCompiled = true anyClassesCompiled = true
currentRemoved = listOf() currentRemoved1 = listOf()
} }
} }
if (exitCode == ExitCode.OK && isIncrementalDecided) { if (exitCode == ExitCode.OK && isIncrementalDecided1) {
buildDirtyLookupSymbols.addAll(javaFilesProcessor.allChangedSymbols) buildDirtyLookupSymbols.addAll(javaFilesProcessor.allChangedSymbols)
} }
if (artifactFile != null && artifactDifferenceRegistry != null) { if (artifactFile != null && artifactDifferenceRegistry != null) {
@@ -441,6 +424,35 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
processCompilerExitCode(exitCode) processCompilerExitCode(exitCode)
} }
private fun relativePathOrCanonical(file: File): String =
file.relativeToOrNull(project.rootProject.projectDir)?.path
?: file.canonicalPath
private fun filesToString(files: Iterable<File>) =
"[" + files.map { relativePathOrCanonical(it) }.sorted().joinToString(separator = ", \n") + "]"
private fun cleanupOnError() {
logger.kotlinInfo("deleting $destinationDir on error")
destinationDir.deleteRecursively()
}
private fun processCompilerExitCode(exitCode: ExitCode) {
if (exitCode != ExitCode.OK) {
cleanupOnError()
}
when (exitCode) {
ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details")
ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details")
ExitCode.SCRIPT_EXECUTION_ERROR -> throw GradleException("Script execution error. See log for more details")
ExitCode.OK -> {
sourceAnnotationsRegistry?.flush()
cacheVersions.forEach { it.saveIfNeeded() }
logger.kotlinInfo("Compilation succeeded")
}
}
}
private data class CompileChangedResults(val exitCode: ExitCode, val generatedFiles: List<GeneratedFile<TargetId>>) private data class CompileChangedResults(val exitCode: ExitCode, val generatedFiles: List<GeneratedFile<TargetId>>)
private fun compileChanged( private fun compileChanged(
@@ -547,9 +559,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
private fun getJavaSourceRoots(): Set<File> = private fun getJavaSourceRoots(): Set<File> =
findRootsForSources(getSource().filter { it.isJavaFile() }) findRootsForSources(getSource().filter { it.isJavaFile() })
private fun File.isJavaFile() =
extension.equals(JavaFileType.INSTANCE.defaultExtension, ignoreCase = true)
private fun File.isKapt2GeneratedDirectory(): Boolean { private fun File.isKapt2GeneratedDirectory(): Boolean {
if (!kapt2GeneratedSourcesDir.isDirectory) return false if (!kapt2GeneratedSourcesDir.isDirectory) return false
return FileUtil.isAncestor(kapt2GeneratedSourcesDir, this, /* strict = */ false) return FileUtil.isAncestor(kapt2GeneratedSourcesDir, this, /* strict = */ false)
@@ -630,11 +639,11 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
return args return args
} }
override fun callCompiler(args: K2JSCompilerArguments, sources: List<File>, isIncremental: Boolean, modified: List<File>, removed: List<File>) { override fun callCompiler(args: K2JSCompilerArguments, allKotlinSources: List<File>, isIncremental: Boolean, modified: List<File>, removed: List<File>) {
val messageCollector = GradleMessageCollector(logger) val messageCollector = GradleMessageCollector(logger)
logger.debug("Calling compiler") logger.debug("Calling compiler")
destinationDir.mkdirs() destinationDir.mkdirs()
args.freeArgs = args.freeArgs + sources.map { it.absolutePath } args.freeArgs = args.freeArgs + allKotlinSources.map { it.absolutePath }
if (args.outputFile == null) { if (args.outputFile == null) {
throw GradleException("$name.kotlinOptions.outputFile should be specified.") throw GradleException("$name.kotlinOptions.outputFile should be specified.")
@@ -719,9 +728,3 @@ internal inline fun Logger.kotlinDebug(message: ()->String) {
kotlinDebug(message()) kotlinDebug(message())
} }
} }
internal fun listClassFiles(path: String): Sequence<File> =
File(path).walk().filter { it.isFile && it.hasClassFileExtension() }
private fun File.hasClassFileExtension(): Boolean =
extension.toLowerCase() == "class"
@@ -0,0 +1,19 @@
package org.jetbrains.kotlin.gradle.tasks
import java.io.File
internal fun File.isJavaFile() =
extension.equals("java", ignoreCase = true)
internal fun File.isKotlinFile(): Boolean =
extension.let {
"kt".equals(it, ignoreCase = true) ||
"kts".equals(it, ignoreCase = true)
}
internal fun File.isClassFile(): Boolean =
extension.equals("class", ignoreCase = true)
internal fun listClassFiles(path: String): Sequence<File> =
File(path).walk().filter { it.isFile && it.isClassFile() }