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)
}
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>) {
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 compilerArguments = arrayListOf<String>()
var expression: String? = null
var noStdLib = false
var noReflect = false
var howtorun = HowToRun.GUESS
fun setExpression(expr: String) {
if (expression == null) {
expression = expr
fun setRunner(newRunner: Runner) {
if (runner == null) {
runner = newRunner
} else {
throw RunnerException("Only single -e/-expression argument supported")
throw AssertionError("conflicting runner settings")
}
}
@@ -66,20 +78,8 @@ object Main {
return args[i]
}
if (collectingExpressions) {
if ("-expression" == arg || "-e" == arg) {
setExpression(next())
i++
continue
} else {
collectingArguments = true
}
}
if (collectingArguments) {
arguments.add(arg)
i++
continue
fun restAsArguments() {
arguments.addAll(args.copyOfRange(i+1, args.size))
}
if ("-help" == arg || "-h" == arg) {
@@ -98,10 +98,26 @@ object Main {
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) {
setExpression(next())
collectingExpressions = true
needsCompiler = true
if (howtorun != HowToRun.GUESS && howtorun != HowToRun.SCRIPT) {
throw RunnerException("expression evaluation is not compatible with -howtorun argument ${howtorun.argName}")
}
setRunner(ExpressionRunner(next()))
restAsArguments()
break
}
else if ("-no-stdlib" == arg) {
noStdLib = true
@@ -115,20 +131,22 @@ object Main {
compilerArguments.add(arg)
}
else if (arg.startsWith("-")) {
throw RunnerException("unsupported argument: $arg")
throw RunnerException("unknown option: $arg")
}
else if (arg.endsWith(".jar")) {
runner = JarRunner(arg)
collectingArguments = true
else if (howtorun == HowToRun.JAR || (howtorun == HowToRun.GUESS && arg.endsWith(".jar"))) {
setRunner(JarRunner(arg))
restAsArguments()
break
}
else if (arg.endsWith(".kts")) {
runner = ScriptRunner(arg)
collectingArguments = true
needsCompiler = true
else if (howtorun == HowToRun.SCRIPT || (howtorun == HowToRun.GUESS && arg.endsWith(".kts"))) {
setRunner(ScriptRunner(arg))
restAsArguments()
break
}
else {
runner = MainClassRunner(arg)
collectingArguments = true
setRunner(MainClassRunner(arg))
restAsArguments()
break
}
i++
}
@@ -145,20 +163,17 @@ object Main {
classpath.addPath("$KOTLIN_HOME/lib/kotlin-reflect.jar")
}
if (expression != null) {
runner = ExpressionRunner(expression!!)
} else if (runner == null) {
runner = ReplRunner()
needsCompiler = true
if (runner == null) {
setRunner(ReplRunner())
}
if (needsCompiler && compilerClasspath.isEmpty()) {
if (runner is RunnerWithCompiler && compilerClasspath.isEmpty()) {
findCompilerJar(this::class.java, KOTLIN_HOME.resolve("lib")).forEach {
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) {
@@ -180,15 +195,9 @@ object Main {
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
(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, passing <arguments> to it
and possible options include:
where possible options include:
-howtorun <value> How to run the supplied command with arguments,
valid values: ${HowToRun.validValues}
-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
@@ -197,6 +206,17 @@ and possible options include:
-X<flag>[=value] Pass -X argument to the compiler
-version Display Kotlin version
-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)
}
@@ -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() {
override fun run(classpath: List<URL>, compilerArguments: List<String>, arguments: List<String>, compilerClasspath: List<URL>) {
val compilerArgs = ArrayList<String>()
compilerArgs.addClasspathArgIfNeeded(classpath)
compilerArgs.addAll(compilerArguments)
val compilerArgs = ArrayList<String>().apply {
addClasspathArgIfNeeded(classpath)
addAll(compilerArguments)
addScriptArguments(arguments)
}
runCompiler(compilerClasspath, compilerArgs)
}
}
@@ -143,10 +152,7 @@ class ScriptRunner(private val path: String) : RunnerWithCompiler() {
addAll(compilerArguments)
add("-script")
add(path)
if (arguments.isNotEmpty() && arguments.first() != "--") {
add("--")
}
addAll(arguments)
addScriptArguments(arguments)
}
runCompiler(compilerClasspath, compilerArgs)
}
@@ -159,10 +165,7 @@ class ExpressionRunner(private val code: String) : RunnerWithCompiler() {
addAll(compilerArguments)
add("-expression")
add(code)
if (arguments.isNotEmpty() && arguments.first() != "--") {
add("--")
}
addAll(arguments)
addScriptArguments(arguments)
}
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() }),
null, testDataDirectory
).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)
val exitCode = process.exitValue()
try {
@@ -198,9 +199,9 @@ class LauncherScriptTest : TestCaseWithTmpdir() {
"-e",
"println(args.joinToString())",
"--",
"-a",
"-e",
"b",
expectedStdout = "-a, b\n"
expectedStdout = "-e, b\n"
)
runProcess(
"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
fun f() : Result<Int> = Result.success(42)
^
"""
"""
)
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", "-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"
)
}
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)
}
}