[CLI] Extract classes of compiler configuration from :compiler:cli to the separate module
Those classes mainly include KotlinCoreEnvironment and its dependencies
This change is needed for two reasons:
1. Splitting of some common configuration of compiler from logic of CLI
makes code structure more clean
2. There is a need to add dependency on `:analysis:analysis-api-standalone`
to `:compiler:cli`, because FIR analogue of AnalysysHandlerExtension uses
services from it. But the problems is that standalone AA itself depends
on classes for compiler configuration, which leads to circular
dependency between those modules. Extracting configuration to
`:compiler:cli-base` solves the problem
This commit is contained in:
committed by
Space Team
parent
507ef3e951
commit
b6a41c6d93
@@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":compiler:cli-common"))
|
||||
api(project(":compiler:resolution.common"))
|
||||
api(project(":compiler:frontend.java"))
|
||||
api(project(":compiler:frontend:cfg"))
|
||||
api(project(":compiler:ir.backend.common"))
|
||||
api(project(":compiler:backend.jvm"))
|
||||
api(project(":compiler:light-classes"))
|
||||
api(project(":compiler:javac-wrapper"))
|
||||
api(project(":compiler:ir.serialization.jvm"))
|
||||
api(project(":js:js.translator"))
|
||||
api(project(":native:frontend.native"))
|
||||
api(project(":wasm:wasm.frontend"))
|
||||
api(project(":kotlin-util-klib"))
|
||||
api(project(":kotlin-util-klib-metadata"))
|
||||
|
||||
compileOnly(toolsJarApi())
|
||||
compileOnly(intellijCore())
|
||||
compileOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
|
||||
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" {
|
||||
projectDefault()
|
||||
}
|
||||
"test" { none() }
|
||||
}
|
||||
|
||||
allprojects {
|
||||
optInToExperimentalCompilerApi()
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.4"
|
||||
apiVersion = "1.4"
|
||||
freeCompilerArgs = freeCompilerArgs - "-progressive" + listOf(
|
||||
"-Xskip-prerelease-check", "-Xsuppress-version-warnings", "-Xuse-mixed-named-arguments"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
testsJar {}
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.common;
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig;
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfigurationService;
|
||||
import org.jetbrains.kotlin.cli.common.config.ContentRoot;
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class CLIConfigurationKeys {
|
||||
// Roots, including dependencies and own sources
|
||||
public static final CompilerConfigurationKey<List<ContentRoot>> CONTENT_ROOTS =
|
||||
CompilerConfigurationKey.create("content roots");
|
||||
|
||||
public static final CompilerConfigurationKey<MessageCollector> MESSAGE_COLLECTOR_KEY =
|
||||
CompilerConfigurationKey.create("message collector");
|
||||
|
||||
// Used by compiler plugins to access delegated message collector in GroupingMessageCollector
|
||||
public static final CompilerConfigurationKey<MessageCollector> ORIGINAL_MESSAGE_COLLECTOR_KEY =
|
||||
CompilerConfigurationKey.create("original message collector");
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> RENDER_DIAGNOSTIC_INTERNAL_NAME =
|
||||
CompilerConfigurationKey.create("render diagnostic internal name");
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> ALLOW_KOTLIN_PACKAGE =
|
||||
CompilerConfigurationKey.create("allow kotlin package");
|
||||
public static final CompilerConfigurationKey<CommonCompilerPerformanceManager> PERF_MANAGER =
|
||||
CompilerConfigurationKey.create("performance manager");
|
||||
|
||||
// Used in Eclipse plugin (see KotlinCLICompiler)
|
||||
public static final CompilerConfigurationKey<String> INTELLIJ_PLUGIN_ROOT =
|
||||
CompilerConfigurationKey.create("intellij plugin root");
|
||||
|
||||
// See K2MetadataCompilerArguments
|
||||
|
||||
public static final CompilerConfigurationKey<File> METADATA_DESTINATION_DIRECTORY =
|
||||
CompilerConfigurationKey.create("metadata destination directory");
|
||||
|
||||
public static final CompilerConfigurationKey<PhaseConfig> PHASE_CONFIG =
|
||||
CompilerConfigurationKey.create("phase configuration");
|
||||
|
||||
public static final CompilerConfigurationKey<PhaseConfigurationService> FLEXIBLE_PHASE_CONFIG =
|
||||
CompilerConfigurationKey.create("flexible phase configuration");
|
||||
|
||||
public static final CompilerConfigurationKey<Integer> REPEAT_COMPILE_MODULES =
|
||||
CompilerConfigurationKey.create("debug key for profiling, repeats compileModules");
|
||||
|
||||
// used in FIR IDE uast tests
|
||||
public static final CompilerConfigurationKey<File> PATH_TO_KOTLIN_COMPILER_JAR =
|
||||
CompilerConfigurationKey.create("jar of Kotlin compiler in Kotlin plugin");
|
||||
|
||||
private CLIConfigurationKeys() {
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common
|
||||
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter
|
||||
import java.io.File
|
||||
import java.lang.management.GarbageCollectorMXBean
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class CommonCompilerPerformanceManager(private val presentableName: String) {
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
protected val measurements: MutableList<PerformanceMeasurement> = mutableListOf()
|
||||
protected var isEnabled: Boolean = false
|
||||
private var initStartNanos = PerformanceCounter.currentTime()
|
||||
private var analysisStart: Long = 0
|
||||
private var generationStart: Long = 0
|
||||
|
||||
private var startGCData = mutableMapOf<String, GCData>()
|
||||
|
||||
private var irTranslationStart: Long = 0
|
||||
private var irLoweringStart: Long = 0
|
||||
private var irGenerationStart: Long = 0
|
||||
|
||||
private var targetDescription: String? = null
|
||||
protected var files: Int? = null
|
||||
protected var lines: Int? = null
|
||||
|
||||
fun getTargetInfo(): String =
|
||||
"$targetDescription, $files files ($lines lines)"
|
||||
|
||||
fun getMeasurementResults(): List<PerformanceMeasurement> = measurements
|
||||
|
||||
fun enableCollectingPerformanceStatistics() {
|
||||
isEnabled = true
|
||||
PerformanceCounter.setTimeCounterEnabled(true)
|
||||
ManagementFactory.getGarbageCollectorMXBeans().associateTo(startGCData) { it.name to GCData(it) }
|
||||
}
|
||||
|
||||
private fun deltaTime(start: Long): Long = PerformanceCounter.currentTime() - start
|
||||
|
||||
open fun notifyCompilerInitialized(files: Int, lines: Int, targetDescription: String) {
|
||||
if (!isEnabled) return
|
||||
recordInitializationTime()
|
||||
|
||||
this.files = files
|
||||
this.lines = lines
|
||||
this.targetDescription = targetDescription
|
||||
}
|
||||
|
||||
open fun notifyCompilationFinished() {
|
||||
if (!isEnabled) return
|
||||
recordGcTime()
|
||||
recordJitCompilationTime()
|
||||
recordPerfCountersMeasurements()
|
||||
}
|
||||
|
||||
open fun addSourcesStats(files: Int, lines: Int) {
|
||||
if (!isEnabled) return
|
||||
this.files = this.files?.plus(files) ?: files
|
||||
this.lines = this.lines?.plus(lines) ?: lines
|
||||
}
|
||||
|
||||
open fun notifyAnalysisStarted() {
|
||||
analysisStart = PerformanceCounter.currentTime()
|
||||
}
|
||||
|
||||
open fun notifyAnalysisFinished() {
|
||||
val time = PerformanceCounter.currentTime() - analysisStart
|
||||
measurements += CodeAnalysisMeasurement(lines, TimeUnit.NANOSECONDS.toMillis(time))
|
||||
}
|
||||
|
||||
open fun notifyGenerationStarted() {
|
||||
generationStart = PerformanceCounter.currentTime()
|
||||
}
|
||||
|
||||
open fun notifyGenerationFinished() {
|
||||
val time = PerformanceCounter.currentTime() - generationStart
|
||||
measurements += CodeGenerationMeasurement(lines, TimeUnit.NANOSECONDS.toMillis(time))
|
||||
}
|
||||
|
||||
open fun notifyIRTranslationStarted() {
|
||||
irTranslationStart = PerformanceCounter.currentTime()
|
||||
}
|
||||
|
||||
open fun notifyIRTranslationFinished() {
|
||||
val time = deltaTime(irTranslationStart)
|
||||
measurements += IRMeasurement(
|
||||
lines,
|
||||
TimeUnit.NANOSECONDS.toMillis(time),
|
||||
IRMeasurement.Kind.TRANSLATION
|
||||
)
|
||||
}
|
||||
|
||||
open fun notifyIRLoweringStarted() {
|
||||
irLoweringStart = PerformanceCounter.currentTime()
|
||||
}
|
||||
|
||||
open fun notifyIRLoweringFinished() {
|
||||
val time = deltaTime(irLoweringStart)
|
||||
measurements += IRMeasurement(
|
||||
lines,
|
||||
TimeUnit.NANOSECONDS.toMillis(time),
|
||||
IRMeasurement.Kind.LOWERING
|
||||
)
|
||||
}
|
||||
|
||||
open fun notifyIRGenerationStarted() {
|
||||
irGenerationStart = PerformanceCounter.currentTime()
|
||||
}
|
||||
|
||||
open fun notifyIRGenerationFinished() {
|
||||
val time = deltaTime(irGenerationStart)
|
||||
measurements += IRMeasurement(
|
||||
lines,
|
||||
TimeUnit.NANOSECONDS.toMillis(time),
|
||||
IRMeasurement.Kind.GENERATION
|
||||
)
|
||||
}
|
||||
|
||||
fun dumpPerformanceReport(destination: File) {
|
||||
destination.writeBytes(createPerformanceReport())
|
||||
}
|
||||
|
||||
private fun recordGcTime() {
|
||||
if (!isEnabled) return
|
||||
|
||||
ManagementFactory.getGarbageCollectorMXBeans().forEach {
|
||||
val startCounts = startGCData[it.name]
|
||||
val startCollectionTime = startCounts?.collectionTime ?: 0
|
||||
val startCollectionCount = startCounts?.collectionCount ?: 0
|
||||
measurements += GarbageCollectionMeasurement(
|
||||
it.name,
|
||||
it.collectionTime - startCollectionTime,
|
||||
it.collectionCount - startCollectionCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordJitCompilationTime() {
|
||||
if (!isEnabled) return
|
||||
|
||||
val bean = ManagementFactory.getCompilationMXBean() ?: return
|
||||
measurements += JitCompilationMeasurement(bean.totalCompilationTime)
|
||||
}
|
||||
|
||||
private fun recordInitializationTime() {
|
||||
val time = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - initStartNanos)
|
||||
measurements += CompilerInitializationMeasurement(time)
|
||||
}
|
||||
|
||||
private fun recordPerfCountersMeasurements() {
|
||||
PerformanceCounter.report { s -> measurements += PerformanceCounterMeasurement(s) }
|
||||
}
|
||||
|
||||
private fun createPerformanceReport(): ByteArray = buildString {
|
||||
append("$presentableName performance report\n")
|
||||
measurements.map { it.render() }.sorted().forEach { append("$it\n") }
|
||||
}.toByteArray()
|
||||
|
||||
open fun notifyRepeat(total: Int, number: Int) {}
|
||||
|
||||
private data class GCData(val name: String, val collectionTime: Long, val collectionCount: Long) {
|
||||
constructor(bean: GarbageCollectorMXBean) : this(bean.name, bean.collectionTime, bean.collectionCount)
|
||||
}
|
||||
|
||||
fun renderCompilerPerformance(): String {
|
||||
val relevantMeasurements = getMeasurementResults().filter {
|
||||
it is CompilerInitializationMeasurement || it is CodeAnalysisMeasurement || it is CodeGenerationMeasurement || it is PerformanceCounterMeasurement
|
||||
}
|
||||
|
||||
return "Compiler perf stats:\n" + relevantMeasurements.joinToString(separator = "\n") { " ${it.render()}" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.config
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
|
||||
interface ContentRoot
|
||||
|
||||
/**
|
||||
* @param isCommon whether this source root contains sources of a common module in a multi-platform project
|
||||
*/
|
||||
data class KotlinSourceRoot(val path: String, val isCommon: Boolean, val hmppModuleName: String?): ContentRoot
|
||||
|
||||
@JvmOverloads
|
||||
fun CompilerConfiguration.addKotlinSourceRoot(path: String, isCommon: Boolean = false, hmppModuleName: String? = null) {
|
||||
add(CLIConfigurationKeys.CONTENT_ROOTS, KotlinSourceRoot(path, isCommon, hmppModuleName))
|
||||
}
|
||||
|
||||
fun CompilerConfiguration.addKotlinSourceRoots(sources: List<String>): Unit =
|
||||
sources.forEach { addKotlinSourceRoot(it) }
|
||||
|
||||
val CompilerConfiguration.kotlinSourceRoots: List<KotlinSourceRoot>
|
||||
get() = get(CLIConfigurationKeys.CONTENT_ROOTS)?.filterIsInstance<KotlinSourceRoot>().orEmpty()
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.extensions
|
||||
|
||||
import com.intellij.core.JavaCoreProjectEnvironment
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
|
||||
import java.io.File
|
||||
|
||||
interface ReplFactoryExtension {
|
||||
companion object : ProjectExtensionDescriptor<ReplFactoryExtension>(
|
||||
"org.jetbrains.kotlin.replFactoryExtension",
|
||||
ReplFactoryExtension::class.java
|
||||
)
|
||||
|
||||
fun makeReplCompiler(
|
||||
templateClassName: String,
|
||||
templateClasspath: List<File>,
|
||||
baseClassLoader: ClassLoader?,
|
||||
configuration: CompilerConfiguration,
|
||||
projectEnvironment: JavaCoreProjectEnvironment
|
||||
): ReplCompiler
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.extensions
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
|
||||
|
||||
interface ScriptEvaluationExtension {
|
||||
companion object : ProjectExtensionDescriptor<ScriptEvaluationExtension>(
|
||||
"org.jetbrains.kotlin.scriptEvaluationExtension",
|
||||
ScriptEvaluationExtension::class.java
|
||||
)
|
||||
|
||||
fun isAccepted(arguments: CommonCompilerArguments): Boolean
|
||||
|
||||
fun eval(
|
||||
arguments: CommonCompilerArguments,
|
||||
configuration: CompilerConfiguration,
|
||||
projectEnvironment: KotlinCoreEnvironment.ProjectEnvironment
|
||||
): ExitCode
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.extensions
|
||||
|
||||
import com.intellij.core.JavaCoreProjectEnvironment
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
|
||||
|
||||
interface ShellExtension {
|
||||
companion object : ProjectExtensionDescriptor<ShellExtension>(
|
||||
"org.jetbrains.kotlin.shellExtension",
|
||||
ShellExtension::class.java
|
||||
)
|
||||
|
||||
fun isAccepted(arguments: CommonCompilerArguments): Boolean
|
||||
|
||||
fun run(
|
||||
arguments: CommonCompilerArguments,
|
||||
configuration: CompilerConfiguration,
|
||||
projectEnvironment: JavaCoreProjectEnvironment
|
||||
): ExitCode
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.common.messages;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
|
||||
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils;
|
||||
|
||||
import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName;
|
||||
|
||||
public class MessageUtil {
|
||||
private MessageUtil() {}
|
||||
|
||||
@Nullable
|
||||
public static CompilerMessageSourceLocation psiElementToMessageLocation(@Nullable PsiElement element) {
|
||||
if (element == null) return null;
|
||||
PsiFile file = element.getContainingFile();
|
||||
return psiFileToMessageLocation(file, "<no path>", DiagnosticUtils.getLineAndColumnRangeInPsiFile(file, element.getTextRange()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static CompilerMessageSourceLocation psiFileToMessageLocation(
|
||||
@NotNull PsiFile file,
|
||||
@Nullable String defaultValue,
|
||||
@NotNull PsiDiagnosticUtils.LineAndColumnRange range
|
||||
) {
|
||||
VirtualFile virtualFile = file.getVirtualFile();
|
||||
String path = virtualFile != null ? virtualFileToPath(virtualFile) : defaultValue;
|
||||
PsiDiagnosticUtils.LineAndColumn start = range.getStart();
|
||||
PsiDiagnosticUtils.LineAndColumn end = range.getEnd();
|
||||
return createMessageLocation(path, start.getLineContent(), start.getLine(), start.getColumn(), end.getLine(), end.getColumn());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static CompilerMessageLocationWithRange createMessageLocation(
|
||||
@Nullable String path,
|
||||
@Nullable String lineContent,
|
||||
int line,
|
||||
int column,
|
||||
int endLine,
|
||||
int endColumn
|
||||
) {
|
||||
return CompilerMessageLocationWithRange.create(path, line, column, endLine, endColumn, lineContent);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String virtualFileToPath(@NotNull VirtualFile virtualFile) {
|
||||
return toSystemDependentName(virtualFile.getPath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.common
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
|
||||
import org.jetbrains.kotlin.load.kotlin.getSourceElement
|
||||
import org.jetbrains.kotlin.load.kotlin.isContainedByCompiledPartOfOurModule
|
||||
import org.jetbrains.kotlin.modules.Module
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.util.ModuleVisibilityHelper
|
||||
import java.io.File
|
||||
|
||||
class ModuleVisibilityHelperImpl : ModuleVisibilityHelper {
|
||||
override fun isInFriendModule(what: DeclarationDescriptor, from: DeclarationDescriptor): Boolean {
|
||||
val fromSource = getSourceElement(from)
|
||||
// We should check accessibility of 'from' in current module (some set of source files, which are compiled together),
|
||||
// so we can assume that 'from' should have sources or is a LazyPackageDescriptor with some package files.
|
||||
val project: Project = if (fromSource is KotlinSourceElement) {
|
||||
fromSource.psi.project
|
||||
} else {
|
||||
(from as? LazyPackageDescriptor)?.declarationProvider?.getPackageFiles()?.firstOrNull()?.project ?: return true
|
||||
}
|
||||
|
||||
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(project)
|
||||
if (!moduleVisibilityManager.enabled) return true
|
||||
|
||||
moduleVisibilityManager.friendPaths.forEach {
|
||||
if (isContainedByCompiledPartOfOurModule(what, File(it))) return true
|
||||
}
|
||||
|
||||
val modules = moduleVisibilityManager.chunk
|
||||
|
||||
val whatSource = getSourceElement(what)
|
||||
if (whatSource is KotlinSourceElement) {
|
||||
if (modules.size > 1 && fromSource is KotlinSourceElement) {
|
||||
return findModule(what, modules) === findModule(from, modules)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (modules.isEmpty()) return false
|
||||
|
||||
if (modules.size == 1 && isContainedByCompiledPartOfOurModule(what, File(modules.single().getOutputDirectory()))) return true
|
||||
|
||||
return findModule(from, modules) === findModule(what, modules)
|
||||
}
|
||||
|
||||
private fun findModule(descriptor: DeclarationDescriptor, modules: Collection<Module>): Module? {
|
||||
val sourceElement = getSourceElement(descriptor)
|
||||
return if (sourceElement is KotlinSourceElement) {
|
||||
modules.singleOrNull() ?: modules.firstOrNull { sourceElement.psi.containingKtFile.virtualFile.path in it.getSourceFiles() }
|
||||
} else {
|
||||
modules.firstOrNull { module ->
|
||||
isContainedByCompiledPartOfOurModule(descriptor, File(module.getOutputDirectory())) ||
|
||||
module.getFriendPaths().any { isContainedByCompiledPartOfOurModule(descriptor, File(it)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
At the moment, there is no proper support for module infrastructure in the compiler.
|
||||
So we add try to remember given list of interdependent modules and use it for checking visibility.
|
||||
*/
|
||||
class CliModuleVisibilityManagerImpl(override val enabled: Boolean) : ModuleVisibilityManager, Disposable {
|
||||
override val chunk: MutableList<Module> = arrayListOf()
|
||||
override val friendPaths: MutableList<String> = arrayListOf()
|
||||
|
||||
override fun addModule(module: Module) {
|
||||
chunk.add(module)
|
||||
}
|
||||
|
||||
override fun addFriendPath(path: String) {
|
||||
friendPaths.add(File(path).absolutePath)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
chunk.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common
|
||||
|
||||
interface PerformanceMeasurement {
|
||||
fun render(): String
|
||||
}
|
||||
|
||||
class JitCompilationMeasurement(private val milliseconds: Long) : PerformanceMeasurement {
|
||||
override fun render(): String = "JIT time is $milliseconds ms"
|
||||
}
|
||||
|
||||
class CompilerInitializationMeasurement(val milliseconds: Long) : PerformanceMeasurement {
|
||||
override fun render(): String = "INIT: Compiler initialized in $milliseconds ms"
|
||||
}
|
||||
|
||||
class CodeAnalysisMeasurement(val lines: Int?, val milliseconds: Long) : PerformanceMeasurement {
|
||||
override fun render(): String = formatMeasurement("ANALYZE", milliseconds, lines)
|
||||
}
|
||||
|
||||
class CodeGenerationMeasurement(val lines: Int?, val milliseconds: Long) : PerformanceMeasurement {
|
||||
override fun render(): String = formatMeasurement("GENERATE", milliseconds, lines)
|
||||
}
|
||||
|
||||
class GarbageCollectionMeasurement(val garbageCollectionKind: String, val milliseconds: Long, val count: Long) : PerformanceMeasurement {
|
||||
override fun render(): String = "GC time for $garbageCollectionKind is $milliseconds ms, $count collections"
|
||||
}
|
||||
|
||||
class PerformanceCounterMeasurement(private val counterReport: String) : PerformanceMeasurement {
|
||||
override fun render(): String = counterReport
|
||||
}
|
||||
|
||||
class IRMeasurement(val lines: Int?, val milliseconds: Long, val kind: Kind) : PerformanceMeasurement {
|
||||
override fun render(): String = formatMeasurement("IR $kind", milliseconds, lines)
|
||||
|
||||
enum class Kind {
|
||||
TRANSLATION, LOWERING, GENERATION
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatMeasurement(name: String, time: Long, lines: Int?): String =
|
||||
"%15s%8s ms".format(name, time) +
|
||||
(lines?.let {
|
||||
val lps = it.toDouble() * 1000 / time
|
||||
"%12.3f loc/s".format(lps)
|
||||
} ?: "")
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.impl.light.LightJavaModule
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.cli.common.config.ContentRoot
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRootBase
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootBase
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.isValidJavaFqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME
|
||||
import java.io.IOException
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.Manifest
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
|
||||
class ClasspathRootsResolver(
|
||||
private val psiManager: PsiManager,
|
||||
private val messageCollector: MessageCollector?,
|
||||
private val additionalModules: List<String>,
|
||||
private val contentRootToVirtualFile: (JvmContentRootBase) -> VirtualFile?,
|
||||
private val javaModuleFinder: CliJavaModuleFinder,
|
||||
private val requireStdlibModule: Boolean,
|
||||
private val outputDirectory: VirtualFile?,
|
||||
private val javaFileManager: KotlinCliJavaFileManager,
|
||||
private val jdkRelease: Int?
|
||||
) {
|
||||
val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
|
||||
|
||||
private val searchScope = GlobalSearchScope.allScope(psiManager.project)
|
||||
|
||||
data class RootsAndModules(val roots: List<JavaRoot>, val modules: List<JavaModule>)
|
||||
|
||||
private data class RootWithPrefix(val root: VirtualFile, val packagePrefix: String?)
|
||||
|
||||
fun convertClasspathRoots(contentRoots: List<ContentRoot>): RootsAndModules {
|
||||
val javaSourceRoots = mutableListOf<RootWithPrefix>()
|
||||
val jvmClasspathRoots = mutableListOf<VirtualFile>()
|
||||
val jvmModulePathRoots = mutableListOf<VirtualFile>()
|
||||
|
||||
for (contentRoot in contentRoots) {
|
||||
if (contentRoot !is JvmContentRootBase) continue
|
||||
val root = contentRootToVirtualFile(contentRoot) ?: continue
|
||||
when (contentRoot) {
|
||||
is JavaSourceRoot -> javaSourceRoots += RootWithPrefix(root, contentRoot.packagePrefix)
|
||||
is JvmClasspathRootBase -> jvmClasspathRoots += root
|
||||
is JvmModulePathRoot -> jvmModulePathRoots += root
|
||||
else -> error("Unknown root type: $contentRoot")
|
||||
}
|
||||
}
|
||||
|
||||
return computeRoots(javaSourceRoots, jvmClasspathRoots, jvmModulePathRoots)
|
||||
}
|
||||
|
||||
private fun computeRoots(
|
||||
javaSourceRoots: List<RootWithPrefix>,
|
||||
jvmClasspathRoots: List<VirtualFile>,
|
||||
jvmModulePathRoots: List<VirtualFile>
|
||||
): RootsAndModules {
|
||||
val result = mutableListOf<JavaRoot>()
|
||||
val modules = mutableListOf<JavaModule>()
|
||||
|
||||
val hasOutputDirectoryInClasspath = outputDirectory in jvmClasspathRoots || outputDirectory in jvmModulePathRoots
|
||||
|
||||
for ((root, packagePrefix) in javaSourceRoots) {
|
||||
val modularRoot = modularSourceRoot(root, hasOutputDirectoryInClasspath)
|
||||
if (modularRoot != null) {
|
||||
modules += modularRoot
|
||||
} else {
|
||||
result += JavaRoot(root, JavaRoot.RootType.SOURCE, packagePrefix?.let { prefix ->
|
||||
if (isValidJavaFqName(prefix)) FqName(prefix)
|
||||
else null.also {
|
||||
report(STRONG_WARNING, "Invalid package prefix name is ignored: $prefix")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (root in jvmClasspathRoots) {
|
||||
result += JavaRoot(root, JavaRoot.RootType.BINARY)
|
||||
}
|
||||
|
||||
val outputDirectoryAddedAsPartOfModule = modules.any { module -> module.moduleRoots.any { it.file == outputDirectory } }
|
||||
|
||||
for (root in jvmModulePathRoots) {
|
||||
// Do not add output directory as a separate module if we're compiling an explicit named module.
|
||||
// It's going to be included as a root of our module in modularSourceRoot.
|
||||
if (outputDirectoryAddedAsPartOfModule && root == outputDirectory) continue
|
||||
|
||||
val module = modularBinaryRoot(root)
|
||||
if (module != null) {
|
||||
modules += module
|
||||
}
|
||||
}
|
||||
if (jdkRelease == null || jdkRelease >= 9) {
|
||||
addModularRoots(modules, result)
|
||||
} else {
|
||||
//TODO: see also `addJvmSdkRoots` usages, some refactoring is required with moving such logic into one place
|
||||
result += JavaRoot(javaModuleFinder.nonModuleRoot.file, JavaRoot.RootType.BINARY_SIG)
|
||||
}
|
||||
|
||||
return RootsAndModules(result, modules)
|
||||
}
|
||||
|
||||
private fun findSourceModuleInfo(root: VirtualFile): Pair<VirtualFile, PsiJavaModule>? {
|
||||
val moduleInfoFile =
|
||||
when {
|
||||
root.isDirectory -> root.findChild(PsiJavaModule.MODULE_INFO_FILE)
|
||||
root.name == PsiJavaModule.MODULE_INFO_FILE -> root
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
val psiFile = psiManager.findFile(moduleInfoFile) ?: return null
|
||||
val psiJavaModule = psiFile.children.singleOrNull { it is PsiJavaModule } as? PsiJavaModule ?: return null
|
||||
|
||||
return moduleInfoFile to psiJavaModule
|
||||
}
|
||||
|
||||
private fun modularSourceRoot(root: VirtualFile, hasOutputDirectoryInClasspath: Boolean): JavaModule.Explicit? {
|
||||
val (moduleInfoFile, psiJavaModule) = findSourceModuleInfo(root) ?: return null
|
||||
val sourceRoot = JavaModule.Root(root, isBinary = false)
|
||||
val roots =
|
||||
if (hasOutputDirectoryInClasspath)
|
||||
listOf(sourceRoot, JavaModule.Root(outputDirectory!!, isBinary = true))
|
||||
else listOf(sourceRoot)
|
||||
return JavaModule.Explicit(JavaModuleInfo.create(psiJavaModule), roots, moduleInfoFile)
|
||||
}
|
||||
|
||||
private fun modularBinaryRoot(root: VirtualFile): JavaModule? {
|
||||
val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL
|
||||
val manifest = lazy(NONE) { readManifestAttributes(root) }
|
||||
|
||||
val moduleInfoFile =
|
||||
root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
|
||||
?: if (isJar) tryLoadVersionSpecificModuleInfo(root, manifest) else null
|
||||
|
||||
if (moduleInfoFile != null) {
|
||||
val moduleInfo = JavaModuleInfo.read(moduleInfoFile, javaFileManager, searchScope) ?: return null
|
||||
return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(root, isBinary = true)), moduleInfoFile)
|
||||
}
|
||||
|
||||
// Only .jar files can be automatic modules
|
||||
if (isJar) {
|
||||
val moduleRoot = listOf(JavaModule.Root(root, isBinary = true))
|
||||
|
||||
val automaticModuleName = manifest.value?.getValue(AUTOMATIC_MODULE_NAME)
|
||||
if (automaticModuleName != null) {
|
||||
return JavaModule.Automatic(automaticModuleName, moduleRoot)
|
||||
}
|
||||
|
||||
val originalFile = VfsUtilCore.virtualToIoFile(root)
|
||||
val moduleName = LightJavaModule.moduleName(originalFile.nameWithoutExtension)
|
||||
if (moduleName.isEmpty()) {
|
||||
report(ERROR, "Cannot infer automatic module name for the file", VfsUtilCore.getVirtualFileForJar(root) ?: root)
|
||||
return null
|
||||
}
|
||||
return JavaModule.Automatic(moduleName, moduleRoot)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun tryLoadVersionSpecificModuleInfo(root: VirtualFile, manifest: Lazy<Attributes?>): VirtualFile? {
|
||||
val versionsDir = root.findChild("META-INF")?.findChild("versions") ?: return null
|
||||
|
||||
val isMultiReleaseJar = manifest.value?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true)
|
||||
if (isMultiReleaseJar != true) return null
|
||||
|
||||
val versions = versionsDir.children.filter {
|
||||
val version = it.name.toIntOrNull()
|
||||
version != null && version >= 9
|
||||
}.sortedBy { it.name.toInt() }
|
||||
for (version in versions) {
|
||||
val file = version.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
|
||||
if (file != null) return file
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun readManifestAttributes(jarRoot: VirtualFile): Attributes? {
|
||||
val manifestFile = jarRoot.findChild("META-INF")?.findChild("MANIFEST.MF")
|
||||
return try {
|
||||
manifestFile?.inputStream?.let(::Manifest)?.mainAttributes
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun addModularRoots(modules: List<JavaModule>, result: MutableList<JavaRoot>) {
|
||||
// In current implementation, at most one source module is supported. This can be relaxed in the future if we support another
|
||||
// compilation mode, similar to java's --module-source-path
|
||||
val sourceModules = modules.filterIsInstance<JavaModule.Explicit>().filter(JavaModule::isSourceModule)
|
||||
if (sourceModules.size > 1) {
|
||||
for (module in sourceModules) {
|
||||
report(ERROR, "Too many source module declarations found", module.moduleInfoFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for (module in modules) {
|
||||
val existing = javaModuleFinder.findModule(module.name)
|
||||
if (existing == null) {
|
||||
javaModuleFinder.addUserModule(module)
|
||||
} else if (module.moduleRoots != existing.moduleRoots) {
|
||||
fun JavaModule.getRootFile() =
|
||||
moduleRoots.firstOrNull()?.file?.let { VfsUtilCore.getVirtualFileForJar(it) ?: it }
|
||||
|
||||
val thisFile = module.getRootFile()
|
||||
val existingFile = existing.getRootFile()
|
||||
val atExistingPath = if (existingFile == null) "" else " at: ${existingFile.path}"
|
||||
report(
|
||||
STRONG_WARNING, "The root is ignored because a module with the same name '${module.name}' " +
|
||||
"has been found earlier on the module path$atExistingPath", thisFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (javaModuleFinder.allObservableModules.none()) return
|
||||
|
||||
val sourceModule = sourceModules.singleOrNull()
|
||||
val addAllModulePathToRoots = "ALL-MODULE-PATH" in additionalModules
|
||||
if (addAllModulePathToRoots && sourceModule != null) {
|
||||
report(ERROR, "-Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module")
|
||||
return
|
||||
}
|
||||
|
||||
val rootModules = when {
|
||||
sourceModule != null -> listOf(sourceModule.name) + additionalModules
|
||||
addAllModulePathToRoots -> modules.map(JavaModule::name)
|
||||
else -> javaModuleFinder.computeDefaultRootModules() + additionalModules
|
||||
}
|
||||
|
||||
val allDependencies = javaModuleGraph.getAllDependencies(rootModules)
|
||||
if (allDependencies.any { moduleName -> javaModuleFinder.findModule(moduleName) is JavaModule.Automatic }) {
|
||||
// According to java.lang.module javadoc, if at least one automatic module is added to the module graph,
|
||||
// all observable automatic modules should be added.
|
||||
// There are no automatic modules in the JDK, so we select all automatic modules out of user modules
|
||||
for (module in modules) {
|
||||
if (module is JavaModule.Automatic) {
|
||||
allDependencies += module.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
report(LOGGING, "Loading modules: $allDependencies")
|
||||
|
||||
for (moduleName in allDependencies) {
|
||||
val module = javaModuleFinder.findModule(moduleName)
|
||||
if (module == null) {
|
||||
report(ERROR, "Module $moduleName cannot be found in the module graph")
|
||||
} else {
|
||||
result.addAll(module.getJavaModuleRoots())
|
||||
}
|
||||
}
|
||||
|
||||
if (requireStdlibModule && sourceModule != null && !javaModuleGraph.reads(sourceModule.name, KOTLIN_STDLIB_MODULE_NAME)) {
|
||||
report(
|
||||
ERROR,
|
||||
"The Kotlin standard library is not found in the module graph. " +
|
||||
"Please ensure you have the 'requires $KOTLIN_STDLIB_MODULE_NAME' clause in your module definition",
|
||||
sourceModule.moduleInfoFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun report(severity: CompilerMessageSeverity, message: String, file: VirtualFile? = null) {
|
||||
if (messageCollector == null) {
|
||||
throw IllegalStateException("${if (file != null) file.path + ":" else ""}$severity: $message (no MessageCollector configured)")
|
||||
}
|
||||
messageCollector.report(
|
||||
severity, message,
|
||||
if (file == null) null else CompilerMessageLocation.create(MessageUtil.virtualFileToPath(file))
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"
|
||||
const val IS_MULTI_RELEASE = "Multi-Release"
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.PsiSearchScopeUtil
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupportBase
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.KtDescriptorBasedFakeLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||
import org.jetbrains.kotlin.load.java.components.FilesByFacadeFqNameIndexer
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
|
||||
class CliKotlinAsJavaSupport(project: Project, private val traceHolder: CliTraceHolder) : KotlinAsJavaSupportBase<KtFile>(project) {
|
||||
override fun findFilesForFacadeByPackage(
|
||||
packageFqName: FqName,
|
||||
searchScope: GlobalSearchScope
|
||||
) = traceHolder.bindingContext.get(FilesByFacadeFqNameIndexer.FACADE_FILES_BY_PACKAGE_NAME, packageFqName)
|
||||
?.filter { PsiSearchScopeUtil.isInScope(searchScope, it) }
|
||||
.orEmpty()
|
||||
|
||||
override fun KtFile.findModule(): KtFile = this
|
||||
|
||||
override fun createInstanceOfDecompiledLightFacade(facadeFqName: FqName, files: List<KtFile>): KtLightClassForFacade? {
|
||||
error("Should not be called")
|
||||
}
|
||||
|
||||
override fun librariesTracker(element: PsiElement): ModificationTracker {
|
||||
error("Should not be called")
|
||||
}
|
||||
|
||||
override fun createInstanceOfLightFacade(facadeFqName: FqName, files: List<KtFile>): KtLightClassForFacade {
|
||||
return LightClassGenerationSupport.getInstance(files.first().project).createUltraLightClassForFacade(facadeFqName, files)
|
||||
}
|
||||
|
||||
override val KtFile.contentSearchScope: GlobalSearchScope get() = GlobalSearchScope.allScope(project)
|
||||
override fun facadeIsApplicable(module: KtFile, file: KtFile): Boolean = !module.isCompiled
|
||||
|
||||
override fun findFilesForFacade(facadeFqName: FqName, searchScope: GlobalSearchScope): List<KtFile> {
|
||||
if (facadeFqName.isRoot) return emptyList()
|
||||
|
||||
return traceHolder.bindingContext.get(FilesByFacadeFqNameIndexer.FACADE_FILES_BY_FQ_NAME, facadeFqName)?.filter {
|
||||
PsiSearchScopeUtil.isInScope(searchScope, it)
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
|
||||
if (scriptFqName.isRoot) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return findFilesForPackage(scriptFqName.parent(), scope).mapNotNull { file ->
|
||||
file.script?.takeIf { it.fqName == scriptFqName }?.let { getLightClassForScript(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getKotlinInternalClasses(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> = emptyList()
|
||||
|
||||
override fun getFakeLightClass(classOrObject: KtClassOrObject): KtFakeLightClass = KtDescriptorBasedFakeLightClass(classOrObject)
|
||||
|
||||
override fun findClassOrObjectDeclarationsInPackage(
|
||||
packageFqName: FqName, searchScope: GlobalSearchScope
|
||||
): Collection<KtClassOrObject> {
|
||||
val files = findFilesForPackage(packageFqName, searchScope)
|
||||
val result = SmartList<KtClassOrObject>()
|
||||
for (file in files) {
|
||||
for (declaration in file.declarations) {
|
||||
if (declaration is KtClassOrObject) {
|
||||
result.add(declaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun packageExists(fqName: FqName, scope: GlobalSearchScope): Boolean = !traceHolder.module.getPackage(fqName).isEmpty()
|
||||
|
||||
override fun getSubPackages(fqn: FqName, scope: GlobalSearchScope): Collection<FqName> {
|
||||
val packageView = traceHolder.module.getPackage(fqn)
|
||||
return packageView.memberScope.getContributedDescriptors(
|
||||
DescriptorKindFilter.PACKAGES,
|
||||
MemberScope.ALL_NAME_FILTER
|
||||
).mapNotNull { member -> (member as? PackageViewDescriptor)?.fqName }
|
||||
}
|
||||
|
||||
override fun createInstanceOfLightScript(script: KtScript): KtLightClass {
|
||||
return LightClassGenerationSupport.getInstance(script.project).createUltraLightClassForScript(script)
|
||||
}
|
||||
|
||||
override fun findClassOrObjectDeclarations(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtClassOrObject> {
|
||||
return ResolveSessionUtils.getClassDescriptorsByFqName(traceHolder.module, fqName).mapNotNull {
|
||||
val element = DescriptorToSourceUtils.getSourceFromDescriptor(it)
|
||||
if (element is KtClassOrObject && PsiSearchScopeUtil.isInScope(searchScope, element)) {
|
||||
element
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFilesForPackage(packageFqName: FqName, searchScope: GlobalSearchScope): Collection<KtFile> {
|
||||
return traceHolder.bindingContext.get(BindingContext.PACKAGE_TO_FILES, packageFqName)?.filter {
|
||||
PsiSearchScopeUtil.isInScope(searchScope, it)
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
override fun createFacadeForSyntheticFile(file: KtFile): KtLightClassForFacade = error("Should not be called")
|
||||
override fun declarationLocation(file: KtFile): DeclarationLocation = DeclarationLocation.ProjectSources
|
||||
override fun createInstanceOfDecompiledLightClass(classOrObject: KtClassOrObject): KtLightClass = error("Should not be called")
|
||||
override fun createInstanceOfLightClass(classOrObject: KtClassOrObject): KtLightClass {
|
||||
return LightClassGenerationSupport.getInstance(classOrObject.project).createUltraLightClass(classOrObject)
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.KtUltraLightSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.cleanFromAnonymousTypes
|
||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||
import org.jetbrains.kotlin.asJava.classes.tryGetPredefinedName
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.builder.LightClassConstructionContext
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
||||
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.load.java.components.JavaDeprecationSettings
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
/**
|
||||
* This class solves the problem of interdependency between analyzing Kotlin code and generating KotlinLightClasses
|
||||
|
||||
* Consider the following example:
|
||||
|
||||
* KClass.kt refers to JClass.java and vice versa
|
||||
|
||||
* To analyze KClass.kt we need to load descriptors from JClass.java, and to do that we need a JetLightClass instance for KClass,
|
||||
* which can only be constructed when the structure of KClass is known.
|
||||
|
||||
* To mitigate this, CliLightClassGenerationSupport hold a trace that is shared between the analyzer and JetLightClasses
|
||||
*/
|
||||
class CliLightClassGenerationSupport(
|
||||
val traceHolder: CliTraceHolder,
|
||||
private val project: Project
|
||||
) : LightClassGenerationSupport() {
|
||||
|
||||
private class CliLightClassSupport(
|
||||
private val project: Project,
|
||||
override val languageVersionSettings: LanguageVersionSettings,
|
||||
override val jvmTarget: JvmTarget
|
||||
) : KtUltraLightSupport {
|
||||
|
||||
// This is the way to untie CliLightClassSupport and CliLightClassGenerationSupport to prevent descriptors leak
|
||||
private val traceHolder: CliTraceHolder
|
||||
get() = (getInstance(project) as CliLightClassGenerationSupport).traceHolder
|
||||
|
||||
override fun possiblyHasAlias(file: KtFile, shortName: Name): Boolean = true
|
||||
|
||||
override val moduleDescriptor get() = traceHolder.module
|
||||
|
||||
override val moduleName: String get() = JvmCodegenUtil.getModuleName(moduleDescriptor)
|
||||
|
||||
override val deprecationResolver: DeprecationResolver
|
||||
get() = DeprecationResolver(
|
||||
LockBasedStorageManager.NO_LOCKS,
|
||||
languageVersionSettings,
|
||||
JavaDeprecationSettings
|
||||
)
|
||||
|
||||
override val typeMapper: KotlinTypeMapper by lazyPub {
|
||||
KotlinTypeMapper(
|
||||
BindingContext.EMPTY,
|
||||
ClassBuilderMode.LIGHT_CLASSES,
|
||||
moduleName,
|
||||
languageVersionSettings,
|
||||
useOldInlineClassesManglingScheme = false,
|
||||
jvmTarget = jvmTarget,
|
||||
typePreprocessor = KotlinType::cleanFromAnonymousTypes,
|
||||
namePreprocessor = ::tryGetPredefinedName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val ultraLightSupport: KtUltraLightSupport by lazyPub {
|
||||
CliLightClassSupport(project, traceHolder.languageVersionSettings, traceHolder.jvmTarget)
|
||||
}
|
||||
|
||||
override fun getUltraLightClassSupport(element: KtElement): KtUltraLightSupport {
|
||||
require(element.project == project) { "ULC support created from another project from requested" }
|
||||
return ultraLightSupport
|
||||
}
|
||||
|
||||
val context: LightClassConstructionContext
|
||||
get() = LightClassConstructionContext(
|
||||
traceHolder.bindingContext,
|
||||
traceHolder.module,
|
||||
traceHolder.languageVersionSettings,
|
||||
traceHolder.jvmTarget,
|
||||
)
|
||||
|
||||
override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor? {
|
||||
return traceHolder.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)
|
||||
}
|
||||
|
||||
override fun analyze(element: KtElement) = traceHolder.bindingContext
|
||||
|
||||
override fun analyzeAnnotation(element: KtAnnotationEntry) = traceHolder.bindingContext.get(BindingContext.ANNOTATION, element)
|
||||
|
||||
override fun analyzeWithContent(element: KtClassOrObject) = traceHolder.bindingContext
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.js.resolve.getAnnotationsOnContainingJsModule
|
||||
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
|
||||
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
|
||||
|
||||
class CliModuleAnnotationsResolver : ModuleAnnotationsResolver {
|
||||
private val packagePartProviders = mutableListOf<PackagePartProvider>()
|
||||
|
||||
fun addPackagePartProvider(packagePartProvider: PackagePartProvider) {
|
||||
packagePartProviders += packagePartProvider
|
||||
}
|
||||
|
||||
override fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List<ClassId> {
|
||||
getAnnotationsOnContainingJsModule(descriptor)?.let { return it }
|
||||
|
||||
val moduleName = getJvmModuleNameForDeserializedDescriptor(descriptor) ?: return emptyList()
|
||||
return packagePartProviders.flatMap { it.getAnnotationsOnBinaryModule(moduleName) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmCodeAnalyzerInitializer
|
||||
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
|
||||
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
|
||||
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
|
||||
|
||||
class CliTraceHolder : JvmCodeAnalyzerInitializer() {
|
||||
lateinit var bindingContext: BindingContext
|
||||
private set
|
||||
lateinit var module: ModuleDescriptor
|
||||
private set
|
||||
lateinit var languageVersionSettings: LanguageVersionSettings
|
||||
private set
|
||||
lateinit var jvmTarget: JvmTarget
|
||||
private set
|
||||
|
||||
override fun initialize(
|
||||
trace: BindingTrace,
|
||||
module: ModuleDescriptor,
|
||||
codeAnalyzer: KotlinCodeAnalyzer,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
jvmTarget: JvmTarget
|
||||
) {
|
||||
this.bindingContext = trace.bindingContext
|
||||
this.module = module
|
||||
this.languageVersionSettings = languageVersionSettings
|
||||
this.jvmTarget = jvmTarget
|
||||
|
||||
if (trace !is CliBindingTrace) {
|
||||
throw IllegalArgumentException("Shared trace is expected to be subclass of ${CliBindingTrace::class.java.simpleName} class")
|
||||
}
|
||||
|
||||
trace.setKotlinCodeAnalyzer(codeAnalyzer)
|
||||
}
|
||||
|
||||
override fun createTrace(): BindingTraceContext {
|
||||
return NoScopeRecordCliBindingTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: needs better name + list of keys to skip somewhere
|
||||
class NoScopeRecordCliBindingTrace : CliBindingTrace() {
|
||||
override fun <K, V> record(slice: WritableSlice<K, V>, key: K, value: V) {
|
||||
if (slice == BindingContext.LEXICAL_SCOPE || slice == BindingContext.DATA_FLOW_INFO_BEFORE) {
|
||||
// In the compiler there's no need to keep scopes
|
||||
return
|
||||
}
|
||||
super.record(slice, key, value)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return NoScopeRecordCliBindingTrace::class.java.name
|
||||
}
|
||||
}
|
||||
|
||||
open class CliBindingTrace @TestOnly constructor() : BindingTraceContext() {
|
||||
private var kotlinCodeAnalyzer: KotlinCodeAnalyzer? = null
|
||||
|
||||
override fun toString(): String {
|
||||
return CliBindingTrace::class.java.name
|
||||
}
|
||||
|
||||
fun setKotlinCodeAnalyzer(kotlinCodeAnalyzer: KotlinCodeAnalyzer) {
|
||||
this.kotlinCodeAnalyzer = kotlinCodeAnalyzer
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? {
|
||||
val value = super.get(slice, key)
|
||||
|
||||
if (value == null) {
|
||||
if (key is KtDeclaration) {
|
||||
// NB: intentional code duplication, see https://youtrack.jetbrains.com/issue/KT-43296
|
||||
if (BindingContext.FUNCTION === slice) {
|
||||
if (!KtPsiUtil.isLocal(key)) {
|
||||
kotlinCodeAnalyzer!!.resolveToDescriptor(key)
|
||||
return super.get(slice, key) as V?
|
||||
}
|
||||
}
|
||||
if (BindingContext.VARIABLE === slice) {
|
||||
if (!KtPsiUtil.isLocal(key)) {
|
||||
kotlinCodeAnalyzer!!.resolveToDescriptor(key)
|
||||
return super.get(slice, key) as V?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import gnu.trove.THashSet
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
|
||||
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
|
||||
import java.io.InputStream
|
||||
|
||||
class CliVirtualFileFinder(
|
||||
private val index: JvmDependenciesIndex,
|
||||
private val scope: GlobalSearchScope,
|
||||
private val enableSearchInCtSym: Boolean
|
||||
) : VirtualFileFinder() {
|
||||
override fun findVirtualFileWithHeader(classId: ClassId): VirtualFile? =
|
||||
findBinaryOrSigClass(classId)
|
||||
|
||||
override fun findSourceOrBinaryVirtualFile(classId: ClassId) =
|
||||
findBinaryOrSigClass(classId)
|
||||
?: findSourceClass(classId, classId.relativeClassName.asString() + ".java")
|
||||
|
||||
override fun findMetadata(classId: ClassId): InputStream? {
|
||||
assert(!classId.isNestedClass) { "Nested classes are not supported here: $classId" }
|
||||
|
||||
return findBinaryClass(
|
||||
classId,
|
||||
classId.shortClassName.asString() + MetadataPackageFragment.DOT_METADATA_FILE_EXTENSION
|
||||
)?.inputStream
|
||||
}
|
||||
|
||||
override fun findMetadataTopLevelClassesInPackage(packageFqName: FqName): Set<String> {
|
||||
val result = THashSet<String>()
|
||||
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
|
||||
for (child in dir.children) {
|
||||
if (child.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION) {
|
||||
result.add(child.nameWithoutExtension)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun hasMetadataPackage(fqName: FqName): Boolean {
|
||||
var found = false
|
||||
index.traverseDirectoriesInPackage(fqName, continueSearch = { dir, _ ->
|
||||
found = found or dir.children.any { it.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION }
|
||||
!found
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
override fun findBuiltInsData(packageFqName: FqName): InputStream? {
|
||||
// "<builtins-metadata>" is just a made-up name
|
||||
// JvmDependenciesIndex requires the ClassId of the class which we're searching for, to cache the last request+result
|
||||
val classId = ClassId(packageFqName, Name.special("<builtins-metadata>"))
|
||||
|
||||
return findBinaryClass(classId, BuiltInSerializerProtocol.getBuiltInsFileName(packageFqName))?.inputStream
|
||||
}
|
||||
|
||||
private fun findClass(classId: ClassId, fileName: String, rootType: Set<JavaRoot.RootType>) =
|
||||
index.findClass(classId, acceptedRootTypes = rootType) { dir, _ ->
|
||||
dir.findChild(fileName)?.takeIf(VirtualFile::isValid)
|
||||
}?.takeIf { it in scope }
|
||||
|
||||
private fun findSigFileIfEnabled(
|
||||
dir: VirtualFile,
|
||||
simpleName: String
|
||||
) = if (enableSearchInCtSym) dir.findChild("$simpleName.sig") else null
|
||||
|
||||
private fun findBinaryOrSigClass(classId: ClassId, simpleName: String, rootType: Set<JavaRoot.RootType>) =
|
||||
index.findClass(classId, acceptedRootTypes = rootType) { dir, _ ->
|
||||
val file = dir.findChild("$simpleName.class") ?: findSigFileIfEnabled(dir, simpleName)
|
||||
if (file != null && file.isValid) file else null
|
||||
}?.takeIf { it in scope }
|
||||
|
||||
private fun findBinaryOrSigClass(classId: ClassId) =
|
||||
findBinaryOrSigClass(classId, classId.relativeClassName.asString().replace('.', '$'), JavaRoot.OnlyBinary)
|
||||
|
||||
private fun findBinaryClass(classId: ClassId, fileName: String) = findClass(classId, fileName, JavaRoot.OnlyBinary)
|
||||
private fun findSourceClass(classId: ClassId, fileName: String) = findClass(classId, fileName, JavaRoot.OnlySource)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
|
||||
|
||||
// TODO: create different JvmDependenciesIndex instances for different sets of source roots to improve performance
|
||||
class CliVirtualFileFinderFactory(private val index: JvmDependenciesIndex, private val enableSearchInCtSym: Boolean) : VirtualFileFinderFactory {
|
||||
override fun create(scope: GlobalSearchScope): VirtualFileFinder = CliVirtualFileFinder(index, scope, enableSearchInCtSym)
|
||||
|
||||
override fun create(project: Project, module: ModuleDescriptor): VirtualFileFinder =
|
||||
CliVirtualFileFinder(index, GlobalSearchScope.allScope(project), enableSearchInCtSym)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.compiler;
|
||||
|
||||
public enum EnvironmentConfigFiles {
|
||||
JVM_CONFIG_FILES,
|
||||
JS_CONFIG_FILES,
|
||||
NATIVE_CONFIG_FILES,
|
||||
WASM_CONFIG_FILES,
|
||||
METADATA_CONFIG_FILES,
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.core.CoreApplicationEnvironment
|
||||
import com.intellij.lang.jvm.facade.JvmElementProvider
|
||||
import com.intellij.openapi.extensions.ExtensionsArea
|
||||
import com.intellij.psi.JavaModuleSystem
|
||||
import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicy
|
||||
|
||||
internal object IdeaExtensionPoints {
|
||||
fun registerVersionSpecificAppExtensionPoints(area: ExtensionsArea) {
|
||||
@Suppress("DEPRECATION")
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, ClsCustomNavigationPolicy.EP_NAME, ClsCustomNavigationPolicy::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, JavaModuleSystem.EP_NAME, JavaModuleSystem::class.java)
|
||||
}
|
||||
|
||||
fun registerVersionSpecificProjectExtensionPoints(area: ExtensionsArea) {
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, JvmElementProvider.EP_NAME, JvmElementProvider::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.LanguageLevelProjectExtension
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
|
||||
fun Project.setupHighestLanguageLevel() {
|
||||
LanguageLevelProjectExtension.getInstance(this).languageLevel =
|
||||
LanguageLevel.values().firstOrNull { it.name == "JDK_17" }
|
||||
?: LanguageLevel.values().firstOrNull { it.name == "JDK_15_PREVIEW" }
|
||||
?: LanguageLevel.JDK_X
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.LOGGING
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartProviderBase
|
||||
import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
|
||||
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmCompilerDeserializationConfiguration
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.EOFException
|
||||
import java.io.PrintStream
|
||||
|
||||
class JvmPackagePartProvider(
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
private val scope: GlobalSearchScope
|
||||
) : JvmPackagePartProviderBase<VirtualFile>() {
|
||||
override val deserializationConfiguration = JvmCompilerDeserializationConfiguration(languageVersionSettings)
|
||||
|
||||
override val loadedModules: MutableList<ModuleMappingInfo<VirtualFile>> = SmartList()
|
||||
|
||||
fun addRoots(roots: List<JavaRoot>, messageCollector: MessageCollector) {
|
||||
for ((root, type) in roots) {
|
||||
if (type != JavaRoot.RootType.BINARY) continue
|
||||
if (root !in scope) continue
|
||||
|
||||
val metaInf = root.findChild("META-INF") ?: continue
|
||||
for (moduleFile in metaInf.children) {
|
||||
if (!moduleFile.name.endsWith(ModuleMapping.MAPPING_FILE_EXT)) continue
|
||||
|
||||
tryLoadModuleMapping(
|
||||
{ moduleFile.contentsToByteArray() }, moduleFile.toString(), moduleFile.path,
|
||||
deserializationConfiguration, messageCollector
|
||||
)?.let {
|
||||
loadedModules.add(ModuleMappingInfo(root, it, moduleFile.nameWithoutExtension))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tryLoadModuleMapping(
|
||||
getModuleBytes: () -> ByteArray,
|
||||
debugName: String,
|
||||
modulePath: String,
|
||||
deserializationConfiguration: CompilerDeserializationConfiguration,
|
||||
messageCollector: MessageCollector
|
||||
): ModuleMapping? = try {
|
||||
ModuleMapping.loadModuleMapping(getModuleBytes(), debugName, deserializationConfiguration) { incompatibleVersion ->
|
||||
messageCollector.report(
|
||||
ERROR,
|
||||
"Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is " +
|
||||
"$incompatibleVersion, expected version is ${JvmMetadataVersion.INSTANCE}.",
|
||||
CompilerMessageLocation.create(modulePath)
|
||||
)
|
||||
}
|
||||
} catch (e: EOFException) {
|
||||
messageCollector.report(
|
||||
ERROR, "Error occurred when reading the module: ${e.message}", CompilerMessageLocation.create(modulePath)
|
||||
)
|
||||
messageCollector.report(
|
||||
LOGGING,
|
||||
String(ByteArrayOutputStream().also { e.printStackTrace(PrintStream(it)) }.toByteArray()),
|
||||
CompilerMessageLocation.create(modulePath)
|
||||
)
|
||||
null
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.core.CoreJavaFileManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.file.PsiPackageImpl
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import gnu.trove.THashMap
|
||||
import gnu.trove.THashSet
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
|
||||
import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex
|
||||
import org.jetbrains.kotlin.load.java.JavaClassFinder
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.isNotTopLevelClass
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.util.*
|
||||
|
||||
// TODO: do not inherit from CoreJavaFileManager to avoid accidental usage of its methods which do not use caches/indices
|
||||
// Currently, the only relevant usage of this class as CoreJavaFileManager is at CoreJavaDirectoryService.getPackage,
|
||||
// which is indirectly invoked from PsiPackage.getSubPackages
|
||||
class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager {
|
||||
private val perfCounter = PerformanceCounter.create("Find Java class")
|
||||
private lateinit var index: JvmDependenciesIndex
|
||||
private lateinit var singleJavaFileRootsIndex: SingleJavaFileRootsIndex
|
||||
private lateinit var packagePartProviders: List<JvmPackagePartProvider>
|
||||
private val topLevelClassesCache: MutableMap<FqName, VirtualFile?> = THashMap()
|
||||
private val allScope = GlobalSearchScope.allScope(myPsiManager.project)
|
||||
private var usePsiClassFilesReading = false
|
||||
|
||||
fun initialize(
|
||||
index: JvmDependenciesIndex,
|
||||
packagePartProviders: List<JvmPackagePartProvider>,
|
||||
singleJavaFileRootsIndex: SingleJavaFileRootsIndex,
|
||||
usePsiClassFilesReading: Boolean
|
||||
) {
|
||||
this.index = index
|
||||
this.packagePartProviders = packagePartProviders
|
||||
this.singleJavaFileRootsIndex = singleJavaFileRootsIndex
|
||||
this.usePsiClassFilesReading = usePsiClassFilesReading
|
||||
}
|
||||
|
||||
private fun findPsiClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? = perfCounter.time {
|
||||
findVirtualFileForTopLevelClass(classId, searchScope)?.findPsiClassInVirtualFile(classId.relativeClassName.asString())
|
||||
}
|
||||
|
||||
private fun findVirtualFileForTopLevelClass(classId: ClassId, searchScope: GlobalSearchScope): VirtualFile? {
|
||||
val relativeClassName = classId.relativeClassName.asString()
|
||||
val outerMostClassFqName = classId.packageFqName.child(classId.relativeClassName.pathSegments().first())
|
||||
return topLevelClassesCache.getOrPut(outerMostClassFqName) {
|
||||
// Search java sources first. For build tools, it makes sense to build new files passing all the
|
||||
// class files for the previous build on the class path.
|
||||
//
|
||||
// Suppose we have A.java and B.kt, we compile them and have the class files in previous.jar.
|
||||
// Now we change both. A field is added to A which is used from B.kt.
|
||||
//
|
||||
// For a compilation such as:
|
||||
//
|
||||
// kotlinc -cp previous.jar A.java B.kt
|
||||
//
|
||||
// we want to make sure that we get the new A.java and not the old version A.class from previous.jar.
|
||||
//
|
||||
// Otherwise B.kt will not see the newly added field in A.
|
||||
val outerMostClassId = ClassId.topLevel(outerMostClassFqName)
|
||||
singleJavaFileRootsIndex.findJavaSourceClass(outerMostClassId)
|
||||
?: index.findClass(outerMostClassId) { dir, type ->
|
||||
findVirtualFileGivenPackage(dir, relativeClassName, type)
|
||||
}
|
||||
|
||||
}?.takeIf { it in searchScope }
|
||||
}
|
||||
|
||||
private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
|
||||
private val signatureParsingComponent = BinaryClassSignatureParser()
|
||||
|
||||
fun findClass(classId: ClassId, searchScope: GlobalSearchScope) = findClass(JavaClassFinder.Request(classId), searchScope)
|
||||
|
||||
override fun findClass(request: JavaClassFinder.Request, searchScope: GlobalSearchScope): JavaClass? {
|
||||
val (classId, classFileContentFromRequest, outerClassFromRequest) = request
|
||||
val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
|
||||
|
||||
if (!usePsiClassFilesReading && (virtualFile.extension == "class" || virtualFile.extension == "sig")) {
|
||||
// We return all class files' names in the directory in knownClassNamesInPackage method, so one may request an inner class
|
||||
return binaryCache.getOrPut(classId) {
|
||||
// Note that currently we implicitly suppose that searchScope for binary classes is constant and we do not use it
|
||||
// as a key in cache
|
||||
// This is a true assumption by now since there are two search scopes in compiler: one for sources and another one for binary
|
||||
// When it become wrong because we introduce the modules into CLI, it's worth to consider
|
||||
// having different KotlinCliJavaFileManagerImpl's for different modules
|
||||
|
||||
classId.outerClassId?.let { outerClassId ->
|
||||
val outerClass = outerClassFromRequest ?: findClass(outerClassId, searchScope)
|
||||
|
||||
return@getOrPut if (outerClass is BinaryJavaClass)
|
||||
outerClass.findInnerClass(classId.shortClassName, classFileContentFromRequest)
|
||||
else
|
||||
outerClass?.findInnerClass(classId.shortClassName)
|
||||
}
|
||||
|
||||
// Here, we assume the class is top-level
|
||||
val classContent = classFileContentFromRequest ?: virtualFile.contentsToByteArray()
|
||||
if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
|
||||
|
||||
val resolver = ClassifierResolutionContext { findClass(it, allScope) }
|
||||
|
||||
BinaryJavaClass(
|
||||
virtualFile, classId.asSingleFqName(), resolver, signatureParsingComponent,
|
||||
outerClass = null, classContent = classContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return virtualFile.findPsiClassInVirtualFile(classId.relativeClassName.asString())?.let(::JavaClassImpl)
|
||||
}
|
||||
|
||||
// this method is called from IDEA to resolve dependencies in Java code
|
||||
// which supposedly shouldn't have errors so the dependencies exist in general
|
||||
override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? {
|
||||
// String cannot be reliably converted to ClassId because we don't know where the package name ends and class names begin.
|
||||
// For example, if qName is "a.b.c.d.e", we should either look for a top level class "e" in the package "a.b.c.d",
|
||||
// or, for example, for a nested class with the relative qualified name "c.d.e" in the package "a.b".
|
||||
// Below, we start by looking for the top level class "e" in the package "a.b.c.d" first, then for the class "d.e" in the package
|
||||
// "a.b.c", and so on, until we find something. Most classes are top level, so most of the times the search ends quickly
|
||||
|
||||
forEachClassId(qName) { classId ->
|
||||
findPsiClass(classId, scope)?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private inline fun forEachClassId(fqName: String, block: (ClassId) -> Unit) {
|
||||
var classId = fqName.toSafeTopLevelClassId() ?: return
|
||||
|
||||
while (true) {
|
||||
block(classId)
|
||||
|
||||
val packageFqName = classId.packageFqName
|
||||
if (packageFqName.isRoot) break
|
||||
|
||||
classId = ClassId(
|
||||
packageFqName.parent(),
|
||||
FqName(packageFqName.shortName().asString() + "." + classId.relativeClassName.asString()),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findClasses(qName: String, scope: GlobalSearchScope): Array<PsiClass> = perfCounter.time {
|
||||
val result = ArrayList<PsiClass>(1)
|
||||
forEachClassId(qName) { classId ->
|
||||
val relativeClassName = classId.relativeClassName.asString()
|
||||
|
||||
// Search java sources first. For build tools, it makes sense to build new files passing all the
|
||||
// class files for the previous build on the class path.
|
||||
result.addIfNotNull(
|
||||
singleJavaFileRootsIndex.findJavaSourceClass(classId)
|
||||
?.takeIf { it in scope }
|
||||
?.findPsiClassInVirtualFile(relativeClassName)
|
||||
)
|
||||
|
||||
index.traverseDirectoriesInPackage(classId.packageFqName) { dir, rootType ->
|
||||
val psiClass =
|
||||
findVirtualFileGivenPackage(dir, relativeClassName, rootType)
|
||||
?.takeIf { it in scope }
|
||||
?.findPsiClassInVirtualFile(relativeClassName)
|
||||
if (psiClass != null) {
|
||||
result.add(psiClass)
|
||||
}
|
||||
// traverse all
|
||||
true
|
||||
}
|
||||
|
||||
if (result.isNotEmpty()) {
|
||||
return@time result.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
PsiClass.EMPTY_ARRAY
|
||||
}
|
||||
|
||||
override fun findPackage(packageName: String): PsiPackage? {
|
||||
var found = false
|
||||
val packageFqName = packageName.toSafeFqName() ?: return null
|
||||
index.traverseDirectoriesInPackage(packageFqName) { _, _ ->
|
||||
found = true
|
||||
//abort on first found
|
||||
false
|
||||
}
|
||||
if (!found) {
|
||||
found = packagePartProviders.any { it.findPackageParts(packageName).isNotEmpty() }
|
||||
}
|
||||
if (!found) {
|
||||
found = singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName).isNotEmpty()
|
||||
}
|
||||
|
||||
if (!found) return null
|
||||
|
||||
return object : PsiPackageImpl(myPsiManager, packageName) {
|
||||
// Do not check validness for packages we just made sure are actually present
|
||||
// It might be important for source roots that have non-trivial package prefix
|
||||
override fun isValid() = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun findVirtualFileGivenPackage(
|
||||
packageDir: VirtualFile,
|
||||
classNameWithInnerClasses: String,
|
||||
rootType: JavaRoot.RootType
|
||||
): VirtualFile? {
|
||||
val topLevelClassName = classNameWithInnerClasses.substringBefore('.')
|
||||
|
||||
val vFile = when (rootType) {
|
||||
JavaRoot.RootType.BINARY -> packageDir.findChild("$topLevelClassName.class")
|
||||
JavaRoot.RootType.BINARY_SIG -> packageDir.findChild("$topLevelClassName.sig")
|
||||
JavaRoot.RootType.SOURCE -> packageDir.findChild("$topLevelClassName.java")
|
||||
} ?: return null
|
||||
|
||||
if (!vFile.isValid) {
|
||||
LOG.error("Invalid child of valid parent: ${vFile.path}; ${packageDir.isValid} path=${packageDir.path}")
|
||||
return null
|
||||
}
|
||||
|
||||
return vFile
|
||||
}
|
||||
|
||||
private fun VirtualFile.findPsiClassInVirtualFile(classNameWithInnerClasses: String): PsiClass? {
|
||||
val file = myPsiManager.findFile(this) as? PsiClassOwner ?: return null
|
||||
return findClassInPsiFile(classNameWithInnerClasses, file)
|
||||
}
|
||||
|
||||
override fun knownClassNamesInPackage(packageFqName: FqName): Set<String> {
|
||||
val result = THashSet<String>()
|
||||
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
|
||||
for (child in dir.children) {
|
||||
if (child.extension == "class" || child.extension == "java" || child.extension == "sig") {
|
||||
result.add(child.nameWithoutExtension)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
|
||||
for (classId in singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName)) {
|
||||
assert(!classId.isNestedClass) { "ClassId of a single .java source class should not be nested: $classId" }
|
||||
result.add(classId.shortClassName.asString())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun findModules(moduleName: String, scope: GlobalSearchScope): Collection<PsiJavaModule> {
|
||||
// TODO
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
override fun getNonTrivialPackagePrefixes(): Collection<String> = emptyList()
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinCliJavaFileManagerImpl::class.java)
|
||||
|
||||
private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? {
|
||||
for (topLevelClass in file.classes) {
|
||||
val candidate = findClassByTopLevelClass(classNameWithInnerClassesDotSeparated, topLevelClass)
|
||||
if (candidate != null) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findClassByTopLevelClass(className: String, topLevelClass: PsiClass): PsiClass? {
|
||||
if (className.indexOf('.') < 0) {
|
||||
return if (className == topLevelClass.name) topLevelClass else null
|
||||
}
|
||||
|
||||
val segments = StringUtil.split(className, ".").iterator()
|
||||
if (!segments.hasNext() || segments.next() != topLevelClass.name) {
|
||||
return null
|
||||
}
|
||||
var curClass = topLevelClass
|
||||
while (segments.hasNext()) {
|
||||
val innerClassName = segments.next()
|
||||
val innerClass = curClass.findInnerClassByName(innerClassName, false) ?: return null
|
||||
curClass = innerClass
|
||||
}
|
||||
return curClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a sad workaround to avoid throwing exception when called from inside IDEA code
|
||||
private fun <T : Any> safely(compute: () -> T): T? =
|
||||
try {
|
||||
compute()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
} catch (e: AssertionError) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun String.toSafeFqName(): FqName? = safely { FqName(this) }
|
||||
private fun String.toSafeTopLevelClassId(): ClassId? = safely { ClassId.topLevel(FqName(this)) }
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import com.intellij.codeInsight.ContainerProvider
|
||||
import com.intellij.codeInsight.runner.JavaMainMethodProvider
|
||||
import com.intellij.core.JavaCoreApplicationEnvironment
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.lang.MetaLanguage
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.psi.FileContextProvider
|
||||
import com.intellij.psi.augment.PsiAugmentProvider
|
||||
import com.intellij.psi.impl.smartPointers.SmartPointerAnchorProvider
|
||||
import com.intellij.psi.meta.MetaDataContributor
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.IdeaExtensionPoints.registerVersionSpecificAppExtensionPoints
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
|
||||
|
||||
class KotlinCoreApplicationEnvironment private constructor(
|
||||
parentDisposable: Disposable, unitTestMode: Boolean
|
||||
) :
|
||||
JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) {
|
||||
|
||||
init {
|
||||
registerFileType(JavaClassFileType.INSTANCE, "sig");
|
||||
}
|
||||
|
||||
override fun createJrtFileSystem(): VirtualFileSystem {
|
||||
return CoreJrtFileSystem()
|
||||
}
|
||||
|
||||
private var fastJarFileSystemField: FastJarFileSystem? = null
|
||||
private var fastJarFileSystemFieldInitialized = false
|
||||
|
||||
val fastJarFileSystem: FastJarFileSystem?
|
||||
get() {
|
||||
synchronized(KotlinCoreEnvironment.APPLICATION_LOCK) {
|
||||
if (!fastJarFileSystemFieldInitialized) {
|
||||
|
||||
// may return null e.g. on the old JDKs, therefore fastJarFileSystemFieldInitialized flag is needed
|
||||
fastJarFileSystemField = FastJarFileSystem.createIfUnmappingPossible()?.also {
|
||||
Disposer.register(parentDisposable) {
|
||||
it.clearHandlersCache()
|
||||
}
|
||||
}
|
||||
fastJarFileSystemFieldInitialized = true
|
||||
}
|
||||
return fastJarFileSystemField
|
||||
}
|
||||
}
|
||||
|
||||
fun idleCleanup() {
|
||||
fastJarFileSystemField?.clearHandlersCache()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
parentDisposable: Disposable, unitTestMode: Boolean
|
||||
): KotlinCoreApplicationEnvironment {
|
||||
val environment = KotlinCoreApplicationEnvironment(parentDisposable, unitTestMode)
|
||||
registerExtensionPoints()
|
||||
return environment
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
private fun registerExtensionPoints() {
|
||||
registerApplicationExtensionPoint(DynamicBundle.LanguageBundleEP.EP_NAME, DynamicBundle.LanguageBundleEP::class.java)
|
||||
registerApplicationExtensionPoint(FileContextProvider.EP_NAME, FileContextProvider::class.java)
|
||||
registerApplicationExtensionPoint(MetaDataContributor.EP_NAME, MetaDataContributor::class.java)
|
||||
registerApplicationExtensionPoint(PsiAugmentProvider.EP_NAME, PsiAugmentProvider::class.java)
|
||||
registerApplicationExtensionPoint(JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider::class.java)
|
||||
registerApplicationExtensionPoint(ContainerProvider.EP_NAME, ContainerProvider::class.java)
|
||||
registerApplicationExtensionPoint(MetaLanguage.EP_NAME, MetaLanguage::class.java)
|
||||
registerApplicationExtensionPoint(SmartPointerAnchorProvider.EP_NAME, SmartPointerAnchorProvider::class.java)
|
||||
registerVersionSpecificAppExtensionPoints(ApplicationManager.getApplication().extensionArea)
|
||||
}
|
||||
}
|
||||
}
|
||||
+775
@@ -0,0 +1,775 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager
|
||||
import com.intellij.codeInsight.InferredAnnotationsManager
|
||||
import com.intellij.core.CoreApplicationEnvironment
|
||||
import com.intellij.core.CoreJavaFileManager
|
||||
import com.intellij.core.JavaCoreProjectEnvironment
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.lang.java.JavaParserDefinition
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.TransactionGuard
|
||||
import com.intellij.openapi.application.TransactionGuardImpl
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.extensions.ExtensionsArea
|
||||
import com.intellij.openapi.fileTypes.PlainTextFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.PersistentFSConstants
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.openapi.vfs.impl.ZipHandler
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.impl.JavaClassSupersImpl
|
||||
import com.intellij.psi.impl.PsiElementFinderImpl
|
||||
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||
import com.intellij.psi.impl.file.impl.JavaFileManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.JavaClassSupers
|
||||
import com.intellij.util.io.URLUtil
|
||||
import com.intellij.util.lang.UrlClassLoader
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.jvm.extensions.ClassGeneratorExtension
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl
|
||||
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
|
||||
import org.jetbrains.kotlin.cli.common.config.ContentRoot
|
||||
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
|
||||
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
|
||||
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
|
||||
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
|
||||
import org.jetbrains.kotlin.cli.common.extensions.ShellExtension
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
import org.jetbrains.kotlin.cli.jvm.config.*
|
||||
import org.jetbrains.kotlin.cli.jvm.index.*
|
||||
import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassFileFactoryFinalizerExtension
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.compiler.plugin.registerInProject
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.extensions.*
|
||||
import org.jetbrains.kotlin.extensions.internal.CandidateInterceptor
|
||||
import org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints
|
||||
import org.jetbrains.kotlin.extensions.internal.TypeResolutionInterceptor
|
||||
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
|
||||
import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory
|
||||
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
|
||||
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
|
||||
import org.jetbrains.kotlin.resolve.extensions.AssignResolutionAltererExtension
|
||||
import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension
|
||||
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.SyntheticJavaResolveExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializerPlugin
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class KotlinCoreEnvironment private constructor(
|
||||
val projectEnvironment: ProjectEnvironment,
|
||||
val configuration: CompilerConfiguration,
|
||||
configFiles: EnvironmentConfigFiles
|
||||
) {
|
||||
|
||||
class ProjectEnvironment(
|
||||
disposable: Disposable,
|
||||
applicationEnvironment: KotlinCoreApplicationEnvironment,
|
||||
configuration: CompilerConfiguration
|
||||
) :
|
||||
KotlinCoreProjectEnvironment(disposable, applicationEnvironment) {
|
||||
|
||||
val jarFileSystem: VirtualFileSystem
|
||||
|
||||
init {
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
setIdeaIoUseFallback()
|
||||
|
||||
if (configuration.getBoolean(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM)) {
|
||||
messageCollector?.report(
|
||||
STRONG_WARNING,
|
||||
"Using new faster version of JAR FS: it should make your build faster, but the new implementation is experimental"
|
||||
)
|
||||
}
|
||||
|
||||
jarFileSystem = when {
|
||||
configuration.getBoolean(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING) -> {
|
||||
applicationEnvironment.jarFileSystem
|
||||
}
|
||||
configuration.getBoolean(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM) || configuration.getBoolean(CommonConfigurationKeys.USE_FIR) -> {
|
||||
val fastJarFs = applicationEnvironment.fastJarFileSystem
|
||||
if (fastJarFs == null) {
|
||||
messageCollector?.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Your JDK doesn't seem to support mapped buffer unmapping, so the slower (old) version of JAR FS will be used"
|
||||
)
|
||||
applicationEnvironment.jarFileSystem
|
||||
} else fastJarFs
|
||||
}
|
||||
|
||||
else -> applicationEnvironment.jarFileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private var extensionRegistered = false
|
||||
|
||||
override fun preregisterServices() {
|
||||
registerProjectExtensionPoints(project.extensionArea)
|
||||
}
|
||||
|
||||
fun registerExtensionsFromPlugins(configuration: CompilerConfiguration) {
|
||||
if (!extensionRegistered) {
|
||||
registerPluginExtensionPoints(project)
|
||||
registerExtensionsFromPlugins(project, configuration)
|
||||
extensionRegistered = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerJavaPsiFacade() {
|
||||
with(project) {
|
||||
registerService(
|
||||
CoreJavaFileManager::class.java,
|
||||
ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager
|
||||
)
|
||||
|
||||
registerKotlinLightClassSupport(project)
|
||||
|
||||
registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager())
|
||||
registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager())
|
||||
}
|
||||
|
||||
super.registerJavaPsiFacade()
|
||||
}
|
||||
}
|
||||
|
||||
private val sourceFiles = mutableListOf<KtFile>()
|
||||
private val rootsIndex: JvmDependenciesDynamicCompoundIndex
|
||||
private val packagePartProviders = mutableListOf<JvmPackagePartProvider>()
|
||||
|
||||
private val classpathRootsResolver: ClasspathRootsResolver
|
||||
private val initialRoots = ArrayList<JavaRoot>()
|
||||
|
||||
init {
|
||||
projectEnvironment.configureProjectEnvironment(configuration, configFiles)
|
||||
val project = projectEnvironment.project
|
||||
project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles))
|
||||
|
||||
sourceFiles += createSourceFilesFromSourceRoots(configuration, project, getSourceRootsCheckingForDuplicates())
|
||||
|
||||
collectAdditionalSources(project)
|
||||
|
||||
sourceFiles.sortBy { it.virtualFile.path }
|
||||
|
||||
val javaFileManager = ServiceManager.getService(project, CoreJavaFileManager::class.java) as KotlinCliJavaFileManagerImpl
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME)
|
||||
val releaseTarget = configuration.get(JVMConfigurationKeys.JDK_RELEASE)
|
||||
val javaModuleFinder = CliJavaModuleFinder(
|
||||
jdkHome,
|
||||
messageCollector,
|
||||
javaFileManager,
|
||||
project,
|
||||
releaseTarget
|
||||
)
|
||||
|
||||
val outputDirectory =
|
||||
configuration.get(JVMConfigurationKeys.MODULES)?.singleOrNull()?.getOutputDirectory()
|
||||
?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)?.absolutePath
|
||||
|
||||
classpathRootsResolver = ClasspathRootsResolver(
|
||||
PsiManager.getInstance(project),
|
||||
messageCollector,
|
||||
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
|
||||
this::contentRootToVirtualFile,
|
||||
javaModuleFinder,
|
||||
!configuration.getBoolean(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE),
|
||||
outputDirectory?.let(this::findLocalFile),
|
||||
javaFileManager,
|
||||
releaseTarget
|
||||
)
|
||||
|
||||
val (initialRoots, javaModules) =
|
||||
classpathRootsResolver.convertClasspathRoots(configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS))
|
||||
this.initialRoots.addAll(initialRoots)
|
||||
|
||||
val (roots, singleJavaFileRoots) =
|
||||
initialRoots.partition { (file) -> file.isDirectory || file.extension != JavaFileType.DEFAULT_EXTENSION }
|
||||
|
||||
// REPL and kapt2 update classpath dynamically
|
||||
rootsIndex = JvmDependenciesDynamicCompoundIndex().apply {
|
||||
addIndex(JvmDependenciesIndexImpl(roots))
|
||||
updateClasspathFromRootsIndex(this)
|
||||
}
|
||||
|
||||
javaFileManager.initialize(
|
||||
rootsIndex,
|
||||
packagePartProviders,
|
||||
SingleJavaFileRootsIndex(singleJavaFileRoots),
|
||||
configuration.getBoolean(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING)
|
||||
)
|
||||
|
||||
project.registerService(
|
||||
JavaModuleResolver::class.java,
|
||||
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList(), project)
|
||||
)
|
||||
|
||||
val finderFactory = CliVirtualFileFinderFactory(rootsIndex, releaseTarget != null)
|
||||
project.registerService(MetadataFinderFactory::class.java, finderFactory)
|
||||
project.registerService(VirtualFileFinderFactory::class.java, finderFactory)
|
||||
|
||||
project.putUserData(APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY, fun(roots: List<File>) {
|
||||
updateClasspath(roots.map { JavaSourceRoot(it, null) })
|
||||
})
|
||||
|
||||
project.setupHighestLanguageLevel()
|
||||
}
|
||||
|
||||
private fun collectAdditionalSources(project: MockProject) {
|
||||
var unprocessedSources: Collection<KtFile> = sourceFiles
|
||||
val processedSources = HashSet<KtFile>()
|
||||
val processedSourcesByExtension = HashMap<CollectAdditionalSourcesExtension, Collection<KtFile>>()
|
||||
// repeat feeding extensions with sources while new sources a being added
|
||||
var sourceCollectionIterations = 0
|
||||
while (unprocessedSources.isNotEmpty()) {
|
||||
if (sourceCollectionIterations++ > 10) { // TODO: consider using some appropriate global constant
|
||||
throw IllegalStateException("Unable to collect additional sources in reasonable number of iterations")
|
||||
}
|
||||
processedSources.addAll(unprocessedSources)
|
||||
val allNewSources = ArrayList<KtFile>()
|
||||
for (extension in CollectAdditionalSourcesExtension.getInstances(project)) {
|
||||
// do not feed the extension with the sources it returned on the previous iteration
|
||||
val sourcesToProcess = unprocessedSources - (processedSourcesByExtension[extension] ?: emptyList())
|
||||
val newSources = extension.collectAdditionalSourcesAndUpdateConfiguration(sourcesToProcess, configuration, project)
|
||||
if (newSources.isNotEmpty()) {
|
||||
allNewSources.addAll(newSources)
|
||||
processedSourcesByExtension[extension] = newSources
|
||||
}
|
||||
}
|
||||
unprocessedSources = allNewSources.filterNot { processedSources.contains(it) }.distinct()
|
||||
sourceFiles += unprocessedSources
|
||||
}
|
||||
}
|
||||
|
||||
fun addKotlinSourceRoots(rootDirs: List<File>) {
|
||||
val roots = rootDirs.map { KotlinSourceRoot(it.absolutePath, isCommon = false, hmppModuleName = null) }
|
||||
sourceFiles += createSourceFilesFromSourceRoots(configuration, project, roots).toSet() - sourceFiles
|
||||
}
|
||||
|
||||
fun createPackagePartProvider(scope: GlobalSearchScope): JvmPackagePartProvider {
|
||||
return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply {
|
||||
addRoots(initialRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY))
|
||||
packagePartProviders += this
|
||||
(ModuleAnnotationsResolver.getInstance(project) as CliModuleAnnotationsResolver).addPackagePartProvider(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val VirtualFile.javaFiles: List<VirtualFile>
|
||||
get() = mutableListOf<VirtualFile>().apply {
|
||||
VfsUtilCore.processFilesRecursively(this@javaFiles) { file ->
|
||||
if (file.extension == JavaFileType.DEFAULT_EXTENSION || file.fileType == JavaFileType.INSTANCE) {
|
||||
add(file)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private val allJavaFiles: List<File>
|
||||
get() = configuration.javaSourceRoots
|
||||
.mapNotNull(this::findLocalFile)
|
||||
.flatMap { it.javaFiles }
|
||||
.map { File(it.canonicalPath) }
|
||||
|
||||
fun registerJavac(
|
||||
javaFiles: List<File> = allJavaFiles,
|
||||
kotlinFiles: List<KtFile> = sourceFiles,
|
||||
arguments: Array<String>? = null,
|
||||
bootClasspath: List<File>? = null,
|
||||
sourcePath: List<File>? = null
|
||||
): Boolean {
|
||||
return JavacWrapperRegistrar.registerJavac(
|
||||
projectEnvironment.project, configuration, javaFiles, kotlinFiles, arguments, bootClasspath, sourcePath,
|
||||
LightClassGenerationSupport.getInstance(project), packagePartProviders
|
||||
)
|
||||
}
|
||||
|
||||
private val applicationEnvironment: CoreApplicationEnvironment
|
||||
get() = projectEnvironment.environment
|
||||
|
||||
val project: Project
|
||||
get() = projectEnvironment.project
|
||||
|
||||
fun countLinesOfCode(sourceFiles: List<KtFile>): Int =
|
||||
sourceFiles.sumBy { sourceFile ->
|
||||
val text = sourceFile.text
|
||||
StringUtil.getLineBreakCount(text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1)
|
||||
}
|
||||
|
||||
private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) {
|
||||
index.indexedRoots.forEach {
|
||||
projectEnvironment.addSourcesToClasspath(it.file)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateClasspath(contentRoots: List<ContentRoot>): List<File>? {
|
||||
// TODO: add new Java modules to CliJavaModuleResolver
|
||||
val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots).roots - initialRoots
|
||||
|
||||
if (packagePartProviders.isEmpty()) {
|
||||
initialRoots.addAll(newRoots)
|
||||
} else {
|
||||
for (packagePartProvider in packagePartProviders) {
|
||||
packagePartProvider.addRoots(newRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY))
|
||||
}
|
||||
}
|
||||
|
||||
return rootsIndex.addNewIndexForRoots(newRoots)?.let { newIndex ->
|
||||
updateClasspathFromRootsIndex(newIndex)
|
||||
newIndex.indexedRoots.mapNotNull { (file) ->
|
||||
VfsUtilCore.virtualToIoFile(VfsUtilCore.getVirtualFileForJar(file) ?: file)
|
||||
}.toList()
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
private fun contentRootToVirtualFile(root: JvmContentRootBase): VirtualFile? =
|
||||
when (root) {
|
||||
is JvmClasspathRoot ->
|
||||
if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Classpath entry")
|
||||
is JvmModulePathRoot ->
|
||||
if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Java module root")
|
||||
is JavaSourceRoot ->
|
||||
findExistingRoot(root, "Java source root")
|
||||
is VirtualJvmClasspathRoot -> root.file
|
||||
else ->
|
||||
throw IllegalStateException("Unexpected root: $root")
|
||||
}
|
||||
|
||||
fun findLocalFile(path: String): VirtualFile? =
|
||||
applicationEnvironment.localFileSystem.findFileByPath(path)
|
||||
|
||||
private fun findExistingRoot(root: JvmContentRoot, rootDescription: String): VirtualFile? {
|
||||
return findLocalFile(root.file.absolutePath).also {
|
||||
if (it == null) {
|
||||
report(STRONG_WARNING, "$rootDescription points to a non-existent location: ${root.file}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findJarRoot(file: File): VirtualFile? =
|
||||
projectEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}")
|
||||
|
||||
private fun getSourceRootsCheckingForDuplicates(): List<KotlinSourceRoot> {
|
||||
val uniqueSourceRoots = hashSetOf<String>()
|
||||
val result = mutableListOf<KotlinSourceRoot>()
|
||||
|
||||
for (root in configuration.kotlinSourceRoots) {
|
||||
if (!uniqueSourceRoots.add(root.path)) {
|
||||
report(STRONG_WARNING, "Duplicate source root: ${root.path}")
|
||||
}
|
||||
result.add(root)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getSourceFiles(): List<KtFile> =
|
||||
ProcessSourcesBeforeCompilingExtension.getInstances(project)
|
||||
.fold(sourceFiles as Collection<KtFile>) { files, extension ->
|
||||
extension.processSources(files, configuration)
|
||||
}.toList()
|
||||
|
||||
internal fun report(severity: CompilerMessageSeverity, message: String) = configuration.report(severity, message)
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinCoreEnvironment::class.java)
|
||||
|
||||
@PublishedApi
|
||||
internal val APPLICATION_LOCK = Object()
|
||||
|
||||
private var ourApplicationEnvironment: KotlinCoreApplicationEnvironment? = null
|
||||
private var ourProjectCount = 0
|
||||
|
||||
inline fun <R> underApplicationLock(action: () -> R): R =
|
||||
synchronized(APPLICATION_LOCK) { action() }
|
||||
|
||||
@JvmStatic
|
||||
fun createForProduction(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
|
||||
): KotlinCoreEnvironment {
|
||||
setupIdeaStandaloneExecution()
|
||||
val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration)
|
||||
val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration)
|
||||
val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles)
|
||||
|
||||
return environment
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createForProduction(
|
||||
projectEnvironment: ProjectEnvironment, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
|
||||
): KotlinCoreEnvironment {
|
||||
return KotlinCoreEnvironment(projectEnvironment, configuration, configFiles)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
@JvmStatic
|
||||
fun createForTests(
|
||||
parentDisposable: Disposable, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles
|
||||
): KotlinCoreEnvironment {
|
||||
val configuration = initialConfiguration.copy()
|
||||
// Tests are supposed to create a single project and dispose it right after use
|
||||
val appEnv =
|
||||
createApplicationEnvironment(
|
||||
parentDisposable, configuration, unitTestMode = true
|
||||
)
|
||||
val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration)
|
||||
return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
@JvmStatic
|
||||
fun createForTests(
|
||||
projectEnvironment: ProjectEnvironment, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles
|
||||
): KotlinCoreEnvironment {
|
||||
return KotlinCoreEnvironment(projectEnvironment, initialConfiguration, extensionConfigs)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun createProjectEnvironmentForTests(parentDisposable: Disposable, configuration: CompilerConfiguration): ProjectEnvironment {
|
||||
val appEnv = createApplicationEnvironment(
|
||||
parentDisposable,
|
||||
configuration,
|
||||
unitTestMode = true
|
||||
)
|
||||
return ProjectEnvironment(parentDisposable, appEnv, configuration)
|
||||
}
|
||||
|
||||
// used in the daemon for jar cache cleanup
|
||||
val applicationEnvironment: KotlinCoreApplicationEnvironment? get() = ourApplicationEnvironment
|
||||
|
||||
fun getOrCreateApplicationEnvironmentForProduction(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration
|
||||
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = false)
|
||||
|
||||
fun getOrCreateApplicationEnvironmentForTests(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration
|
||||
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = true)
|
||||
|
||||
private fun getOrCreateApplicationEnvironment(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
|
||||
): KotlinCoreApplicationEnvironment {
|
||||
synchronized(APPLICATION_LOCK) {
|
||||
if (ourApplicationEnvironment == null) {
|
||||
val disposable = Disposer.newDisposable("Disposable for the KotlinCoreApplicationEnvironment")
|
||||
ourApplicationEnvironment =
|
||||
createApplicationEnvironment(
|
||||
disposable,
|
||||
configuration,
|
||||
unitTestMode
|
||||
)
|
||||
ourProjectCount = 0
|
||||
Disposer.register(disposable, Disposable {
|
||||
synchronized(APPLICATION_LOCK) {
|
||||
ourApplicationEnvironment = null
|
||||
}
|
||||
})
|
||||
}
|
||||
try {
|
||||
val disposeAppEnv =
|
||||
CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value.toBooleanLenient() != true
|
||||
// Disposer uses identity of passed object to deduplicate registered disposables
|
||||
// We should everytime pass new instance to avoid un-registering from previous one
|
||||
@Suppress("ObjectLiteralToLambda")
|
||||
Disposer.register(parentDisposable, object : Disposable {
|
||||
override fun dispose() {
|
||||
synchronized(APPLICATION_LOCK) {
|
||||
// Build-systems may run many instances of the compiler in parallel
|
||||
// All projects share the same ApplicationEnvironment, and when the last project is disposed,
|
||||
// the ApplicationEnvironment is disposed as well
|
||||
if (--ourProjectCount <= 0) {
|
||||
// Do not use this property unless you sure need it, causes Application to MEMORY LEAK
|
||||
// Only valid use-case is when Application should be cached to avoid
|
||||
// initialization costs
|
||||
if (disposeAppEnv) {
|
||||
disposeApplicationEnvironment()
|
||||
} else {
|
||||
ourApplicationEnvironment?.idleCleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
ourProjectCount++
|
||||
}
|
||||
|
||||
return ourApplicationEnvironment!!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is also used in Gradle after configuration phase finished.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun disposeApplicationEnvironment() {
|
||||
synchronized(APPLICATION_LOCK) {
|
||||
val environment = ourApplicationEnvironment ?: return
|
||||
ourApplicationEnvironment = null
|
||||
Disposer.dispose(environment.parentDisposable)
|
||||
ZipHandler.clearFileAccessorCache()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun ProjectEnvironment.configureProjectEnvironment(
|
||||
configuration: CompilerConfiguration,
|
||||
configFiles: EnvironmentConfigFiles
|
||||
) {
|
||||
PersistentFSConstants::class.java.getDeclaredField("ourMaxIntellisenseFileSize")
|
||||
.apply { isAccessible = true }
|
||||
.setInt(null, FileUtilRt.LARGE_FOR_CONTENT_LOADING)
|
||||
|
||||
registerExtensionsFromPlugins(configuration)
|
||||
// otherwise consider that project environment is properly configured before passing to the environment
|
||||
// TODO: consider some asserts to check important extension points
|
||||
|
||||
val isJvm = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||
project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(isJvm))
|
||||
|
||||
registerProjectServicesForCLI(this)
|
||||
|
||||
registerProjectServices(project)
|
||||
|
||||
for (extension in CompilerConfigurationExtension.getInstances(project)) {
|
||||
extension.updateConfiguration(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createApplicationEnvironment(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
|
||||
): KotlinCoreApplicationEnvironment {
|
||||
val applicationEnvironment = KotlinCoreApplicationEnvironment.create(
|
||||
parentDisposable, unitTestMode
|
||||
)
|
||||
|
||||
registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml")
|
||||
|
||||
registerApplicationServicesForCLI(applicationEnvironment)
|
||||
registerApplicationServices(applicationEnvironment)
|
||||
|
||||
return applicationEnvironment
|
||||
}
|
||||
|
||||
private fun registerApplicationExtensionPointsAndExtensionsFrom(configuration: CompilerConfiguration, configFilePath: String) {
|
||||
fun File.hasConfigFile(configFile: String): Boolean =
|
||||
if (isDirectory) File(this, "META-INF" + File.separator + configFile).exists()
|
||||
else try {
|
||||
ZipFile(this).use {
|
||||
it.getEntry("META-INF/$configFile") != null
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
|
||||
val pluginRoot: File =
|
||||
configuration.get(CLIConfigurationKeys.INTELLIJ_PLUGIN_ROOT)?.let(::File)
|
||||
?: PathUtil.getResourcePathForClass(this::class.java).takeIf { it.hasConfigFile(configFilePath) }
|
||||
// hack for load extensions when compiler run directly from project directory (e.g. in tests)
|
||||
?: File("compiler/cli/cli-common/resources").takeIf { it.hasConfigFile(configFilePath) }
|
||||
?: configuration.get(CLIConfigurationKeys.PATH_TO_KOTLIN_COMPILER_JAR)?.takeIf { it.hasConfigFile(configFilePath) }
|
||||
?: throw IllegalStateException(
|
||||
"Unable to find extension point configuration $configFilePath " +
|
||||
"(cp:\n ${(Thread.currentThread().contextClassLoader as? UrlClassLoader)?.urls?.joinToString("\n ") { it.file }})"
|
||||
)
|
||||
|
||||
CoreApplicationEnvironment.registerExtensionPointAndExtensions(
|
||||
FileSystems.getDefault().getPath(pluginRoot.path),
|
||||
configFilePath,
|
||||
ApplicationManager.getApplication().extensionArea
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@OptIn(InternalNonStableExtensionPoints::class)
|
||||
@Suppress("MemberVisibilityCanPrivate") // made public for CLI Android Lint
|
||||
fun registerPluginExtensionPoints(project: MockProject) {
|
||||
ExpressionCodegenExtension.registerExtensionPoint(project)
|
||||
SyntheticResolveExtension.registerExtensionPoint(project)
|
||||
SyntheticJavaResolveExtension.registerExtensionPoint(project)
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension.registerExtensionPoint(project)
|
||||
ClassGeneratorExtension.registerExtensionPoint(project)
|
||||
ClassFileFactoryFinalizerExtension.registerExtensionPoint(project)
|
||||
AnalysisHandlerExtension.registerExtensionPoint(project)
|
||||
PackageFragmentProviderExtension.registerExtensionPoint(project)
|
||||
StorageComponentContainerContributor.registerExtensionPoint(project)
|
||||
DeclarationAttributeAltererExtension.registerExtensionPoint(project)
|
||||
PreprocessedVirtualFileFactoryExtension.registerExtensionPoint(project)
|
||||
JsSyntheticTranslateExtension.registerExtensionPoint(project)
|
||||
CompilerConfigurationExtension.registerExtensionPoint(project)
|
||||
CollectAdditionalSourcesExtension.registerExtensionPoint(project)
|
||||
ProcessSourcesBeforeCompilingExtension.registerExtensionPoint(project)
|
||||
ExtraImportsProviderExtension.registerExtensionPoint(project)
|
||||
IrGenerationExtension.registerExtensionPoint(project)
|
||||
ScriptEvaluationExtension.registerExtensionPoint(project)
|
||||
ShellExtension.registerExtensionPoint(project)
|
||||
TypeResolutionInterceptor.registerExtensionPoint(project)
|
||||
CandidateInterceptor.registerExtensionPoint(project)
|
||||
DescriptorSerializerPlugin.registerExtensionPoint(project)
|
||||
FirExtensionRegistrarAdapter.registerExtensionPoint(project)
|
||||
TypeAttributeTranslatorExtension.registerExtensionPoint(project)
|
||||
AssignResolutionAltererExtension.registerExtensionPoint(project)
|
||||
}
|
||||
|
||||
internal fun registerExtensionsFromPlugins(project: MockProject, configuration: CompilerConfiguration) {
|
||||
fun createErrorMessage(extension: Any): String {
|
||||
return "The provided plugin ${extension.javaClass.name} is not compatible with this version of compiler"
|
||||
}
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) {
|
||||
try {
|
||||
registrar.registerProjectComponents(project, configuration)
|
||||
} catch (e: AbstractMethodError) {
|
||||
val message = createErrorMessage(registrar)
|
||||
// Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible
|
||||
// location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception
|
||||
if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") {
|
||||
messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message")
|
||||
} else {
|
||||
val errorMessageWithStackTrace = "$message.\n" +
|
||||
e.stackTraceToString().lines().take(6).joinToString("\n")
|
||||
messageCollector?.report(ERROR, errorMessageWithStackTrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val extensionStorage = CompilerPluginRegistrar.ExtensionStorage()
|
||||
for (registrar in configuration.getList(CompilerPluginRegistrar.COMPILER_PLUGIN_REGISTRARS)) {
|
||||
with(registrar) { extensionStorage.registerExtensions(configuration) }
|
||||
}
|
||||
extensionStorage.registerInProject(project) { createErrorMessage(it) }
|
||||
}
|
||||
|
||||
private fun registerApplicationServicesForCLI(applicationEnvironment: KotlinCoreApplicationEnvironment) {
|
||||
// ability to get text from annotations xml files
|
||||
applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml")
|
||||
applicationEnvironment.registerParserDefinition(JavaParserDefinition())
|
||||
}
|
||||
|
||||
// made public for Upsource
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@JvmStatic
|
||||
fun registerApplicationServices(applicationEnvironment: KotlinCoreApplicationEnvironment) {
|
||||
with(applicationEnvironment) {
|
||||
registerFileType(KotlinFileType.INSTANCE, "kt")
|
||||
registerFileType(KotlinFileType.INSTANCE, KotlinParserDefinition.STD_SCRIPT_SUFFIX)
|
||||
registerParserDefinition(KotlinParserDefinition())
|
||||
application.registerService(KotlinBinaryClassCache::class.java, KotlinBinaryClassCache())
|
||||
application.registerService(JavaClassSupers::class.java, JavaClassSupersImpl::class.java)
|
||||
application.registerService(TransactionGuard::class.java, TransactionGuardImpl::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun registerProjectExtensionPoints(area: ExtensionsArea) {
|
||||
CoreApplicationEnvironment.registerExtensionPoint(
|
||||
area, PsiTreeChangePreprocessor.EP.name, PsiTreeChangePreprocessor::class.java
|
||||
)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP.name, PsiElementFinder::class.java)
|
||||
|
||||
IdeaExtensionPoints.registerVersionSpecificProjectExtensionPoints(area)
|
||||
}
|
||||
|
||||
// made public for Upsource
|
||||
@JvmStatic
|
||||
@Deprecated("Use registerProjectServices(project) instead.", ReplaceWith("registerProjectServices(projectEnvironment.project)"))
|
||||
fun registerProjectServices(
|
||||
projectEnvironment: JavaCoreProjectEnvironment,
|
||||
@Suppress("UNUSED_PARAMETER") messageCollector: MessageCollector?
|
||||
) {
|
||||
registerProjectServices(projectEnvironment.project)
|
||||
}
|
||||
|
||||
// made public for Android Lint
|
||||
@JvmStatic
|
||||
fun registerProjectServices(project: MockProject) {
|
||||
with(project) {
|
||||
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
|
||||
registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver())
|
||||
}
|
||||
}
|
||||
|
||||
fun registerProjectServicesForCLI(@Suppress("UNUSED_PARAMETER") projectEnvironment: JavaCoreProjectEnvironment) {
|
||||
/**
|
||||
* Note that Kapt may restart code analysis process, and CLI services should be aware of that.
|
||||
* Use PsiManager.getModificationTracker() to ensure that all the data you cached is still valid.
|
||||
*/
|
||||
}
|
||||
|
||||
// made public for Android Lint
|
||||
@JvmStatic
|
||||
fun registerKotlinLightClassSupport(project: MockProject) {
|
||||
with(project) {
|
||||
val traceHolder = CliTraceHolder()
|
||||
val cliLightClassGenerationSupport = CliLightClassGenerationSupport(traceHolder, project)
|
||||
val kotlinAsJavaSupport = CliKotlinAsJavaSupport(project, traceHolder)
|
||||
registerService(LightClassGenerationSupport::class.java, cliLightClassGenerationSupport)
|
||||
registerService(CliLightClassGenerationSupport::class.java, cliLightClassGenerationSupport)
|
||||
registerService(KotlinAsJavaSupport::class.java, kotlinAsJavaSupport)
|
||||
registerService(CodeAnalyzerInitializer::class.java, traceHolder)
|
||||
|
||||
// We don't pass Disposable because in some tests, we manually unregister these extensions, and that leads to LOG.error
|
||||
// exception from `ExtensionPointImpl.doRegisterExtension`, because the registered extension can no longer be found
|
||||
// when the project is being disposed.
|
||||
// For example, see the `unregisterExtension` call in `GenerationUtils.compileFilesUsingFrontendIR`.
|
||||
// TODO: refactor this to avoid registering unneeded extensions in the first place, and avoid using deprecated API.
|
||||
@Suppress("DEPRECATION")
|
||||
PsiElementFinder.EP.getPoint(project).registerExtension(JavaElementFinder(this))
|
||||
@Suppress("DEPRECATION")
|
||||
PsiElementFinder.EP.getPoint(project).registerExtension(PsiElementFinderImpl(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.core.JavaCoreProjectEnvironment
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
|
||||
|
||||
open class KotlinCoreProjectEnvironment(
|
||||
disposable: Disposable,
|
||||
applicationEnvironment: KotlinCoreApplicationEnvironment
|
||||
) : JavaCoreProjectEnvironment(disposable, applicationEnvironment) {
|
||||
override fun createCoreFileManager(): KotlinCliJavaFileManager = KotlinCliJavaFileManagerImpl(PsiManager.getInstance(project))
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
|
||||
class MockExternalAnnotationsManager : ExternalAnnotationsManager() {
|
||||
override fun chooseAnnotationsPlace(element: PsiElement): AnnotationPlace = AnnotationPlace.NOWHERE
|
||||
override fun chooseAnnotationsPlaceNoUi(element: PsiElement): AnnotationPlace = AnnotationPlace.NOWHERE
|
||||
|
||||
override fun isExternalAnnotationWritable(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean = false
|
||||
override fun isExternalAnnotation(annotation: PsiAnnotation): Boolean = false
|
||||
|
||||
override fun findExternalAnnotationsFiles(listOwner: PsiModifierListOwner): List<PsiFile>? = null
|
||||
override fun findExternalAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null
|
||||
override fun findExternalAnnotations(listOwner: PsiModifierListOwner): Array<out PsiAnnotation>? = null
|
||||
|
||||
override fun annotateExternally(
|
||||
listOwner: PsiModifierListOwner,
|
||||
annotationFQName: String,
|
||||
fromFile: PsiFile,
|
||||
value: Array<out PsiNameValuePair>?
|
||||
) {
|
||||
throw UnsupportedOperationException("not implemented")
|
||||
}
|
||||
|
||||
override fun deannotate(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean {
|
||||
throw UnsupportedOperationException("not implemented")
|
||||
}
|
||||
|
||||
override fun editExternalAnnotation(
|
||||
listOwner: PsiModifierListOwner,
|
||||
annotationFQN: String,
|
||||
value: Array<out PsiNameValuePair>?
|
||||
): Boolean {
|
||||
throw UnsupportedOperationException("not implemented")
|
||||
}
|
||||
|
||||
override fun hasAnnotationRootsForFile(file: VirtualFile): Boolean = false
|
||||
|
||||
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass, annotationFQN: String): List<PsiAnnotation> = emptyList()
|
||||
|
||||
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass): List<PsiAnnotation> = emptyList()
|
||||
|
||||
override fun findExternalAnnotations(listOwner: PsiModifierListOwner, annotationFQN: String): List<PsiAnnotation> = emptyList()
|
||||
}
|
||||
+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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.codeInsight.InferredAnnotationsManager
|
||||
import com.intellij.psi.PsiAnnotation
|
||||
import com.intellij.psi.PsiModifierListOwner
|
||||
|
||||
class MockInferredAnnotationsManager : InferredAnnotationsManager() {
|
||||
override fun findInferredAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null
|
||||
override fun findInferredAnnotations(listOwner: PsiModifierListOwner): Array<out PsiAnnotation> = EMPTY_PSI_ANNOTATION_ARRAY
|
||||
override fun isInferredAnnotation(annotation: PsiAnnotation): Boolean = false
|
||||
|
||||
companion object {
|
||||
val EMPTY_PSI_ANNOTATION_ARRAY = arrayOf<PsiAnnotation>()
|
||||
}
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.compiler
|
||||
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
||||
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltInsPackageFragmentProvider
|
||||
import org.jetbrains.kotlin.cli.jvm.config.ClassicFrontendSpecificJvmConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.container.ComponentProvider
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
import org.jetbrains.kotlin.container.get
|
||||
import org.jetbrains.kotlin.container.useImpl
|
||||
import org.jetbrains.kotlin.context.ContextForNewModule
|
||||
import org.jetbrains.kotlin.context.ModuleContext
|
||||
import org.jetbrains.kotlin.context.MutableModuleContext
|
||||
import org.jetbrains.kotlin.context.ProjectContext
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleCapability
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
|
||||
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.frontend.java.di.createContainerForLazyResolveWithJava
|
||||
import org.jetbrains.kotlin.frontend.java.di.initJvmBuiltInsForTopDownAnalysis
|
||||
import org.jetbrains.kotlin.frontend.java.di.initialize
|
||||
import org.jetbrains.kotlin.incremental.components.EnumWhenTracker
|
||||
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
||||
import org.jetbrains.kotlin.incremental.components.InlineConstTracker
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.ir.backend.jvm.jvmLibrariesProvidedByDefault
|
||||
import org.jetbrains.kotlin.javac.components.JavacBasedClassFinder
|
||||
import org.jetbrains.kotlin.javac.components.JavacBasedSourceElementFactory
|
||||
import org.jetbrains.kotlin.javac.components.StubJavaResolverCache
|
||||
import org.jetbrains.kotlin.konan.properties.propertyList
|
||||
import org.jetbrains.kotlin.library.KLIB_PROPERTY_DEPENDS
|
||||
import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.library.metadata.KlibMetadataFactories
|
||||
import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
|
||||
import org.jetbrains.kotlin.load.java.JavaClassesTracker
|
||||
import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolver
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
|
||||
import org.jetbrains.kotlin.load.kotlin.DeserializationComponentsForJava
|
||||
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackageFragmentProvider
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackagePartProvider
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.ImplicitsExtensionsResolutionFilter
|
||||
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.multiplatform.OptionalAnnotationPackageFragmentProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import kotlin.reflect.KFunction1
|
||||
|
||||
object TopDownAnalyzerFacadeForJVM {
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun analyzeFilesWithJavaIntegration(
|
||||
project: Project,
|
||||
files: Collection<KtFile>,
|
||||
trace: BindingTrace,
|
||||
configuration: CompilerConfiguration,
|
||||
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider,
|
||||
declarationProviderFactory: (StorageManager, Collection<KtFile>) -> DeclarationProviderFactory = ::FileBasedDeclarationProviderFactory,
|
||||
sourceModuleSearchScope: GlobalSearchScope = newModuleSearchScope(project, files),
|
||||
klibList: List<KotlinLibrary> = emptyList(),
|
||||
explicitModuleDependencyList: List<ModuleDescriptorImpl> = emptyList(),
|
||||
explicitModuleFriendsList: List<ModuleDescriptorImpl> = emptyList(),
|
||||
explicitCompilerEnvironment: TargetEnvironment = CompilerEnvironment
|
||||
): AnalysisResult {
|
||||
val container = createContainer(
|
||||
project, files, trace, configuration, packagePartProvider, declarationProviderFactory, explicitCompilerEnvironment,
|
||||
sourceModuleSearchScope, klibList, explicitModuleDependencyList = explicitModuleDependencyList,
|
||||
explicitModuleFriendsList = explicitModuleFriendsList
|
||||
)
|
||||
|
||||
val module = container.get<ModuleDescriptor>()
|
||||
val moduleContext = container.get<ModuleContext>()
|
||||
|
||||
val analysisHandlerExtensions = AnalysisHandlerExtension.getInstances(project)
|
||||
|
||||
fun invokeExtensionsOnAnalysisComplete(): AnalysisResult? {
|
||||
container.get<JavaClassesTracker>().onCompletedAnalysis(module)
|
||||
for (extension in analysisHandlerExtensions) {
|
||||
val result = extension.analysisCompleted(project, module, trace, files)
|
||||
if (result != null) return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
for (extension in analysisHandlerExtensions) {
|
||||
val result = extension.doAnalysis(project, module, moduleContext, files, trace, container)
|
||||
if (result != null) {
|
||||
invokeExtensionsOnAnalysisComplete()?.let { return it }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
container.get<LazyTopDownAnalyzer>().analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files)
|
||||
|
||||
invokeExtensionsOnAnalysisComplete()?.let { return it }
|
||||
|
||||
return AnalysisResult.success(trace.bindingContext, module)
|
||||
}
|
||||
|
||||
fun createContainer(
|
||||
project: Project,
|
||||
files: Collection<KtFile>,
|
||||
trace: BindingTrace,
|
||||
configuration: CompilerConfiguration,
|
||||
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider,
|
||||
declarationProviderFactory: (StorageManager, Collection<KtFile>) -> DeclarationProviderFactory,
|
||||
targetEnvironment: TargetEnvironment = CompilerEnvironment,
|
||||
sourceModuleSearchScope: GlobalSearchScope = newModuleSearchScope(project, files),
|
||||
klibList: List<KotlinLibrary> = emptyList(),
|
||||
implicitsResolutionFilter: ImplicitsExtensionsResolutionFilter? = null,
|
||||
explicitModuleDependencyList: List<ModuleDescriptorImpl> = emptyList(),
|
||||
explicitModuleFriendsList: List<ModuleDescriptorImpl> = emptyList(),
|
||||
moduleCapabilities: Map<ModuleCapability<*>, Any?> = emptyMap()
|
||||
): ComponentProvider {
|
||||
val jvmTarget = configuration.get(JVMConfigurationKeys.JVM_TARGET, JvmTarget.DEFAULT)
|
||||
val languageVersionSettings = configuration.languageVersionSettings
|
||||
val jvmPlatform = JvmPlatforms.jvmPlatformByTargetVersion(jvmTarget)
|
||||
|
||||
val moduleContext = createModuleContext(project, configuration, jvmPlatform, moduleCapabilities)
|
||||
|
||||
val storageManager = moduleContext.storageManager
|
||||
val module = moduleContext.module
|
||||
|
||||
val incrementalComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS)
|
||||
val lookupTracker = configuration.get(CommonConfigurationKeys.LOOKUP_TRACKER) ?: LookupTracker.DO_NOTHING
|
||||
val expectActualTracker = configuration.get(CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER) ?: ExpectActualTracker.DoNothing
|
||||
val inlineConstTracker = configuration.get(CommonConfigurationKeys.INLINE_CONST_TRACKER) ?: InlineConstTracker.DoNothing
|
||||
val enumWhenTracker = configuration.get(CommonConfigurationKeys.ENUM_WHEN_TRACKER) ?: EnumWhenTracker.DoNothing
|
||||
val targetIds = configuration.get(JVMConfigurationKeys.MODULES)?.map(::TargetId)
|
||||
|
||||
val moduleClassResolver = SourceOrBinaryModuleClassResolver(sourceModuleSearchScope)
|
||||
|
||||
val fallbackBuiltIns = JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FALLBACK).apply {
|
||||
initialize(builtInsModule, languageVersionSettings)
|
||||
}.builtInsModule
|
||||
|
||||
fun StorageComponentContainer.useJavac() {
|
||||
useImpl<JavacBasedClassFinder>()
|
||||
useImpl<StubJavaResolverCache>()
|
||||
useImpl<JavacBasedSourceElementFactory>()
|
||||
}
|
||||
|
||||
@Suppress("USELESS_CAST")
|
||||
val configureJavaClassFinder =
|
||||
if (configuration.getBoolean(JVMConfigurationKeys.USE_JAVAC)) StorageComponentContainer::useJavac
|
||||
else null as KFunction1<StorageComponentContainer, Unit>?
|
||||
|
||||
val dependencyModule = run {
|
||||
val dependenciesContext = ContextForNewModule(
|
||||
moduleContext, Name.special("<dependencies of ${configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)}>"),
|
||||
module.builtIns, jvmPlatform
|
||||
)
|
||||
|
||||
// Scope for the dependency module contains everything except files present in the scope for the source module
|
||||
val dependencyScope = GlobalSearchScope.notScope(sourceModuleSearchScope)
|
||||
|
||||
val dependenciesContainer = createContainerForLazyResolveWithJava(
|
||||
jvmPlatform,
|
||||
dependenciesContext, trace, DeclarationProviderFactory.EMPTY, dependencyScope, moduleClassResolver,
|
||||
targetEnvironment, lookupTracker, expectActualTracker, inlineConstTracker, enumWhenTracker,
|
||||
packagePartProvider(dependencyScope), languageVersionSettings,
|
||||
useBuiltInsProvider = true,
|
||||
configureJavaClassFinder = configureJavaClassFinder,
|
||||
implicitsResolutionFilter = implicitsResolutionFilter
|
||||
)
|
||||
|
||||
moduleClassResolver.compiledCodeResolver = dependenciesContainer.get()
|
||||
|
||||
dependenciesContext.setDependencies(listOf(dependenciesContext.module, fallbackBuiltIns))
|
||||
dependenciesContext.initializeModuleContents(
|
||||
CompositePackageFragmentProvider(
|
||||
listOf(
|
||||
moduleClassResolver.compiledCodeResolver.packageFragmentProvider,
|
||||
dependenciesContainer.get<JvmBuiltInsPackageFragmentProvider>(),
|
||||
dependenciesContainer.get<OptionalAnnotationPackageFragmentProvider>()
|
||||
),
|
||||
"CompositeProvider@TopDownAnalyzerForJvm for dependencies ${dependenciesContext.module}"
|
||||
)
|
||||
)
|
||||
dependenciesContext.module
|
||||
}
|
||||
|
||||
val partProvider = packagePartProvider(sourceModuleSearchScope).let { fragment ->
|
||||
if (targetIds == null || incrementalComponents == null) fragment
|
||||
else IncrementalPackagePartProvider(fragment, targetIds.map(incrementalComponents::getIncrementalCache))
|
||||
}
|
||||
|
||||
// Note that it's necessary to create container for sources _after_ creation of container for dependencies because
|
||||
// CliLightClassGenerationSupport#initialize is invoked when container is created, so only the last module descriptor is going
|
||||
// to be stored in CliLightClassGenerationSupport, and it better be the source one (otherwise light classes would not be found)
|
||||
// TODO: get rid of duplicate invocation of CodeAnalyzerInitializer#initialize, or refactor CliLightClassGenerationSupport
|
||||
val container = createContainerForLazyResolveWithJava(
|
||||
jvmPlatform,
|
||||
moduleContext, trace, declarationProviderFactory(storageManager, files), sourceModuleSearchScope, moduleClassResolver,
|
||||
targetEnvironment, lookupTracker, expectActualTracker, inlineConstTracker, enumWhenTracker,
|
||||
partProvider, languageVersionSettings,
|
||||
useBuiltInsProvider = true,
|
||||
configureJavaClassFinder = configureJavaClassFinder,
|
||||
javaClassTracker = configuration[ClassicFrontendSpecificJvmConfigurationKeys.JAVA_CLASSES_TRACKER],
|
||||
implicitsResolutionFilter = implicitsResolutionFilter
|
||||
).apply {
|
||||
initJvmBuiltInsForTopDownAnalysis()
|
||||
(partProvider as? IncrementalPackagePartProvider)?.deserializationConfiguration = get()
|
||||
}
|
||||
|
||||
moduleClassResolver.sourceCodeResolver = container.get()
|
||||
val additionalProviders = ArrayList<PackageFragmentProvider>()
|
||||
|
||||
if (incrementalComponents != null) {
|
||||
targetIds?.mapTo(additionalProviders) { targetId ->
|
||||
IncrementalPackageFragmentProvider(
|
||||
files, module, storageManager, container.get<DeserializationComponentsForJava>().components,
|
||||
incrementalComponents.getIncrementalCache(targetId), targetId, container.get()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
additionalProviders.add(container.get<JavaDescriptorResolver>().packageFragmentProvider)
|
||||
|
||||
// TODO: consider putting extension package fragment providers into the dependency module
|
||||
PackageFragmentProviderExtension.getInstances(project).mapNotNullTo(additionalProviders) { extension ->
|
||||
extension.getPackageFragmentProvider(project, module, storageManager, trace, null, lookupTracker)
|
||||
}
|
||||
|
||||
val klibModules = getKlibModules(klibList, dependencyModule)
|
||||
|
||||
// TODO: remove dependencyModule from friends
|
||||
module.setDependencies(
|
||||
listOf(module, dependencyModule, fallbackBuiltIns) + klibModules + explicitModuleDependencyList,
|
||||
setOf(dependencyModule) + explicitModuleFriendsList,
|
||||
)
|
||||
module.initialize(
|
||||
CompositePackageFragmentProvider(
|
||||
listOf(
|
||||
container.get<KotlinCodeAnalyzer>().packageFragmentProvider,
|
||||
container.get<OptionalAnnotationPackageFragmentProvider>()
|
||||
) + additionalProviders,
|
||||
"CompositeProvider@TopDownAnalzyerForJvm for $module"
|
||||
)
|
||||
)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
fun newModuleSearchScope(project: Project, files: Collection<KtFile>): GlobalSearchScope {
|
||||
// In case of separate modules, the source module scope generally consists of the following scopes:
|
||||
// 1) scope which only contains passed Kotlin source files (.kt and .kts)
|
||||
// 2) scope which contains all Java source files (.java) in the project
|
||||
return GlobalSearchScope.filesScope(project, files.map { it.virtualFile }.toSet()).uniteWith(AllJavaSourcesInProjectScope(project))
|
||||
}
|
||||
|
||||
// TODO: limit this scope to the Java source roots, which the module has in its CONTENT_ROOTS
|
||||
class AllJavaSourcesInProjectScope(project: Project) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
|
||||
// 'isDirectory' check is needed because otherwise directories such as 'frontend.java' would be recognized
|
||||
// as Java source files, which makes no sense
|
||||
override fun contains(file: VirtualFile) =
|
||||
(file.extension == JavaFileType.DEFAULT_EXTENSION || file.fileType === JavaFileType.INSTANCE) && !file.isDirectory
|
||||
|
||||
override fun toString() = "All Java sources in the project"
|
||||
}
|
||||
|
||||
class SourceOrBinaryModuleClassResolver(private val sourceScope: GlobalSearchScope) : ModuleClassResolver {
|
||||
lateinit var compiledCodeResolver: JavaDescriptorResolver
|
||||
lateinit var sourceCodeResolver: JavaDescriptorResolver
|
||||
|
||||
override fun resolveClass(javaClass: JavaClass): ClassDescriptor? {
|
||||
val resolver = if (javaClass is VirtualFileBoundJavaClass && javaClass.isFromSourceCodeInScope(sourceScope))
|
||||
sourceCodeResolver
|
||||
else
|
||||
compiledCodeResolver
|
||||
return resolver.resolveClass(javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createModuleContext(
|
||||
project: Project,
|
||||
configuration: CompilerConfiguration,
|
||||
platform: TargetPlatform?,
|
||||
capabilities: Map<ModuleCapability<*>, Any?> = emptyMap()
|
||||
): MutableModuleContext {
|
||||
val projectContext = ProjectContext(project, "TopDownAnalyzer for JVM")
|
||||
val builtIns = JvmBuiltIns(projectContext.storageManager, JvmBuiltIns.Kind.FROM_DEPENDENCIES)
|
||||
return ContextForNewModule(
|
||||
projectContext, Name.special("<${configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)}>"),
|
||||
builtIns, platform, capabilities
|
||||
).apply {
|
||||
builtIns.builtInsModule = module
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From serialization.js....klib.kt
|
||||
|
||||
val jvmFactories = KlibMetadataFactories(
|
||||
{ storageManager -> JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FROM_DEPENDENCIES) },
|
||||
NullFlexibleTypeDeserializer
|
||||
)
|
||||
|
||||
private fun getKlibModules(klibList: List<KotlinLibrary>, dependencyModule: ModuleDescriptorImpl?): List<ModuleDescriptorImpl> {
|
||||
val descriptorMap = mutableMapOf<String, ModuleDescriptorImpl>()
|
||||
return klibList.map { library ->
|
||||
descriptorMap.getOrPut(library.libraryName) { getModuleDescriptorByLibrary(library, descriptorMap, dependencyModule) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getModuleDescriptorByLibrary(
|
||||
current: KotlinLibrary, mapping:
|
||||
Map<String, ModuleDescriptorImpl>,
|
||||
dependencyModule: ModuleDescriptorImpl?
|
||||
): ModuleDescriptorImpl {
|
||||
val module = jvmFactories.DefaultDeserializedDescriptorFactory.createDescriptorOptionalBuiltIns(
|
||||
current,
|
||||
LanguageVersionSettingsImpl.DEFAULT,
|
||||
LockBasedStorageManager.NO_LOCKS,
|
||||
null,
|
||||
packageAccessHandler = null, // TODO: This is a speed optimization used by Native. Don't bother for now.
|
||||
lookupTracker = LookupTracker.DO_NOTHING
|
||||
)
|
||||
|
||||
val dependencies = current.manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true).mapNotNull {
|
||||
mapping[it] ?: run {
|
||||
assert(it in jvmLibrariesProvidedByDefault) { "Unknown library $it" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
module.setDependencies(listOf(module) + dependencies + listOfNotNull(dependencyModule))
|
||||
return module
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.builder
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
|
||||
import com.intellij.psi.stubs.StubElement
|
||||
import com.intellij.util.containers.Stack
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
|
||||
class KotlinLightClassBuilderFactory(private val javaFileStub: PsiJavaFileStub) : ClassBuilderFactory {
|
||||
private val stubStack = Stack<StubElement<PsiElement>>().apply {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
push(javaFileStub as StubElement<PsiElement>)
|
||||
}
|
||||
|
||||
override fun getClassBuilderMode(): ClassBuilderMode = ClassBuilderMode.LIGHT_CLASSES
|
||||
override fun newClassBuilder(origin: JvmDeclarationOrigin) =
|
||||
StubClassBuilder(stubStack, javaFileStub)
|
||||
|
||||
override fun asText(builder: ClassBuilder) = throw UnsupportedOperationException("asText is not implemented")
|
||||
override fun asBytes(builder: ClassBuilder) = throw UnsupportedOperationException("asBytes is not implemented")
|
||||
override fun close() {}
|
||||
|
||||
fun result(): PsiJavaFileStub {
|
||||
val pop = stubStack.pop()
|
||||
if (pop !== javaFileStub) {
|
||||
LOG.error("Unbalanced stack operations: $pop")
|
||||
}
|
||||
|
||||
return javaFileStub
|
||||
}
|
||||
}
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinLightClassBuilderFactory::class.java)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.builder
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
|
||||
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
|
||||
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
|
||||
data class CodeGenerationResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics)
|
||||
|
||||
fun extraJvmDiagnosticsFromBackend(
|
||||
packageFqName: FqName,
|
||||
files: Collection<KtFile>,
|
||||
generateClassFilter: GenerationState.GenerateClassFilter,
|
||||
context: LightClassConstructionContext,
|
||||
generate: (state: GenerationState, files: Collection<KtFile>) -> Unit,
|
||||
): CodeGenerationResult {
|
||||
val project = files.first().project
|
||||
|
||||
try {
|
||||
val classBuilderFactory = KotlinLightClassBuilderFactory(createJavaFileStub(packageFqName, files))
|
||||
val state = GenerationState.Builder(
|
||||
project,
|
||||
classBuilderFactory,
|
||||
context.module,
|
||||
context.bindingContext,
|
||||
context.languageVersionSettings?.let {
|
||||
CompilerConfiguration().apply {
|
||||
languageVersionSettings = it
|
||||
put(JVMConfigurationKeys.JVM_TARGET, context.jvmTarget)
|
||||
isReadOnly = true
|
||||
}
|
||||
} ?: CompilerConfiguration.EMPTY,
|
||||
).generateDeclaredClassFilter(generateClassFilter).wantsDiagnostics(false).build()
|
||||
state.beforeCompile()
|
||||
state.oldBEInitTrace(files)
|
||||
|
||||
generate(state, files)
|
||||
|
||||
val javaFileStub = classBuilderFactory.result()
|
||||
return CodeGenerationResult(javaFileStub, context.bindingContext, state.collectedExtraJvmDiagnostics)
|
||||
} catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
} catch (e: RuntimeException) {
|
||||
logErrorWithOSInfo(e, packageFqName, files.firstOrNull()?.virtualFile)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun createJavaFileStub(packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
|
||||
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), /* compiled = */true)
|
||||
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
|
||||
|
||||
val fakeFile = object : ClsFileImpl(files.first().viewProvider) {
|
||||
override fun getStub() = javaFileStub
|
||||
override fun getPackageName() = packageFqName.asString()
|
||||
override fun isPhysical() = false
|
||||
override fun getText(): String = files.singleOrNull()?.text ?: super.getText()
|
||||
}
|
||||
|
||||
javaFileStub.psi = fakeFile
|
||||
return javaFileStub
|
||||
}
|
||||
|
||||
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
|
||||
val path = virtualFile?.path ?: "<null>"
|
||||
LOG.error(
|
||||
"Could not generate LightClass for $fqName declared in $path\n" +
|
||||
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
|
||||
cause,
|
||||
)
|
||||
}
|
||||
|
||||
private val LOG = Logger.getInstance(CodeGenerationResult::class.java)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.builder
|
||||
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
open class LightClassConstructionContext(
|
||||
val bindingContext: BindingContext,
|
||||
val module: ModuleDescriptor,
|
||||
val languageVersionSettings: LanguageVersionSettings?,
|
||||
val jvmTarget: JvmTarget,
|
||||
)
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.builder;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.compiled.InnerClassSourceStrategy;
|
||||
import com.intellij.psi.impl.compiled.StubBuildingVisitor;
|
||||
import com.intellij.psi.impl.java.stubs.PsiClassStub;
|
||||
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
|
||||
import com.intellij.psi.stubs.StubBase;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory;
|
||||
import org.jetbrains.kotlin.asJava.builder.LightElementOrigin;
|
||||
import org.jetbrains.kotlin.asJava.builder.LightElementOriginKt;
|
||||
import org.jetbrains.kotlin.codegen.AbstractClassBuilder;
|
||||
import org.jetbrains.kotlin.fileClasses.OldPackageFacadeClassUtils;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.psi.KtFile;
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.FieldVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StubClassBuilder extends AbstractClassBuilder {
|
||||
private static final InnerClassSourceStrategy<Object> EMPTY_STRATEGY = new InnerClassSourceStrategy<Object>() {
|
||||
@Override
|
||||
public Object findInnerClass(String s, Object o) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Object innerClass, StubBuildingVisitor<Object> visitor) {
|
||||
throw new UnsupportedOperationException("Shall not be called!");
|
||||
}
|
||||
};
|
||||
private final StubElement parent;
|
||||
private final PsiJavaFileStub fileStub;
|
||||
private StubBuildingVisitor v;
|
||||
private final Stack<StubElement> parentStack;
|
||||
private boolean isPackageClass = false;
|
||||
|
||||
public StubClassBuilder(@NotNull Stack<StubElement> parentStack, @NotNull PsiJavaFileStub fileStub) {
|
||||
this.parentStack = parentStack;
|
||||
this.parent = parentStack.peek();
|
||||
this.fileStub = fileStub;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassVisitor getVisitor() {
|
||||
assert v != null : "Called before class is defined";
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineClass(
|
||||
PsiElement origin,
|
||||
int version,
|
||||
int access,
|
||||
@NotNull String name,
|
||||
@Nullable String signature,
|
||||
@NotNull String superName,
|
||||
@NotNull String[] interfaces
|
||||
) {
|
||||
assert v == null : "defineClass() called twice?";
|
||||
|
||||
//noinspection ConstantConditions
|
||||
v = new StubBuildingVisitor<>(null, EMPTY_STRATEGY, parent, access, calculateShortName(name));
|
||||
|
||||
super.defineClass(origin, version, access, name, signature, superName, interfaces);
|
||||
|
||||
if (origin instanceof KtFile) {
|
||||
FqName packageName = ((KtFile) origin).getPackageFqName();
|
||||
String packageClassName = OldPackageFacadeClassUtils.getPackageClassName(packageName);
|
||||
|
||||
if (name.equals(packageClassName) || name.endsWith("/" + packageClassName)) {
|
||||
isPackageClass = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPackageClass) {
|
||||
parentStack.push(v.getResult());
|
||||
}
|
||||
|
||||
((StubBase) v.getResult()).putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightClassOrigin(origin));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String calculateShortName(@NotNull String internalName) {
|
||||
if (parent instanceof PsiJavaFileStub) {
|
||||
assert parent == fileStub;
|
||||
String packagePrefix = getPackageInternalNamePrefix();
|
||||
assert internalName.startsWith(packagePrefix) : internalName + " : " + packagePrefix;
|
||||
return internalName.substring(packagePrefix.length());
|
||||
}
|
||||
if (parent instanceof PsiClassStub<?>) {
|
||||
String parentPrefix = getClassInternalNamePrefix((PsiClassStub) parent);
|
||||
if (parentPrefix == null) return null;
|
||||
|
||||
assert internalName.startsWith(parentPrefix) : internalName + " : " + parentPrefix;
|
||||
return internalName.substring(parentPrefix.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getClassInternalNamePrefix(@NotNull PsiClassStub classStub) {
|
||||
String packageName = fileStub.getPackageName();
|
||||
|
||||
String classStubQualifiedName = classStub.getQualifiedName();
|
||||
if (classStubQualifiedName == null) return null;
|
||||
|
||||
if (packageName.isEmpty()) {
|
||||
return classStubQualifiedName.replace('.', '$') + "$";
|
||||
}
|
||||
else {
|
||||
return packageName.replace('.', '/') + "/" + classStubQualifiedName.substring(packageName.length() + 1).replace('.', '$') + "$";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private String getPackageInternalNamePrefix() {
|
||||
String packageName = fileStub.getPackageName();
|
||||
if (packageName.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return packageName.replace('.', '/') + "/";
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MethodVisitor newMethod(
|
||||
@NotNull JvmDeclarationOrigin origin,
|
||||
int access,
|
||||
@NotNull String name,
|
||||
@NotNull String desc,
|
||||
@Nullable String signature,
|
||||
@Nullable String[] exceptions
|
||||
) {
|
||||
MethodVisitor internalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions);
|
||||
|
||||
if (internalVisitor != EMPTY_METHOD_VISITOR) {
|
||||
// If stub for method generated
|
||||
markLastChild(origin);
|
||||
}
|
||||
|
||||
return internalVisitor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FieldVisitor newField(
|
||||
@NotNull JvmDeclarationOrigin origin,
|
||||
int access,
|
||||
@NotNull String name,
|
||||
@NotNull String desc,
|
||||
@Nullable String signature,
|
||||
@Nullable Object value
|
||||
) {
|
||||
FieldVisitor internalVisitor = super.newField(origin, access, name, desc, signature, value);
|
||||
|
||||
if (internalVisitor != EMPTY_FIELD_VISITOR) {
|
||||
// If stub for field generated
|
||||
markLastChild(origin);
|
||||
}
|
||||
|
||||
return internalVisitor;
|
||||
}
|
||||
|
||||
private void markLastChild(@NotNull JvmDeclarationOrigin origin) {
|
||||
List children = v.getResult().getChildrenStubs();
|
||||
StubBase last = (StubBase) children.get(children.size() - 1);
|
||||
|
||||
LightElementOrigin oldOrigin = last.getUserData(ClsWrapperStubPsiFactory.ORIGIN);
|
||||
if (oldOrigin != null) {
|
||||
PsiElement originalElement = oldOrigin.getOriginalElement();
|
||||
throw new IllegalStateException("Rewriting origin element: " +
|
||||
(originalElement != null ? originalElement.getText() : null) + " for stub " + last.toString());
|
||||
}
|
||||
|
||||
last.putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightMemberOrigin(origin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done(boolean generateSmapCopyToAnnotation) {
|
||||
if (!isPackageClass) {
|
||||
StubElement pop = parentStack.pop();
|
||||
assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult();
|
||||
}
|
||||
super.done(generateSmapCopyToAnnotation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
||||
|
||||
fun JavaModule.getJavaModuleRoots(): List<JavaRoot> =
|
||||
moduleRoots.map { (root, isBinary, isBinarySignature) ->
|
||||
val type = when {
|
||||
isBinarySignature -> JavaRoot.RootType.BINARY_SIG
|
||||
isBinary -> JavaRoot.RootType.BINARY
|
||||
else -> JavaRoot.RootType.SOURCE
|
||||
}
|
||||
JavaRoot(root, type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the JDK's default root modules. See [JEP 261: Module System](http://openjdk.java.net/jeps/261).
|
||||
*/
|
||||
fun CliJavaModuleFinder.computeDefaultRootModules(): List<String> {
|
||||
val result = arrayListOf<String>()
|
||||
|
||||
val systemModules = systemModules.associateBy(JavaModule::name)
|
||||
val javaSeExists = "java.se" in systemModules
|
||||
if (javaSeExists) {
|
||||
// The java.se module is a root, if it exists.
|
||||
result.add("java.se")
|
||||
}
|
||||
|
||||
fun JavaModule.Explicit.exportsAtLeastOnePackageUnqualified(): Boolean = moduleInfo.exports.any { it.toModules.isEmpty() }
|
||||
|
||||
if (!javaSeExists) {
|
||||
// If it does not exist then every java.* module on the upgrade module path or among the system modules
|
||||
// that exports at least one package, without qualification, is a root.
|
||||
for ((name, module) in systemModules) {
|
||||
if (name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((name, module) in systemModules) {
|
||||
// Every non-java.* module on the upgrade module path or among the system modules that exports at least one package,
|
||||
// without qualification, is also a root.
|
||||
if (!name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
|
||||
object IdeaStandaloneExecutionSetup {
|
||||
private val LOG: Logger = Logger.getInstance(IdeaStandaloneExecutionSetup::class.java)
|
||||
// Copy-pasted from com.intellij.openapi.util.BuildNumber#FALLBACK_VERSION
|
||||
private const val FALLBACK_IDEA_BUILD_NUMBER = "999.SNAPSHOT"
|
||||
|
||||
fun doSetup() {
|
||||
checkInHeadlessMode()
|
||||
|
||||
System.getProperties().setProperty("project.structure.add.tools.jar.to.new.jdk", "false")
|
||||
System.getProperties().setProperty("psi.track.invalidation", "true")
|
||||
System.getProperties().setProperty("psi.incremental.reparse.depth.limit", "1000")
|
||||
System.getProperties().setProperty("ide.hide.excluded.files", "false")
|
||||
System.getProperties().setProperty("ast.loading.filter", "false")
|
||||
System.getProperties().setProperty("idea.ignore.disabled.plugins", "true")
|
||||
// Setting the build number explicitly avoids the command-line compiler
|
||||
// reading /tmp/build.txt in an attempt to get a build number from there.
|
||||
// See intellij platform PluginManagerCore.getBuildNumber.
|
||||
System.getProperties().setProperty("idea.plugins.compatible.build", FALLBACK_IDEA_BUILD_NUMBER)
|
||||
}
|
||||
|
||||
private fun checkInHeadlessMode() {
|
||||
// if application is null it means that we are in progress of set-up application environment i.e. we are not in the running IDEA
|
||||
val application = ApplicationManager.getApplication() ?: return
|
||||
if (!application.isHeadlessEnvironment) {
|
||||
LOG.error(Throwable("IdeaStandaloneExecutionSetup should be called only in headless environment"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We safe this function to minimize the patch for release branches
|
||||
fun setupIdeaStandaloneExecution() = IdeaStandaloneExecutionSetup.doSetup()
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
|
||||
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.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
|
||||
import org.jetbrains.kotlin.extensions.PreprocessedFileCreator
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.modules.Module
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.hmppModuleName
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
|
||||
import java.io.File
|
||||
|
||||
fun CompilerConfiguration.report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation? = null) {
|
||||
get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)?.report(severity, message, location)
|
||||
}
|
||||
|
||||
inline fun List<KotlinSourceRoot>.forAllFiles(
|
||||
configuration: CompilerConfiguration,
|
||||
project: Project,
|
||||
reportLocation: CompilerMessageLocation? = null,
|
||||
body: (VirtualFile, Boolean, moduleName: String?) -> Unit
|
||||
) {
|
||||
val localFileSystem = VirtualFileManager.getInstance()
|
||||
.getFileSystem(StandardFileSystems.FILE_PROTOCOL)
|
||||
|
||||
val processedFiles = hashSetOf<VirtualFile>()
|
||||
|
||||
val virtualFileCreator = PreprocessedFileCreator(project)
|
||||
|
||||
var pluginsConfigured = false
|
||||
|
||||
for ((sourceRootPath, isCommon, hmppModuleName) in this) {
|
||||
val sourceRoot = File(sourceRootPath)
|
||||
val vFile = localFileSystem.findFileByPath(sourceRoot.normalize().path)
|
||||
if (vFile == null) {
|
||||
val message = "Source file or directory not found: $sourceRootPath"
|
||||
|
||||
val buildFilePath = configuration.get(JVMConfigurationKeys.MODULE_XML_FILE)
|
||||
if (buildFilePath != null && Logger.isInitialized()) {
|
||||
Logger.getInstance(KotlinCoreEnvironment::class.java)
|
||||
.warn("$message\n\nbuild file path: $buildFilePath\ncontent:\n${buildFilePath.readText()}")
|
||||
}
|
||||
|
||||
configuration.report(CompilerMessageSeverity.ERROR, message, reportLocation)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!vFile.isDirectory && vFile.extension != KotlinFileType.EXTENSION) {
|
||||
if (!pluginsConfigured) {
|
||||
vFile.registerPluginsSuppliedExtensionsIfNeeded(project)
|
||||
pluginsConfigured = true
|
||||
}
|
||||
if (vFile.fileType != KotlinFileType.INSTANCE) {
|
||||
configuration.report(CompilerMessageSeverity.ERROR, "Source entry is not a Kotlin file: $sourceRootPath", reportLocation)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (file in sourceRoot.walkTopDown()) {
|
||||
if (!file.isFile) continue
|
||||
|
||||
val virtualFile = localFileSystem.findFileByPath(file.absoluteFile.normalize().path)?.let(virtualFileCreator::create)
|
||||
if (virtualFile != null && processedFiles.add(virtualFile)) {
|
||||
if (!pluginsConfigured) {
|
||||
virtualFile.registerPluginsSuppliedExtensionsIfNeeded(project)
|
||||
pluginsConfigured = true
|
||||
}
|
||||
body(virtualFile, isCommon, hmppModuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun VirtualFile.registerPluginsSuppliedExtensionsIfNeeded(project: Project) {
|
||||
if (
|
||||
extension == null ||
|
||||
extension == KotlinFileType.EXTENSION ||
|
||||
extension == JavaFileType.INSTANCE.defaultExtension ||
|
||||
extension == JavaClassFileType.INSTANCE.defaultExtension ||
|
||||
fileType == KotlinFileType.INSTANCE
|
||||
) return
|
||||
for (extension in CompilerConfigurationExtension.getInstances(project)) {
|
||||
extension.updateFileRegistry()
|
||||
}
|
||||
}
|
||||
|
||||
fun createSourceFilesFromSourceRoots(
|
||||
configuration: CompilerConfiguration,
|
||||
project: Project,
|
||||
sourceRoots: List<KotlinSourceRoot>,
|
||||
reportLocation: CompilerMessageLocation? = null
|
||||
): MutableList<KtFile> {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val result = mutableListOf<KtFile>()
|
||||
sourceRoots.forAllFiles(configuration, project, reportLocation) { virtualFile, isCommon, moduleName ->
|
||||
psiManager.findFile(virtualFile)?.let {
|
||||
if (it is KtFile) {
|
||||
it.isCommonSource = isCommon
|
||||
if (moduleName != null) {
|
||||
it.hmppModuleName = moduleName
|
||||
}
|
||||
result.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
val KotlinCoreEnvironment.messageCollector: MessageCollector
|
||||
get() = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
fun CompilerConfiguration.applyModuleProperties(module: Module, buildFile: File?): CompilerConfiguration {
|
||||
return copy().apply {
|
||||
if (buildFile != null) {
|
||||
fun checkKeyIsNull(key: CompilerConfigurationKey<*>, name: String) {
|
||||
assert(get(key) == null) { "$name should be null, when buildFile is used" }
|
||||
}
|
||||
|
||||
checkKeyIsNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, "OUTPUT_DIRECTORY")
|
||||
checkKeyIsNull(JVMConfigurationKeys.OUTPUT_JAR, "OUTPUT_JAR")
|
||||
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(module.getOutputDirectory()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
|
||||
|
||||
class ByteArrayCharSequence(
|
||||
private val bytes: ByteArray,
|
||||
private val start: Int = 0,
|
||||
private val end: Int = bytes.size
|
||||
) : CharSequence {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
error("Do not try computing hashCode ByteArrayCharSequence")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
error("Do not try comparing ByteArrayCharSequence")
|
||||
}
|
||||
|
||||
override val length get() = end - start
|
||||
|
||||
override fun get(index: Int): Char = bytes[index + start].toChar()
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
|
||||
if (startIndex == 0 && endIndex == length) return this
|
||||
return ByteArrayCharSequence(bytes, start + startIndex, start + endIndex)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val chars = CharArray(length)
|
||||
|
||||
for (i in 0 until length) {
|
||||
chars[i] = bytes[i + start].toChar()
|
||||
}
|
||||
|
||||
return String(chars)
|
||||
}
|
||||
}
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
|
||||
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.intellij.util.io.FileAccessorCache
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
private typealias RandomAccessFileAndBuffer = Pair<RandomAccessFile, MappedByteBuffer>
|
||||
|
||||
class FastJarFileSystem private constructor(internal val unmapBuffer: MappedByteBuffer.() -> Unit) : DeprecatedVirtualFileSystem() {
|
||||
private val myHandlers: MutableMap<String, FastJarHandler> =
|
||||
ConcurrentFactoryMap.createMap { key: String -> FastJarHandler(this@FastJarFileSystem, key) }
|
||||
|
||||
internal val cachedOpenFileHandles: FileAccessorCache<File, RandomAccessFileAndBuffer> =
|
||||
object : FileAccessorCache<File, RandomAccessFileAndBuffer>(20, 10) {
|
||||
@Throws(IOException::class)
|
||||
override fun createAccessor(file: File): RandomAccessFileAndBuffer {
|
||||
val randomAccessFile = RandomAccessFile(file, "r")
|
||||
return Pair(randomAccessFile, randomAccessFile.channel.map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length()))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun disposeAccessor(fileAccessor: RandomAccessFileAndBuffer) {
|
||||
fileAccessor.first.close()
|
||||
fileAccessor.second.unmapBuffer()
|
||||
}
|
||||
|
||||
override fun isEqual(val1: File, val2: File): Boolean {
|
||||
return val1 == val2 // reference equality to handle different jars for different ZipHandlers on the same path
|
||||
}
|
||||
}
|
||||
|
||||
override fun getProtocol(): String {
|
||||
return StandardFileSystems.JAR_PROTOCOL
|
||||
}
|
||||
|
||||
override fun findFileByPath(path: String): VirtualFile? {
|
||||
val pair = splitPath(path)
|
||||
return myHandlers[pair.first]!!.findFileByPath(pair.second)
|
||||
}
|
||||
|
||||
override fun refresh(asynchronous: Boolean) {}
|
||||
override fun refreshAndFindFileByPath(path: String): VirtualFile? {
|
||||
return findFileByPath(path)
|
||||
}
|
||||
|
||||
fun clearHandlersCache() {
|
||||
myHandlers.clear()
|
||||
cleanOpenFilesCache()
|
||||
}
|
||||
|
||||
fun cleanOpenFilesCache() {
|
||||
cachedOpenFileHandles.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun splitPath(path: String): Couple<String> {
|
||||
val separator = path.indexOf("!/")
|
||||
require(separator >= 0) { "Path in JarFileSystem must contain a separator: $path" }
|
||||
val localPath = path.substring(0, separator)
|
||||
val pathInJar = path.substring(separator + 2)
|
||||
return Couple.of(localPath, pathInJar)
|
||||
}
|
||||
|
||||
fun createIfUnmappingPossible(): FastJarFileSystem? {
|
||||
val cleanerCallBack = prepareCleanerCallback() ?: return null
|
||||
return FastJarFileSystem(cleanerCallBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val IS_PRIOR_9_JRE = System.getProperty("java.specification.version", "").startsWith("1.")
|
||||
|
||||
private fun prepareCleanerCallback(): ((ByteBuffer) -> Unit)? {
|
||||
return try {
|
||||
if (IS_PRIOR_9_JRE) {
|
||||
val cleaner = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner")
|
||||
cleaner.isAccessible = true
|
||||
|
||||
val clean = Class.forName("sun.misc.Cleaner").getMethod("clean")
|
||||
clean.isAccessible = true
|
||||
|
||||
{ buffer: ByteBuffer -> clean.invoke(cleaner.invoke(buffer)) }
|
||||
} else {
|
||||
val unsafeClass = try {
|
||||
Class.forName("sun.misc.Unsafe")
|
||||
} catch (ex: Exception) {
|
||||
// jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
|
||||
// but that method should be added if sun.misc.Unsafe is removed.
|
||||
Class.forName("jdk.internal.misc.Unsafe")
|
||||
}
|
||||
|
||||
val clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer::class.java)
|
||||
clean.isAccessible = true
|
||||
|
||||
val theUnsafeField = unsafeClass.getDeclaredField("theUnsafe")
|
||||
theUnsafeField.isAccessible = true
|
||||
|
||||
val theUnsafe = theUnsafeField.get(null);
|
||||
|
||||
{ buffer: ByteBuffer -> clean.invoke(theUnsafe, buffer) }
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.channels.FileChannel
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class FastJarHandler(val fileSystem: FastJarFileSystem, path: String) {
|
||||
private val myRoot: VirtualFile?
|
||||
internal val file = File(path)
|
||||
|
||||
private val cachedManifest: ByteArray?
|
||||
|
||||
init {
|
||||
val entries: List<ZipEntryDescription>
|
||||
RandomAccessFile(file, "r").use { randomAccessFile ->
|
||||
val mappedByteBuffer = randomAccessFile.channel.map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length())
|
||||
try {
|
||||
entries = try {
|
||||
mappedByteBuffer.parseCentralDirectory()
|
||||
} catch (e: Exception) {
|
||||
// copying the behavior of ArchiveHandler (and therefore ZipHandler)
|
||||
// TODO: consider propagating to compiler error or warning, but take into account that both javac and K1 simply ignore invalid jars in such cases
|
||||
Logger.getInstance(this::class.java).warn("Error while reading zip file: ${file.path}: $e", e)
|
||||
emptyList()
|
||||
}
|
||||
cachedManifest =
|
||||
entries.singleOrNull { StringUtil.equals(MANIFEST_PATH, it.relativePath) }
|
||||
?.let(mappedByteBuffer::contentsToByteArray)
|
||||
} finally {
|
||||
with(fileSystem) {
|
||||
mappedByteBuffer.unmapBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myRoot = FastJarVirtualFile(this, "", -1, parent = null, entryDescription = null)
|
||||
|
||||
// ByteArrayCharSequence should not be used instead of String
|
||||
// because the former class does not support equals/hashCode properly
|
||||
val filesByRelativePath = HashMap<String, FastJarVirtualFile>(entries.size)
|
||||
filesByRelativePath[""] = myRoot
|
||||
|
||||
for (entryDescription in entries) {
|
||||
if (!entryDescription.isDirectory) {
|
||||
createFile(entryDescription, filesByRelativePath)
|
||||
} else {
|
||||
getOrCreateDirectory(entryDescription.relativePath, filesByRelativePath)
|
||||
}
|
||||
}
|
||||
|
||||
for (node in filesByRelativePath.values) {
|
||||
node.initChildrenArrayFromList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFile(entry: ZipEntryDescription, directories: MutableMap<String, FastJarVirtualFile>): FastJarVirtualFile {
|
||||
val (parentName, shortName) = entry.relativePath.splitPath()
|
||||
|
||||
val parentFile = getOrCreateDirectory(parentName, directories)
|
||||
if ("." == shortName) {
|
||||
return parentFile
|
||||
}
|
||||
|
||||
return FastJarVirtualFile(
|
||||
this, shortName,
|
||||
if (entry.isDirectory) -1 else entry.uncompressedSize,
|
||||
parentFile,
|
||||
entry,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getOrCreateDirectory(entryName: CharSequence, directories: MutableMap<String, FastJarVirtualFile>): FastJarVirtualFile {
|
||||
return directories.getOrPut(entryName.toString()) {
|
||||
val (parentPath, shortName) = entryName.splitPath()
|
||||
val parentFile = getOrCreateDirectory(parentPath, directories)
|
||||
|
||||
FastJarVirtualFile(this, shortName, -1, parentFile, entryDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.splitPath(): Pair<CharSequence, CharSequence> {
|
||||
var slashIndex = this.length - 1
|
||||
|
||||
while (slashIndex >= 0 && this[slashIndex] != '/') {
|
||||
slashIndex--
|
||||
}
|
||||
|
||||
if (slashIndex == -1) return Pair("", this)
|
||||
return Pair(subSequence(0, slashIndex), subSequence(slashIndex + 1, this.length))
|
||||
}
|
||||
|
||||
fun findFileByPath(pathInJar: String): VirtualFile? {
|
||||
return myRoot?.findFileByRelativePath(pathInJar)
|
||||
}
|
||||
|
||||
fun contentsToByteArray(zipEntryDescription: ZipEntryDescription): ByteArray {
|
||||
val relativePath = zipEntryDescription.relativePath
|
||||
if (StringUtil.equals(relativePath, MANIFEST_PATH)) return cachedManifest ?: throw FileNotFoundException("$file!/$relativePath")
|
||||
return fileSystem.cachedOpenFileHandles[file].use {
|
||||
synchronized(it) {
|
||||
it.get().second.contentsToByteArray(zipEntryDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MANIFEST_PATH = "META-INF/MANIFEST.MF"
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
|
||||
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
internal class FastJarVirtualFile(
|
||||
private val handler: FastJarHandler,
|
||||
private val name: CharSequence,
|
||||
private val length: Int,
|
||||
private val parent: FastJarVirtualFile?,
|
||||
private val entryDescription: ZipEntryDescription?,
|
||||
) : VirtualFile() {
|
||||
|
||||
private var myChildrenArray = EMPTY_ARRAY
|
||||
private val myChildrenList: MutableList<VirtualFile> = mutableListOf()
|
||||
|
||||
init {
|
||||
parent?.myChildrenList?.add(this)
|
||||
}
|
||||
|
||||
fun initChildrenArrayFromList() {
|
||||
myChildrenArray = myChildrenList.toTypedArray()
|
||||
myChildrenList.clear()
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return name.toString()
|
||||
}
|
||||
|
||||
override fun getNameSequence(): CharSequence {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getFileSystem(): VirtualFileSystem {
|
||||
return handler.fileSystem
|
||||
}
|
||||
|
||||
override fun getPath(): String {
|
||||
if (parent == null) {
|
||||
return FileUtil.toSystemIndependentName(handler.file.path) + "!/"
|
||||
}
|
||||
val parentPath = parent.path
|
||||
val answer = StringBuilder(parentPath.length + 1 + name.length)
|
||||
answer.append(parentPath)
|
||||
if (answer[answer.length - 1] != '/') {
|
||||
answer.append('/')
|
||||
}
|
||||
answer.append(name)
|
||||
return answer.toString()
|
||||
}
|
||||
|
||||
override fun isWritable(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isDirectory(): Boolean {
|
||||
return length < 0
|
||||
}
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getParent(): VirtualFile? {
|
||||
return parent
|
||||
}
|
||||
|
||||
override fun getChildren(): Array<VirtualFile> {
|
||||
return myChildrenArray
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream {
|
||||
throw UnsupportedOperationException("JarFileSystem is read-only")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun contentsToByteArray(): ByteArray {
|
||||
if (entryDescription == null) return EMPTY_BYTE_ARRAY
|
||||
return handler.contentsToByteArray(entryDescription)
|
||||
}
|
||||
|
||||
override fun getTimeStamp(): Long = 0
|
||||
|
||||
override fun getLength(): Long = length.toLong()
|
||||
|
||||
override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputStream(): InputStream {
|
||||
return BufferExposingByteArrayInputStream(contentsToByteArray())
|
||||
}
|
||||
|
||||
override fun getModificationStamp(): Long {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private val EMPTY_BYTE_ARRAY = ByteArray(0)
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.util.zip.Inflater
|
||||
|
||||
|
||||
class ZipEntryDescription(
|
||||
val relativePath: CharSequence,
|
||||
val compressedSize: Int,
|
||||
val uncompressedSize: Int,
|
||||
val offsetInFile: Int,
|
||||
val compressionKind: CompressionKind,
|
||||
val fileNameSize: Int,
|
||||
) {
|
||||
|
||||
enum class CompressionKind {
|
||||
PLAIN, DEFLATE
|
||||
}
|
||||
|
||||
val isDirectory get() = uncompressedSize == 0
|
||||
}
|
||||
|
||||
private const val END_OF_CENTRAL_DIR_SIZE = 22
|
||||
private const val END_OF_CENTRAL_DIR_ZIP64_SIZE = 56
|
||||
private const val LOCAL_FILE_HEADER_EXTRA_OFFSET = 28
|
||||
private const val LOCAL_FILE_HEADER_SIZE = LOCAL_FILE_HEADER_EXTRA_OFFSET + 2
|
||||
|
||||
fun MappedByteBuffer.contentsToByteArray(
|
||||
zipEntryDescription: ZipEntryDescription
|
||||
): ByteArray {
|
||||
order(ByteOrder.LITTLE_ENDIAN)
|
||||
val extraSize =
|
||||
getUnsignedShort(zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_EXTRA_OFFSET)
|
||||
|
||||
position(
|
||||
zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_SIZE + zipEntryDescription.fileNameSize + extraSize
|
||||
)
|
||||
val compressed = ByteArray(zipEntryDescription.compressedSize + 1)
|
||||
get(compressed, 0, zipEntryDescription.compressedSize)
|
||||
|
||||
return when (zipEntryDescription.compressionKind) {
|
||||
ZipEntryDescription.CompressionKind.DEFLATE -> {
|
||||
val inflater = Inflater(true)
|
||||
inflater.setInput(compressed, 0, zipEntryDescription.compressedSize)
|
||||
|
||||
val result = ByteArray(zipEntryDescription.uncompressedSize)
|
||||
|
||||
inflater.inflate(result)
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
ZipEntryDescription.CompressionKind.PLAIN -> compressed.copyOf(zipEntryDescription.compressedSize)
|
||||
}
|
||||
}
|
||||
|
||||
fun MappedByteBuffer.parseCentralDirectory(): List<ZipEntryDescription> {
|
||||
order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
val (entriesNumber, offsetOfCentralDirectory) = parseCentralDirectoryRecordsNumberAndOffset()
|
||||
|
||||
var currentOffset = offsetOfCentralDirectory
|
||||
|
||||
val result = mutableListOf<ZipEntryDescription>()
|
||||
for (i in 0 until entriesNumber) {
|
||||
val headerConst = getInt(currentOffset)
|
||||
require(headerConst == 0x02014b50) {
|
||||
"$i: $headerConst"
|
||||
}
|
||||
|
||||
val versionNeededToExtract =
|
||||
getShort(currentOffset + 6).toInt()
|
||||
|
||||
val compressionMethod = getShort(currentOffset + 10).toInt()
|
||||
|
||||
val compressedSize = getInt(currentOffset + 20)
|
||||
val uncompressedSize = getInt(currentOffset + 24)
|
||||
val fileNameLength = getUnsignedShort(currentOffset + 28)
|
||||
val extraLength = getUnsignedShort(currentOffset + 30)
|
||||
val fileCommentLength = getUnsignedShort(currentOffset + 32)
|
||||
|
||||
val offsetOfFileData = getInt(currentOffset + 42)
|
||||
|
||||
val bytesForName = ByteArray(fileNameLength)
|
||||
|
||||
position(currentOffset + 46)
|
||||
get(bytesForName)
|
||||
|
||||
val name =
|
||||
if (bytesForName.all { it >= 0 })
|
||||
ByteArrayCharSequence(bytesForName)
|
||||
else
|
||||
String(bytesForName, Charsets.UTF_8)
|
||||
|
||||
currentOffset += 46 + fileNameLength + extraLength + fileCommentLength
|
||||
|
||||
// We support version needed to extract 10 and 20. However, there are zip
|
||||
// files in the eco-system with entries with invalid version to extract
|
||||
// of 0. Therefore, we just check that the version is between 0 and 20.
|
||||
require(versionNeededToExtract in 0..20) {
|
||||
"Unexpected versionNeededToExtract ($versionNeededToExtract) at $name"
|
||||
}
|
||||
|
||||
val compressionKind = when (compressionMethod) {
|
||||
0 -> ZipEntryDescription.CompressionKind.PLAIN
|
||||
8 -> ZipEntryDescription.CompressionKind.DEFLATE
|
||||
else -> error("Unexpected compression method ($compressionMethod) at $name")
|
||||
}
|
||||
|
||||
result += ZipEntryDescription(
|
||||
name, compressedSize, uncompressedSize, offsetOfFileData, compressionKind,
|
||||
fileNameLength
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun MappedByteBuffer.parseCentralDirectoryRecordsNumberAndOffset(): Pair<Int, Int> {
|
||||
var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_SIZE
|
||||
while (endOfCentralDirectoryOffset >= 0) {
|
||||
// header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
|
||||
if (getInt(endOfCentralDirectoryOffset) == 0x06054b50) break
|
||||
endOfCentralDirectoryOffset--
|
||||
}
|
||||
|
||||
val entriesNumber = getUnsignedShort(endOfCentralDirectoryOffset + 10)
|
||||
val offsetOfCentralDirectory = getInt(endOfCentralDirectoryOffset + 16)
|
||||
// Offset of start of central directory, relative to start of archive (or -1 for ZIP64)
|
||||
// (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
|
||||
if (entriesNumber == 0xffff || offsetOfCentralDirectory == -1) return parseZip64CentralDirectoryRecordsNumberAndOffset()
|
||||
|
||||
return Pair(entriesNumber, offsetOfCentralDirectory)
|
||||
}
|
||||
|
||||
private fun MappedByteBuffer.parseZip64CentralDirectoryRecordsNumberAndOffset(): Pair<Int, Int> {
|
||||
var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_ZIP64_SIZE
|
||||
while (endOfCentralDirectoryOffset >= 0) {
|
||||
// header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
|
||||
if (getInt(endOfCentralDirectoryOffset) == 0x06064b50) break
|
||||
endOfCentralDirectoryOffset--
|
||||
}
|
||||
|
||||
val entriesNumber = getLong(endOfCentralDirectoryOffset + 32)
|
||||
val offsetOfCentralDirectory = getLong(endOfCentralDirectoryOffset + 48)
|
||||
|
||||
require(entriesNumber <= Int.MAX_VALUE) {
|
||||
"Jar $entriesNumber entries number equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS"
|
||||
}
|
||||
|
||||
require(offsetOfCentralDirectory <= Int.MAX_VALUE) {
|
||||
"Jar $offsetOfCentralDirectory offset equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS"
|
||||
}
|
||||
|
||||
return Pair(entriesNumber.toInt(), offsetOfCentralDirectory.toInt())
|
||||
}
|
||||
|
||||
private fun ByteBuffer.getUnsignedShort(offset: Int): Int = java.lang.Short.toUnsignedInt(getShort(offset))
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.config
|
||||
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.load.java.JavaClassesTracker
|
||||
|
||||
object ClassicFrontendSpecificJvmConfigurationKeys {
|
||||
@JvmField
|
||||
val JAVA_CLASSES_TRACKER: CompilerConfigurationKey<JavaClassesTracker> = CompilerConfigurationKey.create("Java classes tracker")
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.cli.jvm.config
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.config.ContentRoot
|
||||
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.report
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
|
||||
interface JvmContentRootBase : ContentRoot
|
||||
|
||||
interface JvmClasspathRootBase : JvmContentRootBase {
|
||||
val isSdkRoot: Boolean
|
||||
}
|
||||
|
||||
interface JvmContentRoot : JvmContentRootBase {
|
||||
val file: File
|
||||
}
|
||||
|
||||
data class JvmClasspathRoot(override val file: File, override val isSdkRoot: Boolean) : JvmContentRoot, JvmClasspathRootBase {
|
||||
constructor(file: File) : this(file, false)
|
||||
}
|
||||
|
||||
@Suppress("unused") // Might be useful for external tools which invoke kotlinc with their own file system, not based on java.io.File.
|
||||
data class VirtualJvmClasspathRoot(val file: VirtualFile, override val isSdkRoot: Boolean) : JvmClasspathRootBase {
|
||||
constructor(file: VirtualFile) : this(file, false)
|
||||
}
|
||||
|
||||
data class JavaSourceRoot(override val file: File, val packagePrefix: String?) : JvmContentRoot
|
||||
|
||||
data class JvmModulePathRoot(override val file: File) : JvmContentRoot
|
||||
|
||||
fun CompilerConfiguration.addJvmClasspathRoot(file: File) {
|
||||
add(CLIConfigurationKeys.CONTENT_ROOTS, JvmClasspathRoot(file))
|
||||
}
|
||||
|
||||
fun CompilerConfiguration.addJvmClasspathRoots(files: List<File>) {
|
||||
files.forEach(this::addJvmClasspathRoot)
|
||||
}
|
||||
|
||||
fun CompilerConfiguration.addJvmSdkRoots(files: List<File>) {
|
||||
addAll(CLIConfigurationKeys.CONTENT_ROOTS, 0, files.map { file -> JvmClasspathRoot(file, true) })
|
||||
}
|
||||
|
||||
val CompilerConfiguration.jvmClasspathRoots: List<File>
|
||||
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).filterIsInstance<JvmClasspathRoot>().map(JvmContentRoot::file)
|
||||
|
||||
val CompilerConfiguration.jvmModularRoots: List<File>
|
||||
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).filterIsInstance<JvmModulePathRoot>().map(JvmContentRoot::file)
|
||||
|
||||
@JvmOverloads
|
||||
fun CompilerConfiguration.addJavaSourceRoot(file: File, packagePrefix: String? = null) {
|
||||
add(CLIConfigurationKeys.CONTENT_ROOTS, JavaSourceRoot(file, packagePrefix))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun CompilerConfiguration.addJavaSourceRoots(files: List<File>, packagePrefix: String? = null) {
|
||||
files.forEach { addJavaSourceRoot(it, packagePrefix) }
|
||||
}
|
||||
|
||||
val CompilerConfiguration.javaSourceRoots: Set<String>
|
||||
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNullTo(linkedSetOf()) { root ->
|
||||
when (root) {
|
||||
is KotlinSourceRoot -> root.path
|
||||
is JavaSourceRoot -> root.file.path
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun CompilerConfiguration.configureJdkClasspathRoots() {
|
||||
if (getBoolean(JVMConfigurationKeys.NO_JDK)) return
|
||||
|
||||
val javaRoot = get(JVMConfigurationKeys.JDK_HOME) ?: File(System.getProperty("java.home"))
|
||||
val classesRoots = PathUtil.getJdkClassesRootsFromJdkOrJre(javaRoot)
|
||||
|
||||
if (!CoreJrtFileSystem.isModularJdk(javaRoot)) {
|
||||
if (classesRoots.isEmpty()) {
|
||||
report(CompilerMessageSeverity.ERROR, "No class roots are found in the JDK path: $javaRoot")
|
||||
} else {
|
||||
addJvmSdkRoots(classesRoots)
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.cli.jvm.index
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class JvmDependenciesDynamicCompoundIndex : JvmDependenciesIndex {
|
||||
private val indices = arrayListOf<JvmDependenciesIndex>()
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
|
||||
fun addIndex(index: JvmDependenciesIndex) {
|
||||
lock.write {
|
||||
indices.add(index)
|
||||
}
|
||||
}
|
||||
|
||||
fun addNewIndexForRoots(roots: Iterable<JavaRoot>): JvmDependenciesIndex? =
|
||||
lock.read {
|
||||
val alreadyIndexed = indexedRoots.toHashSet()
|
||||
val newRoots = roots.filter { root -> root !in alreadyIndexed }
|
||||
if (newRoots.isEmpty()) null
|
||||
else JvmDependenciesIndexImpl(newRoots).also(this::addIndex)
|
||||
}
|
||||
|
||||
override val indexedRoots: Sequence<JavaRoot> get() = indices.asSequence().flatMap { it.indexedRoots }
|
||||
|
||||
override fun <T : Any> findClass(
|
||||
classId: ClassId,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType>,
|
||||
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
|
||||
): T? = lock.read {
|
||||
indices.asSequence().mapNotNull { it.findClass(classId, acceptedRootTypes, findClassGivenDirectory) }.firstOrNull()
|
||||
}
|
||||
|
||||
override fun traverseDirectoriesInPackage(
|
||||
packageFqName: FqName,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType>,
|
||||
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
|
||||
) = lock.read {
|
||||
indices.forEach { it.traverseDirectoriesInPackage(packageFqName, acceptedRootTypes, continueSearch) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.cli.jvm.index
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.util.*
|
||||
|
||||
interface JvmDependenciesIndex {
|
||||
val indexedRoots: Sequence<JavaRoot>
|
||||
|
||||
fun <T : Any> findClass(
|
||||
classId: ClassId,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
|
||||
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
|
||||
): T?
|
||||
|
||||
fun traverseDirectoriesInPackage(
|
||||
packageFqName: FqName,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
|
||||
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
|
||||
)
|
||||
}
|
||||
|
||||
data class JavaRoot(val file: VirtualFile, val type: RootType, val prefixFqName: FqName? = null) {
|
||||
enum class RootType {
|
||||
SOURCE,
|
||||
BINARY,
|
||||
BINARY_SIG
|
||||
}
|
||||
|
||||
companion object RootTypes {
|
||||
val OnlyBinary: Set<RootType> = EnumSet.of(RootType.BINARY, RootType.BINARY_SIG)
|
||||
val OnlySource: Set<RootType> = EnumSet.of(RootType.SOURCE)
|
||||
val SourceAndBinary: Set<RootType> = EnumSet.of(RootType.BINARY, RootType.BINARY_SIG, RootType.SOURCE)
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.cli.jvm.index
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import gnu.trove.THashMap
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.utils.IntArrayList
|
||||
import java.util.*
|
||||
|
||||
// speeds up finding files/classes in classpath/java source roots
|
||||
// NOT THREADSAFE, needs to be adapted/removed if we want compiler to be multithreaded
|
||||
// the main idea of this class is for each package to store roots which contains it to avoid excessive file system traversal
|
||||
class JvmDependenciesIndexImpl(_roots: List<JavaRoot>) : JvmDependenciesIndex {
|
||||
//these fields are computed based on _roots passed to constructor which are filled in later
|
||||
private val roots: List<JavaRoot> by lazy { _roots.toList() }
|
||||
|
||||
private val maxIndex: Int
|
||||
get() = roots.size
|
||||
|
||||
// each "Cache" object corresponds to a package
|
||||
private class Cache {
|
||||
private val innerPackageCaches = HashMap<String, Cache>()
|
||||
|
||||
operator fun get(name: String) = innerPackageCaches.getOrPut(name, ::Cache)
|
||||
|
||||
// indices of roots that are known to contain this package
|
||||
// if this list contains [1, 3, 5] then roots with indices 1, 3 and 5 are known to contain this package, 2 and 4 are known not to (no information about roots 6 or higher)
|
||||
// if this list contains maxIndex that means that all roots containing this package are known
|
||||
@Suppress("DEPRECATION") // TODO: fix deprecation
|
||||
val rootIndices = com.intellij.util.containers.IntArrayList(2)
|
||||
}
|
||||
|
||||
// root "Cache" object corresponds to DefaultPackage which exists in every root. Roots with non-default fqname are also listed here but
|
||||
// they will be ignored on requests with invalid fqname prefix.
|
||||
private val rootCache: Cache by lazy {
|
||||
Cache().apply {
|
||||
roots.indices.forEach(rootIndices::add)
|
||||
rootIndices.add(maxIndex)
|
||||
rootIndices.trimToSize()
|
||||
}
|
||||
}
|
||||
|
||||
// holds the request and the result last time we searched for class
|
||||
// helps improve several scenarios, LazyJavaResolverContext.findClassInJava being the most important
|
||||
private var lastClassSearch: Pair<FindClassRequest, SearchResult>? = null
|
||||
|
||||
override val indexedRoots by lazy { roots.asSequence() }
|
||||
|
||||
private val packageCache: Array<out MutableMap<String, VirtualFile?>> by lazy {
|
||||
Array(roots.size) { THashMap<String, VirtualFile?>() }
|
||||
}
|
||||
|
||||
override fun traverseDirectoriesInPackage(
|
||||
packageFqName: FqName,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType>,
|
||||
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
|
||||
) {
|
||||
search(TraverseRequest(packageFqName, acceptedRootTypes)) { dir, rootType ->
|
||||
if (continueSearch(dir, rootType)) null else Unit
|
||||
}
|
||||
}
|
||||
|
||||
// findClassGivenDirectory MUST check whether the class with this classId exists in given package
|
||||
override fun <T : Any> findClass(
|
||||
classId: ClassId,
|
||||
acceptedRootTypes: Set<JavaRoot.RootType>,
|
||||
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
|
||||
): T? {
|
||||
// make a decision based on information saved from last class search
|
||||
if (lastClassSearch?.first?.classId != classId) {
|
||||
return search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
|
||||
}
|
||||
|
||||
val (cachedRequest, cachedResult) = lastClassSearch!!
|
||||
return when (cachedResult) {
|
||||
is SearchResult.NotFound -> {
|
||||
val limitedRootTypes = acceptedRootTypes - cachedRequest.acceptedRootTypes
|
||||
if (limitedRootTypes.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
search(FindClassRequest(classId, limitedRootTypes), findClassGivenDirectory)
|
||||
}
|
||||
}
|
||||
is SearchResult.Found -> {
|
||||
if (cachedRequest.acceptedRootTypes == acceptedRootTypes) {
|
||||
findClassGivenDirectory(cachedResult.packageDirectory, cachedResult.root.type)
|
||||
} else {
|
||||
search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Any> search(request: SearchRequest, handler: (VirtualFile, JavaRoot.RootType) -> T?): T? {
|
||||
// a list of package sub names, ["org", "jb", "kotlin"]
|
||||
val packagesPath = request.packageFqName.pathSegments().map { it.identifier }
|
||||
// a list of caches corresponding to packages, [default, "org", "org.jb", "org.jb.kotlin"]
|
||||
val caches = cachesPath(packagesPath)
|
||||
|
||||
var processedRootsUpTo = -1
|
||||
// traverse caches starting from last, which contains most specific information
|
||||
|
||||
// NOTE: indices manipulation instead of using caches.reversed() is here for performance reasons
|
||||
for (cacheIndex in caches.lastIndex downTo 0) {
|
||||
val cacheRootIndices = caches[cacheIndex].rootIndices
|
||||
for (i in 0 until cacheRootIndices.size()) {
|
||||
val rootIndex = cacheRootIndices[i]
|
||||
if (rootIndex <= processedRootsUpTo) continue // roots with those indices have been processed by now
|
||||
|
||||
val directoryInRoot = travelPath(rootIndex, request.packageFqName, packagesPath, cacheIndex, caches) ?: continue
|
||||
val root = roots[rootIndex]
|
||||
if (root.type in request.acceptedRootTypes) {
|
||||
val result = handler(directoryInRoot, root.type)
|
||||
if (result != null) {
|
||||
if (request is FindClassRequest) {
|
||||
lastClassSearch = Pair(request, SearchResult.Found(directoryInRoot, root))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
processedRootsUpTo = if (cacheRootIndices.isEmpty) processedRootsUpTo else cacheRootIndices[cacheRootIndices.size() - 1]
|
||||
}
|
||||
|
||||
if (request is FindClassRequest) {
|
||||
lastClassSearch = Pair(request, SearchResult.NotFound)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// try to find a target directory corresponding to package represented by packagesPath in a given root represented by index
|
||||
// possibly filling "Cache" objects with new information
|
||||
private fun travelPath(
|
||||
rootIndex: Int,
|
||||
packageFqName: FqName,
|
||||
packagesPath: List<String>,
|
||||
fillCachesAfter: Int,
|
||||
cachesPath: List<Cache>
|
||||
): VirtualFile? {
|
||||
if (rootIndex >= maxIndex) {
|
||||
for (i in (fillCachesAfter + 1) until cachesPath.size) {
|
||||
// we all know roots that contain this package by now
|
||||
cachesPath[i].rootIndices.add(maxIndex)
|
||||
cachesPath[i].rootIndices.trimToSize()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return packageCache[rootIndex].getOrPut(packageFqName.asString()) {
|
||||
doTravelPath(rootIndex, packagesPath, fillCachesAfter, cachesPath)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTravelPath(rootIndex: Int, packagesPath: List<String>, fillCachesAfter: Int, cachesPath: List<Cache>): VirtualFile? {
|
||||
val pathRoot = roots[rootIndex]
|
||||
val prefixPathSegments = pathRoot.prefixFqName?.pathSegments()
|
||||
|
||||
var currentFile = pathRoot.file
|
||||
|
||||
for (pathIndex in packagesPath.indices) {
|
||||
val subPackageName = packagesPath[pathIndex]
|
||||
if (prefixPathSegments != null && pathIndex < prefixPathSegments.size) {
|
||||
// Traverse prefix first instead of traversing real directories
|
||||
if (prefixPathSegments[pathIndex].identifier != subPackageName) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
currentFile = currentFile.findChildPackage(subPackageName, pathRoot.type) ?: return null
|
||||
}
|
||||
|
||||
val correspondingCacheIndex = pathIndex + 1
|
||||
if (correspondingCacheIndex > fillCachesAfter) {
|
||||
// subPackageName exists in this root
|
||||
cachesPath[correspondingCacheIndex].rootIndices.add(rootIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return currentFile
|
||||
}
|
||||
|
||||
private fun VirtualFile.findChildPackage(subPackageName: String, rootType: JavaRoot.RootType): VirtualFile? {
|
||||
val childDirectory = findChild(subPackageName) ?: return null
|
||||
|
||||
val fileExtension = when (rootType) {
|
||||
JavaRoot.RootType.BINARY -> JavaClassFileType.INSTANCE.defaultExtension
|
||||
JavaRoot.RootType.BINARY_SIG -> "sig"
|
||||
JavaRoot.RootType.SOURCE -> JavaFileType.INSTANCE.defaultExtension
|
||||
}
|
||||
|
||||
// If in addition to a directory "foo" there's a class file "foo.class" AND there are no classes anywhere in the directory "foo",
|
||||
// then we ignore the directory and let the resolution choose the class "foo" instead.
|
||||
if (findChild("$subPackageName.$fileExtension")?.isDirectory == false) {
|
||||
if (VfsUtilCore.processFilesRecursively(childDirectory) { file -> file.extension != fileExtension }) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return childDirectory
|
||||
}
|
||||
|
||||
private fun cachesPath(path: List<String>): List<Cache> {
|
||||
val caches = ArrayList<Cache>(path.size + 1)
|
||||
caches.add(rootCache)
|
||||
var currentCache = rootCache
|
||||
for (subPackageName in path) {
|
||||
currentCache = currentCache[subPackageName]
|
||||
caches.add(currentCache)
|
||||
}
|
||||
return caches
|
||||
}
|
||||
|
||||
private data class FindClassRequest(val classId: ClassId, override val acceptedRootTypes: Set<JavaRoot.RootType>) : SearchRequest {
|
||||
override val packageFqName: FqName
|
||||
get() = classId.packageFqName
|
||||
}
|
||||
|
||||
private data class TraverseRequest(
|
||||
override val packageFqName: FqName,
|
||||
override val acceptedRootTypes: Set<JavaRoot.RootType>
|
||||
) : SearchRequest
|
||||
|
||||
private interface SearchRequest {
|
||||
val packageFqName: FqName
|
||||
val acceptedRootTypes: Set<JavaRoot.RootType>
|
||||
}
|
||||
|
||||
private sealed class SearchResult {
|
||||
class Found(val packageDirectory: VirtualFile, val root: JavaRoot) : SearchResult()
|
||||
|
||||
object NotFound : SearchResult()
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.index
|
||||
|
||||
import com.intellij.lang.java.lexer.JavaLexer
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.psi.PsiKeyword
|
||||
import com.intellij.psi.impl.source.tree.ElementType
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class SingleJavaFileRootsIndex(private val roots: List<JavaRoot>) {
|
||||
init {
|
||||
for ((file) in roots) {
|
||||
assert(!file.isDirectory) { "Should not be a directory: $file" }
|
||||
}
|
||||
}
|
||||
|
||||
private val classIdsInRoots = ArrayList<List<ClassId>>(roots.size)
|
||||
|
||||
fun findJavaSourceClass(classId: ClassId): VirtualFile? =
|
||||
roots.indices
|
||||
.find { index -> classId in getClassIdsForRootAt(index) }
|
||||
?.let { index -> roots[index].file }
|
||||
|
||||
fun findJavaSourceClasses(packageFqName: FqName): List<ClassId> =
|
||||
roots.indices.flatMap(this::getClassIdsForRootAt).filter { root -> root.packageFqName == packageFqName }
|
||||
|
||||
private fun getClassIdsForRootAt(index: Int): List<ClassId> {
|
||||
for (i in classIdsInRoots.size..index) {
|
||||
classIdsInRoots.add(JavaSourceClassIdReader(roots[i].file).readClassIds())
|
||||
}
|
||||
return classIdsInRoots[index]
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a .java file, [readClassIds] uses lexer to determine which classes are declared in that file
|
||||
*/
|
||||
private class JavaSourceClassIdReader(file: VirtualFile) {
|
||||
private val lexer = JavaLexer(LanguageLevel.HIGHEST).apply {
|
||||
start(String(file.contentsToByteArray()))
|
||||
}
|
||||
private var braceBalance = 0
|
||||
private var parenthesisBalance = 0
|
||||
|
||||
private fun at(type: IElementType): Boolean = lexer.tokenType == type
|
||||
|
||||
private fun end(): Boolean = lexer.tokenType == null
|
||||
|
||||
private fun advance() {
|
||||
when {
|
||||
at(ElementType.LBRACE) -> braceBalance++
|
||||
at(ElementType.RBRACE) -> braceBalance--
|
||||
at(ElementType.LPARENTH) -> parenthesisBalance++
|
||||
at(ElementType.RPARENTH) -> parenthesisBalance--
|
||||
}
|
||||
lexer.advance()
|
||||
}
|
||||
|
||||
private fun tokenText(): String = lexer.tokenText
|
||||
|
||||
private fun atClass(): Boolean =
|
||||
braceBalance == 0 && parenthesisBalance == 0 && (lexer.tokenType in CLASS_KEYWORDS || atRecord())
|
||||
|
||||
private fun atRecord(): Boolean {
|
||||
// Note that the soft keyword "record" is lexed as IDENTIFIER instead of RECORD_KEYWORD.
|
||||
// This is kind of a sloppy way to parse a soft keyword, but we only do it at the top level, where it seems to work fine.
|
||||
return at(ElementType.IDENTIFIER) && tokenText() == PsiKeyword.RECORD
|
||||
}
|
||||
|
||||
fun readClassIds(): List<ClassId> {
|
||||
var packageFqName = FqName.ROOT
|
||||
while (!end() && !at(ElementType.PACKAGE_KEYWORD) && !atClass()) {
|
||||
advance()
|
||||
}
|
||||
if (at(ElementType.PACKAGE_KEYWORD)) {
|
||||
val packageName = StringBuilder()
|
||||
while (!end() && !at(ElementType.SEMICOLON)) {
|
||||
if (at(ElementType.IDENTIFIER) || at(ElementType.DOT)) {
|
||||
packageName.append(tokenText())
|
||||
}
|
||||
advance()
|
||||
}
|
||||
packageFqName = FqName(packageName.toString())
|
||||
}
|
||||
|
||||
val result = ArrayList<ClassId>(1)
|
||||
|
||||
while (true) {
|
||||
while (!end() && !atClass()) {
|
||||
advance()
|
||||
}
|
||||
if (end()) break
|
||||
advance()
|
||||
while (!end() && !at(ElementType.IDENTIFIER)) {
|
||||
advance()
|
||||
}
|
||||
if (end()) break
|
||||
result.add(ClassId(packageFqName, Name.identifier(tokenText())))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CLASS_KEYWORDS = setOf(ElementType.CLASS_KEYWORD, ElementType.INTERFACE_KEYWORD, ElementType.ENUM_KEYWORD)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.javac
|
||||
|
||||
import com.sun.tools.javac.util.Context
|
||||
import com.sun.tools.javac.util.Log
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import java.io.PrintWriter
|
||||
import java.io.Writer
|
||||
|
||||
class JavacLogger(
|
||||
context: Context,
|
||||
errorWriter: PrintWriter,
|
||||
warningWriter: PrintWriter,
|
||||
infoWriter: PrintWriter
|
||||
) : Log(context, errorWriter, warningWriter, infoWriter) {
|
||||
init {
|
||||
context.put(Log.outKey, infoWriter)
|
||||
}
|
||||
|
||||
override fun printLines(kind: WriterKind, message: String, vararg args: Any?) {}
|
||||
|
||||
companion object {
|
||||
fun preRegister(context: Context, messageCollector: MessageCollector) {
|
||||
context.put(Log.logKey, Context.Factory<Log> {
|
||||
JavacLogger(
|
||||
it,
|
||||
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.ERROR)),
|
||||
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.WARNING)),
|
||||
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.INFO))
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageCollectorAdapter(
|
||||
private val messageCollector: MessageCollector,
|
||||
private val severity: CompilerMessageSeverity
|
||||
) : Writer() {
|
||||
override fun write(buffer: CharArray, offset: Int, length: Int) {
|
||||
if (length == 1 && buffer[0] == '\n') return
|
||||
|
||||
messageCollector.report(severity, String(buffer, offset, length))
|
||||
}
|
||||
|
||||
override fun flush() {
|
||||
(messageCollector as? GroupingMessageCollector)?.flush()
|
||||
}
|
||||
|
||||
override fun close() = flush()
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.javac
|
||||
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.findFacadeClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.javac.JavacWrapperKotlinResolver
|
||||
import org.jetbrains.kotlin.javac.resolve.MockKotlinField
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaField
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
|
||||
class JavacWrapperKotlinResolverImpl(private val lightClassGenerationSupport: LightClassGenerationSupport) : JavacWrapperKotlinResolver {
|
||||
|
||||
private val supersCache = hashMapOf<KtClassOrObject, List<ClassId>>()
|
||||
|
||||
override fun resolveSupertypes(classOrObject: KtClassOrObject): List<ClassId> {
|
||||
val cachedItem = supersCache[classOrObject]
|
||||
if (cachedItem != null) {
|
||||
return cachedItem
|
||||
}
|
||||
|
||||
val classDescriptor =
|
||||
lightClassGenerationSupport.analyze(classOrObject).get(BindingContext.CLASS, classOrObject) ?: return emptyList()
|
||||
val classIds = classDescriptor.defaultType.constructor.supertypes
|
||||
.mapNotNull { (it.constructor.declarationDescriptor as? ClassDescriptor)?.classId }
|
||||
supersCache[classOrObject] = classIds
|
||||
|
||||
return classIds
|
||||
}
|
||||
|
||||
override fun findField(classOrObject: KtClassOrObject, name: String): JavaField? {
|
||||
val lightClass = classOrObject.toLightClass() ?: return null
|
||||
|
||||
return lightClass.allFields.find { it.name == name }?.let(::MockKotlinField)
|
||||
}
|
||||
|
||||
override fun findField(ktFile: KtFile?, name: String): JavaField? {
|
||||
val lightClass = ktFile?.findFacadeClass() ?: return null
|
||||
|
||||
return lightClass.allFields.find { it.name == name }?.let(::MockKotlinField)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.javac
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import com.sun.tools.javac.util.Context
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.javac.JavacWrapper
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
|
||||
object JavacWrapperRegistrar {
|
||||
private const val JAVAC_CONTEXT_CLASS = "com.sun.tools.javac.util.Context"
|
||||
|
||||
fun registerJavac(
|
||||
project: MockProject,
|
||||
configuration: CompilerConfiguration,
|
||||
javaFiles: List<File>,
|
||||
kotlinFiles: List<KtFile>,
|
||||
arguments: Array<String>?,
|
||||
bootClasspath: List<File>?,
|
||||
sourcePath: List<File>?,
|
||||
lightClassGenerationSupport: LightClassGenerationSupport,
|
||||
packagePartsProviders: List<JvmPackagePartProvider>
|
||||
): Boolean {
|
||||
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
try {
|
||||
Class.forName(JAVAC_CONTEXT_CLASS)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
messageCollector.report(ERROR, "'$JAVAC_CONTEXT_CLASS' class can't be found ('tools.jar' is not found)")
|
||||
return false
|
||||
}
|
||||
|
||||
val context = Context()
|
||||
JavacLogger.preRegister(context, messageCollector)
|
||||
|
||||
val jvmClasspathRoots = configuration.jvmClasspathRoots
|
||||
val outputDirectory = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
|
||||
val compileJava = configuration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)
|
||||
val kotlinSupertypesResolver = JavacWrapperKotlinResolverImpl(lightClassGenerationSupport)
|
||||
|
||||
val javacWrapper = JavacWrapper(
|
||||
javaFiles, kotlinFiles, arguments, jvmClasspathRoots, bootClasspath, sourcePath,
|
||||
kotlinSupertypesResolver, packagePartsProviders, compileJava, outputDirectory, context
|
||||
)
|
||||
|
||||
project.registerService(JavacWrapper::class.java, javacWrapper)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
|
||||
import java.io.File
|
||||
|
||||
class CliJavaModuleFinder(
|
||||
private val jdkHome: File?,
|
||||
private val messageCollector: MessageCollector?,
|
||||
private val javaFileManager: KotlinCliJavaFileManager,
|
||||
project: Project,
|
||||
private val jdkRelease: Int?
|
||||
) : JavaModuleFinder {
|
||||
|
||||
private val jrtFileSystemRoot = jdkHome?.path?.let { path ->
|
||||
VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)?.findFileByPath(path + URLUtil.JAR_SEPARATOR)
|
||||
}
|
||||
|
||||
private val modulesRoot = jrtFileSystemRoot?.findChild("modules")
|
||||
|
||||
private val userModules = linkedMapOf<String, JavaModule>()
|
||||
|
||||
private val allScope = GlobalSearchScope.allScope(project)
|
||||
|
||||
private val ctSymFile: VirtualFile? by lazy {
|
||||
if (jdkHome == null) return@lazy reportError("JDK_HOME path is not specified in compiler configuration")
|
||||
|
||||
val jdkRootFile = StandardFileSystems.local().findFileByPath(jdkHome.path)
|
||||
?: return@lazy reportError("Can't create virtual file for JDK root under ${jdkHome.path}")
|
||||
|
||||
val lib = jdkRootFile.findChild("lib") ?: return@lazy reportError("Can't find `lib` folder under JDK root: ${jdkHome.path}")
|
||||
|
||||
val ctSym = lib.findChild("ct.sym")
|
||||
?: return@lazy reportError(
|
||||
"This JDK does not have the 'ct.sym' file required for the '-Xjdk-release=$jdkRelease' option: ${jdkHome.path}"
|
||||
)
|
||||
|
||||
ctSym
|
||||
}
|
||||
|
||||
private val ctSymRootFolder: VirtualFile? by lazy {
|
||||
if (ctSymFile != null) {
|
||||
StandardFileSystems.jar()?.findFileByPath(ctSymFile?.path + URLUtil.JAR_SEPARATOR)
|
||||
?: reportError("Can't open `ct.sym` as jar file, file path: ${ctSymFile?.path} ")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private val compilationJdkVersion by lazy {
|
||||
// Observe all JDK codes from folder name chars in ct.sym file,
|
||||
// there should be maximal one corresponding to used compilation JDK
|
||||
ctSymRootFolder?.children?.maxOf {
|
||||
if (it.name == "META-INF") -1
|
||||
else it.name.substringBeforeLast("-modules").maxOf { char ->
|
||||
char.toString().toIntOrNull(36) ?: -1
|
||||
}
|
||||
} ?: -1
|
||||
}
|
||||
|
||||
val ctSymModules by lazy {
|
||||
collectModuleRoots()
|
||||
}
|
||||
|
||||
private val isCompilationJDK12OrLater
|
||||
get() = compilationJdkVersion >= 12
|
||||
|
||||
private val useLastJdkApi: Boolean
|
||||
get() = jdkRelease == null || jdkRelease == compilationJdkVersion
|
||||
|
||||
fun addUserModule(module: JavaModule) {
|
||||
userModules.putIfAbsent(module.name, module)
|
||||
}
|
||||
|
||||
val allObservableModules: Sequence<JavaModule>
|
||||
get() = systemModules + userModules.values
|
||||
|
||||
//Cache system modules for JDK 9-11 to preserve virtual files as one folder could be mapped to several modules
|
||||
private val systemModulesCache = mutableMapOf<String, JavaModule.Explicit>()
|
||||
|
||||
val systemModules: Sequence<JavaModule.Explicit>
|
||||
get() = if (useLastJdkApi) modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule) else
|
||||
ctSymModules.values.asSequence().mapNotNull { findSystemModule(it, true) }
|
||||
|
||||
override fun findModule(name: String): JavaModule? =
|
||||
when {
|
||||
useLastJdkApi -> modulesRoot?.findChild(name)?.let(this::findSystemModule)
|
||||
else -> ctSymModules[name]?.let { findSystemModule(it, true) }
|
||||
} ?: userModules[name]
|
||||
|
||||
private fun findSystemModule(moduleRoot: VirtualFile, useSig: Boolean = false): JavaModule.Explicit? {
|
||||
val file = moduleRoot.findChild(if (useSig) PsiJavaModule.MODULE_INFO_CLASS + ".sig" else PsiJavaModule.MODULE_INFO_CLS_FILE)
|
||||
?: return null
|
||||
val moduleInfo = JavaModuleInfo.read(file, javaFileManager, allScope) ?: return null
|
||||
return systemModulesCache.getOrPut(moduleInfo.moduleName) {
|
||||
JavaModule.Explicit(
|
||||
moduleInfo,
|
||||
when {
|
||||
useLastJdkApi -> listOf(JavaModule.Root(moduleRoot, isBinary = true, isBinarySignature = useSig))
|
||||
useSig -> createModuleFromSignature(moduleInfo)
|
||||
else -> error("Can't find ${moduleRoot.path} module")
|
||||
},
|
||||
file, !useLastJdkApi && useSig
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun createModuleFromSignature(moduleInfo: JavaModuleInfo): List<JavaModule.Root> {
|
||||
return listOf(createModuleFromSignature(!isCompilationJDK12OrLater, isCompilationJDK12OrLater, moduleInfo))
|
||||
}
|
||||
|
||||
private fun createModuleFromSignature(
|
||||
filterPackages: Boolean,
|
||||
filterModules: Boolean,
|
||||
moduleInfo: JavaModuleInfo
|
||||
): JavaModule.Root {
|
||||
val packageParts =
|
||||
if (!filterPackages) emptyMap()
|
||||
else hashMapOf<String, Boolean>().also { parts ->
|
||||
moduleInfo.exports.forEach {
|
||||
for (part in generateSequence(it.packageFqName) { part -> if (!part.isRoot) part.parent() else null }) {
|
||||
parts[part.asString()] = false
|
||||
}
|
||||
}
|
||||
//Do it separately to avoid reset to false
|
||||
moduleInfo.exports.forEach {
|
||||
parts[it.packageFqName.asString()] = true
|
||||
}
|
||||
}
|
||||
|
||||
val moduleFolders =
|
||||
if (filterModules)
|
||||
listFoldersForRelease.filter { virtualFile -> virtualFile.name == moduleInfo.moduleName }
|
||||
else
|
||||
listFoldersForRelease
|
||||
|
||||
return JavaModule.Root(
|
||||
CtSymDirectoryContainer(
|
||||
ctSymRootFolder ?: ctSymFile,
|
||||
moduleFolders,
|
||||
packageParts,
|
||||
"",
|
||||
moduleInfo.moduleName,
|
||||
skipPackageCheck = !filterPackages
|
||||
),
|
||||
isBinary = true,
|
||||
isBinarySignature = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun codeFor(release: Int): String = release.toString(36).toUpperCase()
|
||||
|
||||
private fun matchesRelease(fileName: String, release: Int) =
|
||||
!fileName.contains("-") && fileName.contains(codeFor(release)) // skip `*-modules`
|
||||
|
||||
|
||||
val nonModuleRoot: JavaModule.Root by lazy {
|
||||
createModuleFromSignature(false, false, JavaModuleInfo("*", emptyList(), emptyList(), emptyList()))
|
||||
}
|
||||
|
||||
private val listFoldersForRelease: List<VirtualFile> by lazy {
|
||||
if (ctSymRootFolder == null) emptyList()
|
||||
else ctSymRootFolder!!.children.filter { matchesRelease(it.name, jdkRelease!!) }.flatMap {
|
||||
if (isCompilationJDK12OrLater)
|
||||
it.children.toList()
|
||||
else {
|
||||
listOf(it)
|
||||
}
|
||||
}.apply {
|
||||
if (isEmpty()) reportError("'-Xjdk-release=${jdkRelease}' option is not supported by used JDK: ${jdkHome?.path}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectModuleRoots(): Map<String, VirtualFile> {
|
||||
val result = mutableMapOf<String, VirtualFile>()
|
||||
if (isCompilationJDK12OrLater) {
|
||||
listFoldersForRelease.forEach { modulesRoot ->
|
||||
modulesRoot.findChild("module-info.sig")?.let {
|
||||
result[modulesRoot.name] = modulesRoot
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.jdkRelease!! > 8 && ctSymRootFolder != null) {
|
||||
ctSymRootFolder!!.findChild(codeFor(jdkRelease) + if (!isCompilationJDK12OrLater) "-modules" else "")?.apply {
|
||||
children.forEach {
|
||||
result[it.name] = it
|
||||
}
|
||||
} ?: reportError("Can't find modules signatures in `ct.sym` file for `-Xjdk-release=$jdkRelease` in ${ctSymFile!!.path}")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun reportError(message: String): VirtualFile? {
|
||||
messageCollector?.report(CompilerMessageSeverity.ERROR, message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.modules
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class CliJavaModuleResolver(
|
||||
private val moduleGraph: JavaModuleGraph,
|
||||
private val userModules: List<JavaModule>,
|
||||
private val systemModules: List<JavaModule.Explicit>,
|
||||
private val project: Project
|
||||
) : JavaModuleResolver {
|
||||
init {
|
||||
assert(userModules.count(JavaModule::isSourceModule) <= 1) {
|
||||
"Modules computed by ClasspathRootsResolver cannot have more than one source module: $userModules"
|
||||
}
|
||||
}
|
||||
|
||||
private val virtualFileFinder by lazy { VirtualFileFinder.getInstance(project) }
|
||||
|
||||
override fun getAnnotationsForModuleOwnerOfClass(classId: ClassId): List<JavaAnnotation>? {
|
||||
val virtualFile = virtualFileFinder.findSourceOrBinaryVirtualFile(classId) ?: return null
|
||||
|
||||
return (findJavaModule(virtualFile) as? JavaModule.Explicit)?.moduleInfo?.annotations
|
||||
}
|
||||
|
||||
private val sourceModule: JavaModule? = userModules.firstOrNull(JavaModule::isSourceModule)
|
||||
|
||||
private fun findJavaModule(file: VirtualFile): JavaModule? {
|
||||
if (file.fileSystem.protocol == StandardFileSystems.JRT_PROTOCOL || file.extension == "sig") {
|
||||
return systemModules.firstOrNull { module -> file in module }
|
||||
}
|
||||
|
||||
return when (file.fileType) {
|
||||
KotlinFileType.INSTANCE, JavaFileType.INSTANCE -> sourceModule
|
||||
JavaClassFileType.INSTANCE -> userModules.firstOrNull { module -> file in module }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun JavaModule.contains(file: VirtualFile): Boolean =
|
||||
moduleRoots.any { (root, isBinary) -> isBinary && VfsUtilCore.isAncestor(root, file, false) }
|
||||
|
||||
override fun checkAccessibility(
|
||||
fileFromOurModule: VirtualFile?, referencedFile: VirtualFile, referencedPackage: FqName?
|
||||
): JavaModuleResolver.AccessError? {
|
||||
val ourModule = fileFromOurModule?.let(this::findJavaModule)
|
||||
val theirModule = this.findJavaModule(referencedFile)
|
||||
|
||||
if (ourModule?.name == theirModule?.name) return null
|
||||
|
||||
if (theirModule == null) {
|
||||
return JavaModuleResolver.AccessError.ModuleDoesNotReadUnnamedModule
|
||||
}
|
||||
|
||||
if (ourModule != null && !moduleGraph.reads(ourModule.name, theirModule.name)) {
|
||||
return JavaModuleResolver.AccessError.ModuleDoesNotReadModule(theirModule.name)
|
||||
}
|
||||
|
||||
val fqName = referencedPackage ?: return null
|
||||
if (!theirModule.exports(fqName) && (ourModule == null || !theirModule.exportsTo(fqName, ourModule.name))) {
|
||||
return JavaModuleResolver.AccessError.ModuleDoesNotExportPackage(theirModule.name)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MODULE_ANNOTATIONS_CACHE_SIZE = 10000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.io.URLUtil
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.FileSystems
|
||||
|
||||
// There's JrtFileSystem in idea-full which we can't use in the compiler because it depends on NewVirtualFileSystem, absent in intellij-core
|
||||
class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
|
||||
private val roots =
|
||||
ConcurrentFactoryMap.createMap<String, CoreJrtVirtualFile?> { jdkHomePath ->
|
||||
val jdkHome = File(jdkHomePath)
|
||||
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
|
||||
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return@createMap null
|
||||
|
||||
/*
|
||||
This ClassLoader actually lives as long as current thread due to ThreadLocal leak in jrt-fs,
|
||||
See https://bugs.openjdk.java.net/browse/JDK-8260621
|
||||
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
|
||||
*/
|
||||
val classLoader = globalJrtFsClassLoaderCache.computeIfAbsent(jrtFsJar) {
|
||||
URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
|
||||
}
|
||||
|
||||
val fileSystem = FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
|
||||
CoreJrtVirtualFile(this, jdkHomePath, fileSystem.getPath(""), parent = null)
|
||||
}
|
||||
|
||||
override fun getProtocol(): String = StandardFileSystems.JRT_PROTOCOL
|
||||
|
||||
override fun findFileByPath(path: String): VirtualFile? {
|
||||
val (jdkHomePath, pathInImage) = splitPath(path)
|
||||
val root = roots[jdkHomePath] ?: return null
|
||||
|
||||
if (pathInImage.isEmpty()) return root
|
||||
|
||||
return root.findFileByRelativePath(pathInImage)
|
||||
}
|
||||
|
||||
override fun refresh(asynchronous: Boolean) {}
|
||||
|
||||
override fun refreshAndFindFileByPath(path: String): VirtualFile? = findFileByPath(path)
|
||||
|
||||
fun clearRoots() {
|
||||
roots.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun loadJrtFsJar(jdkHome: File): File? =
|
||||
File(jdkHome, "lib/jrt-fs.jar").takeIf(File::exists)
|
||||
|
||||
fun isModularJdk(jdkHome: File): Boolean =
|
||||
loadJrtFsJar(jdkHome) != null
|
||||
|
||||
fun splitPath(path: String): Pair<String, String> {
|
||||
val separator = path.indexOf(URLUtil.JAR_SEPARATOR)
|
||||
if (separator < 0) {
|
||||
throw IllegalArgumentException("Path in CoreJrtFileSystem must contain a separator: $path")
|
||||
}
|
||||
val localPath = path.substring(0, separator)
|
||||
val pathInJar = path.substring(separator + URLUtil.JAR_SEPARATOR.length)
|
||||
return Pair(localPath, pathInJar)
|
||||
}
|
||||
|
||||
private val globalJrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap<File, URLClassLoader>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.util.io.URLUtil
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
internal class CoreJrtVirtualFile(
|
||||
private val virtualFileSystem: CoreJrtFileSystem,
|
||||
private val jdkHomePath: String,
|
||||
private val path: Path,
|
||||
private val parent: CoreJrtVirtualFile?,
|
||||
) : VirtualFile() {
|
||||
// TODO: catch IOException?
|
||||
private val attributes: BasicFileAttributes get() = Files.readAttributes(path, BasicFileAttributes::class.java)
|
||||
|
||||
override fun getFileSystem(): VirtualFileSystem = virtualFileSystem
|
||||
|
||||
override fun getName(): String =
|
||||
path.fileName.toString()
|
||||
|
||||
override fun getPath(): String =
|
||||
FileUtil.toSystemIndependentName(jdkHomePath + URLUtil.JAR_SEPARATOR + path)
|
||||
|
||||
override fun isWritable(): Boolean = false
|
||||
|
||||
override fun isDirectory(): Boolean = Files.isDirectory(path)
|
||||
|
||||
override fun isValid(): Boolean = true
|
||||
|
||||
override fun getParent(): VirtualFile? = parent
|
||||
|
||||
private val myChildren by lazy { computeChildren() }
|
||||
|
||||
override fun getChildren(): Array<out VirtualFile> = myChildren
|
||||
|
||||
private fun computeChildren(): Array<out VirtualFile> {
|
||||
val paths = try {
|
||||
Files.newDirectoryStream(path).use(Iterable<Path>::toList)
|
||||
} catch (e: IOException) {
|
||||
emptyList<Path>()
|
||||
}
|
||||
return when {
|
||||
paths.isEmpty() -> EMPTY_ARRAY
|
||||
else -> paths.map { path -> CoreJrtVirtualFile(virtualFileSystem, jdkHomePath, path, parent = this) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream =
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
override fun contentsToByteArray(): ByteArray =
|
||||
Files.readAllBytes(path)
|
||||
|
||||
override fun getTimeStamp(): Long =
|
||||
attributes.lastModifiedTime().toMillis()
|
||||
|
||||
override fun getLength(): Long = attributes.size()
|
||||
|
||||
override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {}
|
||||
|
||||
override fun getInputStream(): InputStream =
|
||||
VfsUtilCore.inputStreamSkippingBOM(Files.newInputStream(path).buffered(), this)
|
||||
|
||||
override fun getModificationStamp(): Long = 0
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is CoreJrtVirtualFile && path == other.path && fileSystem == other.fileSystem
|
||||
|
||||
override fun hashCode(): Int =
|
||||
path.hashCode()
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.modules
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.io.InputStream
|
||||
|
||||
class CtSymClassVirtualFile(
|
||||
private val parent: VirtualFile?,
|
||||
private val file: VirtualFile
|
||||
): VirtualFile() {
|
||||
override fun getName() = file.name
|
||||
|
||||
override fun getFileSystem() = file.fileSystem
|
||||
|
||||
override fun getPath() = file.path
|
||||
|
||||
override fun isWritable() = file.isWritable
|
||||
|
||||
override fun isDirectory() = false
|
||||
|
||||
override fun isValid() = file.isValid
|
||||
|
||||
override fun getParent(): VirtualFile? = parent
|
||||
|
||||
override fun getChildren(): Array<VirtualFile>? = null
|
||||
|
||||
override fun getOutputStream(p0: Any?, p1: Long, p2: Long) = file.getOutputStream(p0, p1, p2)
|
||||
|
||||
override fun contentsToByteArray(): ByteArray = file.contentsToByteArray()
|
||||
|
||||
override fun getTimeStamp() = file.timeStamp
|
||||
|
||||
override fun getLength() = file.length
|
||||
|
||||
override fun refresh(p0: Boolean, p1: Boolean, p2: Runnable?) = file.refresh(p0, p1, p2)
|
||||
|
||||
override fun getInputStream(): InputStream? = file.inputStream
|
||||
|
||||
override fun getFileType(): FileType = JavaClassFileType.INSTANCE
|
||||
|
||||
override fun getModificationStamp(): Long = file.modificationStamp
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.io.InputStream
|
||||
|
||||
class CtSymDirectoryContainer(
|
||||
private val parent: VirtualFile?,
|
||||
private val rootOrPackageParts: List<VirtualFile>,
|
||||
private val packages: Map<String, Boolean>,
|
||||
private val currentPackage: String,
|
||||
private val moduleRoot: String,
|
||||
private val skipPackageCheck: Boolean
|
||||
) : VirtualFile() {
|
||||
|
||||
private val _path by lazy { parent?.path + currentPackage + "/" }
|
||||
|
||||
private val _fileSystem by lazy { parent!!.fileSystem }
|
||||
|
||||
private val _isValid by lazy { rootOrPackageParts.all { it.isValid } }
|
||||
|
||||
private val _children by lazy {
|
||||
val children = rootOrPackageParts.flatMap { it.children?.toList() ?: emptyList() }
|
||||
if (children.isEmpty()) return@lazy emptyArray()
|
||||
|
||||
val isExported = skipPackageCheck || packages.getOrDefault(currentPackage, false)
|
||||
val containers = mutableMapOf<String, Pair<CtSymDirectoryContainer, MutableList<VirtualFile>>>()
|
||||
children.mapNotNull { child ->
|
||||
when {
|
||||
child.isDirectory -> {
|
||||
val childPackage = if (currentPackage.isEmpty()) child.name else currentPackage + "." + child.name
|
||||
var addingEntry: CtSymDirectoryContainer? = null
|
||||
if (skipPackageCheck || packages.contains(childPackage)) {
|
||||
containers.getOrPut(childPackage) {
|
||||
val list = mutableListOf<VirtualFile>()
|
||||
CtSymDirectoryContainer(
|
||||
this,
|
||||
list,
|
||||
packages,
|
||||
childPackage,
|
||||
moduleRoot,
|
||||
skipPackageCheck
|
||||
).also { addingEntry = it } to list
|
||||
}.also { it.second.add(child) }
|
||||
}
|
||||
addingEntry
|
||||
}
|
||||
isExported -> CtSymClassVirtualFile(this, child)
|
||||
else -> null
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
override fun getName() = currentPackage.substringAfterLast(".")
|
||||
|
||||
override fun getFileSystem() = _fileSystem
|
||||
|
||||
override fun getPath() = _path
|
||||
|
||||
override fun isWritable() = false
|
||||
|
||||
override fun isDirectory() = true
|
||||
|
||||
override fun isValid() = _isValid
|
||||
|
||||
override fun getParent(): VirtualFile? = parent
|
||||
|
||||
override fun getChildren(): Array<VirtualFile>? = _children
|
||||
|
||||
override fun getOutputStream(p0: Any?, p1: Long, p2: Long) = error("not supported")
|
||||
|
||||
override fun contentsToByteArray(): ByteArray = error("not supported")
|
||||
|
||||
override fun getTimeStamp() = error("not supported")
|
||||
|
||||
override fun getLength() = error("not supported")
|
||||
|
||||
override fun refresh(p0: Boolean, p1: Boolean, p2: Runnable?) {}
|
||||
|
||||
override fun getInputStream(): InputStream? = error("not supported")
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cli.jvm.modules
|
||||
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
|
||||
class JavaModuleGraph(finder: JavaModuleFinder) {
|
||||
private val module: (String) -> JavaModule? =
|
||||
LockBasedStorageManager.NO_LOCKS.createMemoizedFunctionWithNullableValues(finder::findModule)
|
||||
|
||||
fun getAllDependencies(moduleNames: List<String>): LinkedHashSet<String> {
|
||||
val visited = LinkedHashSet(moduleNames)
|
||||
|
||||
// Every module implicitly depends on java.base
|
||||
visited += "java.base"
|
||||
|
||||
fun dfs(moduleName: String): Boolean {
|
||||
// Automatic modules have no transitive exports, so we only consider explicit modules here
|
||||
val moduleInfo = (module(moduleName) as? JavaModule.Explicit)?.moduleInfo ?: return false
|
||||
for ((dependencyModuleName, isTransitive) in moduleInfo.requires) {
|
||||
if (isTransitive && visited.add(dependencyModuleName)) {
|
||||
dfs(dependencyModuleName)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for (moduleName in moduleNames) {
|
||||
val module = module(moduleName) ?: continue
|
||||
when (module) {
|
||||
is JavaModule.Automatic -> {
|
||||
// Do nothing; all automatic modules should be added to compilation roots at call site as per java.lang.module javadoc
|
||||
}
|
||||
is JavaModule.Explicit -> {
|
||||
for ((dependencyModuleName, isTransitive) in module.moduleInfo.requires) {
|
||||
if (visited.add(dependencyModuleName)) {
|
||||
val moduleExists = dfs(dependencyModuleName)
|
||||
//ct.sym can miss some internal modules from non-transitive dependencies
|
||||
if (!moduleExists && !isTransitive && module.isJdkModuleFromCtSym) visited.remove(dependencyModuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> error("Unknown module: $module (${module.javaClass})")
|
||||
}
|
||||
}
|
||||
|
||||
return visited
|
||||
}
|
||||
|
||||
fun reads(moduleName: String, dependencyName: String): Boolean {
|
||||
if (moduleName == dependencyName || dependencyName == "java.base") return true
|
||||
|
||||
val visited = linkedSetOf<String>()
|
||||
|
||||
fun dfs(name: String): Boolean {
|
||||
if (!visited.add(name)) return false
|
||||
|
||||
val module = module(name) ?: return false
|
||||
when (module) {
|
||||
is JavaModule.Automatic -> return true
|
||||
is JavaModule.Explicit -> {
|
||||
for ((dependencyModuleName, isTransitive) in module.moduleInfo.requires) {
|
||||
if (dependencyModuleName == dependencyName) return true
|
||||
if (isTransitive && dfs(dependencyName)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> error("Unsupported module type: $module")
|
||||
}
|
||||
}
|
||||
|
||||
val module = module(moduleName) ?: return false
|
||||
when (module) {
|
||||
is JavaModule.Automatic -> return true
|
||||
is JavaModule.Explicit -> {
|
||||
for ((dependencyModuleName) in module.moduleInfo.requires) {
|
||||
if (dfs(dependencyModuleName)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dfs(moduleName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.modules
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
|
||||
fun isAtLeastJava9(): Boolean {
|
||||
return SystemInfo.isJavaVersionAtLeast(9, 0, 0)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.resolve
|
||||
|
||||
import org.jetbrains.kotlin.cfg.ControlFlowInformationProviderImpl
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
import org.jetbrains.kotlin.container.useInstance
|
||||
|
||||
object CompilerEnvironment : TargetEnvironment("Compiler") {
|
||||
override fun configure(container: StorageComponentContainer) {
|
||||
configureCompilerEnvironment(container)
|
||||
container.useInstance(ControlFlowInformationProviderImpl.Factory)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user