Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions kscript
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,47 @@ 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
if [[ $(uname) == CYGWIN* ]]; then
jarPath=$(cygpath -w ${jarPath})
[[ ! -z $KOTLIN_HOME ]] && KOTLIN_HOME=$(cygpath -w ${KOTLIN_HOME})
fi

## run it using command substitution to have just the user process once kscript is done
exec $(kotlin -classpath ${jarPath} kscript.app.KscriptKt "$@")
# 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

followupFile=followup-$$

## run it
FOLLOWUP_FILE=$followupFile kotlin "${java_args[@]}" -classpath ${jarPath} kscript.app.KscriptKt "${kscript_args[@]}"
result=$?

followupPath=${HOME}/.kscript/${followupFile}

if [[ -f $followupPath ]]; then
cmd=$(cat $followupPath)
"${RMCMD:=rm}" -f $followupPath
exec $cmd
else
exit $result
fi
1 change: 0 additions & 1 deletion src/main/kotlin/kscript/app/AppHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ fun errorIf(value: Boolean, lazyMessage: () -> Any) {
}

fun quit(status: Int): Nothing {
print(if (status == 0) "true" else "false")
exitProcess(status)
}

Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/kscript/app/JarFileLoader.kt
Original file line number Diff line number Diff line change
@@ -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<URL> = arrayOf()) : URLClassLoader(urls) {
fun addFile(file: File) {
addURL(file.toURI().toURL())
}
}

39 changes: 39 additions & 0 deletions src/main/kotlin/kscript/app/Kotlinc.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package kscript.app

import java.io.File

