Implement resolve top-level functions and props from classloader
#KT-33892 fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+54
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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
|
||||
|
||||
+1
-2
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
+146
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
+19
-11
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Binary file not shown.
+19
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-8
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user