Refactor script definitions loading and discovery:
Improve logic, code reuse and readability Add support for more corner cases Improve reporting Add definitions loading test
This commit is contained in:
+141
-135
@@ -15,7 +15,6 @@ import java.io.File
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
import kotlin.coroutines.experimental.SequenceBuilder
|
|
||||||
import kotlin.coroutines.experimental.buildSequence
|
import kotlin.coroutines.experimental.buildSequence
|
||||||
import kotlin.script.experimental.annotations.KotlinScript
|
import kotlin.script.experimental.annotations.KotlinScript
|
||||||
import kotlin.script.experimental.api.KotlinType
|
import kotlin.script.experimental.api.KotlinType
|
||||||
@@ -29,7 +28,6 @@ internal const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/temp
|
|||||||
|
|
||||||
class ScriptDefinitionsFromClasspathDiscoverySource(
|
class ScriptDefinitionsFromClasspathDiscoverySource(
|
||||||
private val classpath: List<File>,
|
private val classpath: List<File>,
|
||||||
private val defaultScriptDefinitionClasspath: List<File>,
|
|
||||||
private val scriptResolverEnv: Map<String, Any?>,
|
private val scriptResolverEnv: Map<String, Any?>,
|
||||||
private val messageCollector: MessageCollector
|
private val messageCollector: MessageCollector
|
||||||
) : ScriptDefinitionsSource {
|
) : ScriptDefinitionsSource {
|
||||||
@@ -37,7 +35,6 @@ class ScriptDefinitionsFromClasspathDiscoverySource(
|
|||||||
override val definitions: Sequence<KotlinScriptDefinition> = run {
|
override val definitions: Sequence<KotlinScriptDefinition> = run {
|
||||||
discoverScriptTemplatesInClasspath(
|
discoverScriptTemplatesInClasspath(
|
||||||
classpath,
|
classpath,
|
||||||
defaultScriptDefinitionClasspath,
|
|
||||||
this::class.java.classLoader,
|
this::class.java.classLoader,
|
||||||
scriptResolverEnv,
|
scriptResolverEnv,
|
||||||
messageCollector
|
messageCollector
|
||||||
@@ -47,31 +44,12 @@ class ScriptDefinitionsFromClasspathDiscoverySource(
|
|||||||
|
|
||||||
internal fun discoverScriptTemplatesInClasspath(
|
internal fun discoverScriptTemplatesInClasspath(
|
||||||
classpath: List<File>,
|
classpath: List<File>,
|
||||||
defaultScriptDefinitionClasspath: List<File>,
|
|
||||||
baseClassLoader: ClassLoader,
|
baseClassLoader: ClassLoader,
|
||||||
scriptResolverEnv: Map<String, Any?>,
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector
|
||||||
): Sequence<KotlinScriptDefinition> = buildSequence {
|
): Sequence<KotlinScriptDefinition> = buildSequence {
|
||||||
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
|
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
|
||||||
val classLoader by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
val loader = LazyClasspathWithClassLoader(baseClassLoader) { classpath }
|
||||||
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun SequenceBuilder<KotlinScriptDefinition>.yieldAllDirDepDefinitions(
|
|
||||||
directoryBasedDependency: File,
|
|
||||||
foundDefinitionClasses: List<Pair<String, ByteArray>>
|
|
||||||
) {
|
|
||||||
val dependencyClasspath = listOf(directoryBasedDependency) + defaultScriptDefinitionClasspath
|
|
||||||
val dependencyClassLoader =
|
|
||||||
URLClassLoader(dependencyClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
|
||||||
foundDefinitionClasses.forEach { (definitionName, definitionClassBytes) ->
|
|
||||||
loadScriptDefinition(
|
|
||||||
definitionClassBytes, definitionName, dependencyClasspath, { dependencyClassLoader }, scriptResolverEnv, messageCollector
|
|
||||||
)?.also {
|
|
||||||
yield(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for jar files the definition class is expected in the same jar as the discovery file
|
// for jar files the definition class is expected in the same jar as the discovery file
|
||||||
// in case of directories, the class output may come separate from the resources, so some candidates should be deffered and processed later
|
// in case of directories, the class output may come separate from the resources, so some candidates should be deffered and processed later
|
||||||
@@ -81,25 +59,22 @@ internal fun discoverScriptTemplatesInClasspath(
|
|||||||
try {
|
try {
|
||||||
when {
|
when {
|
||||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||||
val jar = JarFile(dep)
|
JarFile(dep).use { jar ->
|
||||||
if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) {
|
if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) {
|
||||||
for (template in jar.entries()) {
|
val definitionNames = jar.entries().asSequence().mapNotNull {
|
||||||
if (!template.isDirectory && template.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) {
|
if (it.isDirectory || !it.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) null
|
||||||
val templateClassName = template.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH)
|
else it.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH)
|
||||||
val templateClass = jar.getJarEntry("${templateClassName.replace('.', '/')}.class")
|
}.toList()
|
||||||
if (templateClass == null) {
|
val (loadedDefinitions, _, notFoundClasses) =
|
||||||
messageCollector.report(
|
definitionNames.partitionLoadJarDefinitions(jar, loader, scriptResolverEnv, messageCollector)
|
||||||
CompilerMessageSeverity.WARNING,
|
if (notFoundClasses.isNotEmpty()) {
|
||||||
"Configure scripting: class not found $templateClassName"
|
messageCollector.report(
|
||||||
)
|
CompilerMessageSeverity.STRONG_WARNING,
|
||||||
} else {
|
"Configure scripting: unable to find script definitions [${notFoundClasses.joinToString(", ")}]"
|
||||||
loadScriptDefinition(
|
)
|
||||||
jar.getInputStream(templateClass).readBytes(),
|
}
|
||||||
templateClassName, classpath, { classLoader }, scriptResolverEnv, messageCollector
|
loadedDefinitions.forEach {
|
||||||
)?.also {
|
yield(it)
|
||||||
yield(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,10 +83,12 @@ internal fun discoverScriptTemplatesInClasspath(
|
|||||||
defferedDirDependencies.add(dep) // there is no way to know that the dependency is fully "used" so we add it to the list anyway
|
defferedDirDependencies.add(dep) // there is no way to know that the dependency is fully "used" so we add it to the list anyway
|
||||||
val discoveryDir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH)
|
val discoveryDir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH)
|
||||||
if (discoveryDir.isDirectory) {
|
if (discoveryDir.isDirectory) {
|
||||||
val foundDefinitionClasses = discoveryDir.listFiles().map { it.name }.partitionIntoExistingDefinitions(dep, defferedDefinitionCandidates)
|
val (foundDefinitionClasses, _, notFoundDefinitions) = discoveryDir.listFiles().map { it.name }
|
||||||
if (foundDefinitionClasses.isNotEmpty()) {
|
.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||||
yieldAllDirDepDefinitions(dep, foundDefinitionClasses)
|
foundDefinitionClasses.forEach {
|
||||||
|
yield(it)
|
||||||
}
|
}
|
||||||
|
defferedDefinitionCandidates.addAll(notFoundDefinitions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -123,44 +100,28 @@ internal fun discoverScriptTemplatesInClasspath(
|
|||||||
messageCollector.report(CompilerMessageSeverity.WARNING, "Configure scripting: unable to process classpath entry $dep: $e")
|
messageCollector.report(CompilerMessageSeverity.WARNING, "Configure scripting: unable to process classpath entry $dep: $e")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var remainingDefinitionCandidates = defferedDefinitionCandidates
|
var remainingDefinitionCandidates: List<String> = defferedDefinitionCandidates
|
||||||
for (dir in defferedDirDependencies) {
|
for (dep in defferedDirDependencies) {
|
||||||
if (remainingDefinitionCandidates.isEmpty()) break
|
if (remainingDefinitionCandidates.isEmpty()) break
|
||||||
try {
|
try {
|
||||||
val notFoundDefinitionCandidates = ArrayList<String>()
|
val (foundDefinitionClasses, notFoundDefinitions) =
|
||||||
val foundDefinitionClasses = remainingDefinitionCandidates.partitionIntoExistingDefinitions(dir, notFoundDefinitionCandidates)
|
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||||
if (foundDefinitionClasses.isNotEmpty()) {
|
foundDefinitionClasses.forEach {
|
||||||
remainingDefinitionCandidates = notFoundDefinitionCandidates
|
yield(it)
|
||||||
yieldAllDirDepDefinitions(dir, foundDefinitionClasses)
|
|
||||||
}
|
}
|
||||||
|
remainingDefinitionCandidates = notFoundDefinitions
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
messageCollector.report(CompilerMessageSeverity.WARNING, "Configure scripting: unable to process classpath entry $dir: $e")
|
messageCollector.report(CompilerMessageSeverity.WARNING, "Configure scripting: unable to process classpath entry $dep: $e")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (remainingDefinitionCandidates.isNotEmpty()) {
|
if (remainingDefinitionCandidates.isNotEmpty()) {
|
||||||
messageCollector.report(
|
messageCollector.report(
|
||||||
CompilerMessageSeverity.WARNING,
|
CompilerMessageSeverity.STRONG_WARNING,
|
||||||
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
|
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<String>.partitionIntoExistingDefinitions(
|
|
||||||
directoryBasedDependency: File,
|
|
||||||
notFoundDefinitionCandidates: ArrayList<String>
|
|
||||||
): List<Pair<String, ByteArray>> {
|
|
||||||
val foundDefinitionClasses = ArrayList<Pair<String, ByteArray>>() // fqn -> file contents
|
|
||||||
for (discoveryFileCandidate in this) {
|
|
||||||
val file = File(directoryBasedDependency, "${discoveryFileCandidate.replace('.', '/')}.class")
|
|
||||||
if (file.exists() && file.isFile) {
|
|
||||||
foundDefinitionClasses.add(discoveryFileCandidate to file.readBytes())
|
|
||||||
} else {
|
|
||||||
notFoundDefinitionCandidates.add(discoveryFileCandidate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundDefinitionClasses
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun loadScriptTemplatesFromClasspath(
|
internal fun loadScriptTemplatesFromClasspath(
|
||||||
scriptTemplates: List<String>,
|
scriptTemplates: List<String>,
|
||||||
classpath: List<File>,
|
classpath: List<File>,
|
||||||
@@ -168,65 +129,45 @@ internal fun loadScriptTemplatesFromClasspath(
|
|||||||
baseClassLoader: ClassLoader,
|
baseClassLoader: ClassLoader,
|
||||||
scriptResolverEnv: Map<String, Any?>,
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector
|
||||||
): Sequence<KotlinScriptDefinition> = buildSequence {
|
): Sequence<KotlinScriptDefinition> =
|
||||||
val templatesLeftToFind = ArrayList<String>()
|
if (scriptTemplates.isEmpty()) emptySequence()
|
||||||
// trying the direct classloading from baseClassloader first, since this is the most performant variant
|
else buildSequence {
|
||||||
for (template in scriptTemplates) {
|
// trying the direct classloading from baseClassloader first, since this is the most performant variant
|
||||||
val def = loadScriptDefinition(baseClassLoader, template, scriptResolverEnv, messageCollector)
|
val (initialLoadedDefinitions, initialNotFoundTemplates) = scriptTemplates.partitionMapNotNull {
|
||||||
if (def == null) {
|
loadScriptDefinition(baseClassLoader, it, scriptResolverEnv, messageCollector)
|
||||||
templatesLeftToFind.add(template)
|
|
||||||
} else {
|
|
||||||
yield(def!!)
|
|
||||||
}
|
}
|
||||||
}
|
initialLoadedDefinitions.forEach {
|
||||||
// then searching the remaining templates in the supplied classpath
|
yield(it)
|
||||||
if (templatesLeftToFind.isNotEmpty()) {
|
|
||||||
val templateClasspath by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
|
||||||
classpath + dependenciesClasspath
|
|
||||||
}
|
|
||||||
val classLoader by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
|
||||||
URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
|
||||||
}
|
}
|
||||||
|
// then searching the remaining templates in the supplied classpath
|
||||||
|
|
||||||
|
var remainingTemplates = initialNotFoundTemplates
|
||||||
|
val classpathAndLoader = LazyClasspathWithClassLoader(baseClassLoader) { classpath + dependenciesClasspath }
|
||||||
for (dep in classpath) {
|
for (dep in classpath) {
|
||||||
|
if (remainingTemplates.isEmpty()) break
|
||||||
|
|
||||||
try {
|
try {
|
||||||
when {
|
val (loadedDefinitions, _, notFoundTemplates) = when {
|
||||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||||
val jar = JarFile(dep)
|
JarFile(dep).use { jar ->
|
||||||
for (templateClassName in templatesLeftToFind) {
|
remainingTemplates.partitionLoadJarDefinitions(jar, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||||
val templateClassEntry = jar.getJarEntry("${templateClassName.replace('.', '/')}.class")
|
|
||||||
if (templateClassEntry != null) {
|
|
||||||
loadScriptDefinition(
|
|
||||||
jar.getInputStream(templateClassEntry).readBytes(),
|
|
||||||
templateClassName, templateClasspath, { classLoader }, scriptResolverEnv, messageCollector
|
|
||||||
)?.let {
|
|
||||||
templatesLeftToFind.remove(templateClassName)
|
|
||||||
yield(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dep.isDirectory -> {
|
dep.isDirectory -> {
|
||||||
for (templateClassName in scriptTemplates) {
|
remainingTemplates.partitionLoadDirDefinitions(dep, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||||
val templateClassFile = File(dep, "${templateClassName.replace('.', '/')}.class")
|
|
||||||
if (templateClassFile.exists()) {
|
|
||||||
loadScriptDefinition(
|
|
||||||
templateClassFile.readBytes(),
|
|
||||||
templateClassName, templateClasspath, { classLoader }, scriptResolverEnv, messageCollector
|
|
||||||
)?.let {
|
|
||||||
templatesLeftToFind.remove(templateClassName)
|
|
||||||
yield(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
||||||
messageCollector.report(
|
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
|
||||||
CompilerMessageSeverity.LOGGING,
|
DefinitionsLoadPartitionResult(listOf(), listOf(), remainingTemplates)
|
||||||
"Configure scripting: Unknown classpath entry $dep"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (loadedDefinitions.isNotEmpty()) {
|
||||||
|
loadedDefinitions.forEach {
|
||||||
|
yield(it)
|
||||||
|
}
|
||||||
|
remainingTemplates = notFoundTemplates
|
||||||
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
messageCollector.report(
|
messageCollector.report(
|
||||||
CompilerMessageSeverity.WARNING,
|
CompilerMessageSeverity.WARNING,
|
||||||
@@ -234,20 +175,66 @@ internal fun loadScriptTemplatesFromClasspath(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (remainingTemplates.isNotEmpty()) {
|
||||||
|
messageCollector.report(
|
||||||
|
CompilerMessageSeverity.STRONG_WARNING,
|
||||||
|
"Configure scripting: unable to find script definition classes: ${remainingTemplates.joinToString(", ")}"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (templatesLeftToFind.isNotEmpty()) {
|
|
||||||
messageCollector.report(
|
private data class DefinitionsLoadPartitionResult(
|
||||||
CompilerMessageSeverity.WARNING,
|
val loaded: List<KotlinScriptDefinition>,
|
||||||
"Configure scripting: unable to find script definition classes: $templatesLeftToFind"
|
val notLoaded: List<String>,
|
||||||
)
|
val notFound: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
private inline fun List<String>.partitionLoadDefinitions(
|
||||||
|
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||||
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
|
messageCollector: MessageCollector,
|
||||||
|
getBytes: (String) -> ByteArray?
|
||||||
|
): DefinitionsLoadPartitionResult {
|
||||||
|
val loaded = ArrayList<KotlinScriptDefinition>()
|
||||||
|
val notLoaded = ArrayList<String>()
|
||||||
|
val notFound = ArrayList<String>()
|
||||||
|
for (definitionName in this) {
|
||||||
|
val classBytes = getBytes(definitionName)
|
||||||
|
val definition = classBytes?.let {
|
||||||
|
loadScriptDefinition(it, definitionName, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
definition != null -> loaded.add(definition)
|
||||||
|
classBytes != null -> notLoaded.add(definitionName)
|
||||||
|
else -> notFound.add(definitionName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return DefinitionsLoadPartitionResult(loaded, notLoaded, notFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<String>.partitionLoadJarDefinitions(
|
||||||
|
jar: JarFile,
|
||||||
|
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||||
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
|
messageCollector: MessageCollector
|
||||||
|
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||||
|
jar.getJarEntry("${definitionName.replace('.', '/')}.class")?.let { jar.getInputStream(it).readBytes() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<String>.partitionLoadDirDefinitions(
|
||||||
|
dir: File,
|
||||||
|
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||||
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
|
messageCollector: MessageCollector
|
||||||
|
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||||
|
File(dir, "${definitionName.replace('.', '/')}.class").takeIf { it.exists() && it.isFile }?.readBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadScriptDefinition(
|
private fun loadScriptDefinition(
|
||||||
templateClassBytes: ByteArray,
|
templateClassBytes: ByteArray,
|
||||||
templateClassName: String,
|
templateClassName: String,
|
||||||
templateClasspath: List<File>,
|
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||||
getClassLoader: () -> ClassLoader,
|
|
||||||
scriptResolverEnv: Map<String, Any?>,
|
scriptResolverEnv: Map<String, Any?>,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector
|
||||||
): KotlinScriptDefinition? {
|
): KotlinScriptDefinition? {
|
||||||
@@ -255,29 +242,26 @@ private fun loadScriptDefinition(
|
|||||||
for (ann in anns) {
|
for (ann in anns) {
|
||||||
var def: KotlinScriptDefinition? = null
|
var def: KotlinScriptDefinition? = null
|
||||||
if (ann.name == KotlinScript::class.simpleName) {
|
if (ann.name == KotlinScript::class.simpleName) {
|
||||||
def = LazyScriptDefinitionFromDiscoveredClass(anns, templateClassName, templateClasspath, messageCollector)
|
def = LazyScriptDefinitionFromDiscoveredClass(anns, templateClassName, classpathAndLoader.classpath, messageCollector)
|
||||||
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
|
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
|
||||||
val templateClass = getClassLoader().loadClass(templateClassName).kotlin
|
val templateClass = classpathAndLoader.classLoader.loadClass(templateClassName).kotlin
|
||||||
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, templateClasspath)
|
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, classpathAndLoader.classpath)
|
||||||
}
|
}
|
||||||
if (def != null) {
|
if (def != null) {
|
||||||
messageCollector.report(
|
messageCollector.report(
|
||||||
CompilerMessageSeverity.LOGGING,
|
CompilerMessageSeverity.LOGGING,
|
||||||
"Configure scripting: Added template $templateClassName from $templateClasspath"
|
"Configure scripting: Added template $templateClassName from ${classpathAndLoader.classpath}"
|
||||||
)
|
)
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messageCollector.report(
|
messageCollector.report(
|
||||||
CompilerMessageSeverity.WARNING,
|
CompilerMessageSeverity.STRONG_WARNING,
|
||||||
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
|
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JarFile.extractClasspath(defaultClasspath: List<File>): List<File> =
|
|
||||||
manifest.mainAttributes.getValue("Class-Path")?.split(" ")?.map(::File) ?: defaultClasspath
|
|
||||||
|
|
||||||
private fun loadScriptDefinition(
|
private fun loadScriptDefinition(
|
||||||
classLoader: ClassLoader,
|
classLoader: ClassLoader,
|
||||||
template: String,
|
template: String,
|
||||||
@@ -306,12 +290,34 @@ private fun loadScriptDefinition(
|
|||||||
)
|
)
|
||||||
return def
|
return def
|
||||||
} catch (ex: ClassNotFoundException) {
|
} catch (ex: ClassNotFoundException) {
|
||||||
// return null
|
// not found - not an error, return null
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
// other exceptions - might be an error
|
||||||
messageCollector.report(
|
messageCollector.report(
|
||||||
CompilerMessageSeverity.ERROR,
|
CompilerMessageSeverity.STRONG_WARNING,
|
||||||
"Error processing script definition template $template: ${ex.message}"
|
"Error on loading script definition $template: ${ex.message}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LazyClasspathWithClassLoader(baseClassLoader: ClassLoader, getClasspath: () -> List<File>) {
|
||||||
|
val classpath by lazy(LazyThreadSafetyMode.PUBLICATION) { getClasspath() }
|
||||||
|
val classLoader by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
|
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T, R> Iterable<T>.partitionMapNotNull(fn: (T) -> R?): Pair<List<R>, List<T>> {
|
||||||
|
val mapped = ArrayList<R>()
|
||||||
|
val failed = ArrayList<T>()
|
||||||
|
for (v in this) {
|
||||||
|
val r = fn(v)
|
||||||
|
if (r != null) {
|
||||||
|
mapped.add(r)
|
||||||
|
} else {
|
||||||
|
failed.add(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapped to failed
|
||||||
|
}
|
||||||
|
|||||||
+4
-8
@@ -55,7 +55,6 @@ class ScriptingCompilerConfigurationExtension(val project: MockProject) : Compil
|
|||||||
JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES,
|
JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES,
|
||||||
ScriptDefinitionsFromClasspathDiscoverySource(
|
ScriptDefinitionsFromClasspathDiscoverySource(
|
||||||
configuration.jvmClasspathRoots,
|
configuration.jvmClasspathRoots,
|
||||||
emptyList(),
|
|
||||||
configuration.get(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION) ?: emptyMap(),
|
configuration.get(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION) ?: emptyMap(),
|
||||||
messageCollector
|
messageCollector
|
||||||
)
|
)
|
||||||
@@ -78,13 +77,10 @@ fun configureScriptDefinitions(
|
|||||||
messageCollector: MessageCollector,
|
messageCollector: MessageCollector,
|
||||||
scriptResolverEnv: Map<String, Any?>
|
scriptResolverEnv: Map<String, Any?>
|
||||||
) {
|
) {
|
||||||
val classpath = configuration.jvmClasspathRoots
|
|
||||||
// TODO: consider using escaping to allow kotlin escaped names in class names
|
// TODO: consider using escaping to allow kotlin escaped names in class names
|
||||||
if (scriptTemplates.isNotEmpty()) {
|
val templatesFromClasspath = loadScriptTemplatesFromClasspath(
|
||||||
loadScriptTemplatesFromClasspath(scriptTemplates, classpath, emptyList(), baseClassloader, scriptResolverEnv, messageCollector)
|
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, scriptResolverEnv, messageCollector
|
||||||
.forEach {
|
)
|
||||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, it)
|
configuration.addAll(JVMConfigurationKeys.SCRIPT_DEFINITIONS, templatesFromClasspath.toList())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import kotlin.script.templates.*
|
import kotlin.script.templates.*
|
||||||
|
|
||||||
@ScriptTemplateDefinition
|
@ScriptTemplateAdditionalCompilerArguments(["-v"])
|
||||||
abstract class TestScriptWithOtherAnnotation
|
abstract class TestScriptWithOtherAnnotation
|
||||||
|
|||||||
+28
-13
@@ -80,10 +80,10 @@ class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testLazyScriptDefinition() {
|
fun testLazyScriptDefinitionDiscovery() {
|
||||||
|
|
||||||
// Two tests in one function: the discovery code separately, and as a part of regular compilation
|
// Three tests in one function: the direct loading, the discovery code separately, and as a part of regular compilation
|
||||||
// tests are combined to avoid double compilation of script definition modules
|
// tests are combined to avoid multiple compilation of script definition modules
|
||||||
|
|
||||||
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/defs")
|
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/defs")
|
||||||
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
|
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
|
||||||
@@ -105,15 +105,33 @@ class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
|||||||
"Compilation of script definitions failed: $messageCollector"
|
"Compilation of script definitions failed: $messageCollector"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messageCollector.clear()
|
||||||
|
|
||||||
|
loadScriptTemplatesFromClasspath(
|
||||||
|
listOf("TestScriptWithReceivers", "TestScriptWithSimpleEnvVars"),
|
||||||
|
listOf(defsOut), emptyList(), this::class.java.classLoader, emptyMap(), messageCollector
|
||||||
|
).toList()
|
||||||
|
|
||||||
|
for (def in defClasses) {
|
||||||
|
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
|
||||||
|
"Missing messages from loading sequence (should contain \"Added template $def\"):\n$messageCollector"
|
||||||
|
}
|
||||||
|
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
|
||||||
|
"Unexpected messages from loading sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messageCollector.clear()
|
||||||
|
|
||||||
|
// chacking lazy discovery
|
||||||
|
|
||||||
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
|
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
|
||||||
for (def in defClasses) {
|
for (def in defClasses) {
|
||||||
File(templatesDir, def).createNewFile()
|
File(templatesDir, def).createNewFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
messageCollector.clear()
|
|
||||||
|
|
||||||
val lazyDefsSeq =
|
val lazyDefsSeq =
|
||||||
discoverScriptTemplatesInClasspath(listOf(defsOut), emptyList(), this::class.java.classLoader, emptyMap(), messageCollector)
|
discoverScriptTemplatesInClasspath(listOf(defsOut), this::class.java.classLoader, emptyMap(), messageCollector)
|
||||||
|
|
||||||
assertTrue(messageCollector.messages.isEmpty()) {
|
assertTrue(messageCollector.messages.isEmpty()) {
|
||||||
"Unexpected messages from discovery sequence (should be empty):\n$messageCollector"
|
"Unexpected messages from discovery sequence (should be empty):\n$messageCollector"
|
||||||
@@ -122,14 +140,10 @@ class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
|||||||
val lazyDefs = lazyDefsSeq.toList()
|
val lazyDefs = lazyDefsSeq.toList()
|
||||||
|
|
||||||
for (def in defClasses) {
|
for (def in defClasses) {
|
||||||
assertTrue(
|
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
|
||||||
messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }
|
|
||||||
) {
|
|
||||||
"Missing messages from discovery sequence (should contain \"Added template $def\"):\n$messageCollector"
|
"Missing messages from discovery sequence (should contain \"Added template $def\"):\n$messageCollector"
|
||||||
}
|
}
|
||||||
assertTrue(
|
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
|
||||||
messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }
|
|
||||||
) {
|
|
||||||
"Unexpected messages from discovery sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
|
"Unexpected messages from discovery sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +197,7 @@ class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
|||||||
|
|
||||||
messageCollector.clear()
|
messageCollector.clear()
|
||||||
|
|
||||||
discoverScriptTemplatesInClasspath(listOf(defsOut), emptyList(), this::class.java.classLoader, emptyMap(), messageCollector).toList()
|
discoverScriptTemplatesInClasspath(listOf(defsOut), this::class.java.classLoader, emptyMap(), messageCollector).toList()
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
messageCollector.messages.isNotEmpty()
|
messageCollector.messages.isNotEmpty()
|
||||||
@@ -194,6 +208,7 @@ class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestMessageCollector : MessageCollector {
|
class TestMessageCollector : MessageCollector {
|
||||||
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation?)
|
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation?)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user