Implement -howtorun option for kotlin runner

Implement -no-stdlib argument support in kotlin runner

#KT-43534 fixed
This commit is contained in:
Ilya Chernikov
2021-01-04 19:33:26 +01:00
parent edc730f70b
commit 979144157f
4 changed files with 164 additions and 66 deletions
@@ -34,24 +34,36 @@ object Main {
KOTLIN_HOME = File(home) KOTLIN_HOME = File(home)
} }
enum class HowToRun(val argName: String) {
GUESS("guess"),
CLASSFILE("classfile"),
JAR("jar"),
SCRIPT("script");
// TODO: consider implementing REPL as well
companion object {
val validValues = "${GUESS.argName} (default), ${CLASSFILE.argName}, ${JAR.argName}, ${SCRIPT.argName} (or .<script filename extension>)"
fun fromArg(name: String): HowToRun? =
HowToRun.values().find { it.argName == name }
}
}
private fun run(args: Array<String>) { private fun run(args: Array<String>) {
val classpath = arrayListOf<URL>() val classpath = arrayListOf<URL>()
val compilerClasspath = arrayListOf<URL>() val compilerClasspath = arrayListOf<URL>()
var runner: Runner? = null var runner: Runner? = null
var collectingArguments = false
var collectingExpressions = false
var needsCompiler = false
val arguments = arrayListOf<String>() val arguments = arrayListOf<String>()
val compilerArguments = arrayListOf<String>() val compilerArguments = arrayListOf<String>()
var expression: String? = null
var noStdLib = false var noStdLib = false
var noReflect = false var noReflect = false
var howtorun = HowToRun.GUESS
fun setExpression(expr: String) { fun setRunner(newRunner: Runner) {
if (expression == null) { if (runner == null) {
expression = expr runner = newRunner
} else { } else {
throw RunnerException("Only single -e/-expression argument supported") throw AssertionError("conflicting runner settings")
} }
} }
@@ -66,20 +78,8 @@ object Main {
return args[i] return args[i]
} }
if (collectingExpressions) { fun restAsArguments() {
if ("-expression" == arg || "-e" == arg) { arguments.addAll(args.copyOfRange(i+1, args.size))
setExpression(next())
i++
continue
} else {
collectingArguments = true
}
}
if (collectingArguments) {
arguments.add(arg)
i++
continue
} }
if ("-help" == arg || "-h" == arg) { if ("-help" == arg || "-h" == arg) {
@@ -98,10 +98,26 @@ object Main {
compilerClasspath.addPath(path) compilerClasspath.addPath(path)
} }
} }
else if ("-howtorun" == arg) {
if (howtorun != HowToRun.GUESS) {
throw RunnerException("-howtorun is already set to ${howtorun.argName}")
}
val howToRunArg = next()
if (howToRunArg.startsWith(".")) {
howtorun = HowToRun.SCRIPT
compilerArguments.add("-Xdefault-script-extension=$howToRunArg")
} else {
howtorun = HowToRun.fromArg(howToRunArg)
?: throw RunnerException("invalid argument to the option -howtorun $howToRunArg, valid arguments are: ${HowToRun.validValues}")
}
}
else if ("-expression" == arg || "-e" == arg) { else if ("-expression" == arg || "-e" == arg) {
setExpression(next()) if (howtorun != HowToRun.GUESS && howtorun != HowToRun.SCRIPT) {
collectingExpressions = true throw RunnerException("expression evaluation is not compatible with -howtorun argument ${howtorun.argName}")
needsCompiler = true }
setRunner(ExpressionRunner(next()))
restAsArguments()
break
} }
else if ("-no-stdlib" == arg) { else if ("-no-stdlib" == arg) {
noStdLib = true noStdLib = true
@@ -115,20 +131,22 @@ object Main {
compilerArguments.add(arg) compilerArguments.add(arg)
} }
else if (arg.startsWith("-")) { else if (arg.startsWith("-")) {
throw RunnerException("unsupported argument: $arg") throw RunnerException("unknown option: $arg")
} }
else if (arg.endsWith(".jar")) { else if (howtorun == HowToRun.JAR || (howtorun == HowToRun.GUESS && arg.endsWith(".jar"))) {
runner = JarRunner(arg) setRunner(JarRunner(arg))
collectingArguments = true restAsArguments()
break
} }
else if (arg.endsWith(".kts")) { else if (howtorun == HowToRun.SCRIPT || (howtorun == HowToRun.GUESS && arg.endsWith(".kts"))) {
runner = ScriptRunner(arg) setRunner(ScriptRunner(arg))
collectingArguments = true restAsArguments()
needsCompiler = true break
} }
else { else {
runner = MainClassRunner(arg) setRunner(MainClassRunner(arg))
collectingArguments = true restAsArguments()
break
} }
i++ i++
} }
@@ -145,20 +163,17 @@ object Main {
classpath.addPath("$KOTLIN_HOME/lib/kotlin-reflect.jar") classpath.addPath("$KOTLIN_HOME/lib/kotlin-reflect.jar")
} }
if (expression != null) { if (runner == null) {
runner = ExpressionRunner(expression!!) setRunner(ReplRunner())
} else if (runner == null) {
runner = ReplRunner()
needsCompiler = true
} }
if (needsCompiler && compilerClasspath.isEmpty()) { if (runner is RunnerWithCompiler && compilerClasspath.isEmpty()) {
findCompilerJar(this::class.java, KOTLIN_HOME.resolve("lib")).forEach { findCompilerJar(this::class.java, KOTLIN_HOME.resolve("lib")).forEach {
compilerClasspath.add(it.absoluteFile.toURI().toURL()) compilerClasspath.add(it.absoluteFile.toURI().toURL())
} }
} }
runner.run(classpath, compilerArguments, arguments, compilerClasspath) runner!!.run(classpath, compilerArguments, arguments, compilerClasspath)
} }
private fun MutableList<URL>.addPath(path: String) { private fun MutableList<URL>.addPath(path: String) {
@@ -180,15 +195,9 @@ object Main {
println("""kotlin: run Kotlin programs, scripts or REPL. println("""kotlin: run Kotlin programs, scripts or REPL.
Usage: kotlin <options> <command> [<arguments>] Usage: kotlin <options> <command> [<arguments>]
where command may be one of: where possible options include:
foo.Bar Runs the 'main' function from the class with the given qualified name -howtorun <value> How to run the supplied command with arguments,
(compiler arguments are ignored) valid values: ${HowToRun.validValues}
app.jar Runs the given JAR file as 'java -jar' would do
(compiler arguments are ignored and no Kotlin stdlib is added to the classpath)
script.kts Compiles and runs the given script, passing <arguments> to it
-expression (-e) '2+2' Evaluates the expression and prints the result, passing <arguments> to it
<no command> Runs Kotlin REPL, passing <arguments> to it
and possible options include:
-classpath (-cp) <path> Paths where to find user class files -classpath (-cp) <path> Paths where to find user class files
-Dname=value Set a system JVM property -Dname=value Set a system JVM property
-J<option> Pass an option directly to JVM -J<option> Pass an option directly to JVM
@@ -197,6 +206,17 @@ and possible options include:
-X<flag>[=value] Pass -X argument to the compiler -X<flag>[=value] Pass -X argument to the compiler
-version Display Kotlin version -version Display Kotlin version
-help (-h) Print a synopsis of options -help (-h) Print a synopsis of options
and command is interpreted according to the -howtorun option argument
or, in case of guess, according to the following rules:
foo.Bar Runs the 'main' function from the class with the given qualified name
(compiler arguments are ignored)
app.jar Runs the given JAR file as 'java -jar' would do
(compiler arguments are ignored and no Kotlin stdlib is added to the classpath)
script.kts Compiles and runs the given script, passing <arguments> to it
-expression (-e) '2+2' Evaluates the expression and prints the result, passing <arguments> to it
<no command> Runs Kotlin REPL
arguments are passed to the main function when running class or jar file, and for standard script definitions
as the 'args' parameter when running script or expression
""") """)
exitProcess(0) exitProcess(0)
} }
@@ -127,11 +127,20 @@ private fun MutableList<String>.addClasspathArgIfNeeded(classpath: List<URL>) {
} }
} }
private fun ArrayList<String>.addScriptArguments(arguments: List<String>) {
if (arguments.isNotEmpty() && arguments.first() != "--") {
add("--")
}
addAll(arguments)
}
class ReplRunner : RunnerWithCompiler() { class ReplRunner : RunnerWithCompiler() {
override fun run(classpath: List<URL>, compilerArguments: List<String>, arguments: List<String>, compilerClasspath: List<URL>) { override fun run(classpath: List<URL>, compilerArguments: List<String>, arguments: List<String>, compilerClasspath: List<URL>) {
val compilerArgs = ArrayList<String>() val compilerArgs = ArrayList<String>().apply {
compilerArgs.addClasspathArgIfNeeded(classpath) addClasspathArgIfNeeded(classpath)
compilerArgs.addAll(compilerArguments) addAll(compilerArguments)
addScriptArguments(arguments)
}
runCompiler(compilerClasspath, compilerArgs) runCompiler(compilerClasspath, compilerArgs)
} }
} }
@@ -143,10 +152,7 @@ class ScriptRunner(private val path: String) : RunnerWithCompiler() {
addAll(compilerArguments) addAll(compilerArguments)
add("-script") add("-script")
add(path) add(path)
if (arguments.isNotEmpty() && arguments.first() != "--") { addScriptArguments(arguments)
add("--")
}
addAll(arguments)
} }
runCompiler(compilerClasspath, compilerArgs) runCompiler(compilerClasspath, compilerArgs)
} }
@@ -159,10 +165,7 @@ class ExpressionRunner(private val code: String) : RunnerWithCompiler() {
addAll(compilerArguments) addAll(compilerArguments)
add("-expression") add("-expression")
add(code) add(code)
if (arguments.isNotEmpty() && arguments.first() != "--") { addScriptArguments(arguments)
add("--")
}
addAll(arguments)
} }
runCompiler(compilerClasspath, compilerArgs) runCompiler(compilerClasspath, compilerArgs)
} }
@@ -0,0 +1,5 @@
@file:CompilerOptions("-Xallow-result-return-type")
fun f() : Result<Int> = Result.success(42)
println(f().getOrNull())
@@ -58,6 +58,7 @@ class LauncherScriptTest : TestCaseWithTmpdir() {
StringUtil.convertLineSeparators(process.errorStream.bufferedReader().use { it.readText() }), StringUtil.convertLineSeparators(process.errorStream.bufferedReader().use { it.readText() }),
null, testDataDirectory null, testDataDirectory
).replace("Picked up [_A-Z]+:.*\n".toRegex(), "") ).replace("Picked up [_A-Z]+:.*\n".toRegex(), "")
.replace("The system cannot find the file specified", "No such file or directory") // win -> unix
process.waitFor(10, TimeUnit.SECONDS) process.waitFor(10, TimeUnit.SECONDS)
val exitCode = process.exitValue() val exitCode = process.exitValue()
try { try {
@@ -198,9 +199,9 @@ class LauncherScriptTest : TestCaseWithTmpdir() {
"-e", "-e",
"println(args.joinToString())", "println(args.joinToString())",
"--", "--",
"-a", "-e",
"b", "b",
expectedStdout = "-a, b\n" expectedStdout = "-e, b\n"
) )
runProcess( runProcess(
"kotlin", "kotlin",
@@ -243,7 +244,7 @@ class LauncherScriptTest : TestCaseWithTmpdir() {
compiler/testData/launcher/funWithResultReturn.kts:2:11: error: 'kotlin.Result' cannot be used as a return type compiler/testData/launcher/funWithResultReturn.kts:2:11: error: 'kotlin.Result' cannot be used as a return type
fun f() : Result<Int> = Result.success(42) fun f() : Result<Int> = Result.success(42)
^ ^
""" """
) )
runProcess("kotlin", "-Xallow-result-return-type", "$testDataDirectory/funWithResultReturn.kts", expectedStdout = "42\n") runProcess("kotlin", "-Xallow-result-return-type", "$testDataDirectory/funWithResultReturn.kts", expectedStdout = "42\n")
} }
@@ -252,7 +253,16 @@ fun f() : Result<Int> = Result.success(42)
runProcess("kotlin", "-e", "println(42)", expectedStdout = "42\n") runProcess("kotlin", "-e", "println(42)", expectedStdout = "42\n")
runProcess( runProcess(
"kotlin", "-no-stdlib", "-e", "println(42)", "kotlin", "-no-stdlib", "-e", "println(42)",
expectedExitCode = 1, expectedStderrContains = Regex("error: unresolved reference: println") expectedExitCode = 1,
expectedStderr = """error: unresolved reference: println (script.kts:1:1)
error: no script runtime was found in the classpath: class 'kotlin.script.templates.standard.ScriptTemplateWithArgs' not found. Please add kotlin-script-runtime.jar to the module dependencies. (script.kts:1:1)
script.kts:1:1: error: unresolved reference: println
println(42)
^
script.kts:1:1: error: no script runtime was found in the classpath: class 'kotlin.script.templates.standard.ScriptTemplateWithArgs' not found. Please add kotlin-script-runtime.jar to the module dependencies.
println(42)
^
"""
) )
} }
@@ -264,4 +274,64 @@ fun f() : Result<Int> = Result.success(42)
workDirectory = tmpdir, expectedStdout = "OK\n" workDirectory = tmpdir, expectedStdout = "OK\n"
) )
} }
fun testHowToRunExpression() {
runProcess(
"kotlin", "-howtorun", "jar", "-e", "println(args.joinToString())", "-a", "b",
expectedExitCode = 1, expectedStderr = "error: expression evaluation is not compatible with -howtorun argument jar\n"
)
runProcess(
"kotlin", "-howtorun", "script", "-e", "println(args.joinToString())", "-a", "b",
expectedStdout = "-a, b\n"
)
}
fun testHowToRunScript() {
runProcess(
"kotlin", "-howtorun", "classfile", "$testDataDirectory/printargs.kts", "--", "-a", "b",
expectedExitCode = 1, expectedStderr = "error: could not find or load main class \$TESTDATA_DIR\$/printargs.kts\n"
)
runProcess(
"kotlin", "-howtorun", "script", "$testDataDirectory/printargs.kts", "--", "-a", "b",
expectedStdout = "-a, b\n"
)
}
fun testHowToRunCustomScript() {
runProcess(
"kotlin", "$testDataDirectory/funWithResultReturn.myscript",
expectedExitCode = 1, expectedStderr = "error: could not find or load main class \$TESTDATA_DIR\$/funWithResultReturn.myscript\n"
)
runProcess(
"kotlin", "-howtorun", "script", "$testDataDirectory/funWithResultReturn.myscript",
expectedExitCode = 1, expectedStderr = "error: unrecognized script type: funWithResultReturn.myscript; Specify path to the script file as the first argument\n"
)
runProcess(
"kotlin", "-howtorun", ".kts", "$testDataDirectory/funWithResultReturn.myscript",
expectedExitCode = 1, expectedStderr = """error: unresolved reference: CompilerOptions (funWithResultReturn.myscript:1:7)
error: 'kotlin.Result' cannot be used as a return type (funWithResultReturn.myscript:3:11)
compiler/testData/launcher/funWithResultReturn.myscript:1:7: error: unresolved reference: CompilerOptions
@file:CompilerOptions("-Xallow-result-return-type")
^
compiler/testData/launcher/funWithResultReturn.myscript:3:11: error: 'kotlin.Result' cannot be used as a return type
fun f() : Result<Int> = Result.success(42)
^
"""
)
runProcess(
"kotlin", "-howtorun", ".main.kts", "$testDataDirectory/funWithResultReturn.myscript",
expectedStdout = "42\n"
)
}
fun testHowToRunClassFile() {
runProcess("kotlinc", "$testDataDirectory/helloWorld.kt", "-d", tmpdir.path)
runProcess(
"kotlin", "-howtorun", "jar", "test.HelloWorldKt", workDirectory = tmpdir,
expectedExitCode = 1,
expectedStderr = "error: could not read manifest from test.HelloWorldKt: test.HelloWorldKt (No such file or directory)\n"
)
runProcess("kotlin", "-howtorun", "classfile", "test.HelloWorldKt", expectedStdout = "Hello!\n", workDirectory = tmpdir)
}
} }