org.jetbrains.kotlin.incremental.buildUtil.kt Maven / Gradle / Ivy
/*
* 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.GeneratedFile
import org.jetbrains.kotlin.build.GeneratedJvmClass
import org.jetbrains.kotlin.build.JvmSourceRoot
import org.jetbrains.kotlin.build.isModuleMappingFile
import org.jetbrains.kotlin.build.report.ICReporter
import org.jetbrains.kotlin.build.report.debug
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.modules.KotlinModuleXmlBuilder
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
import java.io.File
import java.nio.file.Files
const val DELETE_MODULE_FILE_PROPERTY = "kotlin.delete.module.file.after.build"
fun makeModuleFile(
name: String,
isTest: Boolean,
outputDir: File,
sourcesToCompile: Iterable,
commonSources: Iterable,
javaSourceRoots: Iterable,
classpath: Iterable,
friendDirs: Iterable,
isIncrementalMode: Boolean = true
): File {
val builder = KotlinModuleXmlBuilder()
builder.addModule(
name,
outputDir.absolutePath,
// important to transform file to absolute paths,
// otherwise compiler will use module file's parent as base path (a temporary file; see below)
// (see org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.getAbsolutePaths)
sourcesToCompile.map { it.absoluteFile },
javaSourceRoots,
classpath,
commonSources.map { it.absoluteFile },
null,
"java-production",
isTest,
// this excludes the output directories from the class path, to be removed for true incremental compilation
setOf(outputDir),
friendDirs,
isIncrementalMode
)
val scriptFile = Files.createTempFile("kjps", sanitizeJavaIdentifier(name) + ".script.xml").toFile()
scriptFile.writeText(builder.asText().toString())
return scriptFile
}
private fun sanitizeJavaIdentifier(string: String) =
buildString {
for (char in string) {
if (char.isJavaIdentifierPart()) {
if (length == 0 && !char.isJavaIdentifierStart()) {
append('_')
}
append(char)
}
}
}
fun makeCompileServices(
incrementalCaches: Map,
lookupTracker: LookupTracker,
compilationCanceledStatus: CompilationCanceledStatus?
): Services =
with(Services.Builder()) {
register(LookupTracker::class.java, lookupTracker)
register(IncrementalCompilationComponents::class.java, IncrementalCompilationComponentsImpl(incrementalCaches))
compilationCanceledStatus?.let {
register(CompilationCanceledStatus::class.java, it)
}
build()
}
fun updateIncrementalCache(
generatedFiles: Iterable,
cache: IncrementalJvmCache,
changesCollector: ChangesCollector,
javaChangesTracker: JavaClassesTrackerImpl?
) {
for (generatedFile in generatedFiles) {
when {
generatedFile is GeneratedJvmClass -> cache.saveFileToCache(generatedFile, changesCollector)
generatedFile.outputFile.isModuleMappingFile() -> cache.saveModuleMappingToCache(
generatedFile.sourceFiles,
generatedFile.outputFile
)
}
}
javaChangesTracker?.javaClassesUpdates?.forEach { (source, serializedJavaClass) ->
cache.saveJavaClassProto(source, serializedJavaClass, changesCollector)
}
cache.clearCacheForRemovedClasses(changesCollector)
}
fun LookupStorage.update(
lookupTracker: LookupTracker,
filesToCompile: Iterable,
removedFiles: Iterable
) {
if (lookupTracker !is LookupTrackerImpl) throw AssertionError("Lookup tracker is expected to be LookupTrackerImpl, got ${lookupTracker::class.java}")
removeLookupsFrom(filesToCompile.asSequence() + removedFiles.asSequence())
addAll(lookupTracker.lookups, lookupTracker.pathInterner.values)
}
data class DirtyData(
val dirtyLookupSymbols: Collection = emptyList(),
val dirtyClassesFqNames: Collection = emptyList(),
val dirtyClassesFqNamesForceRecompile: Collection = emptyList()
)
/**
* Returns changed symbols from the changes collected by this [ChangesCollector].
*
* If impacted symbols are also needed, use [getChangedAndImpactedSymbols].
*/
fun ChangesCollector.getChangedSymbols(reporter: ICReporter): DirtyData {
// Caches are used to compute impacted symbols. Set `caches = emptyList()` so that we get changed symbols only, not impacted ones.
return changes().getChangedAndImpactedSymbols(caches = emptyList(), reporter)
}
/**
* Returns changed and impacted symbols from the changes collected by this [ChangesCollector].
*
* For example, if `Subclass` extends `Superclass` and `Superclass` has changed, `Subclass` will be impacted.
*/
fun ChangesCollector.getChangedAndImpactedSymbols(
caches: Iterable,
reporter: ICReporter
): DirtyData {
return changes().getChangedAndImpactedSymbols(caches, reporter)
}
/**
* Returns changed and impacted symbols from this list of changes.
*
* For example, if `Subclass` extends `Superclass` and `Superclass` has changed, `Subclass` will be impacted.
*/
fun List.getChangedAndImpactedSymbols(
caches: Iterable,
reporter: ICReporter
): DirtyData {
val dirtyLookupSymbols = HashSet()
val dirtyClassesFqNames = HashSet()
val sealedParents = HashSet()
for (change in this) {
reporter.debug { "Process $change" }
if (change is ChangeInfo.SignatureChanged) {
val fqNames = if (!change.areSubclassesAffected) listOf(change.fqName) else withSubtypes(change.fqName, caches)
dirtyClassesFqNames.addAll(fqNames)
for (classFqName in fqNames) {
assert(!classFqName.isRoot) { "$classFqName is root when processing $change" }
val scope = classFqName.parent().asString()
val name = classFqName.shortName().identifier
dirtyLookupSymbols.add(LookupSymbol(name, scope))
}
} else if (change is ChangeInfo.MembersChanged) {
val fqNames = withSubtypes(change.fqName, caches)
// need to recompile subtypes because changed member might break override
dirtyClassesFqNames.addAll(fqNames)
for (name in change.names) {
fqNames.mapTo(dirtyLookupSymbols) { LookupSymbol(name, it.asString()) }
}
fqNames.mapTo(dirtyLookupSymbols) { LookupSymbol(SAM_LOOKUP_NAME.asString(), it.asString()) }
} else if (change is ChangeInfo.ParentsChanged) {
change.parentsChanged.forEach { parent ->
sealedParents.addAll(findSealedSupertypes(parent, caches))
}
}
}
return DirtyData(dirtyLookupSymbols, dirtyClassesFqNames, sealedParents)
}
fun mapLookupSymbolsToFiles(
lookupStorage: LookupStorage,
lookupSymbols: Iterable,
reporter: ICReporter,
excludes: Set = emptySet()
): Set {
val dirtyFiles = HashSet()
for (lookup in lookupSymbols) {
val affectedFiles = lookupStorage.get(lookup).map(::File).filter { it !in excludes }
reporter.reportMarkDirtyMember(affectedFiles, scope = lookup.scope, name = lookup.name)
dirtyFiles.addAll(affectedFiles)
}
return dirtyFiles
}
fun mapClassesFqNamesToFiles(
caches: Iterable,
classesFqNames: Iterable,
reporter: ICReporter,
excludes: Set = emptySet()
): Set {
val fqNameToAffectedFiles = HashMap>()
for (cache in caches) {
for (classFqName in classesFqNames) {
val srcFile = cache.getSourceFileIfClass(classFqName)
if (srcFile == null || srcFile in excludes || srcFile.isJavaFile()) continue
fqNameToAffectedFiles.getOrPut(classFqName) { HashSet() }.add(srcFile)
}
}
for ((classFqName, affectedFiles) in fqNameToAffectedFiles) {
reporter.reportMarkDirtyClass(affectedFiles, classFqName.asString())
}
return fqNameToAffectedFiles.values.flattenTo(HashSet())
}
fun isSealed(
fqName: FqName,
caches: Iterable
): Boolean = caches.any { cache -> cache.isSealed(fqName) ?: false }
/**
* Finds sealed supertypes of class in same module.
* This method should be used for processing freedomOsSealedClasses feature, because
* mutually declared list of sealed subclasses could be declared only in the same module.
*/
fun findSealedSupertypes(
fqName: FqName,
caches: Iterable
): Collection {
if (isSealed(fqName, caches)) {
return listOf(fqName)
}
return caches.flatMap { cache -> cache.getSupertypesOf(fqName).filter { cache.isSealed(it) ?: false }}
}
fun withSubtypes(
typeFqName: FqName,
caches: Iterable
): Set {
val typesToProccess = LinkedHashSet(listOf(typeFqName))
val proccessedTypes = hashSetOf()
while (typesToProccess.isNotEmpty()) {
val iterator = typesToProccess.iterator()
val unprocessedType = iterator.next()
iterator.remove()
caches.asSequence()
.flatMap { it.getSubtypesOf(unprocessedType) }
.filter { it !in proccessedTypes }
.forEach { typesToProccess.add(it) }
proccessedTypes.add(unprocessedType)
}
return proccessedTypes
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy