Remove unneeded bunch files related to PsiJavaModule
PSI for modules and related classes are already available in AS3.1
This commit is contained in:
-332
@@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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.JvmClasspathRoot
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot
|
|
||||||
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.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: (JvmContentRoot) -> VirtualFile?,
|
|
||||||
private val javaModuleFinder: CliJavaModuleFinder,
|
|
||||||
private val requireStdlibModule: Boolean,
|
|
||||||
private val outputDirectory: VirtualFile?
|
|
||||||
) {
|
|
||||||
val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
|
|
||||||
|
|
||||||
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 JvmContentRoot) continue
|
|
||||||
val root = contentRootToVirtualFile(contentRoot) ?: continue
|
|
||||||
when (contentRoot) {
|
|
||||||
is JavaSourceRoot -> javaSourceRoots += RootWithPrefix(root, contentRoot.packagePrefix)
|
|
||||||
is JvmClasspathRoot -> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addModularRoots(modules, result)
|
|
||||||
|
|
||||||
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)
|
|
||||||
*/
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun modularBinaryRoot(root: VirtualFile): JavaModule? {
|
|
||||||
val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL
|
|
||||||
val manifest: Attributes? by lazy(NONE) { readManifestAttributes(root) }
|
|
||||||
|
|
||||||
/*
|
|
||||||
val moduleInfoFile =
|
|
||||||
root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
|
|
||||||
?: root.takeIf { isJar }?.findFileByRelativePath(MULTI_RELEASE_MODULE_INFO_CLS_FILE)?.takeIf {
|
|
||||||
manifest?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true) == true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleInfoFile != null) {
|
|
||||||
val moduleInfo = JavaModuleInfo.read(moduleInfoFile) ?: 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?.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 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 -> 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 {
|
|
||||||
for ((root, isBinary) in module.moduleRoots) {
|
|
||||||
result.add(JavaRoot(root, if (isBinary) JavaRoot.RootType.BINARY else JavaRoot.RootType.SOURCE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See http://openjdk.java.net/jeps/261
|
|
||||||
private fun computeDefaultRootModules(): List<String> {
|
|
||||||
val result = arrayListOf<String>()
|
|
||||||
|
|
||||||
val systemModules = javaModuleFinder.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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 MULTI_RELEASE_MODULE_INFO_CLS_FILE = "META-INF/versions/9/${PsiJavaModule.MODULE_INFO_CLS_FILE}"
|
|
||||||
const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"
|
|
||||||
const val IS_MULTI_RELEASE = "Multi-Release"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-332
@@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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.JvmClasspathRoot
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot
|
|
||||||
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.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: (JvmContentRoot) -> VirtualFile?,
|
|
||||||
private val javaModuleFinder: CliJavaModuleFinder,
|
|
||||||
private val requireStdlibModule: Boolean,
|
|
||||||
private val outputDirectory: VirtualFile?
|
|
||||||
) {
|
|
||||||
val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
|
|
||||||
|
|
||||||
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 JvmContentRoot) continue
|
|
||||||
val root = contentRootToVirtualFile(contentRoot) ?: continue
|
|
||||||
when (contentRoot) {
|
|
||||||
is JavaSourceRoot -> javaSourceRoots += RootWithPrefix(root, contentRoot.packagePrefix)
|
|
||||||
is JvmClasspathRoot -> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addModularRoots(modules, result)
|
|
||||||
|
|
||||||
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)
|
|
||||||
*/
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun modularBinaryRoot(root: VirtualFile): JavaModule? {
|
|
||||||
val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL
|
|
||||||
val manifest: Attributes? by lazy(NONE) { readManifestAttributes(root) }
|
|
||||||
|
|
||||||
/*
|
|
||||||
val moduleInfoFile =
|
|
||||||
root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
|
|
||||||
?: root.takeIf { isJar }?.findFileByRelativePath(MULTI_RELEASE_MODULE_INFO_CLS_FILE)?.takeIf {
|
|
||||||
manifest?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true) == true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleInfoFile != null) {
|
|
||||||
val moduleInfo = JavaModuleInfo.read(moduleInfoFile) ?: 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?.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 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 -> 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 {
|
|
||||||
for ((root, isBinary) in module.moduleRoots) {
|
|
||||||
result.add(JavaRoot(root, if (isBinary) JavaRoot.RootType.BINARY else JavaRoot.RootType.SOURCE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See http://openjdk.java.net/jeps/261
|
|
||||||
private fun computeDefaultRootModules(): List<String> {
|
|
||||||
val result = arrayListOf<String>()
|
|
||||||
|
|
||||||
val systemModules = javaModuleFinder.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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 MULTI_RELEASE_MODULE_INFO_CLS_FILE = "META-INF/versions/9/${PsiJavaModule.MODULE_INFO_CLS_FILE}"
|
|
||||||
const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"
|
|
||||||
const val IS_MULTI_RELEASE = "Multi-Release"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-291
@@ -1,291 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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.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 useFastClassFilesReading = false
|
|
||||||
|
|
||||||
fun initialize(
|
|
||||||
index: JvmDependenciesIndex,
|
|
||||||
packagePartProviders: List<JvmPackagePartProvider>,
|
|
||||||
singleJavaFileRootsIndex: SingleJavaFileRootsIndex,
|
|
||||||
useFastClassFilesReading: Boolean
|
|
||||||
) {
|
|
||||||
this.index = index
|
|
||||||
this.packagePartProviders = packagePartProviders
|
|
||||||
this.singleJavaFileRootsIndex = singleJavaFileRootsIndex
|
|
||||||
this.useFastClassFilesReading = useFastClassFilesReading
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
return topLevelClassesCache.getOrPut(classId.packageFqName.child(classId.relativeClassName.pathSegments().first())) {
|
|
||||||
index.findClass(classId) { dir, type ->
|
|
||||||
findVirtualFileGivenPackage(dir, relativeClassName, type)
|
|
||||||
} ?: singleJavaFileRootsIndex.findJavaSourceClass(classId)
|
|
||||||
}?.takeIf { it in searchScope }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
|
|
||||||
private val signatureParsingComponent = BinaryClassSignatureParser()
|
|
||||||
|
|
||||||
override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): JavaClass? {
|
|
||||||
val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
|
|
||||||
|
|
||||||
if (useFastClassFilesReading && virtualFile.extension == "class") {
|
|
||||||
// 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
|
|
||||||
val classContent = virtualFile.contentsToByteArray()
|
|
||||||
if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
|
|
||||||
classId.outerClassId?.let { outerClassId ->
|
|
||||||
val outerClass = findClass(outerClassId, searchScope)
|
|
||||||
return@getOrPut outerClass?.findInnerClass(classId.shortClassName)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addIfNotNull(
|
|
||||||
singleJavaFileRootsIndex.findJavaSourceClass(classId)
|
|
||||||
?.takeIf { it in scope }
|
|
||||||
?.findPsiClassInVirtualFile(relativeClassName)
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
return if (found) PsiPackageImpl(myPsiManager, packageName) else null
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 = hashSetOf<String>()
|
|
||||||
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
|
|
||||||
for (child in dir.children) {
|
|
||||||
if (child.extension == "class" || child.extension == "java") {
|
|
||||||
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)) }
|
|
||||||
-291
@@ -1,291 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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.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 useFastClassFilesReading = false
|
|
||||||
|
|
||||||
fun initialize(
|
|
||||||
index: JvmDependenciesIndex,
|
|
||||||
packagePartProviders: List<JvmPackagePartProvider>,
|
|
||||||
singleJavaFileRootsIndex: SingleJavaFileRootsIndex,
|
|
||||||
useFastClassFilesReading: Boolean
|
|
||||||
) {
|
|
||||||
this.index = index
|
|
||||||
this.packagePartProviders = packagePartProviders
|
|
||||||
this.singleJavaFileRootsIndex = singleJavaFileRootsIndex
|
|
||||||
this.useFastClassFilesReading = useFastClassFilesReading
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
return topLevelClassesCache.getOrPut(classId.packageFqName.child(classId.relativeClassName.pathSegments().first())) {
|
|
||||||
index.findClass(classId) { dir, type ->
|
|
||||||
findVirtualFileGivenPackage(dir, relativeClassName, type)
|
|
||||||
} ?: singleJavaFileRootsIndex.findJavaSourceClass(classId)
|
|
||||||
}?.takeIf { it in searchScope }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
|
|
||||||
private val signatureParsingComponent = BinaryClassSignatureParser()
|
|
||||||
|
|
||||||
override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): JavaClass? {
|
|
||||||
val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
|
|
||||||
|
|
||||||
if (useFastClassFilesReading && virtualFile.extension == "class") {
|
|
||||||
// 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
|
|
||||||
val classContent = virtualFile.contentsToByteArray()
|
|
||||||
if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
|
|
||||||
classId.outerClassId?.let { outerClassId ->
|
|
||||||
val outerClass = findClass(outerClassId, searchScope)
|
|
||||||
return@getOrPut outerClass?.findInnerClass(classId.shortClassName)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addIfNotNull(
|
|
||||||
singleJavaFileRootsIndex.findJavaSourceClass(classId)
|
|
||||||
?.takeIf { it in scope }
|
|
||||||
?.findPsiClassInVirtualFile(relativeClassName)
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
return if (found) PsiPackageImpl(myPsiManager, packageName) else null
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 = hashSetOf<String>()
|
|
||||||
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
|
|
||||||
for (child in dir.children) {
|
|
||||||
if (child.extension == "class" || child.extension == "java") {
|
|
||||||
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)) }
|
|
||||||
-471
@@ -1,471 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
|
||||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
|
||||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
|
||||||
import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics
|
|
||||||
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection
|
|
||||||
import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection
|
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory
|
|
||||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
|
||||||
import org.jetbrains.kotlin.cli.common.checkKotlinPackageUsage
|
|
||||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.OUTPUT
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
|
|
||||||
import org.jetbrains.kotlin.cli.common.output.writeAll
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.*
|
|
||||||
import org.jetbrains.kotlin.codegen.*
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationStateEventCallback
|
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
|
||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
|
||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
|
||||||
import org.jetbrains.kotlin.idea.MainFunctionDetector
|
|
||||||
import org.jetbrains.kotlin.javac.JavacWrapper
|
|
||||||
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
|
|
||||||
import org.jetbrains.kotlin.modules.Module
|
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs
|
|
||||||
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
|
|
||||||
import java.io.File
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
|
|
||||||
object KotlinToJVMBytecodeCompiler {
|
|
||||||
|
|
||||||
private fun getAbsolutePaths(buildFile: File, module: Module): List<String> {
|
|
||||||
return module.getSourceFiles().map { sourceFile ->
|
|
||||||
val source = File(sourceFile)
|
|
||||||
if (!source.isAbsolute) {
|
|
||||||
File(buildFile.absoluteFile.parentFile, sourceFile).absolutePath
|
|
||||||
} else {
|
|
||||||
source.absolutePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeOutput(
|
|
||||||
configuration: CompilerConfiguration,
|
|
||||||
outputFiles: OutputFileCollection,
|
|
||||||
mainClass: FqName?
|
|
||||||
) {
|
|
||||||
val reportOutputFiles = configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES)
|
|
||||||
val jarPath = configuration.get(JVMConfigurationKeys.OUTPUT_JAR)
|
|
||||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
|
||||||
if (jarPath != null) {
|
|
||||||
val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false)
|
|
||||||
CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, mainClass, outputFiles)
|
|
||||||
if (reportOutputFiles) {
|
|
||||||
val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath)
|
|
||||||
messageCollector.report(OUTPUT, message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val outputDir = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) ?: File(".")
|
|
||||||
outputFiles.writeAll(outputDir, messageCollector, reportOutputFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createOutputFilesFlushingCallbackIfPossible(configuration: CompilerConfiguration): GenerationStateEventCallback {
|
|
||||||
if (configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) == null) {
|
|
||||||
return GenerationStateEventCallback.DO_NOTHING
|
|
||||||
}
|
|
||||||
return GenerationStateEventCallback { state ->
|
|
||||||
val currentOutput = SimpleOutputFileCollection(state.factory.currentOutput)
|
|
||||||
writeOutput(configuration, currentOutput, mainClass = null)
|
|
||||||
if (!configuration.get(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false)) {
|
|
||||||
state.factory.releaseGeneratedOutput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun compileModules(environment: KotlinCoreEnvironment, buildFile: File, chunk: List<Module>): Boolean {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)
|
|
||||||
|
|
||||||
val projectConfiguration = environment.configuration
|
|
||||||
for (module in chunk) {
|
|
||||||
moduleVisibilityManager.addModule(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
|
|
||||||
for (path in friendPaths) {
|
|
||||||
moduleVisibilityManager.addFriendPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetDescription = "in targets [" + chunk.joinToString { input -> input.getModuleName() + "-" + input.getModuleType() } + "]"
|
|
||||||
|
|
||||||
val result = repeatAnalysisIfNeeded(analyze(environment, targetDescription), environment, targetDescription)
|
|
||||||
if (result == null || !result.shouldGenerateCode) return false
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
result.throwIfError()
|
|
||||||
|
|
||||||
val outputs = newLinkedHashMapWithExpectedSize<Module, GenerationState>(chunk.size)
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
val moduleSourcePaths = getAbsolutePaths(buildFile, module)
|
|
||||||
val ktFiles = environment.getSourceFiles().filter { file -> file.virtualFilePath in moduleSourcePaths }
|
|
||||||
|
|
||||||
if (!checkKotlinPackageUsage(environment, ktFiles)) return false
|
|
||||||
|
|
||||||
val moduleConfiguration = projectConfiguration.copy().apply {
|
|
||||||
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(module.getOutputDirectory()))
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for ((_, state) in outputs) {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
writeOutput(state.configuration, state.factory, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectConfiguration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)) {
|
|
||||||
val singleModule = chunk.singleOrNull()
|
|
||||||
if (singleModule != null) {
|
|
||||||
return JavacWrapper.getInstance(environment.project).use {
|
|
||||||
it.compile(File(singleModule.getOutputDirectory()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
projectConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
|
|
||||||
WARNING,
|
|
||||||
"A chunk contains multiple modules (${chunk.joinToString { it.getModuleName() }}). " +
|
|
||||||
"-Xuse-javac option couldn't be used to compile java files"
|
|
||||||
)
|
|
||||||
JavacWrapper.getInstance(environment.project).close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
} finally {
|
|
||||||
outputs.values.forEach(GenerationState::destroy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun configureSourceRoots(configuration: CompilerConfiguration, chunk: List<Module>, buildFile: File) {
|
|
||||||
for (module in chunk) {
|
|
||||||
configuration.addKotlinSourceRoots(getAbsolutePaths(buildFile, module))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
for ((path, packagePrefix) in module.getJavaSourceRoots()) {
|
|
||||||
configuration.addJavaSourceRoot(File(path), packagePrefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isJava9Module = false /*chunk.any { module ->
|
|
||||||
module.getJavaSourceRoots().any { (path, packagePrefix) ->
|
|
||||||
val file = File(path)
|
|
||||||
packagePrefix == null &&
|
|
||||||
(file.name == PsiJavaModule.MODULE_INFO_FILE ||
|
|
||||||
(file.isDirectory && file.listFiles().any { it.name == PsiJavaModule.MODULE_INFO_FILE }))
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
for (classpathRoot in module.getClasspathRoots()) {
|
|
||||||
configuration.add(
|
|
||||||
CLIConfigurationKeys.CONTENT_ROOTS,
|
|
||||||
if (isJava9Module) JvmModulePathRoot(File(classpathRoot)) else JvmClasspathRoot(File(classpathRoot))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
val modularJdkRoot = module.modularJdkRoot
|
|
||||||
if (modularJdkRoot != null) {
|
|
||||||
// We use the SDK of the first module in the chunk, which is not always correct because some other module in the chunk
|
|
||||||
// might depend on a different SDK
|
|
||||||
configuration.put(JVMConfigurationKeys.JDK_HOME, File(modularJdkRoot))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.addAll(JVMConfigurationKeys.MODULES, chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findMainClass(generationState: GenerationState, files: List<KtFile>): FqName? {
|
|
||||||
val mainFunctionDetector = MainFunctionDetector(generationState.bindingContext)
|
|
||||||
return files.asSequence()
|
|
||||||
.map { file ->
|
|
||||||
if (mainFunctionDetector.hasMain(file.declarations))
|
|
||||||
JvmFileClassUtil.getFileClassInfoNoResolve(file).facadeClassFqName
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
.singleOrNull { it != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
|
|
||||||
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)
|
|
||||||
|
|
||||||
val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
|
|
||||||
for (path in friendPaths) {
|
|
||||||
moduleVisibilityManager.addFriendPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkKotlinPackageUsage(environment, environment.getSourceFiles())) return false
|
|
||||||
|
|
||||||
val generationState = analyzeAndGenerate(environment) ?: return false
|
|
||||||
|
|
||||||
val mainClass = findMainClass(generationState, environment.getSourceFiles())
|
|
||||||
|
|
||||||
try {
|
|
||||||
writeOutput(environment.configuration, generationState.factory, mainClass)
|
|
||||||
return true
|
|
||||||
} finally {
|
|
||||||
generationState.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun compileAndExecuteScript(environment: KotlinCoreEnvironment, scriptArgs: List<String>): ExitCode {
|
|
||||||
val scriptClass = compileScript(environment) ?: return ExitCode.COMPILATION_ERROR
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
tryConstructClassFromStringArgs(scriptClass, scriptArgs)
|
|
||||||
?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs\n")
|
|
||||||
} finally {
|
|
||||||
// NB: these lines are required (see KT-9546) but aren't covered by tests
|
|
||||||
System.out.flush()
|
|
||||||
System.err.flush()
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
reportExceptionFromScript(e)
|
|
||||||
return ExitCode.SCRIPT_EXECUTION_ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExitCode.OK
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun repeatAnalysisIfNeeded(
|
|
||||||
result: AnalysisResult?,
|
|
||||||
environment: KotlinCoreEnvironment,
|
|
||||||
targetDescription: String?
|
|
||||||
): AnalysisResult? {
|
|
||||||
if (result is AnalysisResult.RetryWithAdditionalJavaRoots) {
|
|
||||||
val configuration = environment.configuration
|
|
||||||
|
|
||||||
val oldReadOnlyValue = configuration.isReadOnly
|
|
||||||
configuration.isReadOnly = false
|
|
||||||
configuration.addJavaSourceRoots(result.additionalJavaRoots)
|
|
||||||
configuration.isReadOnly = oldReadOnlyValue
|
|
||||||
|
|
||||||
if (result.addToEnvironment) {
|
|
||||||
environment.updateClasspath(result.additionalJavaRoots.map { JavaSourceRoot(it, null) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear package caches (see KotlinJavaPsiFacade)
|
|
||||||
ApplicationManager.getApplication().runWriteAction {
|
|
||||||
(PsiManager.getInstance(environment.project).modificationTracker as? PsiModificationTrackerImpl)?.incCounter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all diagnostic messages
|
|
||||||
configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]?.clear()
|
|
||||||
|
|
||||||
// Repeat analysis with additional Java roots (kapt generated sources)
|
|
||||||
return analyze(environment, targetDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reportExceptionFromScript(exception: Throwable) {
|
|
||||||
// expecting InvocationTargetException from constructor invocation with cause that describes the actual cause
|
|
||||||
val stream = System.err
|
|
||||||
val cause = exception.cause
|
|
||||||
if (exception !is InvocationTargetException || cause == null) {
|
|
||||||
exception.printStackTrace(stream)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stream.println(cause)
|
|
||||||
val fullTrace = cause.stackTrace
|
|
||||||
for (i in 0 until fullTrace.size - exception.stackTrace.size) {
|
|
||||||
stream.println("\tat " + fullTrace[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileScript(environment: KotlinCoreEnvironment, parentClassLoader: ClassLoader? = null): Class<*>? {
|
|
||||||
val state = analyzeAndGenerate(environment) ?: return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val urls = environment.configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNull { root ->
|
|
||||||
when (root) {
|
|
||||||
is JvmModulePathRoot -> root.file // TODO: only add required modules
|
|
||||||
is JvmClasspathRoot -> root.file
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.map { it.toURI().toURL() }
|
|
||||||
|
|
||||||
val classLoader = GeneratedClassLoader(state.factory, parentClassLoader ?: URLClassLoader(urls.toTypedArray(), null))
|
|
||||||
|
|
||||||
val script = environment.getSourceFiles()[0].script ?: error("Script must be parsed")
|
|
||||||
return classLoader.loadClass(script.fqName.asString())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw RuntimeException("Failed to evaluate script: $e", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun analyzeAndGenerate(environment: KotlinCoreEnvironment): GenerationState? {
|
|
||||||
val result = repeatAnalysisIfNeeded(analyze(environment, null), environment, null) ?: return null
|
|
||||||
|
|
||||||
if (!result.shouldGenerateCode) return null
|
|
||||||
|
|
||||||
result.throwIfError()
|
|
||||||
|
|
||||||
return generate(environment, environment.configuration, result, environment.getSourceFiles(), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun analyze(environment: KotlinCoreEnvironment, targetDescription: String?): AnalysisResult? {
|
|
||||||
val sourceFiles = environment.getSourceFiles()
|
|
||||||
val collector = environment.messageCollector
|
|
||||||
|
|
||||||
// Can be null for Scripts/REPL
|
|
||||||
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
|
|
||||||
performanceManager?.notifyAnalysisStarted()
|
|
||||||
|
|
||||||
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(collector, environment.configuration.languageVersionSettings)
|
|
||||||
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
|
|
||||||
val project = environment.project
|
|
||||||
val moduleOutputs = environment.configuration.get(JVMConfigurationKeys.MODULES)?.mapNotNullTo(hashSetOf()) { module ->
|
|
||||||
environment.findLocalFile(module.getOutputDirectory())
|
|
||||||
}.orEmpty()
|
|
||||||
val sourcesOnly = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles)
|
|
||||||
// To support partial and incremental compilation, we add the scope which contains binaries from output directories
|
|
||||||
// of the compiled modules (.class) to the list of scopes of the source module
|
|
||||||
val scope = if (moduleOutputs.isEmpty()) sourcesOnly else sourcesOnly.uniteWith(DirectoriesScope(project, moduleOutputs))
|
|
||||||
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
|
|
||||||
project,
|
|
||||||
sourceFiles,
|
|
||||||
NoScopeRecordCliBindingTrace(),
|
|
||||||
environment.configuration,
|
|
||||||
environment::createPackagePartProvider,
|
|
||||||
sourceModuleSearchScope = scope
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
performanceManager?.notifyAnalysisFinished(sourceFiles.size, environment.countLinesOfCode(sourceFiles), targetDescription)
|
|
||||||
|
|
||||||
val analysisResult = analyzerWithCompilerReport.analysisResult
|
|
||||||
|
|
||||||
return if (!analyzerWithCompilerReport.hasErrors() || analysisResult is AnalysisResult.RetryWithAdditionalJavaRoots)
|
|
||||||
analysisResult
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirectoriesScope(
|
|
||||||
project: Project,
|
|
||||||
private val directories: Set<VirtualFile>
|
|
||||||
) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
|
|
||||||
private val fileSystems = directories.mapTo(hashSetOf(), VirtualFile::getFileSystem)
|
|
||||||
|
|
||||||
override fun contains(file: VirtualFile): Boolean {
|
|
||||||
if (file.fileSystem !in fileSystems) return false
|
|
||||||
|
|
||||||
var parent: VirtualFile = file
|
|
||||||
while (true) {
|
|
||||||
if (parent in directories) return true
|
|
||||||
parent = parent.parent ?: return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = "All files under: $directories"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun GenerationState.Builder.withModule(module: Module?) =
|
|
||||||
apply {
|
|
||||||
targetId(module?.let { TargetId(it) })
|
|
||||||
moduleName(module?.getModuleName())
|
|
||||||
outDirectory(module?.let { File(it.getOutputDirectory()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generate(
|
|
||||||
environment: KotlinCoreEnvironment,
|
|
||||||
configuration: CompilerConfiguration,
|
|
||||||
result: AnalysisResult,
|
|
||||||
sourceFiles: List<KtFile>,
|
|
||||||
module: Module?
|
|
||||||
): GenerationState {
|
|
||||||
val generationState = GenerationState.Builder(
|
|
||||||
environment.project,
|
|
||||||
ClassBuilderFactories.BINARIES,
|
|
||||||
result.moduleDescriptor,
|
|
||||||
result.bindingContext,
|
|
||||||
sourceFiles,
|
|
||||||
configuration
|
|
||||||
)
|
|
||||||
.codegenFactory(if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory else DefaultCodegenFactory)
|
|
||||||
.withModule(module)
|
|
||||||
.onIndependentPartCompilationEnd(createOutputFilesFlushingCallbackIfPossible(configuration))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
|
|
||||||
performanceManager?.notifyGenerationStarted()
|
|
||||||
|
|
||||||
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
|
||||||
|
|
||||||
performanceManager?.notifyGenerationFinished(
|
|
||||||
sourceFiles.size,
|
|
||||||
environment.countLinesOfCode(sourceFiles),
|
|
||||||
additionalDescription = if (module != null) "target " + module.getModuleName() + "-" + module.getModuleType() + " " else ""
|
|
||||||
)
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
AnalyzerWithCompilerReport.reportDiagnostics(
|
|
||||||
FilteredJvmDiagnostics(
|
|
||||||
generationState.collectedExtraJvmDiagnostics,
|
|
||||||
result.bindingContext.diagnostics
|
|
||||||
),
|
|
||||||
environment.messageCollector
|
|
||||||
)
|
|
||||||
|
|
||||||
AnalyzerWithCompilerReport.reportBytecodeVersionErrors(
|
|
||||||
generationState.extraJvmDiagnosticsTrace.bindingContext, environment.messageCollector
|
|
||||||
)
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
return generationState
|
|
||||||
}
|
|
||||||
|
|
||||||
private val KotlinCoreEnvironment.messageCollector: MessageCollector
|
|
||||||
get() = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
|
||||||
}
|
|
||||||
-471
@@ -1,471 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
|
||||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
|
||||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
|
||||||
import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics
|
|
||||||
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection
|
|
||||||
import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection
|
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory
|
|
||||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
|
||||||
import org.jetbrains.kotlin.cli.common.checkKotlinPackageUsage
|
|
||||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.OUTPUT
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
|
|
||||||
import org.jetbrains.kotlin.cli.common.output.writeAll
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.*
|
|
||||||
import org.jetbrains.kotlin.codegen.*
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationStateEventCallback
|
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
|
||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
|
||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
|
||||||
import org.jetbrains.kotlin.idea.MainFunctionDetector
|
|
||||||
import org.jetbrains.kotlin.javac.JavacWrapper
|
|
||||||
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
|
|
||||||
import org.jetbrains.kotlin.modules.Module
|
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs
|
|
||||||
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
|
|
||||||
import java.io.File
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
|
|
||||||
object KotlinToJVMBytecodeCompiler {
|
|
||||||
|
|
||||||
private fun getAbsolutePaths(buildFile: File, module: Module): List<String> {
|
|
||||||
return module.getSourceFiles().map { sourceFile ->
|
|
||||||
val source = File(sourceFile)
|
|
||||||
if (!source.isAbsolute) {
|
|
||||||
File(buildFile.absoluteFile.parentFile, sourceFile).absolutePath
|
|
||||||
} else {
|
|
||||||
source.absolutePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeOutput(
|
|
||||||
configuration: CompilerConfiguration,
|
|
||||||
outputFiles: OutputFileCollection,
|
|
||||||
mainClass: FqName?
|
|
||||||
) {
|
|
||||||
val reportOutputFiles = configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES)
|
|
||||||
val jarPath = configuration.get(JVMConfigurationKeys.OUTPUT_JAR)
|
|
||||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
|
||||||
if (jarPath != null) {
|
|
||||||
val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false)
|
|
||||||
CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, mainClass, outputFiles)
|
|
||||||
if (reportOutputFiles) {
|
|
||||||
val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath)
|
|
||||||
messageCollector.report(OUTPUT, message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val outputDir = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) ?: File(".")
|
|
||||||
outputFiles.writeAll(outputDir, messageCollector, reportOutputFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createOutputFilesFlushingCallbackIfPossible(configuration: CompilerConfiguration): GenerationStateEventCallback {
|
|
||||||
if (configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) == null) {
|
|
||||||
return GenerationStateEventCallback.DO_NOTHING
|
|
||||||
}
|
|
||||||
return GenerationStateEventCallback { state ->
|
|
||||||
val currentOutput = SimpleOutputFileCollection(state.factory.currentOutput)
|
|
||||||
writeOutput(configuration, currentOutput, mainClass = null)
|
|
||||||
if (!configuration.get(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false)) {
|
|
||||||
state.factory.releaseGeneratedOutput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun compileModules(environment: KotlinCoreEnvironment, buildFile: File, chunk: List<Module>): Boolean {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)
|
|
||||||
|
|
||||||
val projectConfiguration = environment.configuration
|
|
||||||
for (module in chunk) {
|
|
||||||
moduleVisibilityManager.addModule(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
|
|
||||||
for (path in friendPaths) {
|
|
||||||
moduleVisibilityManager.addFriendPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetDescription = "in targets [" + chunk.joinToString { input -> input.getModuleName() + "-" + input.getModuleType() } + "]"
|
|
||||||
|
|
||||||
val result = repeatAnalysisIfNeeded(analyze(environment, targetDescription), environment, targetDescription)
|
|
||||||
if (result == null || !result.shouldGenerateCode) return false
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
result.throwIfError()
|
|
||||||
|
|
||||||
val outputs = newLinkedHashMapWithExpectedSize<Module, GenerationState>(chunk.size)
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
val moduleSourcePaths = getAbsolutePaths(buildFile, module)
|
|
||||||
val ktFiles = environment.getSourceFiles().filter { file -> file.virtualFilePath in moduleSourcePaths }
|
|
||||||
|
|
||||||
if (!checkKotlinPackageUsage(environment, ktFiles)) return false
|
|
||||||
|
|
||||||
val moduleConfiguration = projectConfiguration.copy().apply {
|
|
||||||
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(module.getOutputDirectory()))
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for ((_, state) in outputs) {
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
writeOutput(state.configuration, state.factory, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectConfiguration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)) {
|
|
||||||
val singleModule = chunk.singleOrNull()
|
|
||||||
if (singleModule != null) {
|
|
||||||
return JavacWrapper.getInstance(environment.project).use {
|
|
||||||
it.compile(File(singleModule.getOutputDirectory()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
projectConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
|
|
||||||
WARNING,
|
|
||||||
"A chunk contains multiple modules (${chunk.joinToString { it.getModuleName() }}). " +
|
|
||||||
"-Xuse-javac option couldn't be used to compile java files"
|
|
||||||
)
|
|
||||||
JavacWrapper.getInstance(environment.project).close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
} finally {
|
|
||||||
outputs.values.forEach(GenerationState::destroy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun configureSourceRoots(configuration: CompilerConfiguration, chunk: List<Module>, buildFile: File) {
|
|
||||||
for (module in chunk) {
|
|
||||||
configuration.addKotlinSourceRoots(getAbsolutePaths(buildFile, module))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
for ((path, packagePrefix) in module.getJavaSourceRoots()) {
|
|
||||||
configuration.addJavaSourceRoot(File(path), packagePrefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isJava9Module = false /*chunk.any { module ->
|
|
||||||
module.getJavaSourceRoots().any { (path, packagePrefix) ->
|
|
||||||
val file = File(path)
|
|
||||||
packagePrefix == null &&
|
|
||||||
(file.name == PsiJavaModule.MODULE_INFO_FILE ||
|
|
||||||
(file.isDirectory && file.listFiles().any { it.name == PsiJavaModule.MODULE_INFO_FILE }))
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
for (classpathRoot in module.getClasspathRoots()) {
|
|
||||||
configuration.add(
|
|
||||||
CLIConfigurationKeys.CONTENT_ROOTS,
|
|
||||||
if (isJava9Module) JvmModulePathRoot(File(classpathRoot)) else JvmClasspathRoot(File(classpathRoot))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (module in chunk) {
|
|
||||||
val modularJdkRoot = module.modularJdkRoot
|
|
||||||
if (modularJdkRoot != null) {
|
|
||||||
// We use the SDK of the first module in the chunk, which is not always correct because some other module in the chunk
|
|
||||||
// might depend on a different SDK
|
|
||||||
configuration.put(JVMConfigurationKeys.JDK_HOME, File(modularJdkRoot))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.addAll(JVMConfigurationKeys.MODULES, chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findMainClass(generationState: GenerationState, files: List<KtFile>): FqName? {
|
|
||||||
val mainFunctionDetector = MainFunctionDetector(generationState.bindingContext)
|
|
||||||
return files.asSequence()
|
|
||||||
.map { file ->
|
|
||||||
if (mainFunctionDetector.hasMain(file.declarations))
|
|
||||||
JvmFileClassUtil.getFileClassInfoNoResolve(file).facadeClassFqName
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
.singleOrNull { it != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
|
|
||||||
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)
|
|
||||||
|
|
||||||
val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
|
|
||||||
for (path in friendPaths) {
|
|
||||||
moduleVisibilityManager.addFriendPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkKotlinPackageUsage(environment, environment.getSourceFiles())) return false
|
|
||||||
|
|
||||||
val generationState = analyzeAndGenerate(environment) ?: return false
|
|
||||||
|
|
||||||
val mainClass = findMainClass(generationState, environment.getSourceFiles())
|
|
||||||
|
|
||||||
try {
|
|
||||||
writeOutput(environment.configuration, generationState.factory, mainClass)
|
|
||||||
return true
|
|
||||||
} finally {
|
|
||||||
generationState.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun compileAndExecuteScript(environment: KotlinCoreEnvironment, scriptArgs: List<String>): ExitCode {
|
|
||||||
val scriptClass = compileScript(environment) ?: return ExitCode.COMPILATION_ERROR
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
tryConstructClassFromStringArgs(scriptClass, scriptArgs)
|
|
||||||
?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs\n")
|
|
||||||
} finally {
|
|
||||||
// NB: these lines are required (see KT-9546) but aren't covered by tests
|
|
||||||
System.out.flush()
|
|
||||||
System.err.flush()
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
reportExceptionFromScript(e)
|
|
||||||
return ExitCode.SCRIPT_EXECUTION_ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExitCode.OK
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun repeatAnalysisIfNeeded(
|
|
||||||
result: AnalysisResult?,
|
|
||||||
environment: KotlinCoreEnvironment,
|
|
||||||
targetDescription: String?
|
|
||||||
): AnalysisResult? {
|
|
||||||
if (result is AnalysisResult.RetryWithAdditionalJavaRoots) {
|
|
||||||
val configuration = environment.configuration
|
|
||||||
|
|
||||||
val oldReadOnlyValue = configuration.isReadOnly
|
|
||||||
configuration.isReadOnly = false
|
|
||||||
configuration.addJavaSourceRoots(result.additionalJavaRoots)
|
|
||||||
configuration.isReadOnly = oldReadOnlyValue
|
|
||||||
|
|
||||||
if (result.addToEnvironment) {
|
|
||||||
environment.updateClasspath(result.additionalJavaRoots.map { JavaSourceRoot(it, null) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear package caches (see KotlinJavaPsiFacade)
|
|
||||||
ApplicationManager.getApplication().runWriteAction {
|
|
||||||
(PsiManager.getInstance(environment.project).modificationTracker as? PsiModificationTrackerImpl)?.incCounter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all diagnostic messages
|
|
||||||
configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]?.clear()
|
|
||||||
|
|
||||||
// Repeat analysis with additional Java roots (kapt generated sources)
|
|
||||||
return analyze(environment, targetDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reportExceptionFromScript(exception: Throwable) {
|
|
||||||
// expecting InvocationTargetException from constructor invocation with cause that describes the actual cause
|
|
||||||
val stream = System.err
|
|
||||||
val cause = exception.cause
|
|
||||||
if (exception !is InvocationTargetException || cause == null) {
|
|
||||||
exception.printStackTrace(stream)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stream.println(cause)
|
|
||||||
val fullTrace = cause.stackTrace
|
|
||||||
for (i in 0 until fullTrace.size - exception.stackTrace.size) {
|
|
||||||
stream.println("\tat " + fullTrace[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileScript(environment: KotlinCoreEnvironment, parentClassLoader: ClassLoader? = null): Class<*>? {
|
|
||||||
val state = analyzeAndGenerate(environment) ?: return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val urls = environment.configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNull { root ->
|
|
||||||
when (root) {
|
|
||||||
is JvmModulePathRoot -> root.file // TODO: only add required modules
|
|
||||||
is JvmClasspathRoot -> root.file
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.map { it.toURI().toURL() }
|
|
||||||
|
|
||||||
val classLoader = GeneratedClassLoader(state.factory, parentClassLoader ?: URLClassLoader(urls.toTypedArray(), null))
|
|
||||||
|
|
||||||
val script = environment.getSourceFiles()[0].script ?: error("Script must be parsed")
|
|
||||||
return classLoader.loadClass(script.fqName.asString())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw RuntimeException("Failed to evaluate script: $e", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun analyzeAndGenerate(environment: KotlinCoreEnvironment): GenerationState? {
|
|
||||||
val result = repeatAnalysisIfNeeded(analyze(environment, null), environment, null) ?: return null
|
|
||||||
|
|
||||||
if (!result.shouldGenerateCode) return null
|
|
||||||
|
|
||||||
result.throwIfError()
|
|
||||||
|
|
||||||
return generate(environment, environment.configuration, result, environment.getSourceFiles(), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun analyze(environment: KotlinCoreEnvironment, targetDescription: String?): AnalysisResult? {
|
|
||||||
val sourceFiles = environment.getSourceFiles()
|
|
||||||
val collector = environment.messageCollector
|
|
||||||
|
|
||||||
// Can be null for Scripts/REPL
|
|
||||||
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
|
|
||||||
performanceManager?.notifyAnalysisStarted()
|
|
||||||
|
|
||||||
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(collector, environment.configuration.languageVersionSettings)
|
|
||||||
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
|
|
||||||
val project = environment.project
|
|
||||||
val moduleOutputs = environment.configuration.get(JVMConfigurationKeys.MODULES)?.mapNotNullTo(hashSetOf()) { module ->
|
|
||||||
environment.findLocalFile(module.getOutputDirectory())
|
|
||||||
}.orEmpty()
|
|
||||||
val sourcesOnly = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles)
|
|
||||||
// To support partial and incremental compilation, we add the scope which contains binaries from output directories
|
|
||||||
// of the compiled modules (.class) to the list of scopes of the source module
|
|
||||||
val scope = if (moduleOutputs.isEmpty()) sourcesOnly else sourcesOnly.uniteWith(DirectoriesScope(project, moduleOutputs))
|
|
||||||
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
|
|
||||||
project,
|
|
||||||
sourceFiles,
|
|
||||||
NoScopeRecordCliBindingTrace(),
|
|
||||||
environment.configuration,
|
|
||||||
environment::createPackagePartProvider,
|
|
||||||
sourceModuleSearchScope = scope
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
performanceManager?.notifyAnalysisFinished(sourceFiles.size, environment.countLinesOfCode(sourceFiles), targetDescription)
|
|
||||||
|
|
||||||
val analysisResult = analyzerWithCompilerReport.analysisResult
|
|
||||||
|
|
||||||
return if (!analyzerWithCompilerReport.hasErrors() || analysisResult is AnalysisResult.RetryWithAdditionalJavaRoots)
|
|
||||||
analysisResult
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirectoriesScope(
|
|
||||||
project: Project,
|
|
||||||
private val directories: Set<VirtualFile>
|
|
||||||
) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
|
|
||||||
private val fileSystems = directories.mapTo(hashSetOf(), VirtualFile::getFileSystem)
|
|
||||||
|
|
||||||
override fun contains(file: VirtualFile): Boolean {
|
|
||||||
if (file.fileSystem !in fileSystems) return false
|
|
||||||
|
|
||||||
var parent: VirtualFile = file
|
|
||||||
while (true) {
|
|
||||||
if (parent in directories) return true
|
|
||||||
parent = parent.parent ?: return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = "All files under: $directories"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun GenerationState.Builder.withModule(module: Module?) =
|
|
||||||
apply {
|
|
||||||
targetId(module?.let { TargetId(it) })
|
|
||||||
moduleName(module?.getModuleName())
|
|
||||||
outDirectory(module?.let { File(it.getOutputDirectory()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generate(
|
|
||||||
environment: KotlinCoreEnvironment,
|
|
||||||
configuration: CompilerConfiguration,
|
|
||||||
result: AnalysisResult,
|
|
||||||
sourceFiles: List<KtFile>,
|
|
||||||
module: Module?
|
|
||||||
): GenerationState {
|
|
||||||
val generationState = GenerationState.Builder(
|
|
||||||
environment.project,
|
|
||||||
ClassBuilderFactories.BINARIES,
|
|
||||||
result.moduleDescriptor,
|
|
||||||
result.bindingContext,
|
|
||||||
sourceFiles,
|
|
||||||
configuration
|
|
||||||
)
|
|
||||||
.codegenFactory(if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory else DefaultCodegenFactory)
|
|
||||||
.withModule(module)
|
|
||||||
.onIndependentPartCompilationEnd(createOutputFilesFlushingCallbackIfPossible(configuration))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
|
|
||||||
performanceManager?.notifyGenerationStarted()
|
|
||||||
|
|
||||||
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
|
||||||
|
|
||||||
performanceManager?.notifyGenerationFinished(
|
|
||||||
sourceFiles.size,
|
|
||||||
environment.countLinesOfCode(sourceFiles),
|
|
||||||
additionalDescription = if (module != null) "target " + module.getModuleName() + "-" + module.getModuleType() + " " else ""
|
|
||||||
)
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
|
|
||||||
AnalyzerWithCompilerReport.reportDiagnostics(
|
|
||||||
FilteredJvmDiagnostics(
|
|
||||||
generationState.collectedExtraJvmDiagnostics,
|
|
||||||
result.bindingContext.diagnostics
|
|
||||||
),
|
|
||||||
environment.messageCollector
|
|
||||||
)
|
|
||||||
|
|
||||||
AnalyzerWithCompilerReport.reportBytecodeVersionErrors(
|
|
||||||
generationState.extraJvmDiagnosticsTrace.bindingContext, environment.messageCollector
|
|
||||||
)
|
|
||||||
|
|
||||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
|
|
||||||
return generationState
|
|
||||||
}
|
|
||||||
|
|
||||||
private val KotlinCoreEnvironment.messageCollector: MessageCollector
|
|
||||||
get() = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.VirtualFile
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
|
|
||||||
|
|
||||||
class CliJavaModuleFinder(jrtFileSystemRoot: VirtualFile?) : JavaModuleFinder {
|
|
||||||
private val modulesRoot = jrtFileSystemRoot?.findChild("modules")
|
|
||||||
private val userModules = linkedMapOf<String, JavaModule>()
|
|
||||||
|
|
||||||
fun addUserModule(module: JavaModule) {
|
|
||||||
userModules.putIfAbsent(module.name, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
val allObservableModules: Sequence<JavaModule>
|
|
||||||
get() = systemModules + userModules.values
|
|
||||||
|
|
||||||
val systemModules: Sequence<JavaModule.Explicit>
|
|
||||||
get() = modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule)
|
|
||||||
|
|
||||||
override fun findModule(name: String): JavaModule? =
|
|
||||||
modulesRoot?.findChild(name)?.let(this::findSystemModule) ?: userModules[name]
|
|
||||||
|
|
||||||
private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? {
|
|
||||||
/*
|
|
||||||
val file = moduleRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) ?: return null
|
|
||||||
val moduleInfo = JavaModuleInfo.read(file) ?: return null
|
|
||||||
return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(moduleRoot, isBinary = true)), file)
|
|
||||||
*/
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.VirtualFile
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
|
|
||||||
|
|
||||||
class CliJavaModuleFinder(jrtFileSystemRoot: VirtualFile?) : JavaModuleFinder {
|
|
||||||
private val modulesRoot = jrtFileSystemRoot?.findChild("modules")
|
|
||||||
private val userModules = linkedMapOf<String, JavaModule>()
|
|
||||||
|
|
||||||
fun addUserModule(module: JavaModule) {
|
|
||||||
userModules.putIfAbsent(module.name, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
val allObservableModules: Sequence<JavaModule>
|
|
||||||
get() = systemModules + userModules.values
|
|
||||||
|
|
||||||
val systemModules: Sequence<JavaModule.Explicit>
|
|
||||||
get() = modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule)
|
|
||||||
|
|
||||||
override fun findModule(name: String): JavaModule? =
|
|
||||||
modulesRoot?.findChild(name)?.let(this::findSystemModule) ?: userModules[name]
|
|
||||||
|
|
||||||
private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? {
|
|
||||||
/*
|
|
||||||
val file = moduleRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) ?: return null
|
|
||||||
val moduleInfo = JavaModuleInfo.read(file) ?: return null
|
|
||||||
return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(moduleRoot, isBinary = true)), file)
|
|
||||||
*/
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-89
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.resolve.jvm.modules
|
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
//import com.intellij.psi.PsiJavaModule
|
|
||||||
import com.intellij.psi.PsiModifier
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.utils.compact
|
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
|
||||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
|
||||||
//import org.jetbrains.org.objectweb.asm.ModuleVisitor
|
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
|
||||||
//import org.jetbrains.org.objectweb.asm.Opcodes.ACC_TRANSITIVE
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class JavaModuleInfo(
|
|
||||||
val moduleName: String,
|
|
||||||
val requires: List<Requires>,
|
|
||||||
val exports: List<Exports>
|
|
||||||
) {
|
|
||||||
data class Requires(val moduleName: String, val isTransitive: Boolean)
|
|
||||||
|
|
||||||
data class Exports(val packageFqName: FqName, val toModules: List<String>)
|
|
||||||
|
|
||||||
override fun toString(): String =
|
|
||||||
"Module $moduleName (${requires.size} requires, ${exports.size} exports)"
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/*fun create(psiJavaModule: PsiJavaModule): JavaModuleInfo {
|
|
||||||
return JavaModuleInfo(
|
|
||||||
psiJavaModule.name,
|
|
||||||
psiJavaModule.requires.mapNotNull { statement ->
|
|
||||||
statement.moduleName?.let { moduleName ->
|
|
||||||
JavaModuleInfo.Requires(moduleName, statement.hasModifierProperty(PsiModifier.TRANSITIVE))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
psiJavaModule.exports.mapNotNull { statement ->
|
|
||||||
statement.packageName?.let { packageName ->
|
|
||||||
JavaModuleInfo.Exports(FqName(packageName), statement.moduleNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*fun read(file: VirtualFile): JavaModuleInfo? {
|
|
||||||
val contents = try { file.contentsToByteArray() } catch (e: IOException) { return null }
|
|
||||||
|
|
||||||
var moduleName: String? = null
|
|
||||||
val requires = arrayListOf<Requires>()
|
|
||||||
val exports = arrayListOf<Exports>()
|
|
||||||
|
|
||||||
ClassReader(contents).accept(object : ClassVisitor(Opcodes.ASM6) {
|
|
||||||
override fun visitModule(name: String, access: Int, version: String?): ModuleVisitor {
|
|
||||||
moduleName = name
|
|
||||||
|
|
||||||
return object : ModuleVisitor(Opcodes.ASM6) {
|
|
||||||
override fun visitRequire(module: String, access: Int, version: String?) {
|
|
||||||
requires.add(Requires(module, (access and ACC_TRANSITIVE) != 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitExport(packageFqName: String, access: Int, modules: Array<String>?) {
|
|
||||||
// For some reason, '/' is the delimiter in packageFqName here
|
|
||||||
exports.add(Exports(FqName(packageFqName.replace('/', '.')), modules?.toList().orEmpty()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, ClassReader.SKIP_DEBUG or ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES)
|
|
||||||
|
|
||||||
return if (moduleName != null)
|
|
||||||
JavaModuleInfo(moduleName!!, requires.compact(), exports.compact())
|
|
||||||
else null
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-89
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.resolve.jvm.modules
|
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
|
||||||
//import com.intellij.psi.PsiJavaModule
|
|
||||||
import com.intellij.psi.PsiModifier
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.utils.compact
|
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
|
||||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
|
||||||
//import org.jetbrains.org.objectweb.asm.ModuleVisitor
|
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
|
||||||
//import org.jetbrains.org.objectweb.asm.Opcodes.ACC_TRANSITIVE
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class JavaModuleInfo(
|
|
||||||
val moduleName: String,
|
|
||||||
val requires: List<Requires>,
|
|
||||||
val exports: List<Exports>
|
|
||||||
) {
|
|
||||||
data class Requires(val moduleName: String, val isTransitive: Boolean)
|
|
||||||
|
|
||||||
data class Exports(val packageFqName: FqName, val toModules: List<String>)
|
|
||||||
|
|
||||||
override fun toString(): String =
|
|
||||||
"Module $moduleName (${requires.size} requires, ${exports.size} exports)"
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/*fun create(psiJavaModule: PsiJavaModule): JavaModuleInfo {
|
|
||||||
return JavaModuleInfo(
|
|
||||||
psiJavaModule.name,
|
|
||||||
psiJavaModule.requires.mapNotNull { statement ->
|
|
||||||
statement.moduleName?.let { moduleName ->
|
|
||||||
JavaModuleInfo.Requires(moduleName, statement.hasModifierProperty(PsiModifier.TRANSITIVE))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
psiJavaModule.exports.mapNotNull { statement ->
|
|
||||||
statement.packageName?.let { packageName ->
|
|
||||||
JavaModuleInfo.Exports(FqName(packageName), statement.moduleNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*fun read(file: VirtualFile): JavaModuleInfo? {
|
|
||||||
val contents = try { file.contentsToByteArray() } catch (e: IOException) { return null }
|
|
||||||
|
|
||||||
var moduleName: String? = null
|
|
||||||
val requires = arrayListOf<Requires>()
|
|
||||||
val exports = arrayListOf<Exports>()
|
|
||||||
|
|
||||||
ClassReader(contents).accept(object : ClassVisitor(Opcodes.ASM6) {
|
|
||||||
override fun visitModule(name: String, access: Int, version: String?): ModuleVisitor {
|
|
||||||
moduleName = name
|
|
||||||
|
|
||||||
return object : ModuleVisitor(Opcodes.ASM6) {
|
|
||||||
override fun visitRequire(module: String, access: Int, version: String?) {
|
|
||||||
requires.add(Requires(module, (access and ACC_TRANSITIVE) != 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitExport(packageFqName: String, access: Int, modules: Array<String>?) {
|
|
||||||
// For some reason, '/' is the delimiter in packageFqName here
|
|
||||||
exports.add(Exports(FqName(packageFqName.replace('/', '.')), modules?.toList().orEmpty()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, ClassReader.SKIP_DEBUG or ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES)
|
|
||||||
|
|
||||||
return if (moduleName != null)
|
|
||||||
JavaModuleInfo(moduleName!!, requires.compact(), exports.compact())
|
|
||||||
else null
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user