Skip to content

Fix scala runner exit codes #15604

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 11, 2022
Merged
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
59 changes: 33 additions & 26 deletions compiler/src/dotty/tools/MainGenericRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import dotty.tools.io.Jar
import dotty.tools.runner.ScalaClassLoader
import java.nio.file.Paths
import dotty.tools.dotc.config.CommandLineParser
import dotty.tools.scripting.StringDriver
import dotty.tools.scripting.{StringDriver, StringDriverException, ScriptingException}

enum ExecuteMode:
case Guess
Expand Down Expand Up @@ -118,40 +118,40 @@ object MainGenericRunner {
@sharable val scalaOption = raw"""@.*""".r
@sharable val colorOption = raw"""-color:.*""".r
@tailrec
def process(args: List[String], settings: Settings): Settings = args match
def processArgs(args: List[String], settings: Settings): Settings = args match
case Nil =>
settings
case "-run" :: fqName :: tail =>
process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
processArgs(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
val (tailargs, newEntries) = processClasspath(cp, tail)
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
processArgs(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
case ("-version" | "--version") :: _ =>
settings.copy(
executeMode = ExecuteMode.Repl,
residualArgs = List("-version")
)
case ("-v" | "-verbose" | "--verbose") :: tail =>
process(
processArgs(
tail,
settings.copy(
verbose = true,
residualArgs = settings.residualArgs :+ "-verbose"
)
)
case "-save" :: tail =>
process(tail, settings.withSave)
processArgs(tail, settings.withSave)
case "-nosave" :: tail =>
process(tail, settings.noSave)
processArgs(tail, settings.noSave)
case "-with-compiler" :: tail =>
process(tail, settings.withCompiler)
processArgs(tail, settings.withCompiler)
case (o @ javaOption(striped)) :: tail =>
process(tail, settings.withJavaArgs(striped).withScalaArgs(o))
processArgs(tail, settings.withJavaArgs(striped).withScalaArgs(o))
case (o @ scalaOption(_*)) :: tail =>
val remainingArgs = (CommandLineParser.expandArg(o) ++ tail).toList
process(remainingArgs, settings)
processArgs(remainingArgs, settings)
case (o @ colorOption(_*)) :: tail =>
process(tail, settings.withScalaArgs(o))
processArgs(tail, settings.withScalaArgs(o))
case "-e" :: expression :: tail =>
val mainSource = s"@main def main(args: String *): Unit =\n ${expression}"
settings
Expand All @@ -169,13 +169,13 @@ object MainGenericRunner {
.withScriptArgs(tail*)
else
val newSettings = if arg.startsWith("-") then settings else settings.withPossibleEntryPaths(arg).withModeShouldBePossibleRun
process(tail, newSettings.withResidualArgs(arg))
end process
processArgs(tail, newSettings.withResidualArgs(arg))
end processArgs

def main(args: Array[String]): Unit =
def process(args: Array[String]): Boolean =
val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")).filter(_.nonEmpty)
val allArgs = scalaOpts ++ args
val settings = process(allArgs.toList, Settings())
val settings = processArgs(allArgs.toList, Settings())
if settings.exitCode != 0 then System.exit(settings.exitCode)

def removeCompiler(cp: Array[String]) =
Expand All @@ -185,12 +185,13 @@ object MainGenericRunner {
else
cp

def run(settings: Settings): Unit = settings.executeMode match
def run(settings: Settings): Option[Throwable] = settings.executeMode match
case ExecuteMode.Repl =>
val properArgs =
List("-classpath", settings.classPath.mkString(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty))
++ settings.residualArgs
repl.Main.main(properArgs.toArray)
None

case ExecuteMode.PossibleRun =>
val newClasspath = (settings.classPath :+ ".").flatMap(_.split(classpathSeparator).filter(_.nonEmpty)).map(File(_).toURI.toURL)
Expand All @@ -207,10 +208,11 @@ object MainGenericRunner {
case None =>
settings.withExecuteMode(ExecuteMode.Repl)
run(newSettings)

case ExecuteMode.Run =>
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL)
val res = ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap {
ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap {
case ex: ClassNotFoundException if ex.getMessage == settings.targetToRun =>
val file = settings.targetToRun
Jar(file).mainClass match
Expand All @@ -220,7 +222,7 @@ object MainGenericRunner {
Some(IllegalArgumentException(s"No main class defined in manifest in jar: $file"))
case ex => Some(ex)
}
errorFn("", res)

case ExecuteMode.Script =>
val targetScript = Paths.get(settings.targetScript).toFile
val targetJar = settings.targetScript.replaceAll("[.][^\\/]*$", "")+".jar"
Expand All @@ -232,11 +234,10 @@ object MainGenericRunner {
sys.props("script.path") = targetScript.toPath.toAbsolutePath.normalize.toString
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL)
val res = if mainClass.nonEmpty then
if mainClass.nonEmpty then
ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs)
else
Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar"))
errorFn("", res)

else
val properArgs =
Expand All @@ -246,7 +247,8 @@ object MainGenericRunner {
++ settings.scalaArgs
++ List("-script", settings.targetScript)
++ settings.scriptArgs
scripting.Main.main(properArgs.toArray)
scripting.Main.process(properArgs.toArray)

case ExecuteMode.Expression =>
val cp = settings.classPath match {
case Nil => ""
Expand All @@ -265,12 +267,17 @@ object MainGenericRunner {
else
run(settings.withExecuteMode(ExecuteMode.Repl))

run(settings)
run(settings) match
case Some(ex: (StringDriverException | ScriptingException)) => errorFn(ex.getMessage)
case e @ Some(ex) => errorFn("", e)
case _ => true


def errorFn(str: String, e: Option[Throwable] = None, isFailure: Boolean = true): Boolean = {
def errorFn(str: String, e: Option[Throwable] = None): Boolean =
if (str.nonEmpty) Console.err.println(str)
e.foreach(_.printStackTrace())
!isFailure
}
false

def main(args: Array[String]): Unit =
if (!process(args)) System.exit(1)

}
18 changes: 10 additions & 8 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,22 @@ object TastyPrinter:
for arg <- args do
if arg == "-color:never" then () // skip
else if arg.startsWith("-") then println(s"bad option '$arg' was ignored")
else if arg.endsWith(".tasty") then {
else if arg.endsWith(".tasty") then
val path = Paths.get(arg)
if Files.exists(path) then printTasty(arg, Files.readAllBytes(path).nn)
else println("File not found: " + arg)
}
else if arg.endsWith(".jar") then {
if Files.exists(path) then
printTasty(arg, Files.readAllBytes(path).nn)
else
println("File not found: " + arg)
System.exit(1)
else if arg.endsWith(".jar") then
val jar = JarArchive.open(Path(arg), create = false)
try
for file <- jar.iterator() if file.name.endsWith(".tasty") do
printTasty(s"$arg ${file.path}", file.toByteArray)
finally jar.close()

}
else println(s"Not a '.tasty' or '.jar' file: $arg")
else
println(s"Not a '.tasty' or '.jar' file: $arg")
System.exit(1)

if printLastLine then
println(line)
Expand Down
15 changes: 7 additions & 8 deletions compiler/src/dotty/tools/scripting/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,23 @@ object Main:
(compilerArgs, file, scriptArgs, saveJar, invokeFlag)
end distinguishArgs

def main(args: Array[String]): Unit =
def process(args: Array[String]): Option[Throwable] =
val (compilerArgs, scriptFile, scriptArgs, saveJar, invokeFlag) = distinguishArgs(args)
val driver = ScriptingDriver(compilerArgs, scriptFile, scriptArgs)
try driver.compileAndRun { (outDir:Path, classpathEntries:Seq[Path], mainClass: String) =>
driver.compileAndRun { (outDir:Path, classpathEntries:Seq[Path], mainClass: String) =>
// write expanded classpath to java.class.path property, so called script can see it
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
if saveJar then
// write a standalone jar to the script parent directory
writeJarfile(outDir, scriptFile, scriptArgs, classpathEntries, mainClass)
invokeFlag
}
catch
case ScriptingException(msg) =>
println(s"Error: $msg")
sys.exit(1)

case e: java.lang.reflect.InvocationTargetException =>
throw e.getCause
def main(args: Array[String]): Unit =
process(args).map {
case ScriptingException(msg) => println(msg)
case ex => ex.printStackTrace
}.foreach(_ => System.exit(1))

private def writeJarfile(outDir: Path, scriptFile: File, scriptArgs:Array[String],
classpathEntries:Seq[Path], mainClassName: String): Unit =
Expand Down
41 changes: 20 additions & 21 deletions compiler/src/dotty/tools/scripting/ScriptingDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
import Util.*

class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver:
def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Unit =
def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Option[Throwable] =
val outDir = Files.createTempDirectory("scala3-scripting")
outDir.toFile.deleteOnExit()
setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh) match
Expand All @@ -20,26 +20,25 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs:
new PlainDirectory(Directory(outDir)))

if doCompile(newCompiler, toCompile).hasErrors then
throw ScriptingException("Errors encountered during compilation")

try
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString)
val invokeMain: Boolean =
Option(pack) match
case Some(func) =>
func(outDir, classpathEntries, mainClass)
case None =>
true
end match
if invokeMain then mainMethod.invoke(null, scriptArgs)
catch
case e: java.lang.reflect.InvocationTargetException =>
throw e.getCause
finally
deleteFile(outDir.toFile)
case None =>
Some(ScriptingException("Errors encountered during compilation"))
else
try
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString) match
case Right((mainClass, mainMethod)) =>
val invokeMain: Boolean = Option(pack).map { func =>
func(outDir, classpathEntries, mainClass)
}.getOrElse(true)
if invokeMain then mainMethod.invoke(null, scriptArgs)
None
case Left(ex) => Some(ex)
catch
case e: java.lang.reflect.InvocationTargetException =>
Some(e.getCause)
finally
deleteFile(outDir.toFile)
case None => None
end compileAndRun

end ScriptingDriver
Expand Down
41 changes: 23 additions & 18 deletions compiler/src/dotty/tools/scripting/StringDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,43 @@ import dotty.tools.dotc.Driver
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx }
import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
import Util.*
import dotty.tools.dotc.util.SourceFile

class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Driver:
override def sourcesRequired: Boolean = false

def compileAndRun(classpath: List[String] = Nil): Unit =
def compileAndRun(classpath: List[String] = Nil): Option[Throwable] =
val outDir = Files.createTempDirectory("scala3-expression")
outDir.toFile.deleteOnExit()

setup(compilerArgs, initCtx.fresh) match
case Some((toCompile, rootCtx)) =>
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
new PlainDirectory(Directory(outDir)))
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, new PlainDirectory(Directory(outDir)))

val compiler = newCompiler
compiler.newRun.compileFromStrings(List(scalaSource))

val source = SourceFile.virtual("expression", scalaSource)
compiler.newRun.compileSources(List(source))

val output = ctx.settings.outputDir.value
if ctx.reporter.hasErrors then
throw StringDriverException("Errors encountered during compilation")

try
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource)
mainMethod.invoke(null, Array.empty[String])
catch
case e: java.lang.reflect.InvocationTargetException =>
throw e.getCause
finally
deleteFile(outDir.toFile)
case None =>
Some(StringDriverException("Errors encountered during compilation"))
else
try
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
detectMainClassAndMethod(outDir, classpathEntries, scalaSource) match
case Right((mainClass, mainMethod)) =>
mainMethod.invoke(null, Array.empty[String])
None
case Left(ex) => Some(ex)
catch
case e: java.lang.reflect.InvocationTargetException =>
Some(e.getCause)
finally
deleteFile(outDir.toFile)
case None => None
end compileAndRun

end StringDriver
Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/scripting/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import java.net.{ URLClassLoader }
import java.lang.reflect.{ Modifier, Method }

object Util:

def deleteFile(target: File): Unit =
if target.isDirectory then
for member <- target.listFiles.toList
do deleteFile(member)
target.delete()
end deleteFile

def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (String, Method) =
def detectMainClassAndMethod(
outDir: Path,
classpathEntries: Seq[Path],
srcFile: String
): Either[Throwable, (String, Method)] =
val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL }
val cl = URLClassLoader(classpathUrls.toArray)

Expand Down Expand Up @@ -48,11 +51,10 @@ object Util:

mains match
case Nil =>
throw StringDriverException(s"No main methods detected for [${srcFile}]")
Left(StringDriverException(s"No main methods detected for [${srcFile}]"))
case _ :: _ :: _ =>
throw StringDriverException(
s"internal error: Detected the following main methods:\n${mains.mkString("\n")}")
case m :: Nil => m
Left(StringDriverException(s"Internal error: Detected the following main methods:\n${mains.mkString("\n")}"))
case mainMethod :: Nil => Right(mainMethod)
end match
end detectMainClassAndMethod

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def compileError = prin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def positiveTest = println("Hello World!")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def runtimeError = throw RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def main = prin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def scriptPositive = println("Hello world!")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def scriptRuntimeError = throw RuntimeException()
Loading