Introduce 'kotlin' script for running programs

'kotlin' is to 'kotlinc' what 'java' is to 'javac'. However it will support
much more: running class by name, jar, scripts, expressions, REPL
This commit is contained in:
Alexander Udalov
2015-06-14 18:26:01 +03:00
parent 3c4b2994a9
commit 4dc29bf0b2
13 changed files with 429 additions and 33 deletions
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
</component>
</module>
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.runner
import java.io.File
import java.net.URL
class Classpath {
private val classpath = arrayListOf<URL>()
fun add(paths: String) {
for (path in paths.split(File.pathSeparator)) {
classpath.add(File(path).absoluteFile.toURI().toURL())
}
}
fun getURLs(): Array<URL> {
return classpath.toTypedArray()
}
}
@@ -0,0 +1,145 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.runner
import java.io.File
import java.io.FileNotFoundException
import java.util.*
public object Main {
private val KOTLIN_HOME: File
init {
val home = System.getProperty("kotlin.home")
if (home == null) {
System.err.println("error: no kotlin.home system property was passed")
System.exit(1)
}
KOTLIN_HOME = File(home)
}
private fun run(args: Array<String>) {
val classpath = Classpath()
var runner: Runner? = null
var collectingArguments = false
val arguments = arrayListOf<String>()
classpath.add(".")
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()) {
throw RunnerException("argument expected to $arg")
}
return args[i]
}
if ("-help" == arg || "-h" == arg) {
printUsageAndExit()
}
else if ("-version" == arg) {
printVersionAndExit()
}
else if ("-classpath" == arg || "-cp" == arg) {
classpath.add(next())
}
else if ("-expression" == arg || "-e" == arg) {
runner = ExpressionRunner(next())
collectingArguments = true
}
else if (arg.startsWith("-")) {
throw RunnerException("unsupported argument: $arg")
}
else if (arg.endsWith(".jar")) {
runner = JarRunner(arg)
collectingArguments = true
}
else if (arg.endsWith(".kts")) {
runner = ScriptRunner(arg)
collectingArguments = true
}
else {
runner = MainClassRunner(arg)
collectingArguments = true
}
i++
}
classpath.add(KOTLIN_HOME.toString() + "/lib/kotlin-runtime.jar")
// TODO: provide a way to disable including kotlin-reflect.jar to the classpath
classpath.add(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
if (runner == null) {
runner = ReplRunner()
}
runner.run(classpath, arguments)
}
@jvmStatic
public fun main(args: Array<String>) {
try {
run(args)
}
catch (e: RunnerException) {
System.err.println("error: " + e.getMessage())
System.exit(1)
}
}
private fun printUsageAndExit() {
println("""kotlin: run Kotlin programs, scripts or REPL.
Usage: kotlin <options> <command> <arguments>
where command may be one of:
foo.Bar Runs the 'main' function from the class with the given qualified name
app.jar Runs the given JAR file as 'java -jar' would do
(-classpath argument is ignored and no Kotlin runtime is added to the classpath)
""" +
// script.kts Compiles and runs the given script
// -expression (-e) '2+2' Evaluates the expression and prints the result
"""and possible options include:
-classpath (-cp) <path> Paths where to find user class files
-Dname=value Set a system JVM property
-J<option> Pass an option directly to JVM
-version Display Kotlin version
-help (-h) Print a synopsis of options
""")
System.exit(0)
}
private fun printVersionAndExit() {
val version = try {
Scanner(File(KOTLIN_HOME, "build.txt")).nextLine()
}
catch (e: FileNotFoundException) {
throw RunnerException("no build.txt was found at home=$KOTLIN_HOME")
}
println("Kotlin version " + version + " (JRE " + System.getProperty("java.runtime.version") + ")")
System.exit(0)
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.runner
interface Runner {
fun run(classpath: Classpath, arguments: List<String>)
}
@@ -0,0 +1,119 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.runner
import java.io.File
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier
import java.net.URLClassLoader
import java.util.jar.Attributes
import java.util.jar.JarFile
class RunnerException(message: String) : RuntimeException(message)
abstract class AbstractRunner : Runner {
protected abstract val className: String
protected abstract fun createClassLoader(classpath: Classpath): ClassLoader
override fun run(classpath: Classpath, arguments: List<String>) {
val classLoader = createClassLoader(classpath)
val mainClass = try {
classLoader.loadClass(className)
}
catch (e: ClassNotFoundException) {
throw RunnerException("could not find or load main class $className")
}
val main = try {
mainClass.getDeclaredMethod("main", Array<String>::class.java)
}
catch (e: NoSuchMethodException) {
throw RunnerException("'main' method not found in class $className")
}
if (!Modifier.isStatic(main.modifiers)) {
throw RunnerException(
"'main' method of class $className is not static. " +
"Please ensure that 'main' is either a top level Kotlin function, " +
"a member function annotated with @JvmStatic, or a static Java method"
)
}
try {
main.invoke(null, arguments.toTypedArray())
}
catch (e: IllegalAccessException) {
throw RunnerException("'main' method of class $className is not public")
}
catch (e: InvocationTargetException) {
throw e.targetException
}
}
}
class MainClassRunner(override val className: String) : AbstractRunner() {
override fun createClassLoader(classpath: Classpath): ClassLoader =
URLClassLoader(classpath.getURLs(), null)
}
class JarRunner(private val path: String) : AbstractRunner() {
override val className: String =
try {
val jar = JarFile(path)
try {
jar.manifest.mainAttributes.getValue(Attributes.Name.MAIN_CLASS)
}
finally {
jar.close()
}
}
catch (e: IOException) {
throw RunnerException("could not read manifest from " + path + ": " + e.getMessage())
}
?: throw RunnerException("no Main-Class entry found in manifest in $path")
override fun createClassLoader(classpath: Classpath): ClassLoader {
// 'kotlin *.jar' ignores the passed classpath as 'java -jar' does
// TODO: warn on non-empty classpath?
return URLClassLoader(arrayOf(File(path).toURI().toURL()), null)
}
}
class ReplRunner : Runner {
override fun run(classpath: Classpath, arguments: List<String>) {
// TODO: run REPL instead
throw RunnerException("please specify at least one name or file to run")
}
}
class ScriptRunner(private val path: String) : Runner {
override fun run(classpath: Classpath, arguments: List<String>) {
// TODO
throw RunnerException("running Kotlin scripts is not yet supported")
}
}
class ExpressionRunner(private val code: String) : Runner {
override fun run(classpath: Classpath, arguments: List<String>) {
// TODO
throw RunnerException("evaluating expressions is not yet supported")
}
}