Implement resolve top-level functions and props from classloader

#KT-33892 fixed
This commit is contained in:
Ilya Chernikov
2019-09-30 15:50:43 +02:00
parent 7b93970ce2
commit 2219b950f1
13 changed files with 461 additions and 93 deletions
+1
View File
@@ -534,6 +534,7 @@ tasks {
dependsOn(":kotlin-script-util:test")
dependsOn(":kotlin-scripting-compiler:test")
dependsOn(":kotlin-scripting-common:test")
dependsOn(":kotlin-scripting-jvm:test")
dependsOn(":kotlin-scripting-jvm-host-test:test")
dependsOn(":kotlin-scripting-jsr223-test:test")
dependsOn(":kotlin-scripting-jvm-host-test:embeddableTest")
@@ -25,14 +25,11 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.LOGGING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartProviderBase
import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
import org.jetbrains.kotlin.serialization.deserialization.MetadataPartProvider
import java.io.ByteArrayOutputStream
import java.io.EOFException
import java.io.PrintStream
@@ -40,49 +37,10 @@ import java.io.PrintStream
class JvmPackagePartProvider(
languageVersionSettings: LanguageVersionSettings,
private val scope: GlobalSearchScope
) : PackagePartProvider, MetadataPartProvider {
private data class ModuleMappingInfo(val root: VirtualFile, val mapping: ModuleMapping, val name: String)
) : JvmPackagePartProviderBase<VirtualFile>() {
private val deserializationConfiguration = CompilerDeserializationConfiguration(languageVersionSettings)
private val loadedModules: MutableList<ModuleMappingInfo> = SmartList()
override fun findPackageParts(packageFqName: String): List<String> {
val rootToPackageParts = getPackageParts(packageFqName)
if (rootToPackageParts.isEmpty()) return emptyList()
val result = linkedSetOf<String>()
val visitedMultifileFacades = linkedSetOf<String>()
for ((_, packageParts) in rootToPackageParts) {
for (name in packageParts.parts) {
val facadeName = packageParts.getMultifileFacadeName(name)
if (facadeName == null || facadeName !in visitedMultifileFacades) {
result.add(name)
}
}
packageParts.parts.mapNotNullTo(visitedMultifileFacades, packageParts::getMultifileFacadeName)
}
return result.toList()
}
override fun findMetadataPackageParts(packageFqName: String): List<String> =
getPackageParts(packageFqName).values.flatMap(PackageParts::metadataParts).distinct()
@Synchronized
private fun getPackageParts(packageFqName: String): Map<VirtualFile, PackageParts> {
val result = mutableMapOf<VirtualFile, PackageParts>()
for ((root, mapping) in loadedModules) {
val newParts = mapping.findPackageParts(packageFqName) ?: continue
result[root]?.let { parts -> parts += newParts } ?: result.put(root, newParts)
}
return result
}
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
return loadedModules.mapNotNull { (_, mapping, name) ->
if (name == moduleName) mapping.moduleData.annotations.map(ClassId::fromString) else null
}.flatten()
}
override val loadedModules: MutableList<ModuleMappingInfo<VirtualFile>> = SmartList()
fun addRoots(roots: List<JavaRoot>, messageCollector: MessageCollector) {
for ((root, type) in roots) {
@@ -93,29 +51,40 @@ class JvmPackagePartProvider(
for (moduleFile in metaInf.children) {
if (!moduleFile.name.endsWith(ModuleMapping.MAPPING_FILE_EXT)) continue
try {
val mapping = ModuleMapping.loadModuleMapping(
moduleFile.contentsToByteArray(), moduleFile.toString(), deserializationConfiguration
) { incompatibleVersion ->
messageCollector.report(
ERROR,
"Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is " +
"$incompatibleVersion, expected version is ${JvmMetadataVersion.INSTANCE}.",
CompilerMessageLocation.create(moduleFile.path)
)
}
loadedModules.add(ModuleMappingInfo(root, mapping, moduleFile.nameWithoutExtension))
} catch (e: EOFException) {
messageCollector.report(
ERROR, "Error occurred when reading the module: ${e.message}", CompilerMessageLocation.create(moduleFile.path)
)
messageCollector.report(
LOGGING,
String(ByteArrayOutputStream().also { e.printStackTrace(PrintStream(it)) }.toByteArray()),
CompilerMessageLocation.create(moduleFile.path)
)
tryLoadModuleMapping(
{ moduleFile.contentsToByteArray() }, moduleFile.toString(), moduleFile.path,
deserializationConfiguration, messageCollector
)?.let {
loadedModules.add(ModuleMappingInfo(root, it, moduleFile.nameWithoutExtension))
}
}
}
}
}
fun tryLoadModuleMapping(
getModuleBytes: () -> ByteArray,
debugName: String,
modulePath: String,
deserializationConfiguration: CompilerDeserializationConfiguration,
messageCollector: MessageCollector
): ModuleMapping? = try {
ModuleMapping.loadModuleMapping(getModuleBytes(), debugName, deserializationConfiguration) { incompatibleVersion ->
messageCollector.report(
ERROR,
"Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is " +
"$incompatibleVersion, expected version is ${JvmMetadataVersion.INSTANCE}.",
CompilerMessageLocation.create(modulePath)
)
}
} catch (e: EOFException) {
messageCollector.report(
ERROR, "Error occurred when reading the module: ${e.message}", CompilerMessageLocation.create(modulePath)
)
messageCollector.report(
LOGGING,
String(ByteArrayOutputStream().also { e.printStackTrace(PrintStream(it)) }.toByteArray()),
CompilerMessageLocation.create(modulePath)
)
null
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.load.kotlin
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.serialization.deserialization.MetadataPartProvider
abstract class JvmPackagePartProviderBase<MappingsKey> : PackagePartProvider, MetadataPartProvider {
protected data class ModuleMappingInfo<MappingsKey>(val key: MappingsKey, val mapping: ModuleMapping, val name: String)
protected abstract val loadedModules: MutableList<ModuleMappingInfo<MappingsKey>>
override fun findPackageParts(packageFqName: String): List<String> {
val rootToPackageParts: Collection<PackageParts> = getPackageParts(packageFqName)
if (rootToPackageParts.isEmpty()) return emptyList()
val result = linkedSetOf<String>()
val visitedMultifileFacades = linkedSetOf<String>()
for (packageParts in rootToPackageParts) {
for (name in packageParts.parts) {
val facadeName = packageParts.getMultifileFacadeName(name)
if (facadeName == null || facadeName !in visitedMultifileFacades) {
result.add(name)
}
}
packageParts.parts.mapNotNullTo(visitedMultifileFacades, packageParts::getMultifileFacadeName)
}
return result.toList()
}
override fun findMetadataPackageParts(packageFqName: String): List<String> =
getPackageParts(packageFqName).flatMap(PackageParts::metadataParts).distinct()
private fun getPackageParts(packageFqName: String): Collection<PackageParts> {
val result = mutableMapOf<MappingsKey, PackageParts>()
for ((root, mapping) in loadedModules) {
val newParts = mapping.findPackageParts(packageFqName) ?: continue
result[root]?.let { parts -> parts += newParts } ?: result.put(root, newParts)
}
return result.values
}
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
return loadedModules.mapNotNull { (_, mapping, name) ->
if (name == moduleName) mapping.moduleData.annotations.map(ClassId::fromString) else null
}.flatten()
}
}
@@ -106,14 +106,15 @@ fun makeLazyJavaPackageFragmentFromClassLoaderProvider(
notFoundClasses: NotFoundClasses,
reflectKotlinClassFinder: KotlinClassFinder,
deserializedDescriptorResolver: DeserializedDescriptorResolver,
singleModuleClassResolver: ModuleClassResolver
singleModuleClassResolver: ModuleClassResolver,
packagePartProvider: PackagePartProvider = PackagePartProvider.Empty
): LazyJavaPackageFragmentProvider {
val annotationTypeQualifierResolver = AnnotationTypeQualifierResolver(storageManager, Jsr305State.DISABLED)
val javaResolverComponents = JavaResolverComponents(
storageManager, ReflectJavaClassFinder(classLoader), reflectKotlinClassFinder, deserializedDescriptorResolver,
SignaturePropagator.DO_NOTHING, RuntimeErrorReporter, JavaResolverCache.EMPTY,
JavaPropertyInitializerEvaluator.DoNothing, SamConversionResolver.Empty, RuntimeSourceElementFactory,
singleModuleClassResolver, PackagePartProvider.Empty, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module,
singleModuleClassResolver, packagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module,
ReflectionTypes(module, notFoundClasses), annotationTypeQualifierResolver,
SignatureEnhancement(annotationTypeQualifierResolver, Jsr305State.DISABLED),
JavaClassesTracker.Default, JavaResolverSettings.Default, NewKotlinTypeChecker.Default
@@ -52,10 +52,9 @@ class ResolveDependenciesTest : TestCase() {
runScriptAndCheckResult(classImportScript, configurationWithDependenciesFromClasspath, null, 42)
}
@Ignore
@Test
// This doesn't work since there is no way to resolve a top-level function/property via reflection now (see #KT-33892)
fun ignore_testResolveFunAndValFromClassloader() {
fun testResolveFunAndValFromClassloader() {
runScriptAndCheckResult(funAndValAccessScript, configurationWithDependenciesFromClassloader, null, 42)
runScriptAndCheckResult(funAndValImportScript, configurationWithDependenciesFromClassloader, null, 42)
}
+2 -1
View File
@@ -10,11 +10,12 @@ dependencies {
compile(project(":kotlin-script-runtime"))
compile(kotlinStdlib())
compile(project(":kotlin-scripting-common"))
testCompile(commonDep("junit"))
}
sourceSets {
"main" { projectDefault() }
"test" {}
"test" { projectDefault() }
}
publish()
@@ -0,0 +1,146 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.util
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.net.JarURLConnection
import java.net.URL
import java.util.jar.JarFile
import java.util.jar.JarInputStream
import kotlin.script.experimental.jvm.impl.toFileOrNull
fun ClassLoader.forAllMatchingFiles(namePattern: String, body: (String, InputStream) -> Unit) {
val processedDirs = HashSet<File>()
val processedJars = HashSet<URL>()
val nameRegex = namePatternToRegex(namePattern)
fun iterateResources(vararg keyResourcePaths: String) {
for (keyResourcePath in keyResourcePaths) {
val resourceRootCalc = ClassLoaderResourceRootFIlePathCalculator(keyResourcePath)
for (url in getResources(keyResourcePath)) {
if (url.protocol == "jar") {
val jarConnection = url.openConnection() as? JarURLConnection
val jarUrl = jarConnection?.jarFileURL
if (jarUrl != null && !processedJars.contains(jarUrl)) {
processedJars.add(jarUrl)
try {
jarConnection.jarFile
} catch (_: IOException) {
// TODO: consider error reporting
null
}?.let {
forAllMatchingFilesInJarFile(it, nameRegex, body)
}
}
} else {
val rootDir = url.toFileOrNull()?.let { resourceRootCalc(it) }
if (rootDir != null && rootDir.isDirectory && !processedDirs.contains(rootDir)) {
processedDirs.add(rootDir)
forAllMatchingFilesInDirectory(rootDir, namePattern, body)
}
}
}
}
}
iterateResources("", JAR_MANIFEST_RESOURCE_NAME)
}
internal val wildcardChars = "*?".toCharArray()
internal val patternCharsToEscape = ".*?+()[]^\${}|".toCharArray().also { assert(wildcardChars.all { wc -> it.contains(wc) }) }
private fun Char.escape(): String = (if (patternCharsToEscape.contains(this)) "\\" else "") + this
internal val pathSeparatorChars = "/".let { if (File.separatorChar == '/') it else it + File.separator }.toCharArray()
internal val pathElementPattern = if (File.separatorChar == '/') "[^/]*" else "[^/${File.separatorChar.escape()}]*"
internal val pathSeparatorPattern = if (File.separatorChar == '/') "/" else "[/${File.separatorChar.escape()}]."
internal val specialPatternChars = patternCharsToEscape + pathSeparatorChars
internal fun forAllMatchingFilesInDirectory(baseDir: File, namePattern: String, body: (String, InputStream) -> Unit) {
val patternStart = namePattern.indexOfAny(wildcardChars)
if (patternStart < 0) {
// assuming a single file
baseDir.resolve(namePattern).takeIf { it.exists() && it.isFile }?.let { file ->
body(file.relativeToOrSelf(baseDir).path, file.inputStream())
}
} else {
val patternDirStart = namePattern.lastIndexOfAny(pathSeparatorChars, patternStart)
val root = if (patternDirStart <= 0) baseDir else baseDir.resolve(namePattern.substring(0, patternDirStart))
if (root.exists() && root.isDirectory) {
val re = namePatternToRegex(namePattern.substring(patternDirStart + 1))
root.walkTopDown().filter {
re.matches(it.relativeToOrSelf(root).path)
}.forEach { file ->
body(file.relativeToOrSelf(baseDir).path, file.inputStream())
}
}
}
}
internal fun forAllMatchingFilesInJarStream(jarInputStream: JarInputStream, nameRegex: Regex, body: (String, InputStream) -> Unit) {
do {
val entry = jarInputStream.nextJarEntry
if (entry != null) {
try {
if (!entry.isDirectory && nameRegex.matches(entry.name)) {
body(entry.name, jarInputStream)
}
} finally {
jarInputStream.closeEntry()
}
}
} while (entry != null)
}
internal fun forAllMatchingFilesInJar(jarFile: File, nameRegex: Regex, body: (String, InputStream) -> Unit) {
JarInputStream(FileInputStream(jarFile)).use {
forAllMatchingFilesInJarStream(it, nameRegex, body)
}
}
internal fun forAllMatchingFilesInJarFile(jarFile: JarFile, nameRegex: Regex, body: (String, InputStream) -> Unit) {
jarFile.entries().asSequence().forEach { entry ->
if (!entry.isDirectory && nameRegex.matches(entry.name)) {
jarFile.getInputStream(entry).use { stream ->
body(entry.name, stream)
}
}
}
}
internal fun namePatternToRegex(pattern: String): Regex = Regex(
buildString {
var current = 0
loop@ while (current < pattern.length) {
val nextIndex = pattern.indexOfAny(specialPatternChars, current)
val next = if (nextIndex < 0) pattern.length else nextIndex
append(pattern.substring(current, next))
current = next + 1
when {
next >= pattern.length -> break@loop
pathSeparatorChars.contains(pattern[next]) -> append(pathSeparatorPattern)
pattern[next] == '?' -> append('.')
pattern[next] == '*' && next + 1 < pattern.length && pattern[next + 1] == '*' -> {
append(".*")
current++
}
pattern[next] == '*' -> append(pathElementPattern)
else -> {
append('\\')
append(pattern[next])
}
}
}
}
)
@@ -39,7 +39,7 @@ internal const val KOTLIN_COMPILER_JAR = "$KOTLIN_COMPILER_NAME.jar"
private val JAR_COLLECTIONS_CLASSES_PATHS = arrayOf("BOOT-INF/classes", "WEB-INF/classes")
private val JAR_COLLECTIONS_LIB_PATHS = arrayOf("BOOT-INF/lib", "WEB-INF/lib")
private val JAR_COLLECTIONS_KEY_PATHS = JAR_COLLECTIONS_CLASSES_PATHS + JAR_COLLECTIONS_LIB_PATHS
private const val JAR_MANIFEST_RESOURCE_NAME = "META-INF/MANIFEST.MF"
internal const val JAR_MANIFEST_RESOURCE_NAME = "META-INF/MANIFEST.MF"
internal const val KOTLIN_SCRIPT_CLASSPATH_PROPERTY = "kotlin.script.classpath"
internal const val KOTLIN_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
@@ -108,20 +108,28 @@ private fun ClassLoader.classPathFromGetUrlsMethodOrNull(): Sequence<File>? {
}
}
internal class ClassLoaderResourceRootFIlePathCalculator(private val keyResourcePath: String) {
private var keyResourcePathDepth = -1
operator fun invoke(resourceFile: File): File {
if (keyResourcePathDepth < 0) {
keyResourcePathDepth = if (keyResourcePath.isBlank()) 0 else (keyResourcePath.trim('/').count { it == '/' } + 1)
}
var root = resourceFile
for (i in 0 until keyResourcePathDepth) {
root = root.parentFile
}
return root
}
}
internal fun ClassLoader.rawClassPathFromKeyResourcePath(keyResourcePath: String): Sequence<File> {
var keyResourcePathDepth = -1
val resourceRootCalc = ClassLoaderResourceRootFIlePathCalculator(keyResourcePath)
return getResources(keyResourcePath).asSequence().mapNotNull { url ->
if (url.protocol == "jar") {
(url.openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
} else url.toFileOrNull()?.let { file ->
if (keyResourcePathDepth < 0) {
keyResourcePathDepth = if (keyResourcePath.isBlank()) 0 else (keyResourcePath.trim('/').count { it == '/' } + 1)
}
var root = file
for (i in 0 until keyResourcePathDepth) {
root = root.parentFile
}
root
} else {
url.toFileOrNull()?.let { resourceRootCalc(it) }
}
}
}
@@ -0,0 +1,126 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.test
import junit.framework.TestCase
import org.junit.Test
import java.io.File
import java.util.jar.JarFile
import java.util.jar.JarInputStream
import kotlin.script.experimental.jvm.util.*
class UtilsTest : TestCase() {
@Test
fun testPatternConversionWildcards() {
assertPattern("a${pathSeparatorPattern}b\\.$pathElementPattern", "a/b.*")
assertPattern("a$pathSeparatorPattern$pathElementPattern\\.txt", "a/*.txt")
assertPattern("a$pathSeparatorPattern.*/b", "a/**/b")
assertPattern("a${pathSeparatorPattern}b.\\.txt", "a/b?.txt")
assertPattern("$pathElementPattern/b\\.txt", "*/b.txt")
assertPattern(".*${pathSeparatorPattern}b\\.txt", "**/b.txt")
}
@Test
fun testPatternConversionEscaping() {
assertPattern("aa\\+\\(\\)\\[\\]\\^\\\$\\{\\}\\|", "aa+()[]^\${}|")
assertPattern("\\+\\(\\)\\[\\]\\^\\\$\\{\\}\\|bb", "+()[]^\${}|bb")
}
@Test
fun testSelectFilesInDir() {
val rootDir = File(".")
fun assertProjectFilesBy(pattern: String, vararg paths: String) {
val res = ArrayList<Pair<String, String>>()
forAllMatchingFilesInDirectory(rootDir, pattern) { path, stream ->
res.add(path to stream.reader().readText())
}
assertEquals(paths.toSet(), res.mapTo(HashSet()) { it.first })
res.forEach { (path, bytes) ->
val data = File(path).readText()
assertEquals("Mismatching data for $path", data, bytes)
}
}
assertProjectFilesBy("*.kt") // none
assertProjectFilesBy("**/sss/*.kt") // none
assertProjectFilesBy(
"src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt",
"src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt"
)
assertProjectFilesBy(
"src/kotlin/script/experimental/jvm/util/jvm?lassLoaderUtil.kt",
"src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt"
)
assertProjectFilesBy(
"src/kotlin/script/experimental/jvm/util/jvm*LoaderUtil.kt",
"src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt"
)
assertProjectFilesBy("**/jvmClassLoaderUtil.kt", "src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt")
assertProjectFilesBy("**/script/**/jvmClassLoaderUtil.kt", "src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt")
assertProjectFilesBy("src/**/jvmClassLoaderUtil.kt", "src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt")
assertProjectFilesBy("test/**/?????Test.*", "test/kotlin/script/experimental/jvm/test/utilsTest.kt")
val allSrcKtFiles = HashSet<String>()
forAllMatchingFilesInDirectory(rootDir, "src/**/*.kt") { path, _ ->
allSrcKtFiles.add(path)
}
val allExpectedSrcKtFiles =
rootDir.walkTopDown().filter {
it.relativeToOrSelf(rootDir).path.startsWith("src") && it.extension == "kt"
}.mapTo(HashSet()) {
it.relativeToOrSelf(rootDir).path
}
assertEquals(allExpectedSrcKtFiles, allSrcKtFiles)
}
@Test
fun testSelectFilesInJar() {
fun JarFile.filesBy(pattern: String): Map<String, String> {
val res = HashMap<String, String>()
forAllMatchingFilesInJarFile(this, namePatternToRegex(pattern)) { path, stream ->
res[path] = stream.reader().readText().trim()
}
return res
}
fun JarInputStream.filesBy(pattern: String): Map<String, String> {
val res = HashMap<String, String>()
forAllMatchingFilesInJarStream(this, namePatternToRegex(pattern)) { path, stream ->
res[path] = stream.reader().readText().trim()
}
return res
}
fun assertFiles(actual: Map<String, String>, vararg expected: Pair<String, String>) {
val expectedAsMap = expected.toMap()
assertEquals(expectedAsMap, actual)
}
fun assertMatchingFilesInJarTwoWay(jar: File, pattern: String, vararg expected: Pair<String, String>) {
assertFiles( JarFile(jar).filesBy(pattern), *expected)
assertFiles( JarInputStream(jar.inputStream()).use { it.filesBy(pattern) }, *expected)
}
val jar = File("testData/testJar.jar")
assertTrue(jar.exists())
assertMatchingFilesInJarTwoWay(jar, "META-INF/*.kotlin_module", "META-INF/abc.kotlin_module" to "module")
assertMatchingFilesInJarTwoWay(jar, "META-INF/*.kotlin") // none
assertMatchingFilesInJarTwoWay(jar, "**/*.class", "a/b/c/d1.class" to "d1", "a/b/c/d1\$s1.class" to "d1s1")
assertMatchingFilesInJarTwoWay(jar, "**/*\$*.class", "a/b/c/d1\$s1.class" to "d1s1")
}
private fun assertPattern(expected: String, pattern: String) {
assertEquals(expected, namePatternToRegex(pattern).pattern)
}
}
Binary file not shown.
@@ -7,23 +7,29 @@ package org.jetbrains.kotlin.scripting.compiler.plugin.impl
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.runtime.components.*
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.java.components.JavaResolverCache
import org.jetbrains.kotlin.load.java.lazy.SingleModuleClassResolver
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.load.kotlin.*
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.jvm.ClassLoaderByConfiguration
import kotlin.script.experimental.jvm.util.classpathFromClassloader
class PackageFragmentFromClassLoaderProviderExtension(
val classLoaderGetter: ClassLoaderByConfiguration,
val scriptCompilationConfiguration: ScriptCompilationConfiguration
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val compilerConfiguration: CompilerConfiguration
) : PackageFragmentProviderExtension {
override fun getPackageFragmentProvider(
@@ -40,11 +46,18 @@ class PackageFragmentFromClassLoaderProviderExtension(
val deserializedDescriptorResolver = DeserializedDescriptorResolver()
val singleModuleClassResolver = SingleModuleClassResolver()
val notFoundClasses = NotFoundClasses(storageManager, module)
val packagePartProvider =
PackagePartFromClassLoaderProvider(
classLoader,
compilerConfiguration.languageVersionSettings,
compilerConfiguration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!!
)
val lazyJavaPackageFragmentProvider =
makeLazyJavaPackageFragmentFromClassLoaderProvider(
classLoader, module, storageManager, notFoundClasses,
reflectKotlinClassFinder, deserializedDescriptorResolver, singleModuleClassResolver
reflectKotlinClassFinder, deserializedDescriptorResolver, singleModuleClassResolver,
packagePartProvider
)
val deserializationComponentsForJava =
@@ -55,6 +68,9 @@ class PackageFragmentFromClassLoaderProviderExtension(
deserializedDescriptorResolver.setComponents(deserializationComponentsForJava)
val javaDescriptorResolver = JavaDescriptorResolver(lazyJavaPackageFragmentProvider, JavaResolverCache.EMPTY)
singleModuleClassResolver.resolver = javaDescriptorResolver
return lazyJavaPackageFragmentProvider
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.impl
import com.intellij.util.SmartList
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.tryLoadModuleMapping
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartProviderBase
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
import kotlin.script.experimental.jvm.util.forAllMatchingFiles
class PackagePartFromClassLoaderProvider(
classLoader: ClassLoader,
languageVersionSettings: LanguageVersionSettings,
messageCollector: MessageCollector
) : JvmPackagePartProviderBase<String>() {
private val deserializationConfiguration = CompilerDeserializationConfiguration(languageVersionSettings)
override val loadedModules: MutableList<ModuleMappingInfo<String>> = SmartList()
init {
classLoader.forAllMatchingFiles("META-INF/*.${ModuleMapping.MAPPING_FILE_EXT}") { name, stream ->
tryLoadModuleMapping(
{ stream.readBytes() }, name, name, deserializationConfiguration, messageCollector
)?.let {
val moduleName = name.removePrefix("META-INF/").removeSuffix(".${ModuleMapping.MAPPING_FILE_EXT}")
loadedModules.add(ModuleMappingInfo(name, it, moduleName))
}
}
}
}
@@ -132,6 +132,23 @@ private fun compileImpl(
}
}
internal fun registerPackageFragmetProvidersIfNeeded(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
environment: KotlinCoreEnvironment
) {
scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.forEach { dependency ->
if (dependency is JvmDependencyFromClassLoader) {
// TODO: consider implementing deduplication
PackageFragmentProviderExtension.registerExtension(
environment.project,
PackageFragmentFromClassLoaderProviderExtension(
dependency.classLoaderGetter, scriptCompilationConfiguration, environment.configuration
)
)
}
}
}
private fun doCompile(
context: SharedScriptCompilationContext,
script: SourceCode,
@@ -141,14 +158,7 @@ private fun doCompile(
getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration
): ResultWithDiagnostics<KJvmCompiledScript<Any>> {
context.baseScriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.forEach { dependency ->
if (dependency is JvmDependencyFromClassLoader) {
PackageFragmentProviderExtension.registerExtension(
context.environment.project,
PackageFragmentFromClassLoaderProviderExtension(dependency.classLoaderGetter, context.baseScriptCompilationConfiguration)
)
}
}
registerPackageFragmetProvidersIfNeeded(getScriptConfiguration(sourceFiles.first()), context.environment)
val analysisResult = analyze(sourceFiles, context.environment)