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:
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user