Use KotlinCoreEnvironment for parsing Kotlin in IC

Creating and disposing CoreApplicationEnvironment on each call leads to
problems with parallel builds, while KotlinCoreEnvironment
avoids disposing application environment in case of parallel builds.

    #KT-23694 fixed
This commit is contained in:
Alexey Tsvetkov
2018-04-10 16:08:11 +03:00
parent 13ac64b84a
commit bd50ad87ba
2 changed files with 24 additions and 58 deletions
@@ -5,44 +5,48 @@
package org.jetbrains.kotlin.incremental.parsing package org.jetbrains.kotlin.incremental.parsing
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.core.CoreProjectEnvironment
import com.intellij.lang.MetaLanguage
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.local.CoreLocalFileSystem import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import java.io.File import java.io.File
import java.util.* import java.util.*
fun classesFqNames(files: Set<File>): Set<String> = withPsiSetup { fun classesFqNames(files: Set<File>): Set<String> {
val psiManager = PsiManager.getInstance(project) val existingKotlinFiles = files.filter { it.name.endsWith(".kt", ignoreCase = true) && it.isFile }
if (existingKotlinFiles.isEmpty()) return emptySet()
val disposable = Disposer.newDisposable()
return try {
classesFqNames(existingKotlinFiles, disposable)
} finally {
Disposer.dispose(disposable)
}
}
private fun classesFqNames(kotlinFiles: Collection<File>, disposable: Disposable): Set<String> {
val config = CompilerConfiguration()
val configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES
val environment = KotlinCoreEnvironment.createForProduction(disposable, config, configFiles)
val psiManager = PsiManager.getInstance(environment.project)
val fileManager = VirtualFileManager.getInstance() val fileManager = VirtualFileManager.getInstance()
val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
classesFqNames(files, psiManager, localFS)
}
private fun classesFqNames(files: Collection<File>, psiManager: PsiManager, localFS: CoreLocalFileSystem): Set<String> {
val result = HashSet<String>() val result = HashSet<String>()
for (file in files) { for (file in kotlinFiles) {
if (!file.name.endsWith(".kt", ignoreCase = true)) continue val virtualFile = localFS.findFileByIoFile(file)!!
val virtualFile = localFS.findFileByIoFile(file) ?: continue
for (psiFile in SingleRootFileViewProvider(psiManager, virtualFile).allFiles) { for (psiFile in SingleRootFileViewProvider(psiManager, virtualFile).allFiles) {
if (psiFile !is KtFile) continue if (psiFile !is KtFile) continue
@@ -65,41 +69,3 @@ private fun classesFqNames(files: Collection<File>, psiManager: PsiManager, loca
private fun Collection<KtDeclaration>.filterClassesTo(classes: Deque<KtClassOrObject>) { private fun Collection<KtDeclaration>.filterClassesTo(classes: Deque<KtClassOrObject>) {
filterIsInstanceTo<KtClassOrObject, Deque<KtClassOrObject>>(classes) filterIsInstanceTo<KtClassOrObject, Deque<KtClassOrObject>>(classes)
} }
private data class PsiSetup(
val applicationEnvironment: CoreApplicationEnvironment,
val projectEnvironment: CoreProjectEnvironment,
val project: Project,
val disposable: Disposable
)
private inline fun <T> withPsiSetup(fn: PsiSetup.() -> T): T {
val disposable = Disposer.newDisposable()
return try {
val applicationEnvironment = CoreApplicationEnvironment(disposable, false)
val projectEnvironment = CoreProjectEnvironment(disposable, applicationEnvironment)
CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), MetaLanguage.EP_NAME, MetaLanguage::class.java)
applicationEnvironment.registerApplicationService(ScriptDefinitionProvider::class.java,
NoopScriptDefinitionProvider()
)
applicationEnvironment.registerFileType(KotlinFileType.INSTANCE, "kt")
applicationEnvironment.registerParserDefinition(KotlinParserDefinition())
val project = projectEnvironment.project
val setup = PsiSetup(applicationEnvironment, projectEnvironment, project, disposable)
setup.fn()
} finally {
Disposer.dispose(disposable)
}
}
private class NoopScriptDefinitionProvider : ScriptDefinitionProvider {
override fun isScript(fileName: String): Boolean {
return false
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? {
return null
}
}
@@ -104,7 +104,7 @@ object CompilerRunnerUtil {
environment: JpsCompilerEnvironment, environment: JpsCompilerEnvironment,
files: Set<File> files: Set<File>
): Set<String> = withCompilerClassloader(environment) { classLoader -> ): Set<String> = withCompilerClassloader(environment) { classLoader ->
val klass = Class.forName("org.jetbrains.kotlin.parsing.util.ParseFileUtilsKt", true, classLoader) val klass = Class.forName("org.jetbrains.kotlin.incremental.parsing.ParseFileUtilsKt", true, classLoader)
val method = klass.getMethod("classesFqNames", Set::class.java) val method = klass.getMethod("classesFqNames", Set::class.java)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
method.invoke(klass, files) as? Set<String> method.invoke(klass, files) as? Set<String>