Skip to content

Commit 6efd92d

Browse files
authored
Merge pull request #15604 from rochala/fix-exit-codes
Fix scala runner exit codes
2 parents 1fb83bd + 6507a32 commit 6efd92d

File tree

14 files changed

+298
-88
lines changed

14 files changed

+298
-88
lines changed

compiler/src/dotty/tools/MainGenericRunner.scala

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import dotty.tools.io.Jar
1515
import dotty.tools.runner.ScalaClassLoader
1616
import java.nio.file.Paths
1717
import dotty.tools.dotc.config.CommandLineParser
18-
import dotty.tools.scripting.StringDriver
18+
import dotty.tools.scripting.{StringDriver, StringDriverException, ScriptingException}
1919

2020
enum ExecuteMode:
2121
case Guess
@@ -118,40 +118,40 @@ object MainGenericRunner {
118118
@sharable val scalaOption = raw"""@.*""".r
119119
@sharable val colorOption = raw"""-color:.*""".r
120120
@tailrec
121-
def process(args: List[String], settings: Settings): Settings = args match
121+
def processArgs(args: List[String], settings: Settings): Settings = args match
122122
case Nil =>
123123
settings
124124
case "-run" :: fqName :: tail =>
125-
process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
125+
processArgs(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
126126
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
127127
val (tailargs, newEntries) = processClasspath(cp, tail)
128-
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
128+
processArgs(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
129129
case ("-version" | "--version") :: _ =>
130130
settings.copy(
131131
executeMode = ExecuteMode.Repl,
132132
residualArgs = List("-version")
133133
)
134134
case ("-v" | "-verbose" | "--verbose") :: tail =>
135-
process(
135+
processArgs(
136136
tail,
137137
settings.copy(
138138
verbose = true,
139139
residualArgs = settings.residualArgs :+ "-verbose"
140140
)
141141
)
142142
case "-save" :: tail =>
143-
process(tail, settings.withSave)
143+
processArgs(tail, settings.withSave)
144144
case "-nosave" :: tail =>
145-
process(tail, settings.noSave)
145+
processArgs(tail, settings.noSave)
146146
case "-with-compiler" :: tail =>
147-
process(tail, settings.withCompiler)
147+
processArgs(tail, settings.withCompiler)
148148
case (o @ javaOption(striped)) :: tail =>
149-
process(tail, settings.withJavaArgs(striped).withScalaArgs(o))
149+
processArgs(tail, settings.withJavaArgs(striped).withScalaArgs(o))
150150
case (o @ scalaOption(_*)) :: tail =>
151151
val remainingArgs = (CommandLineParser.expandArg(o) ++ tail).toList
152-
process(remainingArgs, settings)
152+
processArgs(remainingArgs, settings)
153153
case (o @ colorOption(_*)) :: tail =>
154-
process(tail, settings.withScalaArgs(o))
154+
processArgs(tail, settings.withScalaArgs(o))
155155
case "-e" :: expression :: tail =>
156156
val mainSource = s"@main def main(args: String *): Unit =\n ${expression}"
157157
settings
@@ -169,13 +169,13 @@ object MainGenericRunner {
169169
.withScriptArgs(tail*)
170170
else
171171
val newSettings = if arg.startsWith("-") then settings else settings.withPossibleEntryPaths(arg).withModeShouldBePossibleRun
172-
process(tail, newSettings.withResidualArgs(arg))
173-
end process
172+
processArgs(tail, newSettings.withResidualArgs(arg))
173+
end processArgs
174174

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

181181
def removeCompiler(cp: Array[String]) =
@@ -185,12 +185,13 @@ object MainGenericRunner {
185185
else
186186
cp
187187

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

195196
case ExecuteMode.PossibleRun =>
196197
val newClasspath = (settings.classPath :+ ".").flatMap(_.split(classpathSeparator).filter(_.nonEmpty)).map(File(_).toURI.toURL)
@@ -207,10 +208,11 @@ object MainGenericRunner {
207208
case None =>
208209
settings.withExecuteMode(ExecuteMode.Repl)
209210
run(newSettings)
211+
210212
case ExecuteMode.Run =>
211213
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
212214
val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL)
213-
val res = ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap {
215+
ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap {
214216
case ex: ClassNotFoundException if ex.getMessage == settings.targetToRun =>
215217
val file = settings.targetToRun
216218
Jar(file).mainClass match
@@ -220,7 +222,7 @@ object MainGenericRunner {
220222
Some(IllegalArgumentException(s"No main class defined in manifest in jar: $file"))
221223
case ex => Some(ex)
222224
}
223-
errorFn("", res)
225+
224226
case ExecuteMode.Script =>
225227
val targetScript = Paths.get(settings.targetScript).toFile
226228
val targetJar = settings.targetScript.replaceAll("[.][^\\/]*$", "")+".jar"
@@ -232,11 +234,10 @@ object MainGenericRunner {
232234
sys.props("script.path") = targetScript.toPath.toAbsolutePath.normalize.toString
233235
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
234236
val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL)
235-
val res = if mainClass.nonEmpty then
237+
if mainClass.nonEmpty then
236238
ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs)
237239
else
238240
Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar"))
239-
errorFn("", res)
240241

241242
else
242243
val properArgs =
@@ -246,7 +247,8 @@ object MainGenericRunner {
246247
++ settings.scalaArgs
247248
++ List("-script", settings.targetScript)
248249
++ settings.scriptArgs
249-
scripting.Main.main(properArgs.toArray)
250+
scripting.Main.process(properArgs.toArray)
251+
250252
case ExecuteMode.Expression =>
251253
val cp = settings.classPath match {
252254
case Nil => ""
@@ -265,12 +267,17 @@ object MainGenericRunner {
265267
else
266268
run(settings.withExecuteMode(ExecuteMode.Repl))
267269

268-
run(settings)
270+
run(settings) match
271+
case Some(ex: (StringDriverException | ScriptingException)) => errorFn(ex.getMessage)
272+
case e @ Some(ex) => errorFn("", e)
273+
case _ => true
269274

270-
271-
def errorFn(str: String, e: Option[Throwable] = None, isFailure: Boolean = true): Boolean = {
275+
def errorFn(str: String, e: Option[Throwable] = None): Boolean =
272276
if (str.nonEmpty) Console.err.println(str)
273277
e.foreach(_.printStackTrace())
274-
!isFailure
275-
}
278+
false
279+
280+
def main(args: Array[String]): Unit =
281+
if (!process(args)) System.exit(1)
282+
276283
}

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,22 @@ object TastyPrinter:
3737
for arg <- args do
3838
if arg == "-color:never" then () // skip
3939
else if arg.startsWith("-") then println(s"bad option '$arg' was ignored")
40-
else if arg.endsWith(".tasty") then {
40+
else if arg.endsWith(".tasty") then
4141
val path = Paths.get(arg)
42-
if Files.exists(path) then printTasty(arg, Files.readAllBytes(path).nn)
43-
else println("File not found: " + arg)
44-
}
45-
else if arg.endsWith(".jar") then {
42+
if Files.exists(path) then
43+
printTasty(arg, Files.readAllBytes(path).nn)
44+
else
45+
println("File not found: " + arg)
46+
System.exit(1)
47+
else if arg.endsWith(".jar") then
4648
val jar = JarArchive.open(Path(arg), create = false)
4749
try
4850
for file <- jar.iterator() if file.name.endsWith(".tasty") do
4951
printTasty(s"$arg ${file.path}", file.toByteArray)
5052
finally jar.close()
51-
52-
}
53-
else println(s"Not a '.tasty' or '.jar' file: $arg")
53+
else
54+
println(s"Not a '.tasty' or '.jar' file: $arg")
55+
System.exit(1)
5456

5557
if printLastLine then
5658
println(line)

compiler/src/dotty/tools/scripting/Main.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,23 @@ object Main:
3333
(compilerArgs, file, scriptArgs, saveJar, invokeFlag)
3434
end distinguishArgs
3535

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

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

5554
private def writeJarfile(outDir: Path, scriptFile: File, scriptArgs:Array[String],
5655
classpathEntries:Seq[Path], mainClassName: String): Unit =

compiler/src/dotty/tools/scripting/ScriptingDriver.scala

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
1111
import Util.*
1212

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

2222
if doCompile(newCompiler, toCompile).hasErrors then
23-
throw ScriptingException("Errors encountered during compilation")
24-
25-
try
26-
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
27-
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
28-
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString)
29-
val invokeMain: Boolean =
30-
Option(pack) match
31-
case Some(func) =>
32-
func(outDir, classpathEntries, mainClass)
33-
case None =>
34-
true
35-
end match
36-
if invokeMain then mainMethod.invoke(null, scriptArgs)
37-
catch
38-
case e: java.lang.reflect.InvocationTargetException =>
39-
throw e.getCause
40-
finally
41-
deleteFile(outDir.toFile)
42-
case None =>
23+
Some(ScriptingException("Errors encountered during compilation"))
24+
else
25+
try
26+
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
27+
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
28+
detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString) match
29+
case Right((mainClass, mainMethod)) =>
30+
val invokeMain: Boolean = Option(pack).map { func =>
31+
func(outDir, classpathEntries, mainClass)
32+
}.getOrElse(true)
33+
if invokeMain then mainMethod.invoke(null, scriptArgs)
34+
None
35+
case Left(ex) => Some(ex)
36+
catch
37+
case e: java.lang.reflect.InvocationTargetException =>
38+
Some(e.getCause)
39+
finally
40+
deleteFile(outDir.toFile)
41+
case None => None
4342
end compileAndRun
4443

4544
end ScriptingDriver

compiler/src/dotty/tools/scripting/StringDriver.scala

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,43 @@ import dotty.tools.dotc.Driver
88
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx }
99
import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
1010
import Util.*
11+
import dotty.tools.dotc.util.SourceFile
1112

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

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

1920
setup(compilerArgs, initCtx.fresh) match
2021
case Some((toCompile, rootCtx)) =>
21-
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
22-
new PlainDirectory(Directory(outDir)))
22+
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, new PlainDirectory(Directory(outDir)))
2323

2424
val compiler = newCompiler
25-
compiler.newRun.compileFromStrings(List(scalaSource))
25+
26+
val source = SourceFile.virtual("expression", scalaSource)
27+
compiler.newRun.compileSources(List(source))
2628

2729
val output = ctx.settings.outputDir.value
2830
if ctx.reporter.hasErrors then
29-
throw StringDriverException("Errors encountered during compilation")
30-
31-
try
32-
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
33-
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
34-
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
35-
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource)
36-
mainMethod.invoke(null, Array.empty[String])
37-
catch
38-
case e: java.lang.reflect.InvocationTargetException =>
39-
throw e.getCause
40-
finally
41-
deleteFile(outDir.toFile)
42-
case None =>
31+
Some(StringDriverException("Errors encountered during compilation"))
32+
else
33+
try
34+
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
35+
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
36+
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
37+
detectMainClassAndMethod(outDir, classpathEntries, scalaSource) match
38+
case Right((mainClass, mainMethod)) =>
39+
mainMethod.invoke(null, Array.empty[String])
40+
None
41+
case Left(ex) => Some(ex)
42+
catch
43+
case e: java.lang.reflect.InvocationTargetException =>
44+
Some(e.getCause)
45+
finally
46+
deleteFile(outDir.toFile)
47+
case None => None
4348
end compileAndRun
4449

4550
end StringDriver

compiler/src/dotty/tools/scripting/Util.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ import java.net.{ URLClassLoader }
88
import java.lang.reflect.{ Modifier, Method }
99

1010
object Util:
11-
1211
def deleteFile(target: File): Unit =
1312
if target.isDirectory then
1413
for member <- target.listFiles.toList
1514
do deleteFile(member)
1615
target.delete()
1716
end deleteFile
1817

19-
def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (String, Method) =
18+
def detectMainClassAndMethod(
19+
outDir: Path,
20+
classpathEntries: Seq[Path],
21+
srcFile: String
22+
): Either[Throwable, (String, Method)] =
2023
val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL }
2124
val cl = URLClassLoader(classpathUrls.toArray)
2225

@@ -48,11 +51,10 @@ object Util:
4851

4952
mains match
5053
case Nil =>
51-
throw StringDriverException(s"No main methods detected for [${srcFile}]")
54+
Left(StringDriverException(s"No main methods detected for [${srcFile}]"))
5255
case _ :: _ :: _ =>
53-
throw StringDriverException(
54-
s"internal error: Detected the following main methods:\n${mains.mkString("\n")}")
55-
case m :: Nil => m
56+
Left(StringDriverException(s"Internal error: Detected the following main methods:\n${mains.mkString("\n")}"))
57+
case mainMethod :: Nil => Right(mainMethod)
5658
end match
5759
end detectMainClassAndMethod
5860

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def compileError = prin
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def positiveTest = println("Hello World!")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def runtimeError = throw RuntimeException()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def main = prin
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def scriptPositive = println("Hello world!")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def scriptRuntimeError = throw RuntimeException()

0 commit comments

Comments
 (0)