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.1.0-Beta1
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.
 */

// these functions are used in the kotlin gradle plugin
@file:Suppress("unused")

package org.jetbrains.kotlin.incremental

import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
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.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.IncrementalCompilation
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.utils.keysToMap
import java.io.File
import java.util.*


fun Iterable.javaSourceRoots(roots: Iterable): Iterable =
        filter { it.isJavaFile() }
                .map { findSrcDirRoot(it, roots) }
                .filterNotNull()

fun makeModuleFile(name: String, isTest: Boolean, outputDir: File, sourcesToCompile: Iterable, javaSourceRoots: Iterable, classpath: Iterable, friendDirs: Iterable): File {
    val builder = KotlinModuleXmlBuilder()
    builder.addModule(
            name,
            outputDir.absolutePath,
            sourcesToCompile,
            javaSourceRoots.map { JvmSourceRoot(it) },
            classpath,
            "java-production",
            isTest,
            // this excludes the output directories from the class path, to be removed for true incremental compilation
            setOf(outputDir),
            friendDirs
    )

    val scriptFile = File.createTempFile("kjps", StringUtil.sanitizeJavaIdentifier(name) + ".script.xml")
    FileUtil.writeToFile(scriptFile, builder.asText().toString())
    return scriptFile
}

fun makeCompileServices(
        incrementalCaches: Map,
        lookupTracker: LookupTracker,
        compilationCanceledStatus: CompilationCanceledStatus?
): Services =
    with(Services.Builder()) {
        register(IncrementalCompilationComponents::class.java, 
                 IncrementalCompilationComponentsImpl(incrementalCaches, lookupTracker))
        compilationCanceledStatus?.let {
            register(CompilationCanceledStatus::class.java, it)
        }
        build()
    }

fun makeLookupTracker(parentLookupTracker: LookupTracker = LookupTracker.DO_NOTHING): LookupTracker =
        if (IncrementalCompilation.isExperimental()) LookupTrackerImpl(parentLookupTracker)
        else parentLookupTracker

fun makeIncrementalCachesMap(
        targets: Iterable,
        getDependencies: (Target) -> Iterable,
        getCache: (Target) -> IncrementalCacheImpl,
        getTargetId: Target.() -> TargetId
): Map>
{
    val dependents = targets.keysToMap { hashSetOf() }
    val targetsWithDependents = targets.toHashSet()

    for (target in targets) {
        for (dependency in getDependencies(target)) {
            if (dependency !in targets) continue

            dependents[dependency]!!.add(target)
            targetsWithDependents.add(target)
        }
    }

    val caches = targetsWithDependents.keysToMap { getCache(it) }

    for ((target, cache) in caches) {
        dependents[target]?.forEach {
            cache.addDependentCache(caches[it]!!)
        }
    }

    return caches.mapKeys { it.key.getTargetId() }
}

fun updateIncrementalCaches(
        targets: Iterable,
        generatedFiles: List>,
        compiledWithErrors: Boolean,
        getIncrementalCache: (Target) -> IncrementalCacheImpl
): CompilationResult {

    var changesInfo = CompilationResult.NO_CHANGES
    for (generatedFile in generatedFiles) {
        val ic = getIncrementalCache(generatedFile.target)
        when {
            generatedFile is GeneratedJvmClass -> changesInfo += ic.saveFileToCache(generatedFile)
            generatedFile.outputFile.isModuleMappingFile() -> changesInfo += ic.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile)
        }
    }

    if (!compiledWithErrors) {
        targets.forEach {
            val newChangesInfo = getIncrementalCache(it).clearCacheForRemovedClasses()
            changesInfo += newChangesInfo
        }
    }

    return changesInfo
}

fun LookupStorage.update(
        lookupTracker: LookupTracker,
        filesToCompile: Iterable,
        removedFiles: Iterable
) {
    if (lookupTracker !is LookupTrackerImpl) throw AssertionError("Lookup tracker is expected to be LookupTrackerImpl, got ${lookupTracker.javaClass}")

    removeLookupsFrom(filesToCompile.asSequence() + removedFiles.asSequence())

    addAll(lookupTracker.lookups.entrySet(), lookupTracker.pathInterner.values)
}

fun OutputItemsCollectorImpl.generatedFiles(
        targets: Collection,
        representativeTarget: Target,
        getSources: (Target) -> Iterable,
        getOutputDir: (Target) -> File?
): List> {
    // If there's only one target, this map is empty: get() always returns null, and the representativeTarget will be used below
    val sourceToTarget =
            if (targets.size >1) targets.flatMap { target -> getSources(target).map { Pair(it, target) } }.toMap()
            else mapOf()

    return outputs.map { outputItem ->
        val target =
                outputItem.sourceFiles.firstOrNull()?.let { sourceToTarget[it] } ?:
                targets.filter { getOutputDir(it)?.let { outputItem.outputFile.startsWith(it) } ?: false }.singleOrNull() ?:
                representativeTarget
        if (outputItem.outputFile.name.endsWith(".class"))
            GeneratedJvmClass(target, outputItem.sourceFiles, outputItem.outputFile)
        else
            GeneratedFile(target, outputItem.sourceFiles, outputItem.outputFile)
    }
}

data class DirtyData(
        val dirtyLookupSymbols: Collection = emptyList(),
        val dirtyClassesFqNames: Collection = emptyList()
)

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

    for (change in changes) {
        reporter.report { "Process $change" }

        if (change is ChangeInfo.SignatureChanged) {
            val fqNames = if (!change.areSubclassesAffected) listOf(change.fqName) else withSubtypes(change.fqName, caches)

            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) {
                for (fqName in fqNames) {
                    dirtyLookupSymbols.add(LookupSymbol(name, fqName.asString()))
                }
            }
        }
    }

    return DirtyData(dirtyLookupSymbols, dirtyClassesFqNames)
}

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.report { "${lookup.scope}#${lookup.name} caused recompilation of: ${reporter.pathsAsString(affectedFiles)}" }
        dirtyFiles.addAll(affectedFiles)
    }

    return dirtyFiles
}

fun  mapClassesFqNamesToFiles(
        caches: Iterable>,
        classesFqNames: Iterable,
        reporter: ICReporter,
        excludes: Set = emptySet()
): Set {
    val dirtyFiles = HashSet()

    for (cache in caches) {
        for (dirtyClassFqName in classesFqNames) {
            val srcFile = cache.getSourceFileIfClass(dirtyClassFqName)
            if (srcFile == null || srcFile in excludes) continue

            reporter.report { ("Class $dirtyClassFqName caused recompilation of: ${reporter.pathsAsString(srcFile)}") }
            dirtyFiles.add(srcFile)
        }
    }

    return dirtyFiles
}

private fun findSrcDirRoot(file: File, roots: Iterable): File? =
        roots.firstOrNull { FileUtil.isAncestor(it, file, false) }

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

    while (types.isNotEmpty()) {
        val unprocessedType = types.pollFirst()

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

        subtypes.add(unprocessedType)
    }

    return subtypes
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy