From 88cb91bb00c7b702a5b79a57214da6c5ba660520 Mon Sep 17 00:00:00 2001 From: Hwijae Lee Date: Tue, 4 Dec 2018 21:12:35 -0500 Subject: [PATCH 1/5] Use gradle 4.10.3 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6a30918..ae45383b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3a62ac0e5b90fb663853ff4e87738e4c418494d9 Mon Sep 17 00:00:00 2001 From: Hwijae Lee Date: Fri, 18 May 2018 17:37:40 -0400 Subject: [PATCH 2/5] Disable relocate by changing shadow plugin --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index d741f34e..ec8b5840 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,4 +50,4 @@ val jar by tasks.getting { // Build shadowJar when val assemble by tasks.getting { dependsOn(shadowJar) -} \ No newline at end of file +} From a4515b43e5360c8efcd9cbcb19c4c0c65be33830 Mon Sep 17 00:00:00 2001 From: Hwijae Lee Date: Mon, 15 Jan 2018 12:16:41 -0500 Subject: [PATCH 3/5] use classloader to run the script main and more Support for -J and -D arguments was added. Interactive shell can access functions defined in .kt file --- src/kscript | 34 +++++- src/main/kotlin/kscript/app/AppHelpers.kt | 4 +- src/main/kotlin/kscript/app/DependencyUtil.kt | 2 +- src/main/kotlin/kscript/app/JarFileLoader.kt | 14 +++ src/main/kotlin/kscript/app/KotlinRunner.kt | 103 +++++++++++++++++ src/main/kotlin/kscript/app/Kscript.kt | 104 +++++++----------- test/test_suite.sh | 4 +- 7 files changed, 196 insertions(+), 69 deletions(-) create mode 100644 src/main/kotlin/kscript/app/JarFileLoader.kt create mode 100644 src/main/kotlin/kscript/app/KotlinRunner.kt diff --git a/src/kscript b/src/kscript index 1fbede4a..1ed24a21 100755 --- a/src/kscript +++ b/src/kscript @@ -22,7 +22,6 @@ abs_kscript_path=$(resolve_symlink $(absolute_path $0)) ## resolve application jar path from script location and convert to windows path when using cygwin jarPath=$(dirname $abs_kscript_path)/kscript.jar -if [[ $(uname) == CYGWIN* ]]; then jarPath=$(cygpath -w ${jarPath}); fi ## prefer KOTLIN_HOME instead of PATH to resolve `kotlin` location (see #145) if [[ -z "$KOTLIN_HOME" ]]; then KOTLIN_EXEC="kotlin"; else KOTLIN_EXEC="$KOTLIN_HOME/bin/kotlin"; fi @@ -30,5 +29,34 @@ if [[ -z "$KOTLIN_HOME" ]]; then KOTLIN_EXEC="kotlin"; else KOTLIN_EXEC="$KOTLIN ## expose the name of the script being run to the script itself export KSCRIPT_FILE="$1" -## run it using command substitution to have just the user process once kscript is done -eval "exec $(${KOTLIN_EXEC} -classpath ${jarPath} kscript.app.KscriptKt "$@")" +if [[ $(uname) == CYGWIN* ]]; then + jarPath=$(cygpath -w ${jarPath}) + [[ ! -z $KOTLIN_HOME ]] && KOTLIN_HOME=$(cygpath -w ${KOTLIN_HOME}) +fi + +# from 'kotlinc' + +[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M" + +declare -a java_args +declare -a kscript_args + +while [ $# -gt 0 ]; do + case "$1" in + -D*) + java_args=("${java_args[@]}" "$1") + shift + ;; + -J*) + java_args=("${java_args[@]}" "$1") + shift + ;; + *) + kscript_args=("${kscript_args[@]}" "$1") + shift + ;; + esac +done + +## run it +exec ${KOTLIN_EXEC} "${java_args[@]}" -classpath ${jarPath} kscript.app.KscriptKt "${kscript_args[@]}" diff --git a/src/main/kotlin/kscript/app/AppHelpers.kt b/src/main/kotlin/kscript/app/AppHelpers.kt index 50b036fe..29285287 100644 --- a/src/main/kotlin/kscript/app/AppHelpers.kt +++ b/src/main/kotlin/kscript/app/AppHelpers.kt @@ -9,6 +9,9 @@ import java.security.MessageDigest import java.util.function.Consumer import kotlin.system.exitProcess +val IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows") + +fun joinToPathString(vararg parts: String) = parts.joinToString("${File.separatorChar}") data class ProcessResult(val command: String, val exitCode: Int, val stdout: String, val stderr: String) { @@ -116,7 +119,6 @@ fun errorIf(value: Boolean, lazyMessage: () -> Any) { } fun quit(status: Int): Nothing { - print(if (status == 0) "true" else "false") exitProcess(status) } diff --git a/src/main/kotlin/kscript/app/DependencyUtil.kt b/src/main/kotlin/kscript/app/DependencyUtil.kt index 94a24c50..54610715 100644 --- a/src/main/kotlin/kscript/app/DependencyUtil.kt +++ b/src/main/kotlin/kscript/app/DependencyUtil.kt @@ -11,7 +11,7 @@ import java.io.File val DEP_LOOKUP_CACHE_FILE = File(KSCRIPT_CACHE_DIR, "dependency_cache.txt") -val CP_SEPARATOR_CHAR = if (System.getProperty("os.name").toLowerCase().contains("windows")) ";" else ":" +val CP_SEPARATOR_CHAR = if (IS_WINDOWS) ";" else ":" fun resolveDependencies(depIds: List, customRepos: List = emptyList(), loggingEnabled: Boolean): String? { diff --git a/src/main/kotlin/kscript/app/JarFileLoader.kt b/src/main/kotlin/kscript/app/JarFileLoader.kt new file mode 100644 index 00000000..fb14c23d --- /dev/null +++ b/src/main/kotlin/kscript/app/JarFileLoader.kt @@ -0,0 +1,14 @@ +package kscript.app + +import java.io.File +import java.net.URL +import java.net.URLClassLoader + +// https://dzone.com/articles/add-jar-file-java-load-path + +class JarFileLoader(urls: Array = arrayOf()) : URLClassLoader(urls) { + fun addFile(file: File) { + addURL(file.toURI().toURL()) + } +} + diff --git a/src/main/kotlin/kscript/app/KotlinRunner.kt b/src/main/kotlin/kscript/app/KotlinRunner.kt new file mode 100644 index 00000000..9954ca9e --- /dev/null +++ b/src/main/kotlin/kscript/app/KotlinRunner.kt @@ -0,0 +1,103 @@ +package kscript.app + +import java.io.File +import kotlin.system.exitProcess + +class KotlinRunner(val kotlinHome: String) { + companion object { + const val PRELOADER_CLASS = "org.jetbrains.kotlin.preloading.Preloader" + const val RUNNER_MAIN_CLASS = "org.jetbrains.kotlin.runner.Main" + } + private val jarFileLoader by lazy { JarFileLoader() } + + private val compilerBaseArgs = listOf("-cp", joinToPathString(kotlinHome, "lib", "kotlin-compiler.jar"), + "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") + + private val preloaderJar = File(joinToPathString(kotlinHome, "lib", "kotlin-preloader.jar")) + + private val preloaderMainMethod by lazy { + jarFileLoader.addFile(preloaderJar) + jarFileLoader.loadClass(PRELOADER_CLASS).getDeclaredMethod("main", Array::class.java) + } + + fun compile(jarFile: File, scriptFile: File, wrapperFile: File?, classpath: String?, compilerOpts: String) { + val compilerArgs = if (compilerOpts.isNotEmpty()) compilerOpts.split("\\s+".toRegex()) else listOf() + val baseArgs = listOf("-Xskip-runtime-version-check") + + compilerArgs + + listOf("-d", jarFile.absolutePath, scriptFile.absolutePath) + val wrapperArgs = listOfNotNull(wrapperFile?.absolutePath) + val cpArgs = classPathArgs(classpath) + runKotlinc(baseArgs + wrapperArgs + cpArgs) + } + + fun runScriptAndExit(scriptClassPath: String, execClassName: String, userArgs: List, kotlinOpts: String) { + val runnerArgs = listOf("-cp", scriptClassPath, execClassName) + userArgs + if (kotlinOpts.isNotEmpty()) { + exitProcess(execKotlin(kotlinOpts, runnerArgs)) + } else { + runKotlin(runnerArgs) + exitProcess(0) + } + } + + fun interactiveShell(jarFile: File, classpath: String?, compilerOpts: String, kotlinOpts: String) { + val compilerArgs = if (compilerOpts.isNotEmpty()) compilerOpts.split("\\s+".toRegex()) else listOf() + val jarPath = if (jarFile.isFile) jarFile.absolutePath else null + val args = compilerArgs + classPathArgs(classpath, jarPath) + if (kotlinOpts.isNotEmpty()) { + execKotlinc(kotlinOpts, args) + } else { + runKotlinc(args) + } + } + + private fun classPathArgs(vararg paths: String?): List { + val combinedClasspath = listOfNotNull(*paths).filter { it.isNotEmpty() }.joinToString(CP_SEPARATOR_CHAR) + return if (combinedClasspath.isNotEmpty()) { + listOf("-cp", combinedClasspath) + } else listOf() + } + + private fun runKotlinc(args: Collection) { + preloaderMainMethod.invoke(jarFileLoader, (compilerBaseArgs + args).toTypedArray()) + } + + private fun execKotlinc(kotlinOpts: String, args: List): Int { + val kotlinOptsList = kotlinOpts.split("\\s+".toRegex()) + return if (IS_WINDOWS) { + val (javaOpts, pureKotlinOpts) = kotlinOptsList.partition { it.startsWith("-J") || it.startsWith("-D") } + val pureJavaOpts = javaOpts.map { if (it.startsWith("-J")) it.substring(2) else it } + val javaCmd = System.getenv("JAVACMD") ?: "java" + exec(listOf(javaCmd) + pureJavaOpts + compilerBaseArgs + pureKotlinOpts + args) + } else { + exec(listOf("kotlinc") + kotlinOptsList + args) + } + } + + + private fun runKotlin(args: Collection) { + val runnerMain = jarFileLoader.loadClass(RUNNER_MAIN_CLASS).getDeclaredMethod("main", Array::class.java) + runnerMain.invoke(jarFileLoader, args.toTypedArray()) + } + + private fun execKotlin(kotlinOpts: String, args: List): Int { + val kotlinOptsList = kotlinOpts.split("\\s+".toRegex()) + return if (IS_WINDOWS) { + val runnerJar = joinToPathString(kotlinHome, "lib", "kotlin-runner.jar") + val (javaOpts, pureKotlinOpts) = kotlinOptsList.partition { it.startsWith("-J") || it.startsWith("-D") } + val pureJavaOpts = javaOpts.map { if (it.startsWith("-J")) it.substring(2) else it } + val javaCmd = System.getenv("JAVACMD") ?: "java" + exec(listOf(javaCmd) + pureJavaOpts + listOf("-Dkotlin.home=${kotlinHome}", "-cp", runnerJar, RUNNER_MAIN_CLASS) + + pureKotlinOpts + args) + } else { + exec(listOf("kotlin") + kotlinOptsList + args) + } + } + + private fun exec(commandList: List) = + ProcessBuilder(commandList) + .inheritIO() + .apply { environment()["KOTLIN_RUNNER"] = "" } + .start() + .waitFor() +} diff --git a/src/main/kotlin/kscript/app/Kscript.kt b/src/main/kotlin/kscript/app/Kscript.kt index b6542255..739cc73b 100644 --- a/src/main/kotlin/kscript/app/Kscript.kt +++ b/src/main/kotlin/kscript/app/Kscript.kt @@ -1,6 +1,5 @@ package kscript.app -import kscript.app.ShellUtils.requireInPath import org.docopt.DocOptWrapper import org.intellij.lang.annotations.Language import java.io.BufferedReader @@ -45,7 +44,8 @@ Options: -s --silent Suppress status logging to stderr --package Package script and dependencies into self-dependent binary --add-bootstrap-header Prepend bash header that installs kscript if necessary - + -J -J is stripped and passed to Java as-is (if no KOTLIN_OPTS is given) + -Dname=value Set a system JVM property (if no KOTLIN_OPTS is given) Copyright : 2017 Holger Brandl License : MIT @@ -106,28 +106,7 @@ fun main(args: Array) { // optionally self-update kscript ot the newest version // (if not local copy is not being maintained by sdkman) if (docopt.getBoolean(("self-update"))) { - if (true || evalBash("which kscript | grep .sdkman").stdout.isNotBlank()) { - info("Installing latest version of kscript...") - // println("sdkman_auto_answer=true && sdk install kscript") - - // create update script - val updateScript = File(KSCRIPT_CACHE_DIR, "self_update.sh").apply { - writeText(""" - #!/usr/bin/env bash - export SDKMAN_DIR="${"$"}{HOME}/.sdkman" - source "${"$"}{SDKMAN_DIR}/bin/sdkman-init.sh" - sdkman_auto_answer=true && sdk install kscript - """.trimIndent()) - setExecutable(true) - } - - println(updateScript.absolutePath) - } else { - info("Self-update is currently just supported via sdkman.") - info("Please download a new release from https://github.com/holgerbrandl/kscript") - // todo port sdkman-indpendent self-update - } - + selfUpdate() quit(0) } @@ -181,29 +160,17 @@ fun main(args: Array) { // Create temporary dev environment if (docopt.getBoolean("idea")) { - println(launchIdeaWithKscriptlet(rawScript, dependencies, customRepos, includeURLs)) + evalBash(launchIdeaWithKscriptlet(rawScript, dependencies, customRepos, includeURLs)) exitProcess(0) } val classpath = resolveDependencies(dependencies, customRepos, loggingEnabled) ?: "" - val optionalCpArg = if (classpath.isNotEmpty()) "-classpath '${classpath}'" else "" // Extract kotlin arguments val kotlinOpts = script.collectRuntimeOptions() val compilerOpts = script.collectCompilerOptions() - - // Optionally enter interactive mode - if (docopt.getBoolean("interactive")) { - infoMsg("Creating REPL from ${scriptFile}") - // System.err.println("kotlinc ${kotlinOpts} -classpath '${classpath}'") - - println("kotlinc ${compilerOpts} ${kotlinOpts} ${optionalCpArg}") - - exitProcess(0) - } - val scriptFileExt = scriptFile.extension val scriptCheckSum = md5(scriptFile) @@ -242,12 +209,12 @@ fun main(args: Array) { // infer KOTLIN_HOME if not set - val KOTLIN_HOME = System.getenv("KOTLIN_HOME") ?: guessKotlinHome() - - errorIf(KOTLIN_HOME == null) { - "KOTLIN_HOME is not set and could not be inferred from context" + val KOTLIN_HOME = System.getenv("KOTLIN_HOME") ?: guessKotlinHome() ?: run { + errorMsg("KOTLIN_HOME is not set and could not be inferred from context") + quit(1) } + val kotlinRunner = KotlinRunner(KOTLIN_HOME) // If scriplet jar ist not cached yet, build it if (!jarFile.isFile) { @@ -261,12 +228,9 @@ fun main(args: Array) { // }).forEach { it.delete() } - requireInPath("kotlinc") - - // create main-wrapper for kts scripts - val wrapperSrcArg = if (scriptFileExt == "kts") { + val wrapperFile = if (scriptFileExt == "kts") { val mainKotlin = File(createTempDir("kscript"), execClassName + ".kt") val classReference = (script.pckg ?: "") + className @@ -282,21 +246,17 @@ fun main(args: Array) { } } """.trimIndent()) - - "'${mainKotlin.absolutePath}'" - } else { - "" - } - - val scriptCompileResult = evalBash("kotlinc ${compilerOpts} ${optionalCpArg} -d '${jarFile.absolutePath}' '${scriptFile.absolutePath}' ${wrapperSrcArg}") - with(scriptCompileResult) { - errorIf(exitCode != 0) { "compilation of '$scriptResource' failed\n$stderr" } - } + mainKotlin + } else null + kotlinRunner.compile(jarFile, scriptFile, wrapperFile, classpath, compilerOpts) } - - // print the final command to be run by eval+exec - val joinedUserArgs = userArgs.map { "\"${it.replace("\"", "\\\"")}\"" }.joinToString(" ") + // Optionally enter interactive mode + if (docopt.getBoolean("interactive")) { + System.err.println("Creating REPL from ${scriptFile}") + kotlinRunner.interactiveShell(jarFile, classpath, compilerOpts, kotlinOpts) + exitProcess(0) + } //if requested try to package the into a standalone binary if (docopt.getBoolean("package")) { @@ -311,11 +271,31 @@ fun main(args: Array) { quit(0) } - var extClassPath = "${jarFile}${CP_SEPARATOR_CHAR}${KOTLIN_HOME}${File.separatorChar}lib${File.separatorChar}kotlin-script-runtime.jar" - if (classpath.isNotEmpty()) - extClassPath += kscript.app.CP_SEPARATOR_CHAR + classpath + val scriptClassPath = listOf(jarFile, joinToPathString(KOTLIN_HOME, "lib", "kotlin-script-runtime.jar"), classpath).joinToString(CP_SEPARATOR_CHAR) - println("kotlin ${kotlinOpts} -classpath \"${extClassPath}\" ${execClassName} ${joinedUserArgs} ") + kotlinRunner.runScriptAndExit(scriptClassPath, execClassName, userArgs, kotlinOpts) +} + +private fun selfUpdate() { + if (true || evalBash("which kscript | grep .sdkman").stdout.isNotBlank()) { + info("Installing latest version of kscript...") + // create update script + val updateScript = File(KSCRIPT_CACHE_DIR, "self_update.sh").apply { + writeText(""" + #!/usr/bin/env bash + export SDKMAN_DIR="${"$"}{HOME}/.sdkman" + source "${"$"}{SDKMAN_DIR}/bin/sdkman-init.sh" + sdkman_auto_answer=true && sdk install kscript + """.trimIndent()) + setExecutable(true) + } + + evalBash(updateScript.absolutePath) + } else { + info("Self-update is currently just supported via sdkman.") + info("Please download a new release from https://github.com/holgerbrandl/kscript") + // todo port sdkman-indpendent self-update + } } diff --git a/test/test_suite.sh b/test/test_suite.sh index 06d79aac..6aeee418 100755 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -133,10 +133,10 @@ export -f resolve_deps assert_stderr "resolve_deps log4j:log4j:1.2.14" "${HOME}/.m2/repository/log4j/log4j/1.2.14/log4j-1.2.14.jar" ## impossible version -assert "resolve_deps log4j:log4j:9.8.76" "false" +assert_raises "resolve_deps log4j:log4j:9.8.76" 1 ## wrong format should exit with 1 -assert "resolve_deps log4j:1.0" "false" +assert_raises "resolve_deps log4j:1.0" 1 assert_stderr "resolve_deps log4j:1.0" "[ERROR] Invalid dependency locator: 'log4j:1.0'. Expected format is groupId:artifactId:version[:classifier][@type]" From d642056e9f045fd1aaf4036bc797a426673c1b21 Mon Sep 17 00:00:00 2001 From: Hwijae Lee Date: Fri, 9 Feb 2018 20:36:41 -0500 Subject: [PATCH 4/5] refactoring --- src/main/kotlin/kscript/app/AppHelpers.kt | 6 +- src/main/kotlin/kscript/app/KotlinRunner.kt | 74 ++++++++++----------- src/main/kotlin/kscript/app/Kscript.kt | 23 ++++--- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/kscript/app/AppHelpers.kt b/src/main/kotlin/kscript/app/AppHelpers.kt index 29285287..f2b7fdfc 100644 --- a/src/main/kotlin/kscript/app/AppHelpers.kt +++ b/src/main/kotlin/kscript/app/AppHelpers.kt @@ -13,6 +13,8 @@ val IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows") fun joinToPathString(vararg parts: String) = parts.joinToString("${File.separatorChar}") +fun argsToList(args: String): List = if (args.isEmpty()) listOf() else args.split("\\s+".toRegex()) + data class ProcessResult(val command: String, val exitCode: Int, val stdout: String, val stderr: String) { override fun toString(): String { @@ -284,7 +286,7 @@ private fun createSymLink(link: File, target: File) { * Create and use a temporary gradle project to package the compiled script using capsule. * See https://github.com/puniverse/capsule */ -fun packageKscript(scriptJar: File, wrapperClassName: String, dependencies: List, customRepos: List, runtimeOptions: String, appName: String) { +fun packageKscript(scriptJar: File, wrapperClassName: String, dependencies: List, customRepos: List, runtimeOptions: List, appName: String) { requireInPath("gradle", "gradle is required to package kscripts") infoMsg("Packaging script '$appName' into standalone executable...") @@ -297,7 +299,7 @@ fun packageKscript(scriptJar: File, wrapperClassName: String, dependencies: List val stringifiedDeps = dependencies.map { " compile \"$it\"" }.joinToString("\n") val stringifiedRepos = customRepos.map { " maven {\n url '${it.url}'\n }\n" }.joinToString("\n") - val jvmOptions = runtimeOptions.split(" ") + val jvmOptions = runtimeOptions .filter { it.startsWith("-J") } .map { it.removePrefix("-J") } .map { '"' + it + '"' } diff --git a/src/main/kotlin/kscript/app/KotlinRunner.kt b/src/main/kotlin/kscript/app/KotlinRunner.kt index 9954ca9e..232095cd 100644 --- a/src/main/kotlin/kscript/app/KotlinRunner.kt +++ b/src/main/kotlin/kscript/app/KotlinRunner.kt @@ -1,49 +1,51 @@ package kscript.app import java.io.File -import kotlin.system.exitProcess -class KotlinRunner(val kotlinHome: String) { +class KotlinRunner(private val kotlinHome: String) { companion object { - const val PRELOADER_CLASS = "org.jetbrains.kotlin.preloading.Preloader" - const val RUNNER_MAIN_CLASS = "org.jetbrains.kotlin.runner.Main" + private const val PRELOADER_CLASS = "org.jetbrains.kotlin.preloading.Preloader" + private const val RUNNER_MAIN_CLASS = "org.jetbrains.kotlin.runner.Main" } + private val jarFileLoader by lazy { JarFileLoader() } private val compilerBaseArgs = listOf("-cp", joinToPathString(kotlinHome, "lib", "kotlin-compiler.jar"), "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") - private val preloaderJar = File(joinToPathString(kotlinHome, "lib", "kotlin-preloader.jar")) private val preloaderMainMethod by lazy { + val preloaderJar = File(joinToPathString(kotlinHome, "lib", "kotlin-preloader.jar")) jarFileLoader.addFile(preloaderJar) jarFileLoader.loadClass(PRELOADER_CLASS).getDeclaredMethod("main", Array::class.java) } + private val runnerMainMethod by lazy { + System.getProperties().setProperty("kotlin.home", kotlinHome) + jarFileLoader.addFile(File(joinToPathString(kotlinHome, "lib", "kotlin-runner.jar"))) + jarFileLoader.loadClass(RUNNER_MAIN_CLASS).getDeclaredMethod("main", Array::class.java) + } - fun compile(jarFile: File, scriptFile: File, wrapperFile: File?, classpath: String?, compilerOpts: String) { - val compilerArgs = if (compilerOpts.isNotEmpty()) compilerOpts.split("\\s+".toRegex()) else listOf() - val baseArgs = listOf("-Xskip-runtime-version-check") + - compilerArgs + - listOf("-d", jarFile.absolutePath, scriptFile.absolutePath) - val wrapperArgs = listOfNotNull(wrapperFile?.absolutePath) + fun compile(compilerOpts: List, targetJarFile: File, sourceFiles: List, classpath: String?) { + val baseArgs = listOf("-Xskip-runtime-version-check") + + compilerOpts + listOf("-d", targetJarFile.absolutePath) val cpArgs = classPathArgs(classpath) - runKotlinc(baseArgs + wrapperArgs + cpArgs) + runKotlinc(baseArgs + sourceFiles.map { it.absolutePath } + cpArgs) } - fun runScriptAndExit(scriptClassPath: String, execClassName: String, userArgs: List, kotlinOpts: String) { + fun runScript(scriptClassPath: String, execClassName: String, userArgs: List, kotlinOpts: List): Int { val runnerArgs = listOf("-cp", scriptClassPath, execClassName) + userArgs - if (kotlinOpts.isNotEmpty()) { - exitProcess(execKotlin(kotlinOpts, runnerArgs)) + return if (kotlinOpts.isNotEmpty()) { + execKotlin(kotlinOpts, runnerArgs) } else { runKotlin(runnerArgs) - exitProcess(0) + // it would be unreachable if the script calls exitProcess() + 0 } } - fun interactiveShell(jarFile: File, classpath: String?, compilerOpts: String, kotlinOpts: String) { - val compilerArgs = if (compilerOpts.isNotEmpty()) compilerOpts.split("\\s+".toRegex()) else listOf() + fun interactiveShell(jarFile: File, classpath: String?, compilerOpts: List, kotlinOpts: List) { val jarPath = if (jarFile.isFile) jarFile.absolutePath else null - val args = compilerArgs + classPathArgs(classpath, jarPath) + val args = compilerOpts + classPathArgs(classpath, jarPath) if (kotlinOpts.isNotEmpty()) { execKotlinc(kotlinOpts, args) } else { @@ -62,38 +64,35 @@ class KotlinRunner(val kotlinHome: String) { preloaderMainMethod.invoke(jarFileLoader, (compilerBaseArgs + args).toTypedArray()) } - private fun execKotlinc(kotlinOpts: String, args: List): Int { - val kotlinOptsList = kotlinOpts.split("\\s+".toRegex()) + private fun execKotlinc(kotlinOpts: List, args: List): Int { return if (IS_WINDOWS) { - val (javaOpts, pureKotlinOpts) = kotlinOptsList.partition { it.startsWith("-J") || it.startsWith("-D") } - val pureJavaOpts = javaOpts.map { if (it.startsWith("-J")) it.substring(2) else it } - val javaCmd = System.getenv("JAVACMD") ?: "java" - exec(listOf(javaCmd) + pureJavaOpts + compilerBaseArgs + pureKotlinOpts + args) + execJava(kotlinOpts, compilerBaseArgs, args) } else { - exec(listOf("kotlinc") + kotlinOptsList + args) + exec(listOf("kotlinc") + kotlinOpts + args) } } - private fun runKotlin(args: Collection) { - val runnerMain = jarFileLoader.loadClass(RUNNER_MAIN_CLASS).getDeclaredMethod("main", Array::class.java) - runnerMain.invoke(jarFileLoader, args.toTypedArray()) + runnerMainMethod.invoke(jarFileLoader, args.toTypedArray()) } - private fun execKotlin(kotlinOpts: String, args: List): Int { - val kotlinOptsList = kotlinOpts.split("\\s+".toRegex()) + private fun execKotlin(kotlinOpts: List, args: List): Int { return if (IS_WINDOWS) { val runnerJar = joinToPathString(kotlinHome, "lib", "kotlin-runner.jar") - val (javaOpts, pureKotlinOpts) = kotlinOptsList.partition { it.startsWith("-J") || it.startsWith("-D") } - val pureJavaOpts = javaOpts.map { if (it.startsWith("-J")) it.substring(2) else it } - val javaCmd = System.getenv("JAVACMD") ?: "java" - exec(listOf(javaCmd) + pureJavaOpts + listOf("-Dkotlin.home=${kotlinHome}", "-cp", runnerJar, RUNNER_MAIN_CLASS) - + pureKotlinOpts + args) + val kotlinAppArgs = listOf("-Dkotlin.home=$kotlinHome", "-cp", runnerJar, RUNNER_MAIN_CLASS) + execJava(kotlinOpts, kotlinAppArgs, args) } else { - exec(listOf("kotlin") + kotlinOptsList + args) + exec(listOf("kotlin") + kotlinOpts + args) } } + private fun execJava(kotlinOpts: List, kotlinAppArgs: List, args: List): Int { + val (javaOpts, pureKotlinOpts) = kotlinOpts.partition { it.startsWith("-J") || it.startsWith("-D") } + val pureJavaOpts = javaOpts.map { if (it.startsWith("-J")) it.substring(2) else it } + val javaCmd = System.getenv("JAVACMD") ?: "java" + return exec(listOf(javaCmd) + pureJavaOpts + kotlinAppArgs + pureKotlinOpts + args) + } + private fun exec(commandList: List) = ProcessBuilder(commandList) .inheritIO() @@ -101,3 +100,4 @@ class KotlinRunner(val kotlinHome: String) { .start() .waitFor() } + diff --git a/src/main/kotlin/kscript/app/Kscript.kt b/src/main/kotlin/kscript/app/Kscript.kt index 739cc73b..0ec3ef1f 100644 --- a/src/main/kotlin/kscript/app/Kscript.kt +++ b/src/main/kotlin/kscript/app/Kscript.kt @@ -168,8 +168,8 @@ fun main(args: Array) { val classpath = resolveDependencies(dependencies, customRepos, loggingEnabled) ?: "" // Extract kotlin arguments - val kotlinOpts = script.collectRuntimeOptions() - val compilerOpts = script.collectCompilerOptions() + val kotlinOpts = argsToList(script.collectRuntimeOptions()) + val compilerOpts = argsToList(script.collectCompilerOptions()) val scriptFileExt = scriptFile.extension val scriptCheckSum = md5(scriptFile) @@ -248,14 +248,8 @@ fun main(args: Array) { """.trimIndent()) mainKotlin } else null - kotlinRunner.compile(jarFile, scriptFile, wrapperFile, classpath, compilerOpts) - } - - // Optionally enter interactive mode - if (docopt.getBoolean("interactive")) { - System.err.println("Creating REPL from ${scriptFile}") - kotlinRunner.interactiveShell(jarFile, classpath, compilerOpts, kotlinOpts) - exitProcess(0) + val sourceFiles = listOfNotNull(scriptFile, wrapperFile) + kotlinRunner.compile(compilerOpts, jarFile, sourceFiles, classpath) } //if requested try to package the into a standalone binary @@ -271,9 +265,16 @@ fun main(args: Array) { quit(0) } + // Optionally enter interactive mode + if (docopt.getBoolean("interactive")) { + System.err.println("Creating REPL from ${scriptFile}") + kotlinRunner.interactiveShell(jarFile, classpath, compilerOpts, kotlinOpts) + exitProcess(0) + } + val scriptClassPath = listOf(jarFile, joinToPathString(KOTLIN_HOME, "lib", "kotlin-script-runtime.jar"), classpath).joinToString(CP_SEPARATOR_CHAR) - kotlinRunner.runScriptAndExit(scriptClassPath, execClassName, userArgs, kotlinOpts) + exitProcess(kotlinRunner.runScript(scriptClassPath, execClassName, userArgs, kotlinOpts)) } private fun selfUpdate() { From 43de25f38a77e4f49cb03ae2272b7f642829f315 Mon Sep 17 00:00:00 2001 From: Hwijae Lee Date: Fri, 18 May 2018 13:46:51 -0400 Subject: [PATCH 5/5] Use java instead of kotlin --- build.gradle.kts | 7 ++++++- src/kscript | 7 ++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ec8b5840..42a2cea6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,12 +3,17 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { kotlin("jvm") version "1.2.70" id("com.github.johnrengelman.shadow") version "2.0.4" + application +} + +application { + mainClassName = "kscript.app.KscriptKt" } group = "com.github.holgerbrandl.kscript.launcher" dependencies { - compileOnly("org.jetbrains.kotlin:kotlin-stdlib") + compile("org.jetbrains.kotlin:kotlin-stdlib") compile("com.offbytwo:docopt:0.6.0.20150202") diff --git a/src/kscript b/src/kscript index 1ed24a21..277ac67a 100755 --- a/src/kscript +++ b/src/kscript @@ -23,9 +23,6 @@ abs_kscript_path=$(resolve_symlink $(absolute_path $0)) ## resolve application jar path from script location and convert to windows path when using cygwin jarPath=$(dirname $abs_kscript_path)/kscript.jar -## prefer KOTLIN_HOME instead of PATH to resolve `kotlin` location (see #145) -if [[ -z "$KOTLIN_HOME" ]]; then KOTLIN_EXEC="kotlin"; else KOTLIN_EXEC="$KOTLIN_HOME/bin/kotlin"; fi - ## expose the name of the script being run to the script itself export KSCRIPT_FILE="$1" @@ -48,7 +45,7 @@ while [ $# -gt 0 ]; do shift ;; -J*) - java_args=("${java_args[@]}" "$1") + java_args=("${java_args[@]}" "${1:2}") shift ;; *) @@ -59,4 +56,4 @@ while [ $# -gt 0 ]; do done ## run it -exec ${KOTLIN_EXEC} "${java_args[@]}" -classpath ${jarPath} kscript.app.KscriptKt "${kscript_args[@]}" +exec ${JAVACMD:=java} "${java_args[@]}" -jar ${jarPath} "${kscript_args[@]}"