From 221b206f36107bc6f29485559c2ce18facda8207 Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 20 Feb 2021 18:19:58 -0700 Subject: [PATCH] Fixes for various problems related to scripting. 1. set $scala_exit_status & trap onExit; script exit code correct whether called via scalac or java -jar 2. added missing 'scala -version' option to via call to 'scalac -version' 3. extended and verified REPL and scripting for Cygwin, MinGW, Msys2, and Windows shells with a cygpath tool 4. MinGW based Windows shell environments require TERM=dumb to support REPL 5. simpify and extend jar manifest classpath to support for changing default drive 6. tested for the following Windows and Linux environments: 7. added tests CYGWIN_NT-10.0 host 3.1.7(0.340/5/3) 2020-08-22 17:48 x86_64 Cygwin MINGW64_NT-10.0-19042 host 3.1.7-340.x86_64 2020-10-23 13:08 UTC x86_64 Msys MSYS_NT-10.0-19042 host 3.1.7-340.x86_64 2020-11-08 12:32 UTC x86_64 Msys Linux host 5.4.0-62-generic #70~18.04.1-Ubuntu SMP Tue Jan 12 17:18:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux Linux host 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Oct 28 23:40:43 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux Manual test results for executing a script via compile mode and then java -jar mode: uname -a :: CYGWIN_NT-10.0 d5 3.1.7(0.340/5/3) 2020-08-22 17:48 x86_64 Cygwin uname -a :: Linux quadd 5.4.0-62-generic #70~18.04.1-Ubuntu SMP Tue Jan 12 17:18:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux uname -a :: Linux d5 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Oct 28 23:40:43 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux uname -a :: MINGW64_NT-10.0-19042 d5 3.1.7-340.x86_64 2020-10-23 13:08 UTC x86_64 Msys uname -a :: MSYS_NT-10.0-19042 d5 3.1.7-340.x86_64 2020-11-08 12:32 UTC x86_64 Msys uname -a :: CYGWIN_NT-10.0 d5 3.1.7(0.340/5/3) 2020-08-22 17:48 x86_64 Cygwin --- .../src/dotty/tools/repl/JLineTerminal.scala | 3 +- compiler/src/dotty/tools/scripting/Main.scala | 17 +++--- .../test-resources/scripting/hashBang.scala | 20 +++++++ .../scripting/mainClassOnStack.scala | 22 +++++++ .../test-resources/scripting/scriptName.scala | 9 +++ dist/bin/common | 57 +++++++++++++++---- dist/bin/scala | 26 +++++++-- dist/bin/scalac | 30 +++++----- 8 files changed, 141 insertions(+), 43 deletions(-) create mode 100755 compiler/test-resources/scripting/hashBang.scala create mode 100755 compiler/test-resources/scripting/mainClassOnStack.scala create mode 100755 compiler/test-resources/scripting/scriptName.scala diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 916df851cbf9..5f43a7483841 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -20,9 +20,10 @@ final class JLineTerminal extends java.io.Closeable { private val terminal = TerminalBuilder.builder() - .dumb(false) // fail early if not able to create a terminal + .dumb(dumbTerminal) // fail early if not able to create a terminal .build() private val history = new DefaultHistory + def dumbTerminal = Option(System.getenv("TERM")) == Some("dumb") private def blue(str: String)(using Context) = if (ctx.settings.color.value != "never") Console.BLUE + str + Console.RESET diff --git a/compiler/src/dotty/tools/scripting/Main.scala b/compiler/src/dotty/tools/scripting/Main.scala index 403af7b46435..cf1e76994441 100644 --- a/compiler/src/dotty/tools/scripting/Main.scala +++ b/compiler/src/dotty/tools/scripting/Main.scala @@ -1,7 +1,7 @@ package dotty.tools.scripting import java.io.File -import java.nio.file.Path +import java.nio.file.{Path, Paths} import dotty.tools.dotc.config.Properties.isWin /** Main entry point to the Scripting execution engine */ @@ -60,7 +60,7 @@ object Main: def scriptBasename = scriptFile.getName.takeWhile(_!='.') val jarPath = s"$jarTargetDir/$scriptBasename.jar" - val cpPaths = runtimeClasspath.split(pathsep).map(_.absPath) + val cpPaths = runtimeClasspath.split(pathsep).map(_.toUrl) import java.util.jar.Attributes.Name val cpString:String = cpPaths.distinct.mkString(" ") @@ -80,16 +80,11 @@ object Main: def pathsep = sys.props("path.separator") - - extension(file: File){ - def norm: String = file.toString.norm - } - extension(path: String) { // Normalize path separator, convert relative path to absolute def norm: String = path.replace('\\', '/') match { - case s if s.secondChar == ":" => s.drop(2) + case s if s.secondChar == ":" => s case s if s.startsWith("./") => s.drop(2) case s => s } @@ -97,12 +92,14 @@ object Main: // convert to absolute path relative to cwd. def absPath: String = norm match case str if str.isAbsolute => norm - case _ => s"/${sys.props("user.dir").norm}/$norm" + case _ => Paths.get(userDir,norm).toString.norm - def absFile: File = File(path.absPath) + def toUrl: String = Paths.get(absPath).toUri.toURL.toString // Treat norm paths with a leading '/' as absolute. // Windows java.io.File#isAbsolute treats them as relative. def isAbsolute = path.norm.startsWith("/") || (isWin && path.secondChar == ":") def secondChar: String = path.take(2).drop(1).mkString("") } + + lazy val userDir = sys.props("user.dir").norm diff --git a/compiler/test-resources/scripting/hashBang.scala b/compiler/test-resources/scripting/hashBang.scala new file mode 100755 index 000000000000..1aab26269f86 --- /dev/null +++ b/compiler/test-resources/scripting/hashBang.scala @@ -0,0 +1,20 @@ +#!/usr/bin/env scala +# comment +STUFF=nada +!# + +def main(args: Array[String]): Unit = + System.err.printf("mainClassFromStack: %s\n",mainFromStack) + assert(mainFromStack.contains("hashBang"),s"fromStack[$mainFromStack]") + + lazy val mainFromStack:String = { + val result = new java.io.StringWriter() + new RuntimeException("stack").printStackTrace(new java.io.PrintWriter(result)) + val stack = result.toString.split("[\r\n]+").toList + //for( s <- stack ){ System.err.printf("[%s]\n",s) } + stack.filter { str => str.contains(".main(") }.map { + _.replaceAll(".*[(]",""). + replaceAll("\\.main\\(.*",""). + replaceAll(".scala.*","") + }.distinct.take(1).mkString("") + } diff --git a/compiler/test-resources/scripting/mainClassOnStack.scala b/compiler/test-resources/scripting/mainClassOnStack.scala new file mode 100755 index 000000000000..901cf5911892 --- /dev/null +++ b/compiler/test-resources/scripting/mainClassOnStack.scala @@ -0,0 +1,22 @@ +#!/usr/bin/env scala +export STUFF=nada +#lots of other stuff that isn't valid scala +!# +object Zoo { + def main(args: Array[String]): Unit = + printf("script.name: %s\n",sys.props("script.name")) + printf("mainClassFromStack: %s\n",mainFromStack) + assert(mainFromStack == "Zoo",s"fromStack[$mainFromStack]") + + lazy val mainFromStack:String = { + val result = new java.io.StringWriter() + new RuntimeException("stack").printStackTrace(new java.io.PrintWriter(result)) + val stack = result.toString.split("[\r\n]+").toList + // for( s <- stack ){ System.err.printf("[%s]\n",s) } + val shortStack = stack.filter { str => str.contains(".main(") && ! str.contains("$") }.map { + _.replaceAll("[.].*","").replaceAll("\\s+at\\s+","") + } + // for( s <- shortStack ){ System.err.printf("[%s]\n",s) } + shortStack.take(1).mkString("|") + } +} diff --git a/compiler/test-resources/scripting/scriptName.scala b/compiler/test-resources/scripting/scriptName.scala new file mode 100755 index 000000000000..21aec32fe0bb --- /dev/null +++ b/compiler/test-resources/scripting/scriptName.scala @@ -0,0 +1,9 @@ +#!/usr/bin/env scala + + def main(args: Array[String]): Unit = + val name = Option(sys.props("script.name")) match { + case None => printf("no script.name property is defined\n") + case Some(name) => + printf("script.name: %s\n",name) + assert(name == "scriptName.scala") + } diff --git a/dist/bin/common b/dist/bin/common index eaa9376fe90e..a8cfe8e52eeb 100755 --- a/dist/bin/common +++ b/dist/bin/common @@ -4,14 +4,39 @@ # * Credits: This script is based on the script generated by sbt-pack. # *--------------------------------------------------------------------------*/ +# save terminal settings +saved_stty=$(stty -g 2>/dev/null) +# clear on error so we don't later try to restore them +if [[ ! $? ]]; then + saved_stty="" +fi + +# restore stty settings (echo in particular) +function restoreSttySettings() { + stty $saved_stty + saved_stty="" +} + +scala_exit_status=127 +function onExit() { + [[ "$saved_stty" != "" ]] && restoreSttySettings + exit $scala_exit_status +} + +# to reenable echo if we are interrupted before completing. +trap onExit INT TERM + cygwin=false mingw=false +msys=false darwin=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true ;; + MSYS*) msys=true + ;; Darwin*) darwin=true if [ -z "$JAVA_VERSION" ] ; then JAVA_VERSION="CurrentJDK" @@ -25,6 +50,15 @@ case "`uname`" in ;; esac +if [[ ($cygwin||$mingw||$msys) ]]; then + case "$TERM" in + rxvt* | xterm* | cygwin*) + stty -icanon min 1 -echo + SCALA_OPTS="$SCALA_OPTS -Djline.terminal=unix" + ;; + esac +fi + # Resolve JAVA_HOME from javac command path if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" @@ -41,9 +75,8 @@ if [ -z "$JAVA_HOME" ]; then fi fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then +if [ -z "${JAVACMD-}" ] ; then + if [ -n "${JAVA_HOME-}" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" @@ -69,18 +102,20 @@ CLASSPATH_SUFFIX="" # Path separator used in EXTRA_CLASSPATH PSEP=":" -# For Cygwin, switch paths to Windows-mixed format before running java -if $cygwin; then +# cygpath is used by various windows shells: cygwin, git-sdk, gitbash, msys, etc. +has_cygpath=false +if `which cygpath >/dev/null 2>&1`; then has_cygpath=true; fi + +# if $has_cypath, translate paths to Windows-mixed format before running java +if $has_cygpath; then [ -n "$PROG_HOME" ] && PROG_HOME=`cygpath -am "$PROG_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath -am "$JAVA_HOME"` CLASSPATH_SUFFIX=";" PSEP=";" -fi - -# For Migwn, ensure paths are in UNIX format before anything is touched -if $mingw ; then +elif ($mingw || $msys); then + # For Mingw / Msys, convert paths to UNIX format before anything is touched [ -n "$PROG_HOME" ] && PROG_HOME="`(cd "$PROG_HOME"; pwd -W | sed 's|/|\\\\|g')`" [ -n "$JAVA_HOME" ] && @@ -95,9 +130,9 @@ fi find_lib () { local lib=$(find $PROG_HOME/lib/ -name "$1") - if $cygwin; then + if $has_cygpath; then cygpath -am $lib - elif $mingw; then + elif ($mingw || $msys); then echo $lib | sed 's|/|\\\\|g' else echo $lib diff --git a/dist/bin/scala b/dist/bin/scala index 6762310f70da..baab654f3012 100755 --- a/dist/bin/scala +++ b/dist/bin/scala @@ -92,6 +92,14 @@ while [[ $# -gt 0 ]]; do DEBUG="$DEBUG_STR" shift ;; + -version) + # defer to scalac, then exit + addScalacOptions "${1}" + shift + eval "\"$PROG_HOME/bin/scalac\" ${cp_arg-} ${java_options[@]}" + scala_exit_status=$? + onExit + ;; -J*) addJvmOptions "${1:2}" addScalacOptions "${1}" @@ -115,7 +123,7 @@ while [[ $# -gt 0 ]]; do done if [ $execute_script == true ]; then - [ -n "$script_trace" ] && set -x + [ -n "${script_trace-}" ] && set -x if [ "$CLASS_PATH" ]; then cp_arg="-classpath \"$CLASS_PATH\"" fi @@ -123,18 +131,21 @@ if [ $execute_script == true ]; then setScriptName="-Dscript.path=$target_script" target_jar="${target_script%.*}.jar" if [[ $save_compiled == true && "$target_jar" -nt "$target_script" ]]; then - eval exec "\"$JAVACMD\"" $setScriptName -jar "$target_jar" "${script_args[@]}" + eval "\"$JAVACMD\"" $setScriptName -jar "$target_jar" "${script_args[@]}" + scala_exit_status=$? else [[ $save_compiled == true ]] && rm -f $target_jar residual_args+=($setScriptName) - eval "\"$PROG_HOME/bin/scalac\" $cp_arg ${java_options[@]} ${residual_args[@]} -script $target_script ${script_args[@]}" + eval "\"$PROG_HOME/bin/scalac\" ${cp_arg-} ${java_options[@]} ${residual_args[@]} -script $target_script ${script_args[@]}" + scala_exit_status=$? fi elif [ $execute_repl == true ] || ([ $execute_run == false ] && [ $options_indicator == 0 ]); then if [ "$CLASS_PATH" ]; then cp_arg="-classpath \"$CLASS_PATH\"" fi echo "Starting scala3 REPL..." - eval "\"$PROG_HOME/bin/scalac\" $cp_arg ${java_options[@]} -repl ${residual_args[@]}" + eval "\"$PROG_HOME/bin/scalac\" ${cp_arg-} ${java_options[@]} -repl ${residual_args[@]}" + scala_exit_status=$? elif [ $execute_repl == true ] || [ ${#residual_args[@]} -ne 0 ]; then cp_arg="$DOTTY_LIB$PSEP$SCALA_LIB" if [ -z "$CLASS_PATH" ]; then @@ -148,7 +159,10 @@ elif [ $execute_repl == true ] || [ ${#residual_args[@]} -ne 0 ]; then if [ $with_compiler == true ]; then cp_arg+="$PSEP$DOTTY_COMP$PSEP$TASTY_CORE$PSEP$DOTTY_INTF$PSEP$SCALA_ASM$PSEP$DOTTY_STAGING$PSEP$DOTTY_TASTY_INSPECTOR" fi - eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${jvm_options[@]}" "${residual_args[@]}" + # exec here would prevent onExit from being called, leaving terminal in unusable state + eval "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${jvm_options[@]}" "${residual_args[@]}" + scala_exit_status=$? else - echo "warning: command option is not correct." + echo "warning: command option is not correct." fi +onExit diff --git a/dist/bin/scalac b/dist/bin/scalac index a03601e4fcee..1caa4834036d 100755 --- a/dist/bin/scalac +++ b/dist/bin/scalac @@ -60,21 +60,21 @@ classpathArgs () { # echo "sbt-intface: $SBT_INTF" toolchain="" - toolchain+="$SCALA_LIB$PSEP" - toolchain+="$DOTTY_LIB$PSEP" - toolchain+="$SCALA_ASM$PSEP" - toolchain+="$SBT_INTF$PSEP" - toolchain+="$DOTTY_INTF$PSEP" - toolchain+="$DOTTY_COMP$PSEP" - toolchain+="$TASTY_CORE$PSEP" - toolchain+="$DOTTY_STAGING$PSEP" - toolchain+="$DOTTY_TASTY_INSPECTOR$PSEP" + [ -n "${SCALA_LIB-}" ] && toolchain+="$SCALA_LIB$PSEP" + [ -n "${DOTTY_LIB-}" ] && toolchain+="$DOTTY_LIB$PSEP" + [ -n "${SCALA_ASM-}" ] && toolchain+="$SCALA_ASM$PSEP" + [ -n "${SBT_INTF-}" ] && toolchain+="$SBT_INTF$PSEP" + [ -n "${DOTTY_INTF-}" ] && toolchain+="$DOTTY_INTF$PSEP" + [ -n "${DOTTY_COMP-}" ] && toolchain+="$DOTTY_COMP$PSEP" + [ -n "${TASTY_CORE-}" ] && toolchain+="$TASTY_CORE$PSEP" + [ -n "${DOTTY_STAGING-}" ] && toolchain+="$DOTTY_STAGING$PSEP" + [ -n "${DOTTY_TASTY_INSPECTOR-}" ] && toolchain+="$DOTTY_TASTY_INSPECTOR$PSEP" # jine - toolchain+="$JLINE_READER$PSEP" - toolchain+="$JLINE_TERMINAL$PSEP" - toolchain+="$JLINE_TERMINAL_JNA$PSEP" - toolchain+="$JNA" + [ -n "${JLINE_READER-}" ] && toolchain+="$JLINE_READER$PSEP" + [ -n "${JLINE_TERMINAL-}" ] && toolchain+="$JLINE_TERMINAL$PSEP" + [ -n "${JLINE_TERMINAL_JNA-}" ] && toolchain+="$JLINE_TERMINAL_JNA$PSEP" + [ -n "${JNA-}" ] && toolchain+="$JNA$PSEP" jvm_cp_args="-classpath \"$toolchain\"" } @@ -123,12 +123,12 @@ fi eval exec "\"$JAVACMD\"" \ ${JAVA_OPTS:-$default_java_opts} \ - "$DEBUG" \ + "${DEBUG-}" \ "${java_args[@]}" \ "$jvm_cp_args" \ -Dscala.usejavacp=true \ "$PROG_NAME" \ "${scala_args[@]}" \ "${residual_args[@]}" \ - "$scripting_string" + "${scripting_string-}" exit $?