class Kotlinc(val KOTLIN_HOME: String) {
val cl = JarFileLoader()
val preloaderJar = File("${KOTLIN_HOME}${File.separatorChar}lib${File.separatorChar}kotlin-preloader.jar")
init {
cl.addFile(preloaderJar)
}
val baseArgs = listOf<String>("-cp", "${KOTLIN_HOME}${File.separatorChar}lib${File.separatorChar}kotlin-compiler.jar",
"org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
val preloaderMain = cl.loadClass("org.jetbrains.kotlin.preloading.Preloader").getDeclaredMethod("main", Array<String>::class.java)

fun classPathArgs(vararg paths: String?): List<String> {
val combinedClasspath = listOfNotNull(*paths).filter { it.isNotEmpty() }.joinToString(CP_SEPARATOR_CHAR)
return if (combinedClasspath.isNotEmpty()) {
listOf("-cp", combinedClasspath)
} else listOf()
}

fun runKotlinc(args: Collection<String>) {
preloaderMain.invoke(cl, (baseArgs + args).toTypedArray())
}

fun compile(jarFile: File, scriptFile: File, wrapperFile: File?, classpath: String?) {
val baseArgs = listOf<String>("-Xskip-runtime-version-check",
"-d", jarFile.absolutePath, scriptFile.absolutePath)
val wrapperArgs = listOfNotNull(wrapperFile?.absolutePath)
val cpArgs = classPathArgs(classpath)
runKotlinc(baseArgs + wrapperArgs + cpArgs)
}

fun interactiveShell(jarFile: File, classpath: String?) {
val jarPath = if (jarFile.isFile) jarFile.absolutePath else null
val args = classPathArgs(classpath, jarPath)
runKotlinc(args)
}
}
121 changes: 72 additions & 49 deletions src/main/kotlin/kscript/app/Kscript.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Options:
-t --text Enable stdin support API for more streamlined text processing
--idea Open script in temporary Intellij session
-s --silent Suppress status logging to stderr
-J<arg> -J is stripped and <arg> passed to Java as-is

Copyright : 2017 Holger Brandl
License : MIT
Expand All @@ -51,7 +52,17 @@ Website : https://github.com/holgerbrandl/kscript
val KSCRIPT_CACHE_DIR = File(System.getenv("HOME")!!, ".kscript")
val SCRIPT_TEMP_DIR = createTempDir()

val FOLLOWUP_FILE = run {
val fileName = System.getenv("FOLLOWUP_FILE")
if (fileName == null) null
else
File(KSCRIPT_CACHE_DIR, fileName)
}


fun main(args: Array<String>) {
FOLLOWUP_FILE?.let { if (it.isFile) it.delete() }

// skip org.docopt for version and help to allow for lazy version-check
if (args.size == 1 && listOf("--help", "-h", "--version", "-v").contains(args[0])) {
info(USAGE)
Expand Down Expand Up @@ -83,28 +94,7 @@ fun main(args: Array<String>) {
// 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)
}

Expand All @@ -123,7 +113,7 @@ fun main(args: Array<String>) {

// Create temopary dev environment
if (docopt.getBoolean("idea")) {
println(launchIdeaWithKscriptlet(scriptFile, dependencies, customRepos))
evalBash(launchIdeaWithKscriptlet(scriptFile, dependencies, customRepos))
exitProcess(0)
}

Expand All @@ -133,18 +123,6 @@ fun main(args: Array<String>) {
// Extract kotlin arguments
val kotlinOpts = script.collectRuntimeOptions()


// Optionally enter interactive mode
if (docopt.getBoolean("interactive")) {
System.err.println("Creating REPL from ${scriptFile}")
// System.err.println("kotlinc ${kotlinOpts} -classpath '${classpath}'")

val optionalCP = if (classpath != null && classpath.isNotEmpty()) "-classpath ${classpath}" else ""
println("kotlinc ${kotlinOpts} ${optionalCP}")

exitProcess(0)
}

val scriptFileExt = scriptFile.extension
val scriptCheckSum = md5(scriptFile)

Expand Down Expand Up @@ -187,6 +165,8 @@ fun main(args: Array<String>) {
}


val kotlinc: Kotlinc by lazy { Kotlinc(KOTLIN_HOME!!)}

// If scriplet jar ist not cached yet, build it
if (!jarFile.isFile) {
// disabled logging because it seems too much
Expand All @@ -199,12 +179,9 @@ fun main(args: Array<String>) {
// }).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
Expand All @@ -220,23 +197,69 @@ fun main(args: Array<String>) {
}
}
""".trimIndent())
mainKotlin
} else null

"'${mainKotlin.absolutePath}'"
} else {
""
}
kotlinc.compile(jarFile, scriptFile, wrapperFile, classpath)
}

val scriptCompileResult = evalBash("kotlinc -classpath '$classpath' -d '${jarFile.absolutePath}' '${scriptFile.absolutePath}' ${wrapperSrcArg}")
with(scriptCompileResult) {
errorIf(exitCode != 0) { "compilation of '$scriptResource' failed\n$stderr" }
// Optionally enter interactive mode
if (docopt.getBoolean("interactive")) {
System.err.println("Creating REPL from ${scriptFile}")
// System.err.println("kotlinc ${kotlinOpts} -classpath '${classpath}'")

if (kotlinOpts.isNotEmpty()) {
val optionalCP = if (classpath != null && classpath.isNotEmpty()) "-classpath ${classpath}" else ""
writeToFollowUpFile("kotlinc ${kotlinOpts} ${optionalCP}")
exitProcess(0)
}
kotlinc.interactiveShell(jarFile, classpath)
exitProcess(0)
}

val scriptClassPath = "${jarFile}${CP_SEPARATOR_CHAR}${KOTLIN_HOME}${File.separatorChar}lib${File.separatorChar}kotlin-script-runtime.jar${CP_SEPARATOR_CHAR}${classpath}"
if (kotlinOpts.isNotEmpty()) {
// print the final command to be run by exec
val joinedUserArgs = userArgs.joinToString(" ")
writeToFollowUpFile("kotlin ${kotlinOpts} -classpath ${scriptClassPath} ${execClassName} ${joinedUserArgs} ")
exitProcess(0)
}
// run the main method
val cl = JarFileLoader()
val runnerMain = cl.loadClass("org.jetbrains.kotlin.runner.Main").getDeclaredMethod("main", Array<String>::class.java)
val runnerArgs = arrayOf("-cp", scriptClassPath, execClassName, *userArgs.toTypedArray())
runnerMain.invoke(cl, runnerArgs)
}

fun writeToFollowUpFile(content: String) {
errorIf(FOLLOWUP_FILE == null) {
"Cannot determine FOLLOWUP_FILE"
}
FOLLOWUP_FILE!!.writeText(content)
}

// print the final command to be run by exec
val joinedUserArgs = userArgs.joinToString(" ")
private fun selfUpdate() {
if (true || evalBash("which kscript | grep .sdkman").stdout.isNotBlank()) {
info("Installing latest version of kscript...")
// println("sdkman_auto_answer=true && sdk install kscript")

println("kotlin ${kotlinOpts} -classpath ${jarFile}${CP_SEPARATOR_CHAR}${KOTLIN_HOME}${File.separatorChar}lib${File.separatorChar}kotlin-script-runtime.jar${CP_SEPARATOR_CHAR}${classpath} ${execClassName} ${joinedUserArgs} ")
// 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
}
}


Expand Down
4 changes: 2 additions & 2 deletions test/test_suite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,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]"

Expand Down