Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
168 lines (150 sloc) 7.58 KB
/*
* Copyright 2010-2020 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.script.examples.simpleMainKts
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.script.examples.simpleMainKts.impl.IvyResolver
import org.jetbrains.kotlin.script.examples.simpleMainKts.impl.resolveFromAnnotations
import org.jetbrains.kotlin.script.util.CompilerOptions
import org.jetbrains.kotlin.script.util.DependsOn
import org.jetbrains.kotlin.script.util.Import
import org.jetbrains.kotlin.script.util.Repository
import java.io.File
import java.net.JarURLConnection
import java.net.URL
import java.security.MessageDigest
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.CompoundDependenciesResolver
import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver
import kotlin.script.experimental.host.FileBasedScriptSource
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvmhost.CompiledScriptJarsCache
@Suppress("unused")
@KotlinScript(
fileExtension = "smain.kts",
compilationConfiguration = SimpleMainKtsScriptDefinition::class,
evaluationConfiguration = MainKtsEvaluationConfiguration::class
)
abstract class SimpleMainKtsScript(val args: Array<String>)
const val COMPILED_SCRIPTS_CACHE_DIR_ENV_VAR = "KOTLIN_SIMPLE_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR"
const val COMPILED_SCRIPTS_CACHE_DIR_PROPERTY = "kotlin.simple.main.kts.compiled.scripts.cache.dir"
class SimpleMainKtsScriptDefinition : ScriptCompilationConfiguration(
{
defaultImports(DependsOn::class, Repository::class, Import::class, CompilerOptions::class)
jvm {
val keyResource = SimpleMainKtsScriptDefinition::class.java.name.replace('.', '/') + ".class"
val thisJarFile = SimpleMainKtsScriptDefinition::class.java.classLoader.getResource(keyResource)?.toContainingJarOrNull()
if (thisJarFile != null) {
dependenciesFromClassContext(
SimpleMainKtsScriptDefinition::class,
thisJarFile.name, "kotlin-stdlib", "kotlin-reflect", "kotlin-script-util"
)
} else {
dependenciesFromClassContext(SimpleMainKtsScriptDefinition::class, wholeClasspath = true)
}
}
refineConfiguration {
onAnnotations(DependsOn::class, Repository::class, Import::class, CompilerOptions::class, handler = MainKtsConfigurator())
}
ide {
acceptedLocations(ScriptAcceptedLocation.Everywhere)
}
hostConfiguration(ScriptingHostConfiguration {
jvm {
val cacheExtSetting = System.getProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY)
?: System.getenv(COMPILED_SCRIPTS_CACHE_DIR_ENV_VAR)
val cacheBaseDir = when {
cacheExtSetting == null -> System.getProperty("java.io.tmpdir")
?.let(::File)?.takeIf { it.exists() && it.isDirectory }
?.let { File(it, "main.kts.compiled.cache").apply { mkdir() } }
cacheExtSetting.isBlank() -> null
else -> File(cacheExtSetting)
}?.takeIf { it.exists() && it.isDirectory }
if (cacheBaseDir != null)
compilationCache(
CompiledScriptJarsCache { script, scriptCompilationConfiguration ->
File(cacheBaseDir, compiledScriptUniqueName(script, scriptCompilationConfiguration) + ".jar")
}
)
}
})
})
object MainKtsEvaluationConfiguration : ScriptEvaluationConfiguration(
{
scriptsInstancesSharing(true)
refineConfigurationBeforeEvaluate(::configureConstructorArgsFromMainArgs)
}
)
fun configureConstructorArgsFromMainArgs(context: ScriptEvaluationConfigurationRefinementContext): ResultWithDiagnostics<ScriptEvaluationConfiguration> {
val mainArgs = context.evaluationConfiguration[ScriptEvaluationConfiguration.jvm.mainArguments]
val res = if (context.evaluationConfiguration[ScriptEvaluationConfiguration.constructorArgs] == null && mainArgs != null) {
context.evaluationConfiguration.with {
constructorArgs(mainArgs)
}
} else context.evaluationConfiguration
return res.asSuccess()
}
class MainKtsConfigurator : RefineScriptCompilationConfigurationHandler {
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), IvyResolver())
override operator fun invoke(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> =
processAnnotations(context)
private fun processAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
val diagnostics = arrayListOf<ScriptDiagnostic>()
val annotations = context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
val scriptBaseDir = (context.script as? FileBasedScriptSource)?.file?.parentFile
val importedSources = annotations.flatMap {
(it as? Import)?.paths?.map { sourceName ->
FileScriptSource(scriptBaseDir?.resolve(sourceName) ?: File(sourceName))
} ?: emptyList()
}
val compileOptions = annotations.flatMap {
(it as? CompilerOptions)?.options?.toList() ?: emptyList()
}
val resolveResult = try {
runBlocking {
resolveFromAnnotations(resolver, annotations.filter { it is DependsOn || it is Repository })
}
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(*diagnostics.toTypedArray(), e.asDiagnostics(path = context.script.locationId))
}
return resolveResult.onSuccess { resolvedClassPath ->
ScriptCompilationConfiguration(context.compilationConfiguration) {
updateClasspath(resolvedClassPath)
if (importedSources.isNotEmpty()) importScripts.append(importedSources)
if (compileOptions.isNotEmpty()) compilerOptions.append(compileOptions)
}.asSuccess()
}
}
}
private fun compiledScriptUniqueName(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): String {
val digestWrapper = MessageDigest.getInstance("MD5")
digestWrapper.update(script.text.toByteArray())
scriptCompilationConfiguration.notTransientData.entries
.sortedBy { it.key.name }
.forEach {
digestWrapper.update(it.key.name.toByteArray())
digestWrapper.update(it.value.toString().toByteArray())
}
return digestWrapper.digest().toHexString()
}
private fun ByteArray.toHexString(): String = joinToString("", transform = { "%02x".format(it) })
internal fun URL.toContainingJarOrNull(): File? =
if (protocol == "jar") {
(openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
} else null
internal fun URL.toFileOrNull() =
try {
File(toURI())
} catch (e: IllegalArgumentException) {
null
} catch (e: java.net.URISyntaxException) {
null
} ?: run {
if (protocol != "file") null
else File(file)
}
You can’t perform that action at this time.