Fix classpath detection from classloader resources
This commit is contained in:
+45
-2
@@ -13,19 +13,50 @@ import java.io.FileOutputStream
|
|||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.jar.JarEntry
|
import java.util.jar.JarEntry
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
|
import java.util.jar.Manifest
|
||||||
|
import kotlin.script.experimental.jvm.util.classPathFromTypicalResourceUrls
|
||||||
import kotlin.script.experimental.jvm.util.classpathFromClassloader
|
import kotlin.script.experimental.jvm.util.classpathFromClassloader
|
||||||
|
|
||||||
class ClassPathTest : TestCase() {
|
class ClassPathTest : TestCase() {
|
||||||
|
|
||||||
|
lateinit var tempDir: File
|
||||||
|
|
||||||
|
override fun setUp() {
|
||||||
|
tempDir = createTempDir(ClassPathTest::class.simpleName!!)
|
||||||
|
super.setUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
tempDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExtractFromFat() {
|
fun testExtractFromFat() {
|
||||||
val collection = createTempFile("col", ".jar").apply { createCollectionJar(emulatedCollectionFiles, "BOOT-INF") }
|
val collection = createTempFile("col", ".jar", directory = tempDir).apply { createCollectionJar(emulatedCollectionFiles, "BOOT-INF") }
|
||||||
val cl = URLClassLoader(arrayOf(collection.toURI().toURL()), null)
|
val cl = URLClassLoader(arrayOf(collection.toURI().toURL()), null)
|
||||||
val cp = classpathFromClassloader(cl, true)
|
val cp = classpathFromClassloader(cl, true)
|
||||||
Assert.assertTrue(cp != null && cp.isNotEmpty())
|
Assert.assertTrue(cp != null && cp.isNotEmpty())
|
||||||
|
|
||||||
testUnpackedCollection(cp!!, emulatedCollectionFiles)
|
testUnpackedCollection(cp!!, emulatedCollectionFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDetectClasspathFromResources() {
|
||||||
|
val root1 = createTempDir("root1", directory = tempDir)
|
||||||
|
val jar = createTempFile("jar1", ".jar", directory = tempDir).apply { createJarWithManifest() }
|
||||||
|
val cl = URLClassLoader(
|
||||||
|
(emulatedClasspath.map { File(root1, it).apply { mkdirs() }.toURI().toURL() }
|
||||||
|
+ jar.toURI().toURL()).toTypedArray(),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
val cp = cl.classPathFromTypicalResourceUrls().toList()
|
||||||
|
|
||||||
|
Assert.assertTrue(cp.contains(jar.canonicalFile))
|
||||||
|
for (el in emulatedClasspath) {
|
||||||
|
Assert.assertTrue(cp.contains(File(root1, el).canonicalFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val emulatedCollectionFiles = arrayOf(
|
private val emulatedCollectionFiles = arrayOf(
|
||||||
@@ -33,6 +64,11 @@ private val emulatedCollectionFiles = arrayOf(
|
|||||||
"lib/c-d.jar"
|
"lib/c-d.jar"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val emulatedClasspath = arrayOf(
|
||||||
|
"module1/classes/kotlin/main/",
|
||||||
|
"module2/classes/java/test/"
|
||||||
|
)
|
||||||
|
|
||||||
fun File.createCollectionJar(fileNames: Array<String>, infDirName: String) {
|
fun File.createCollectionJar(fileNames: Array<String>, infDirName: String) {
|
||||||
FileOutputStream(this).use { fileStream ->
|
FileOutputStream(this).use { fileStream ->
|
||||||
val jarStream = JarOutputStream(fileStream)
|
val jarStream = JarOutputStream(fileStream)
|
||||||
@@ -59,4 +95,11 @@ fun testUnpackedCollection(classpath: List<File>, fileNames: Array<String>) {
|
|||||||
Assert.assertTrue(cpClasses.size == 1)
|
Assert.assertTrue(cpClasses.size == 1)
|
||||||
classes.checkFiles(cpClasses.first().parentFile)
|
classes.checkFiles(cpClasses.first().parentFile)
|
||||||
jars.checkFiles(cpJars.first().parentFile.parentFile)
|
jars.checkFiles(cpJars.first().parentFile.parentFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun File.createJarWithManifest() {
|
||||||
|
FileOutputStream(this).use { fileStream ->
|
||||||
|
val jarStream = JarOutputStream(fileStream, Manifest())
|
||||||
|
jarStream.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ internal fun URL.toFileOrNull() =
|
|||||||
else File(file).canonicalFile
|
else File(file).canonicalFile
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun URL.toContainingFileOrNull(): File? =
|
internal fun URL.toContainingJarOrNull(): File? =
|
||||||
if (protocol == "jar") {
|
if (protocol == "jar") {
|
||||||
(openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
|
(openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
|
||||||
} else toFileOrNull()
|
} else null
|
||||||
|
|||||||
+32
-6
@@ -8,11 +8,13 @@ package kotlin.script.experimental.jvm.util
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.net.JarURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.script.experimental.jvm.impl.toContainingFileOrNull
|
import kotlin.script.experimental.jvm.impl.toContainingJarOrNull
|
||||||
|
import kotlin.script.experimental.jvm.impl.toFileOrNull
|
||||||
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClass
|
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClass
|
||||||
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClassByName
|
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClassByName
|
||||||
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
||||||
@@ -67,7 +69,7 @@ fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollectio
|
|||||||
// if cache dir is specified, find all jar collections (spring boot fat jars and WARs so far, and unpack it accordingly
|
// if cache dir is specified, find all jar collections (spring boot fat jars and WARs so far, and unpack it accordingly
|
||||||
val jarCollections = JAR_COLLECTIONS_KEY_PATHS.asSequence().flatMap { currentClassLoader.getResources(it).asSequence() }
|
val jarCollections = JAR_COLLECTIONS_KEY_PATHS.asSequence().flatMap { currentClassLoader.getResources(it).asSequence() }
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
it.toContainingFileOrNull()?.takeIf { file ->
|
it.toContainingJarOrNull()?.takeIf { file ->
|
||||||
// additionally mark/check processed collection jars since unpacking is expensive
|
// additionally mark/check processed collection jars since unpacking is expensive
|
||||||
file.extension in validJarCollectionFilesExtensions && processedJars.add(file)
|
file.extension in validJarCollectionFilesExtensions && processedJars.add(file)
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,7 @@ fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollectio
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
classLoader.classPathFromGetUrlsMethodOrNull()
|
classLoader.classPathFromGetUrlsMethodOrNull()
|
||||||
?: classLoader.classPathFromManifestResourceUrls()
|
?: classLoader.classPathFromTypicalResourceUrls()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
classPath
|
classPath
|
||||||
@@ -88,7 +90,8 @@ fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollectio
|
|||||||
.toList().takeIf { it.isNotEmpty() }
|
.toList().takeIf { it.isNotEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun URL.toValidClasspathFileOrNull(): File? = toContainingFileOrNull()?.takeIf { it.isValidClasspathFile() }
|
internal fun URL.toValidClasspathFileOrNull(): File? =
|
||||||
|
(toContainingJarOrNull() ?: toFileOrNull())?.takeIf { it.isValidClasspathFile() }
|
||||||
|
|
||||||
internal fun File.isValidClasspathFile(): Boolean =
|
internal fun File.isValidClasspathFile(): Boolean =
|
||||||
isDirectory || (isFile && extension in validClasspathFilesExtensions)
|
isDirectory || (isFile && extension in validClasspathFilesExtensions)
|
||||||
@@ -105,8 +108,31 @@ private fun ClassLoader.classPathFromGetUrlsMethodOrNull(): Sequence<File>? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ClassLoader.classPathFromManifestResourceUrls(): Sequence<File> =
|
internal fun ClassLoader.rawClassPathFromKeyResourcePath(keyResourcePath: String): Sequence<File> {
|
||||||
getResources(JAR_MANIFEST_RESOURCE_NAME).asSequence().distinct().mapNotNull { it.toValidClasspathFileOrNull() }
|
var keyResourcePathDepth = -1
|
||||||
|
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 = keyResourcePath.trim('/').count { it == '/' }
|
||||||
|
}
|
||||||
|
var root = file
|
||||||
|
for (i in 1..keyResourcePathDepth) {
|
||||||
|
root = root.parentFile
|
||||||
|
}
|
||||||
|
root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ClassLoader.classPathFromTypicalResourceUrls(): Sequence<File> =
|
||||||
|
// roots without manifest cases are detected in some test scenarios
|
||||||
|
// manifests without containing directory entries are detected in some optimized jars, e.g. after proguard
|
||||||
|
// TODO: investigate whether getting resources with empty name works in all situations
|
||||||
|
(rawClassPathFromKeyResourcePath("") + rawClassPathFromKeyResourcePath(JAR_MANIFEST_RESOURCE_NAME))
|
||||||
|
.distinct()
|
||||||
|
.filter { it.isValidClasspathFile() }
|
||||||
|
|
||||||
private fun File.unpackJarCollection(rootTempDir: File?): Sequence<File> {
|
private fun File.unpackJarCollection(rootTempDir: File?): Sequence<File> {
|
||||||
val targetDir = createTempDir(nameWithoutExtension, directory = rootTempDir)
|
val targetDir = createTempDir(nameWithoutExtension, directory = rootTempDir)
|
||||||
|
|||||||
Reference in New Issue
Block a user