Move IC from Gradle to compiler
Reasons for moving: 1. Integration with Kotlin daemon for Gradle. Gradle bundles Kotlin compiler (kotlin-compiler-embeddable) since Gradle 3.2. There is no plugin isolation in Gradle, so strange compilation errors/exceptions may happen. We decided to fix this problem by compiling out-of-process using Kotlin daemon. Reasons for moving IC to the compiler: * Better isolation from Gradle process. * Incremental compilation logic is not scattered across two process and multiple modules. * Makes it possible to implement standalone CLI IC
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="intellij-core" level="project" />
|
||||
<orderEntry type="library" name="kotlin-runtime" level="project" />
|
||||
<orderEntry type="module" module-name="build-common" />
|
||||
<orderEntry type="module" module-name="cli" />
|
||||
<orderEntry type="module" module-name="descriptor.loader.java" />
|
||||
<orderEntry type="module" module-name="frontend" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
</component>
|
||||
</module>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.annotation
|
||||
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
|
||||
/**
|
||||
* Annotation file is generated by collecting annotated elements of generated files.
|
||||
* When compiling incrementally, the compiler generates only subset of all classes,
|
||||
* so after compilation annotation file contains only a subset of annotated elements,
|
||||
* which breaks the build.
|
||||
*
|
||||
* The workaround is to:
|
||||
* 1. backup old file before incremental compilation;
|
||||
* 2. after each iteration of IC:
|
||||
* 2.1 remove classes corresponding to dirty source files
|
||||
* 2.2 add annotations from newly generated annotations file
|
||||
*/
|
||||
interface AnnotationFileUpdater {
|
||||
fun updateAnnotations(outdatedClasses: Iterable<JvmClassName>)
|
||||
fun revert()
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import java.io.*
|
||||
|
||||
internal data class BuildInfo(val startTS: Long) : Serializable {
|
||||
companion object {
|
||||
fun read(file: File): BuildInfo? =
|
||||
try {
|
||||
ObjectInputStream(FileInputStream(file)).use {
|
||||
it.readObject() as BuildInfo
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
fun write(buildInfo: BuildInfo, file: File) {
|
||||
ObjectOutputStream(FileOutputStream(file)).use {
|
||||
it.writeObject(buildInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal sealed class ChangedFiles {
|
||||
class Known(val modified: List<File>, val removed: List<File>) : ChangedFiles()
|
||||
class Unknown : ChangedFiles()
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.gradle.api.logging.Logging
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiJavaFile
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal class ChangedJavaFilesProcessor(private val reporter: ICReporter) {
|
||||
private val allSymbols = HashSet<LookupSymbol>()
|
||||
private val javaLang = JavaLanguage.INSTANCE
|
||||
private val psiFileFactory: PsiFileFactory by lazy {
|
||||
val rootDisposable = Disposer.newDisposable()
|
||||
val configuration = CompilerConfiguration()
|
||||
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
val project = environment.project
|
||||
PsiFileFactory.getInstance(project)
|
||||
}
|
||||
|
||||
val allChangedSymbols: Collection<LookupSymbol>
|
||||
get() = allSymbols
|
||||
|
||||
fun process(filesDiff: ChangedFiles.Known): ChangesEither {
|
||||
val modifiedJava = filesDiff.modified.filter(File::isJavaFile)
|
||||
val removedJava = filesDiff.removed.filter(File::isJavaFile)
|
||||
|
||||
if (removedJava.any()) {
|
||||
reporter.report { "Some java files are removed: [${removedJava.joinToString()}]" }
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
val symbols = HashSet<LookupSymbol>()
|
||||
for (javaFile in modifiedJava) {
|
||||
assert(javaFile.extension.equals("java", ignoreCase = true))
|
||||
|
||||
val psiFile = javaFile.psiFile()
|
||||
if (psiFile !is PsiJavaFile) {
|
||||
reporter.report { "Expected PsiJavaFile, got ${psiFile?.javaClass}" }
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
psiFile.classes.forEach { it.addLookupSymbols(symbols) }
|
||||
}
|
||||
allSymbols.addAll(symbols)
|
||||
return ChangesEither.Known(lookupSymbols = symbols)
|
||||
}
|
||||
|
||||
private fun PsiClass.addLookupSymbols(symbols: MutableSet<LookupSymbol>) {
|
||||
val fqn = qualifiedName.orEmpty()
|
||||
|
||||
symbols.add(LookupSymbol(name.orEmpty(), if (fqn == name) "" else fqn.removeSuffix("." + name!!)))
|
||||
methods.forEach { symbols.add(LookupSymbol(it.name, fqn)) }
|
||||
fields.forEach { symbols.add(LookupSymbol(it.name.orEmpty(), fqn)) }
|
||||
innerClasses.forEach { it.addLookupSymbols(symbols) }
|
||||
}
|
||||
|
||||
private fun File.psiFile(): PsiFile? =
|
||||
psiFileFactory.createFileFromText(nameWithoutExtension, javaLang, readText())
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
internal sealed class ChangesEither {
|
||||
internal class Known(
|
||||
val lookupSymbols: Collection<LookupSymbol> = emptyList(),
|
||||
val fqNames: Collection<FqName> = emptyList()
|
||||
) : ChangesEither()
|
||||
internal class Unknown : ChangesEither()
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.build.GeneratedJvmClass
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||
import org.jetbrains.kotlin.incremental.snapshots.FileSnapshotMap
|
||||
import org.jetbrains.kotlin.incremental.storage.BasicStringMap
|
||||
import org.jetbrains.kotlin.incremental.storage.PathStringDescriptor
|
||||
import org.jetbrains.kotlin.incremental.storage.StringCollectionExternalizer
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
import java.io.File
|
||||
|
||||
internal class GradleIncrementalCacheImpl(
|
||||
targetDataRoot: File,
|
||||
targetOutputDir: File?,
|
||||
target: TargetId,
|
||||
private val reporter: ICReporter
|
||||
) : IncrementalCacheImpl<TargetId>(targetDataRoot, targetOutputDir, target) {
|
||||
companion object {
|
||||
private val SOURCES_TO_CLASSFILES = "sources-to-classfiles"
|
||||
private val GENERATED_SOURCE_SNAPSHOTS = "generated-source-snapshot"
|
||||
private val SOURCE_SNAPSHOTS = "source-snapshot"
|
||||
}
|
||||
|
||||
internal val sourceToClassfilesMap = registerMap(SourceToClassfilesMap(SOURCES_TO_CLASSFILES.storageFile))
|
||||
internal val generatedSourceSnapshotMap = registerMap(FileSnapshotMap(GENERATED_SOURCE_SNAPSHOTS.storageFile))
|
||||
internal val sourceSnapshotMap = registerMap(FileSnapshotMap(SOURCE_SNAPSHOTS.storageFile))
|
||||
|
||||
fun removeClassfilesBySources(sources: Iterable<File>): Unit =
|
||||
sources.forEach { sourceToClassfilesMap.remove(it) }
|
||||
|
||||
override fun saveFileToCache(generatedClass: GeneratedJvmClass<TargetId>): CompilationResult {
|
||||
generatedClass.sourceFiles.forEach { sourceToClassfilesMap.add(it, generatedClass.outputFile) }
|
||||
return super.saveFileToCache(generatedClass)
|
||||
}
|
||||
|
||||
inner class SourceToClassfilesMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, PathStringDescriptor, StringCollectionExternalizer) {
|
||||
fun add(sourceFile: File, classFile: File) {
|
||||
storage.append(sourceFile.absolutePath, classFile.absolutePath)
|
||||
}
|
||||
|
||||
operator fun get(sourceFile: File): Collection<File> =
|
||||
storage[sourceFile.absolutePath].orEmpty().map(::File)
|
||||
|
||||
override fun dumpValue(value: Collection<String>) = value.dumpCollection()
|
||||
|
||||
fun remove(file: File) {
|
||||
// TODO: do it in the code that uses cache, since cache should not generally delete anything outside of it!
|
||||
// but for a moment it is an easiest solution to implement
|
||||
get(file).forEach {
|
||||
reporter.report { "Deleting $it on clearing cache for $file" }
|
||||
it.delete()
|
||||
}
|
||||
storage.remove(file.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
import java.io.File
|
||||
|
||||
internal class IncrementalCachesManager (
|
||||
private val targetId: TargetId,
|
||||
private val cacheDirectory: File,
|
||||
private val outputDir: File,
|
||||
private val reporter: ICReporter
|
||||
) {
|
||||
private val incrementalCacheDir = File(cacheDirectory, "increCache.${targetId.name}")
|
||||
private val lookupCacheDir = File(cacheDirectory, "lookups")
|
||||
private var incrementalCacheField: GradleIncrementalCacheImpl? = null
|
||||
private var lookupCacheField: LookupStorage? = null
|
||||
|
||||
val incrementalCache: GradleIncrementalCacheImpl
|
||||
get() {
|
||||
if (incrementalCacheField == null) {
|
||||
val targetDataRoot = incrementalCacheDir.apply { mkdirs() }
|
||||
incrementalCacheField = GradleIncrementalCacheImpl(targetDataRoot, outputDir, targetId, reporter)
|
||||
}
|
||||
|
||||
return incrementalCacheField!!
|
||||
}
|
||||
|
||||
val lookupCache: LookupStorage
|
||||
get() {
|
||||
if (lookupCacheField == null) {
|
||||
lookupCacheField = LookupStorage(lookupCacheDir.apply { mkdirs() })
|
||||
}
|
||||
|
||||
return lookupCacheField!!
|
||||
}
|
||||
|
||||
fun clean() {
|
||||
close(flush = false)
|
||||
cacheDirectory.deleteRecursively()
|
||||
}
|
||||
|
||||
fun close(flush: Boolean = false) {
|
||||
incrementalCacheField?.let {
|
||||
if (flush) {
|
||||
it.flush(false)
|
||||
}
|
||||
it.close()
|
||||
incrementalCacheField = null
|
||||
}
|
||||
|
||||
lookupCacheField?.let {
|
||||
if (flush) {
|
||||
it.flush(false)
|
||||
}
|
||||
it.close()
|
||||
lookupCacheField = null
|
||||
}
|
||||
}
|
||||
}
|
||||
+509
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.annotation.AnnotationFileUpdater
|
||||
import org.jetbrains.kotlin.build.GeneratedFile
|
||||
import org.jetbrains.kotlin.build.GeneratedJvmClass
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumeratorBase
|
||||
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
|
||||
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollector
|
||||
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifference
|
||||
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import java.io.EOFException
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
internal fun makeIncrementally(
|
||||
cachesDir: File,
|
||||
sourceRoots: Iterable<File>,
|
||||
args: K2JVMCompilerArguments,
|
||||
messageCollector: MessageCollector = MessageCollector.NONE,
|
||||
reporter: ICReporter = EmptyICReporter
|
||||
) {
|
||||
val versions = listOf(normalCacheVersion(cachesDir),
|
||||
experimentalCacheVersion(cachesDir),
|
||||
dataContainerCacheVersion(cachesDir),
|
||||
standaloneCacheVersion(cachesDir))
|
||||
|
||||
val kotlinExtensions = listOf("kt", "kts")
|
||||
val allExtensions = kotlinExtensions + listOf("java")
|
||||
val rootsWalk = sourceRoots.asSequence().map { it.walk() }.flatten()
|
||||
val files = rootsWalk.filter(File::isFile)
|
||||
val sourceFiles = files.filter { it.extension.toLowerCase() in allExtensions }.toList()
|
||||
val kotlinFiles = sourceFiles.filter { it.extension.toLowerCase() in kotlinExtensions }
|
||||
|
||||
enableIC {
|
||||
val compiler = IncrementalJvmCompilerRunner(cachesDir, /* javaSourceRoots = */sourceRoots.toSet(), versions, reporter)
|
||||
compiler.compile(kotlinFiles, args, messageCollector) {
|
||||
it.incrementalCache.sourceSnapshotMap.compareAndUpdate(sourceFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object EmptyICReporter : ICReporter() {
|
||||
override fun report(message: ()->String) {
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun enableIC(fn: ()->Unit) {
|
||||
val isEnabledBackup = IncrementalCompilation.isEnabled()
|
||||
val isExperimentalBackup = IncrementalCompilation.isExperimental()
|
||||
IncrementalCompilation.setIsEnabled(true)
|
||||
IncrementalCompilation.setIsExperimental(true)
|
||||
|
||||
try {
|
||||
fn()
|
||||
}
|
||||
finally {
|
||||
IncrementalCompilation.setIsEnabled(isEnabledBackup)
|
||||
IncrementalCompilation.setIsExperimental(isExperimentalBackup)
|
||||
}
|
||||
}
|
||||
|
||||
internal class IncrementalJvmCompilerRunner(
|
||||
workingDir: File,
|
||||
private val javaSourceRoots: Set<File>,
|
||||
private val cacheVersions: List<CacheVersion>,
|
||||
private val reporter: ICReporter,
|
||||
private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
|
||||
private val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null,
|
||||
private val artifactFile: File? = null
|
||||
) {
|
||||
var anyClassesCompiled: Boolean = false
|
||||
private set
|
||||
private val cacheDirectory = File(workingDir, CACHES_DIR_NAME)
|
||||
private val dirtySourcesSinceLastTimeFile = File(workingDir, DIRTY_SOURCES_FILE_NAME)
|
||||
private val lastBuildInfoFile = File(workingDir, LAST_BUILD_INFO_FILE_NAME)
|
||||
|
||||
fun compile(
|
||||
allKotlinSources: List<File>,
|
||||
args: K2JVMCompilerArguments,
|
||||
messageCollector: MessageCollector,
|
||||
getChangedFiles: (IncrementalCachesManager)->ChangedFiles
|
||||
): ExitCode {
|
||||
val targetId = TargetId(name = args.moduleName, type = "java-production")
|
||||
var caches = IncrementalCachesManager(targetId, cacheDirectory, File(args.destination), reporter)
|
||||
|
||||
fun onError(e: Exception): ExitCode {
|
||||
caches.clean()
|
||||
artifactDifferenceRegistryProvider?.clean()
|
||||
|
||||
// todo: warn?
|
||||
reporter.report { "Possible cache corruption. Rebuilding. $e" }
|
||||
// try to rebuild
|
||||
val javaFilesProcessor = ChangedJavaFilesProcessor(reporter)
|
||||
caches = IncrementalCachesManager(targetId, cacheDirectory, args.destinationAsFile, reporter)
|
||||
return compileIncrementally(args, caches, javaFilesProcessor, allKotlinSources, targetId, CompilationMode.Rebuild, messageCollector)
|
||||
}
|
||||
|
||||
return try {
|
||||
val javaFilesProcessor = ChangedJavaFilesProcessor(reporter)
|
||||
val changedFiles = getChangedFiles(caches)
|
||||
val compilationMode = calculateSourcesToCompile(javaFilesProcessor, caches, changedFiles, args)
|
||||
compileIncrementally(args, caches, javaFilesProcessor, allKotlinSources, targetId, compilationMode, messageCollector)
|
||||
}
|
||||
catch (e: PersistentEnumeratorBase.CorruptedException) {
|
||||
onError(e)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
onError(e)
|
||||
}
|
||||
finally {
|
||||
artifactDifferenceRegistryProvider?.withRegistry(reporter) {
|
||||
it.flush(memoryCachesOnly = true)
|
||||
}
|
||||
caches.close(flush = true)
|
||||
reporter.report { "flushed incremental caches" }
|
||||
}
|
||||
}
|
||||
|
||||
private data class CompileChangedResults(val exitCode: ExitCode, val generatedFiles: List<GeneratedFile<TargetId>>)
|
||||
|
||||
private sealed class CompilationMode {
|
||||
class Incremental(val dirtyFiles: Set<File>) : CompilationMode()
|
||||
object Rebuild : CompilationMode()
|
||||
}
|
||||
|
||||
private fun calculateSourcesToCompile(
|
||||
javaFilesProcessor: ChangedJavaFilesProcessor,
|
||||
caches: IncrementalCachesManager,
|
||||
changedFiles: ChangedFiles,
|
||||
args: K2JVMCompilerArguments
|
||||
): CompilationMode {
|
||||
fun rebuild(reason: ()->String): CompilationMode {
|
||||
reporter.report {"Non-incremental compilation will be performed: ${reason()}"}
|
||||
caches.clean()
|
||||
dirtySourcesSinceLastTimeFile.delete()
|
||||
args.destinationAsFile.deleteRecursively()
|
||||
return CompilationMode.Rebuild
|
||||
}
|
||||
|
||||
if (changedFiles !is ChangedFiles.Known) return rebuild {"inputs' changes are unknown (first or clean build)"}
|
||||
|
||||
val removedClassFiles = changedFiles.removed.filter(File::isClassFile)
|
||||
if (removedClassFiles.any()) return rebuild {"Removed class files: ${reporter.pathsAsString(removedClassFiles)}"}
|
||||
|
||||
val modifiedClassFiles = changedFiles.modified.filter(File::isClassFile)
|
||||
if (modifiedClassFiles.any()) return rebuild {"Modified class files: ${reporter.pathsAsString(modifiedClassFiles)}"}
|
||||
|
||||
val classpathSet = args.classpathAsList.toHashSet()
|
||||
val modifiedClasspathEntries = changedFiles.modified.filter {it in classpathSet}
|
||||
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile)
|
||||
reporter.report { "Last Kotlin Build info -- $lastBuildInfo" }
|
||||
val classpathChanges = getClasspathChanges(modifiedClasspathEntries, lastBuildInfo)
|
||||
if (classpathChanges !is ChangesEither.Known) {
|
||||
return rebuild {"could not get changes from modified classpath entries: ${reporter.pathsAsString(modifiedClasspathEntries)}"}
|
||||
}
|
||||
|
||||
val javaFilesChanges = javaFilesProcessor.process(changedFiles)
|
||||
val affectedJavaSymbols = when (javaFilesChanges) {
|
||||
is ChangesEither.Known -> javaFilesChanges.lookupSymbols
|
||||
is ChangesEither.Unknown -> return rebuild {"Could not get changes for java files"}
|
||||
}
|
||||
|
||||
val dirtyFiles = HashSet<File>(with(changedFiles) {modified.size + removed.size})
|
||||
with(changedFiles) {
|
||||
modified.asSequence() + removed.asSequence()
|
||||
}.forEach {if (it.isKotlinFile()) dirtyFiles.add(it)}
|
||||
|
||||
val lookupSymbols = HashSet<LookupSymbol>()
|
||||
lookupSymbols.addAll(affectedJavaSymbols)
|
||||
lookupSymbols.addAll(classpathChanges.lookupSymbols)
|
||||
|
||||
if (lookupSymbols.any()) {
|
||||
val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, reporter)
|
||||
dirtyFiles.addAll(dirtyFilesFromLookups)
|
||||
}
|
||||
|
||||
val dirtyClassesFqNames = classpathChanges.fqNames.flatMap {withSubtypes(it, listOf(caches.incrementalCache))}
|
||||
if (dirtyClassesFqNames.any()) {
|
||||
val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassesFqNames, reporter)
|
||||
dirtyFiles.addAll(dirtyFilesFromFqNames)
|
||||
}
|
||||
|
||||
if (dirtySourcesSinceLastTimeFile.exists()) {
|
||||
val files = dirtySourcesSinceLastTimeFile.readLines().map(::File).filter(File::exists)
|
||||
if (files.isNotEmpty()) {
|
||||
reporter.report {"Source files added since last compilation: ${reporter.pathsAsString(files)}"}
|
||||
}
|
||||
|
||||
dirtyFiles.addAll(files)
|
||||
}
|
||||
|
||||
return CompilationMode.Incremental(dirtyFiles)
|
||||
}
|
||||
|
||||
private fun getClasspathChanges(
|
||||
modifiedClasspath: List<File>,
|
||||
lastBuildInfo: BuildInfo?
|
||||
): ChangesEither {
|
||||
if (modifiedClasspath.isEmpty()) {
|
||||
reporter.report {"No classpath changes"}
|
||||
return ChangesEither.Known()
|
||||
}
|
||||
if (artifactDifferenceRegistryProvider == null) {
|
||||
reporter.report {"No artifact history provider"}
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
val lastBuildTS = lastBuildInfo?.startTS
|
||||
if (lastBuildTS == null) {
|
||||
reporter.report {"Could not determine last build timestamp"}
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
val symbols = HashSet<LookupSymbol>()
|
||||
val fqNames = HashSet<FqName>()
|
||||
for (file in modifiedClasspath) {
|
||||
val diffs = artifactDifferenceRegistryProvider.withRegistry(reporter) {artifactDifferenceRegistry ->
|
||||
artifactDifferenceRegistry[file]
|
||||
}
|
||||
if (diffs == null) {
|
||||
reporter.report {"Could not get changes for file: $file"}
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
val (beforeLastBuild, afterLastBuild) = diffs.partition {it.buildTS < lastBuildTS}
|
||||
if (beforeLastBuild.isEmpty()) {
|
||||
reporter.report {"No known build preceding timestamp $lastBuildTS for file $file"}
|
||||
return ChangesEither.Unknown()
|
||||
}
|
||||
|
||||
afterLastBuild.forEach {
|
||||
symbols.addAll(it.dirtyData.dirtyLookupSymbols)
|
||||
fqNames.addAll(it.dirtyData.dirtyClassesFqNames)
|
||||
}
|
||||
}
|
||||
|
||||
return ChangesEither.Known(symbols, fqNames)
|
||||
}
|
||||
|
||||
private fun compileIncrementally(
|
||||
args: K2JVMCompilerArguments,
|
||||
caches: IncrementalCachesManager,
|
||||
javaFilesProcessor: ChangedJavaFilesProcessor,
|
||||
allKotlinSources: List<File>,
|
||||
targetId: TargetId,
|
||||
compilationMode: CompilationMode,
|
||||
messageCollector: MessageCollector
|
||||
): ExitCode {
|
||||
assert(IncrementalCompilation.isEnabled()) { "Incremental compilation is not enabled" }
|
||||
assert(IncrementalCompilation.isExperimental()) { "Experimental incremental compilation is not enabled" }
|
||||
|
||||
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
|
||||
val dirtySources: MutableList<File>
|
||||
|
||||
when (compilationMode) {
|
||||
is CompilationMode.Incremental -> {
|
||||
dirtySources = ArrayList(compilationMode.dirtyFiles)
|
||||
args.classpathAsList += args.destinationAsFile
|
||||
}
|
||||
is CompilationMode.Rebuild -> {
|
||||
dirtySources = allKotlinSources.toMutableList()
|
||||
// there is no point in updating annotation file since all files will be compiled anyway
|
||||
kaptAnnotationsFileUpdater = null
|
||||
}
|
||||
else -> throw IllegalStateException("Unknown CompilationMode ${compilationMode.javaClass}")
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var compilationMode = compilationMode
|
||||
|
||||
reporter.report { "Artifact to register difference for: $artifactFile" }
|
||||
val currentBuildInfo = BuildInfo(startTS = System.currentTimeMillis())
|
||||
BuildInfo.write(currentBuildInfo, lastBuildInfoFile)
|
||||
val buildDirtyLookupSymbols = HashSet<LookupSymbol>()
|
||||
val buildDirtyFqNames = HashSet<FqName>()
|
||||
val allSourcesToCompile = HashSet<File>()
|
||||
|
||||
var exitCode = ExitCode.OK
|
||||
while (dirtySources.any()) {
|
||||
val lookupTracker = LookupTrackerImpl(LookupTracker.DO_NOTHING)
|
||||
val outdatedClasses = caches.incrementalCache.classesBySources(dirtySources)
|
||||
caches.incrementalCache.markOutputClassesDirty(dirtySources)
|
||||
caches.incrementalCache.removeClassfilesBySources(dirtySources)
|
||||
|
||||
val (sourcesToCompile, removedKotlinSources) = dirtySources.partition(File::exists)
|
||||
if (sourcesToCompile.isNotEmpty()) {
|
||||
reporter.report { "compile iteration: ${reporter.pathsAsString(sourcesToCompile)}" }
|
||||
}
|
||||
|
||||
// todo: more optimal to save only last iteration, but it will require adding standalone-ic specific logs
|
||||
// (because jps rebuilds all files from last build if it failed and gradle rebuilds everything)
|
||||
allSourcesToCompile.addAll(sourcesToCompile)
|
||||
val text = allSourcesToCompile.map { it.canonicalPath }.joinToString(separator = System.getProperty("line.separator"))
|
||||
dirtySourcesSinceLastTimeFile.writeText(text)
|
||||
|
||||
val compilerOutput = compileChanged(listOf(targetId), sourcesToCompile.toSet(), args, { caches.incrementalCache }, lookupTracker, messageCollector)
|
||||
exitCode = compilerOutput.exitCode
|
||||
val generatedClassFiles = compilerOutput.generatedFiles
|
||||
anyClassesCompiled = anyClassesCompiled || generatedClassFiles.isNotEmpty() || removedKotlinSources.isNotEmpty()
|
||||
|
||||
if (exitCode == ExitCode.OK) {
|
||||
dirtySourcesSinceLastTimeFile.delete()
|
||||
kaptAnnotationsFileUpdater?.updateAnnotations(outdatedClasses)
|
||||
} else {
|
||||
kaptAnnotationsFileUpdater?.revert()
|
||||
break
|
||||
}
|
||||
|
||||
if (compilationMode is CompilationMode.Incremental) {
|
||||
val dirtySourcesSet = dirtySources.toHashSet()
|
||||
val additionalDirtyFiles = additionalDirtyFiles(caches, generatedClassFiles).filter { it !in dirtySourcesSet }
|
||||
if (additionalDirtyFiles.isNotEmpty()) {
|
||||
dirtySources.addAll(additionalDirtyFiles)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
allGeneratedFiles.addAll(generatedClassFiles)
|
||||
val compilationResult = updateIncrementalCaches(listOf(targetId), generatedClassFiles,
|
||||
compiledWithErrors = exitCode != ExitCode.OK,
|
||||
getIncrementalCache = { caches.incrementalCache })
|
||||
|
||||
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources)
|
||||
|
||||
if (compilationMode is CompilationMode.Rebuild) {
|
||||
artifactFile?.let { artifactFile ->
|
||||
artifactDifferenceRegistryProvider?.withRegistry(reporter) { registry ->
|
||||
registry.remove(artifactFile)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(listOf(caches.incrementalCache), reporter)
|
||||
val compiledInThisIterationSet = sourcesToCompile.toHashSet()
|
||||
|
||||
with (dirtySources) {
|
||||
clear()
|
||||
addAll(mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, reporter, excludes = compiledInThisIterationSet))
|
||||
addAll(mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassFqNames, reporter, excludes = compiledInThisIterationSet))
|
||||
}
|
||||
|
||||
buildDirtyLookupSymbols.addAll(dirtyLookupSymbols)
|
||||
buildDirtyFqNames.addAll(dirtyClassFqNames)
|
||||
}
|
||||
|
||||
if (exitCode == ExitCode.OK && compilationMode is CompilationMode.Incremental) {
|
||||
buildDirtyLookupSymbols.addAll(javaFilesProcessor.allChangedSymbols)
|
||||
}
|
||||
if (artifactFile != null && artifactDifferenceRegistryProvider != null) {
|
||||
val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames)
|
||||
val artifactDifference = ArtifactDifference(currentBuildInfo.startTS, dirtyData)
|
||||
artifactDifferenceRegistryProvider.withRegistry(reporter) {registry ->
|
||||
registry.add(artifactFile, artifactDifference)
|
||||
}
|
||||
reporter.report {
|
||||
val dirtySymbolsSorted = buildDirtyLookupSymbols.map { it.scope + "#" + it.name }.sorted()
|
||||
"Added artifact difference for $artifactFile (ts: ${currentBuildInfo.startTS}): " +
|
||||
"[\n\t${dirtySymbolsSorted.joinToString(",\n\t")}]"
|
||||
}
|
||||
}
|
||||
|
||||
if (exitCode == ExitCode.OK) {
|
||||
cacheVersions.forEach { it.saveIfNeeded() }
|
||||
}
|
||||
|
||||
return exitCode
|
||||
}
|
||||
|
||||
private fun additionalDirtyFiles(
|
||||
caches: IncrementalCachesManager,
|
||||
generatedFiles: List<GeneratedFile<TargetId>>
|
||||
): Collection<File> {
|
||||
val result = HashSet<File>()
|
||||
|
||||
fun partsByFacadeName(facadeInternalName: String): List<File> {
|
||||
val parts = caches.incrementalCache.getStableMultifileFacadeParts(facadeInternalName) ?: emptyList()
|
||||
return parts.flatMap { caches.incrementalCache.sourcesByInternalName(it) }
|
||||
}
|
||||
|
||||
for (generatedFile in generatedFiles) {
|
||||
if (generatedFile !is GeneratedJvmClass<*>) continue
|
||||
|
||||
val outputClass = generatedFile.outputClass
|
||||
|
||||
when (outputClass.classHeader.kind) {
|
||||
KotlinClassHeader.Kind.CLASS -> {
|
||||
val fqName = outputClass.className.fqNameForClassNameWithoutDollars
|
||||
val cachedSourceFile = caches.incrementalCache.getSourceFileIfClass(fqName)
|
||||
|
||||
if (cachedSourceFile != null) {
|
||||
result.add(cachedSourceFile)
|
||||
}
|
||||
}
|
||||
// todo: more optimal is to check if public API or parts list changed
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
|
||||
result.addAll(partsByFacadeName(outputClass.className.internalName))
|
||||
}
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
|
||||
result.addAll(partsByFacadeName(outputClass.classHeader.multifileClassName!!))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun compileChanged(
|
||||
targets: List<TargetId>,
|
||||
sourcesToCompile: Set<File>,
|
||||
args: K2JVMCompilerArguments,
|
||||
getIncrementalCache: (TargetId)->GradleIncrementalCacheImpl,
|
||||
lookupTracker: LookupTracker,
|
||||
messageCollector: MessageCollector
|
||||
): CompileChangedResults {
|
||||
val compiler = K2JVMCompiler()
|
||||
val outputDir = args.destinationAsFile
|
||||
val classpath = args.classpathAsList
|
||||
val moduleFile = makeModuleFile(args.moduleName,
|
||||
isTest = false,
|
||||
outputDir = outputDir,
|
||||
sourcesToCompile = sourcesToCompile,
|
||||
javaSourceRoots = javaSourceRoots,
|
||||
classpath = classpath,
|
||||
friendDirs = listOf())
|
||||
args.module = moduleFile.absolutePath
|
||||
val outputItemCollector = OutputItemsCollectorImpl()
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val messageCollector = MessageCollectorWrapper(messageCollector, outputItemCollector)
|
||||
|
||||
try {
|
||||
val incrementalCaches = makeIncrementalCachesMap(targets, { listOf<TargetId>() }, getIncrementalCache, { this })
|
||||
val compilationCanceledStatus = object : CompilationCanceledStatus {
|
||||
override fun checkCanceled() {
|
||||
}
|
||||
}
|
||||
|
||||
reporter.report { "compiling with args: ${ArgumentUtils.convertArgumentsToStringList(args)}" }
|
||||
reporter.report { "compiling with classpath: ${classpath.toList().sorted().joinToString()}" }
|
||||
val compileServices = makeCompileServices(incrementalCaches, lookupTracker, compilationCanceledStatus)
|
||||
val exitCode = compiler.exec(messageCollector, compileServices, args)
|
||||
val generatedFiles = outputItemCollector.generatedFiles(targets, targets.first(), {sourcesToCompile}, {outputDir})
|
||||
reporter.reportCompileIteration(sourcesToCompile, exitCode)
|
||||
return CompileChangedResults(exitCode, generatedFiles)
|
||||
}
|
||||
finally {
|
||||
moduleFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageCollectorWrapper(
|
||||
private val delegate: MessageCollector,
|
||||
private val outputCollector: OutputItemsCollector
|
||||
) : MessageCollector by delegate {
|
||||
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
|
||||
// TODO: consider adding some other way of passing input -> output mapping from compiler, e.g. dedicated service
|
||||
OutputMessageUtil.parseOutputMessage(message)?.let {
|
||||
outputCollector.add(it.sourceFiles, it.outputFile)
|
||||
}
|
||||
delegate.report(severity, message, location)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CACHES_DIR_NAME = "caches"
|
||||
const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt"
|
||||
const val LAST_BUILD_INFO_FILE_NAME = "last-build.bin"
|
||||
}
|
||||
}
|
||||
|
||||
internal var K2JVMCompilerArguments.destinationAsFile: File
|
||||
get() = File(destination)
|
||||
set(value) { destination = value.path }
|
||||
|
||||
internal var K2JVMCompilerArguments.classpathAsList: List<File>
|
||||
get() = classpath.split(File.pathSeparator).map(::File)
|
||||
set(value) { classpath = value.joinToString(separator = File.pathSeparator, transform = { it.path }) }
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
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() }
|
||||
|
||||
internal fun File.relativeOrCanonical(base: File): String =
|
||||
relativeToOrNull(base)?.path ?: canonicalPath
|
||||
|
||||
internal fun Iterable<File>.pathsAsStringRelativeTo(base: File): String =
|
||||
map { it.relativeOrCanonical(base) }.sorted().joinToString()
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.multiproject
|
||||
|
||||
import org.jetbrains.kotlin.incremental.DirtyData
|
||||
import org.jetbrains.kotlin.incremental.ICReporter
|
||||
import java.io.File
|
||||
|
||||
internal interface ArtifactDifferenceRegistry {
|
||||
operator fun get(artifact: File): Iterable<ArtifactDifference>?
|
||||
fun add(artifact: File, difference: ArtifactDifference)
|
||||
fun remove(artifact: File)
|
||||
fun flush(memoryCachesOnly: Boolean)
|
||||
}
|
||||
|
||||
internal class ArtifactDifference(val buildTS: Long, val dirtyData: DirtyData)
|
||||
|
||||
internal interface ArtifactDifferenceRegistryProvider {
|
||||
fun <T> withRegistry(
|
||||
report: (String) -> Unit,
|
||||
fn: (ArtifactDifferenceRegistry) -> T
|
||||
): T?
|
||||
|
||||
fun <T> withRegistry(
|
||||
reporter: ICReporter,
|
||||
fn: (ArtifactDifferenceRegistry) -> T
|
||||
): T? {
|
||||
return withRegistry({reporter.report {it}}, fn)
|
||||
}
|
||||
|
||||
fun clean()
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.snapshots
|
||||
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal class FileSnapshot(
|
||||
val file: File,
|
||||
val length: Long,
|
||||
val hash: ByteArray
|
||||
) {
|
||||
init {
|
||||
assert(!file.isDirectory)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
||||
other as FileSnapshot
|
||||
|
||||
if (file != other.file) return false
|
||||
if (length != other.length) return false
|
||||
if (!Arrays.equals(hash, other.hash)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = file.hashCode()
|
||||
result = 31 * result + length.hashCode()
|
||||
result = 31 * result + Arrays.hashCode(hash)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FileSnapshot(file=$file, length=$length, hash=${Arrays.toString(hash)})"
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.snapshots
|
||||
|
||||
import org.jetbrains.kotlin.com.intellij.util.io.DataExternalizer
|
||||
import java.io.DataInput
|
||||
import java.io.DataOutput
|
||||
import java.io.File
|
||||
|
||||
internal object FileSnapshotExternalizer : DataExternalizer<FileSnapshot> {
|
||||
override fun save(out: DataOutput, value: FileSnapshot) {
|
||||
out.writeUTF(value.file.canonicalPath)
|
||||
out.writeLong(value.length)
|
||||
out.writeInt(value.hash.size)
|
||||
out.write(value.hash)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): FileSnapshot {
|
||||
val file = File(input.readUTF())
|
||||
val length = input.readLong()
|
||||
val hashSize = input.readInt()
|
||||
val hash = ByteArray(hashSize)
|
||||
input.readFully(hash)
|
||||
return FileSnapshot(file, length, hash)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.snapshots
|
||||
|
||||
import org.jetbrains.kotlin.incremental.ChangedFiles
|
||||
import org.jetbrains.kotlin.incremental.storage.BasicStringMap
|
||||
import org.jetbrains.kotlin.incremental.storage.PathStringDescriptor
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal class FileSnapshotMap(storageFile: File) : BasicStringMap<FileSnapshot>(storageFile, PathStringDescriptor, FileSnapshotExternalizer) {
|
||||
override fun dumpValue(value: FileSnapshot): String =
|
||||
value.toString()
|
||||
|
||||
fun compareAndUpdate(newFiles: Iterable<File>): ChangedFiles.Known {
|
||||
val snapshotProvider = SimpleFileSnapshotProviderImpl()
|
||||
val newOrModified = ArrayList<File>()
|
||||
val removed = ArrayList<File>()
|
||||
|
||||
val newPaths = newFiles.mapTo(HashSet()) { it.canonicalPath }
|
||||
for (oldPath in storage.keys) {
|
||||
if (oldPath !in newPaths) {
|
||||
storage.remove(oldPath)
|
||||
removed.add(File(oldPath))
|
||||
}
|
||||
}
|
||||
|
||||
for (path in newPaths) {
|
||||
val file = File(path)
|
||||
val oldSnapshot = storage[path]
|
||||
val newSnapshot = snapshotProvider[file]
|
||||
|
||||
if (oldSnapshot == null || oldSnapshot != newSnapshot) {
|
||||
newOrModified.add(file)
|
||||
storage[path] = newSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
return ChangedFiles.Known(newOrModified, removed)
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.snapshots
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal interface FileSnapshotProvider {
|
||||
operator fun get(file: File): FileSnapshot
|
||||
}
|
||||
|
||||
internal class SimpleFileSnapshotProviderImpl : FileSnapshotProvider {
|
||||
override fun get(file: File): FileSnapshot {
|
||||
val length = file.length()
|
||||
val hash = file.md5
|
||||
return FileSnapshot(file, length, hash)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.snapshots
|
||||
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
|
||||
internal val File.md5: ByteArray
|
||||
get() {
|
||||
val messageDigest = MessageDigest.getInstance("MD5")
|
||||
val buffer = ByteArray(4048)
|
||||
inputStream().use { input ->
|
||||
while (true) {
|
||||
val len = input.read(buffer)
|
||||
if (len < 0) {
|
||||
break
|
||||
}
|
||||
messageDigest.update(buffer, 0, len)
|
||||
}
|
||||
}
|
||||
return messageDigest.digest()
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import java.io.File
|
||||
|
||||
internal const val STANDALONE_CACHE_VERSION = 0
|
||||
internal const val STANDALONE_VERSION_FILE_NAME = "standalone-ic-format-version.txt"
|
||||
|
||||
internal fun standaloneCacheVersion(dataRoot: File): CacheVersion =
|
||||
CacheVersion(ownVersion = STANDALONE_CACHE_VERSION,
|
||||
versionFile = File(dataRoot, STANDALONE_VERSION_FILE_NAME),
|
||||
whenVersionChanged = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOn = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOff = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||
isEnabled = { IncrementalCompilation.isExperimental() })
|
||||
Reference in New Issue
Block a user