All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.incremental.buildUtil.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * 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.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
import java.util.*
import kotlin.collections.HashSet
import kotlin.collections.LinkedHashSet

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
): 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
    )

    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()
)

fun ChangesCollector.getDirtyData(
    caches: Iterable,
    reporter: ICReporter
): DirtyData {
    val dirtyLookupSymbols = HashSet()
    val dirtyClassesFqNames = HashSet()

    val sealedParents = HashMap>()
    val notSealedParents = HashSet()

    for (change in changes()) {
        reporter.reportVerbose { "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) {
            fun FqName.isSealed(): Boolean {
                if (notSealedParents.contains(this)) return false
                if (sealedParents.containsKey(this)) return true
                return isSealed(this, caches).also { sealed ->
                    if (sealed) {
                        sealedParents[this] = HashSet()
                    } else {
                        notSealedParents.add(this)
                    }
                }
            }
            change.parentsChanged.forEach { parent ->
                if (parent.isSealed()) {
                    sealedParents.getOrPut(parent) { HashSet() }.add(change.fqName)
                }
            }
        }
    }

    val forceRecompile = HashSet().apply {
        addAll(sealedParents.keys)
        //we should recompile all inheritors with parent sealed class: add known subtypes
        addAll(sealedParents.keys.flatMap { withSubtypes(it, caches) })
        //we should recompile all inheritors with parent sealed class: add new subtypes
        addAll(sealedParents.values.flatten())
    }

    return DirtyData(dirtyLookupSymbols, dirtyClassesFqNames, forceRecompile)
}

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 { it.isSealed(fqName) ?: false }

fun withSubtypes(
    typeFqName: FqName,
    caches: Iterable
): Set {
    val types = LinkedHashSet(listOf(typeFqName))
    val subtypes = hashSetOf()

    while (types.isNotEmpty()) {
        val iterator = types.iterator()
        val unprocessedType = iterator.next()
        iterator.remove()

        caches.asSequence()
            .flatMap { it.getSubtypesOf(unprocessedType) }
            .filter { it !in subtypes }
            .forEach { types.add(it) }

        subtypes.add(unprocessedType)
    }

    return subtypes
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy