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
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash --posix
# 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.
export KOTLIN_RUNNER=1
"$(dirname "$(which "$0")")"/kotlinc "$@"
+20
View File
@@ -0,0 +1,20 @@
@echo off
rem Copyright 2010-2015 JetBrains s.r.o.
rem
rem Licensed under the Apache License, Version 2.0 (the "License");
rem you may not use this file except in compliance with the License.
rem You may obtain a copy of the License at
rem
rem http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
setlocal
set _KOTLIN_RUNNER=1
call %~dps0kotlinc.bat %*
+14 -16
View File
@@ -9,8 +9,6 @@
# PARTICULAR PURPOSE.
##############################################################################
[ -n "$KOTLIN_COMPILER" ] || KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
cygwin=false;
case "`uname`" in
CYGWIN*) cygwin=true ;;
@@ -34,11 +32,8 @@ if $cygwin; then
KOTLIN_HOME=`cygpath --windows --short-name "$KOTLIN_HOME"`
fi
[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M -noverify"
[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M"
# break out -D and -J options and add them to JAVA_OPTS as well
# so they reach the underlying JVM in time to do some good. The
# -D options will be available as system properties.
declare -a java_args
declare -a kotlin_args
@@ -59,17 +54,20 @@ while [ $# -gt 0 ]; do
esac
done
# reset "$@" to the remaining args
set -- "${kotlin_args[@]}"
if [ -z "$JAVACMD" -a -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then
JAVACMD="$JAVA_HOME/bin/java"
fi
"${JAVACMD:=java}" \
$JAVA_OPTS \
"${java_args[@]}" \
-cp "${KOTLIN_HOME}/lib/kotlin-preloader.jar" \
org.jetbrains.kotlin.preloading.Preloader \
-cp "${KOTLIN_HOME}/lib/kotlin-compiler.jar" \
$KOTLIN_COMPILER "$@"
declare -a kotlin_app
if [ -n "$KOTLIN_RUNNER" ];
then
java_args=("${java_args[@]}" "-Dkotlin.home=${KOTLIN_HOME}")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-runner.jar" "org.jetbrains.kotlin.runner.Main")
else
[ -n "$KOTLIN_COMPILER" ] || KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
java_args=("${java_args[@]}" "-noverify")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-preloader.jar" "org.jetbrains.kotlin.preloading.Preloader" "-cp" "${KOTLIN_HOME}/lib/kotlin-compiler.jar" $KOTLIN_COMPILER)
fi
"${JAVACMD:=java}" $JAVA_OPTS "${java_args[@]}" -cp "${kotlin_app[@]}" "${kotlin_args[@]}"
+2 -1
View File
@@ -14,6 +14,7 @@ rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
set KOTLIN_COMPILER=org.jetbrains.kotlin.cli.js.K2JSCompiler
setlocal
set _KOTLIN_COMPILER=org.jetbrains.kotlin.cli.js.K2JSCompiler
call %~dps0kotlinc.bat %*
+12 -7
View File
@@ -13,10 +13,10 @@ rem We adopt the following conventions:
rem - System/user environment variables start with a letter
rem - Local batch variables start with an underscore ('_')
@setlocal
setlocal
call :set_home
if "%KOTLIN_COMPILER%"=="" set KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
if "%_KOTLIN_COMPILER%"=="" set _KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
if not "%JAVA_HOME%"=="" (
if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
@@ -25,11 +25,16 @@ if not "%JAVA_HOME%"=="" (
if "%_JAVACMD%"=="" set _JAVACMD=java
rem We use the value of the JAVA_OPTS environment variable if defined
set _JAVA_OPTS=-Xmx256M -Xms32M -noverify
set _JAVA_OPTS=-Xmx256M -Xms32M
"%_JAVACMD%" %_JAVA_OPTS% -cp "%_KOTLIN_HOME%\lib\kotlin-preloader.jar" ^
org.jetbrains.kotlin.preloading.Preloader -cp "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^
%KOTLIN_COMPILER% %*
if not "%_KOTLIN_RUNNER%"=="" (
"%_JAVACMD%" %_JAVA_OPTS% "-Dkotlin.home=%_KOTLIN_HOME%" -cp "%_KOTLIN_HOME%\lib\kotlin-runner.jar" ^
org.jetbrains.kotlin.runner.Main %*
) else (
"%_JAVACMD%" %_JAVA_OPTS% -noverify -cp "%_KOTLIN_HOME%\lib\kotlin-preloader.jar" ^
org.jetbrains.kotlin.preloading.Preloader -cp "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^
%_KOTLIN_COMPILER% %*
)
exit /b %ERRORLEVEL%
goto end
@@ -44,5 +49,5 @@ rem # subroutines
goto :eof
:end
@endlocal
endlocal
+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")
}
}