Implement repl, script and expressions evaluation support in kotlin runner
This commit is contained in:
@@ -36,19 +36,18 @@ object Main {
|
||||
|
||||
private fun run(args: Array<String>) {
|
||||
val classpath = arrayListOf<URL>()
|
||||
val compilerClasspath = arrayListOf<URL>()
|
||||
var runner: Runner? = null
|
||||
var collectingArguments = false
|
||||
var collectingExpressions = false
|
||||
var needsCompiler = false
|
||||
val arguments = arrayListOf<String>()
|
||||
val expressions = arrayListOf<String>()
|
||||
var noReflect = false
|
||||
|
||||
var i = 0
|
||||
while (i < args.size) {
|
||||
val arg = args[i]
|
||||
if (collectingArguments) {
|
||||
arguments.add(arg)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
fun next(): String {
|
||||
if (++i == args.size) {
|
||||
@@ -57,6 +56,22 @@ object Main {
|
||||
return args[i]
|
||||
}
|
||||
|
||||
if (collectingExpressions) {
|
||||
if ("-expression" == arg || "-e" == arg) {
|
||||
expressions.add(next())
|
||||
i++
|
||||
continue
|
||||
} else {
|
||||
collectingArguments = true
|
||||
}
|
||||
}
|
||||
|
||||
if (collectingArguments) {
|
||||
arguments.add(arg)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if ("-help" == arg || "-h" == arg) {
|
||||
printUsageAndExit()
|
||||
}
|
||||
@@ -68,9 +83,15 @@ object Main {
|
||||
classpath.addPath(path)
|
||||
}
|
||||
}
|
||||
else if ("-compiler-path" == arg) {
|
||||
for (path in next().split(File.pathSeparator).filter(String::isNotEmpty)) {
|
||||
compilerClasspath.addPath(path)
|
||||
}
|
||||
}
|
||||
else if ("-expression" == arg || "-e" == arg) {
|
||||
runner = ExpressionRunner(next())
|
||||
collectingArguments = true
|
||||
expressions.add(next())
|
||||
collectingExpressions = true
|
||||
needsCompiler = true
|
||||
}
|
||||
else if ("-no-reflect" == arg) {
|
||||
noReflect = true
|
||||
@@ -85,6 +106,7 @@ object Main {
|
||||
else if (arg.endsWith(".kts")) {
|
||||
runner = ScriptRunner(arg)
|
||||
collectingArguments = true
|
||||
needsCompiler = true
|
||||
}
|
||||
else {
|
||||
runner = MainClassRunner(arg)
|
||||
@@ -103,11 +125,20 @@ object Main {
|
||||
classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
|
||||
}
|
||||
|
||||
if (runner == null) {
|
||||
if (expressions.isNotEmpty()) {
|
||||
runner = ExpressionRunner(expressions)
|
||||
} else if (runner == null) {
|
||||
runner = ReplRunner()
|
||||
needsCompiler = true
|
||||
}
|
||||
|
||||
runner.run(classpath, arguments)
|
||||
if (needsCompiler && compilerClasspath.isEmpty()) {
|
||||
findCompilerJar(this::class.java, KOTLIN_HOME.resolve("lib")).forEach {
|
||||
compilerClasspath.add(it.absoluteFile.toURI().toURL())
|
||||
}
|
||||
}
|
||||
|
||||
runner.run(classpath, arguments, compilerClasspath)
|
||||
}
|
||||
|
||||
private fun MutableList<URL>.addPath(path: String) {
|
||||
|
||||
@@ -19,5 +19,9 @@ package org.jetbrains.kotlin.runner
|
||||
import java.net.URL
|
||||
|
||||
interface Runner {
|
||||
fun run(classpath: List<URL>, arguments: List<String>)
|
||||
fun run(
|
||||
classpath: List<URL>,
|
||||
arguments: List<String>,
|
||||
compilerClasspath: List<URL>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.runner
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
fun findCompilerJar(classFromJarInTheSameLocation: Class<*>, kotlinHome: File): List<File> {
|
||||
val baseDir = (tryGetResourcePathForClass(classFromJarInTheSameLocation)?.parentFile ?: kotlinHome).takeIf { it.isDirectory }
|
||||
?: return emptyList()
|
||||
val compilerJars = baseDir.listFiles { f: File ->
|
||||
COMPILER_JARS.any { expected ->
|
||||
f.matchMaybeVersionedFile(expected) && f.extension == "jar"
|
||||
}
|
||||
}?.takeIf { it.size >= COMPILER_JARS.size }?.toList()
|
||||
return compilerJars ?: emptyList()
|
||||
}
|
||||
|
||||
private val COMPILER_JARS = listOf("kotlin-compiler", "kotlin-stdlib", "kotlin-reflect")
|
||||
|
||||
// below is a copy from kotlin.script.experimental.jvm.impl, but we do not want to introduce dependency to that implementation, and
|
||||
// there is no other good place for sharing this functionality yet
|
||||
// TODO: find a good place and put the shared code into it
|
||||
|
||||
internal fun tryGetResourcePathForClass(aClass: Class<*>): File? {
|
||||
val path = "/" + aClass.name.replace('.', '/') + ".class"
|
||||
return getResourceRoot(aClass, path)?.let {
|
||||
File(it).absoluteFile
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResourceRoot(context: Class<*>, path: String): String? {
|
||||
var url: URL? = context.getResource(path)
|
||||
if (url == null) {
|
||||
url = ClassLoader.getSystemResource(path.substring(1))
|
||||
}
|
||||
return if (url != null) extractRoot(url, path) else null
|
||||
}
|
||||
|
||||
private const val JAR_PROTOCOL = "jar"
|
||||
private const val FILE_PROTOCOL = "file"
|
||||
private const val JAR_SEPARATOR = "!/"
|
||||
private const val SCHEME_SEPARATOR = "://"
|
||||
|
||||
private fun extractRoot(resourceURL: URL, resourcePath: String): String? {
|
||||
if (!resourcePath.startsWith('/') || resourcePath.startsWith('\\')) return null
|
||||
|
||||
var resultPath: String? = null
|
||||
val protocol = resourceURL.protocol
|
||||
if (protocol == FILE_PROTOCOL) {
|
||||
val path = resourceURL.toFileOrNull()!!.path
|
||||
val testPath = path.replace('\\', '/')
|
||||
val testResourcePath = resourcePath.replace('\\', '/')
|
||||
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
|
||||
resultPath = path.substring(0, path.length - resourcePath.length)
|
||||
}
|
||||
} else if (protocol == JAR_PROTOCOL) {
|
||||
val paths = splitJarUrl(resourceURL.file)
|
||||
if (paths?.first != null) {
|
||||
resultPath = File(paths.first).canonicalPath
|
||||
}
|
||||
}
|
||||
|
||||
return resultPath?.trimEnd(File.separatorChar)
|
||||
}
|
||||
|
||||
private fun splitJarUrl(url: String): Pair<String, String>? {
|
||||
val pivot = url.indexOf(JAR_SEPARATOR).takeIf { it >= 0 } ?: return null
|
||||
|
||||
val resourcePath = url.substring(pivot + 2)
|
||||
var jarPath = url.substring(0, pivot)
|
||||
|
||||
if (jarPath.startsWith(JAR_PROTOCOL + ":")) {
|
||||
jarPath = jarPath.substring(JAR_PROTOCOL.length + 1)
|
||||
}
|
||||
|
||||
if (jarPath.startsWith(FILE_PROTOCOL)) {
|
||||
try {
|
||||
jarPath = URL(jarPath).toFileOrNull()!!.path.replace('\\', '/')
|
||||
} catch (e: Exception) {
|
||||
jarPath = jarPath.substring(FILE_PROTOCOL.length)
|
||||
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
|
||||
jarPath = jarPath.substring(SCHEME_SEPARATOR.length)
|
||||
} else if (jarPath.startsWith(':')) {
|
||||
jarPath = jarPath.substring(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Pair(jarPath, resourcePath)
|
||||
}
|
||||
|
||||
private fun URL.toFileOrNull() =
|
||||
try {
|
||||
File(toURI())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
} catch (e: java.net.URISyntaxException) {
|
||||
null
|
||||
} ?: run {
|
||||
if (protocol != "file") null
|
||||
else File(file)
|
||||
}
|
||||
|
||||
private fun File.matchMaybeVersionedFile(baseName: String) =
|
||||
name == baseName ||
|
||||
name == baseName.removeSuffix(".jar") || // for classes dirs
|
||||
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
|
||||
@@ -32,7 +32,7 @@ abstract class AbstractRunner : Runner {
|
||||
|
||||
protected abstract fun createClassLoader(classpath: List<URL>): ClassLoader
|
||||
|
||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||
val classLoader = createClassLoader(classpath)
|
||||
|
||||
val mainClass = try {
|
||||
@@ -105,23 +105,58 @@ class JarRunner(private val path: String) : AbstractRunner() {
|
||||
}
|
||||
}
|
||||
|
||||
class ReplRunner : Runner {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
||||
// TODO: run REPL instead
|
||||
throw RunnerException("please specify at least one name or file to run")
|
||||
abstract class RunnerWithCompiler : Runner {
|
||||
|
||||
fun runCompiler(compilerClasspath: List<URL>, arguments: List<String>) {
|
||||
val classLoader =
|
||||
if (arguments.isEmpty()) RunnerWithCompiler::class.java.classLoader
|
||||
else URLClassLoader(compilerClasspath.toTypedArray(), null)
|
||||
val compilerClass = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
|
||||
val mainMethod = compilerClass.getMethod("main", Array<String>::class.java)
|
||||
mainMethod.invoke(null, arguments.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptRunner(private val path: String) : Runner {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
||||
// TODO
|
||||
throw RunnerException("running Kotlin scripts is not yet supported")
|
||||
private fun MutableList<String>.addClasspathArgIfNeeded(classpath: List<URL>) {
|
||||
if (classpath.isNotEmpty()) {
|
||||
add("-cp")
|
||||
add(classpath.map {
|
||||
if (it.protocol == "file") it.path
|
||||
else it.toExternalForm()
|
||||
}.joinToString(File.pathSeparator))
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionRunner(private val code: String) : Runner {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
||||
// TODO
|
||||
throw RunnerException("evaluating expressions is not yet supported")
|
||||
class ReplRunner : RunnerWithCompiler() {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||
val compilerArgs = ArrayList<String>()
|
||||
compilerArgs.addClasspathArgIfNeeded(classpath)
|
||||
runCompiler(compilerClasspath, compilerArgs)
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptRunner(private val path: String) : RunnerWithCompiler() {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||
val compilerArgs = ArrayList<String>().apply {
|
||||
addClasspathArgIfNeeded(classpath)
|
||||
add("-script")
|
||||
add(path)
|
||||
addAll(arguments)
|
||||
}
|
||||
runCompiler(compilerClasspath, compilerArgs)
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionRunner(private val code: List<String>) : RunnerWithCompiler() {
|
||||
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||
val compilerArgs = ArrayList<String>().apply {
|
||||
addClasspathArgIfNeeded(classpath)
|
||||
code.forEach {
|
||||
add("-expression")
|
||||
add(it)
|
||||
}
|
||||
addAll(arguments)
|
||||
}
|
||||
runCompiler(compilerClasspath, compilerArgs)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user