diff --git a/README.md b/README.md index 7c61827..9e11bfb 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Prerequisite: jdk11+ * [Attach a debugger (remote jvm debug)](#attach-a-debugger-remote-jvm-debug) - [Server mode](#server-mode) - [Embed into your own project](#embed-into-your-own-project) -- [Global predef file: `~/.srp.sc`](#global-predef-file-srpsc) +- [Global predef file: `~/.scala-repl-pp.sc`](#global-predef-file-scala-repl-ppsc) - [Verbose mode](#verbose-mode) - [Inherited classpath](#inherited-classpath) - [Parameters cheat sheet: the most important ones](#parameters-cheat-sheet-the-most-important-ones) @@ -479,11 +479,11 @@ stringcalc> add(One, Two) val res0: stringcalc.Number = Number(3) ``` -## Global predef file: `~/.srp.sc` -Code that should be available across all srp sessions can be written into your local `~/.srp.sc`. +## Global predef file: `~/.scala-repl-pp.sc` +Code that should be available across all srp sessions can be written into your local `~/.scala-repl-pp.sc`. ``` -echo 'def bar = 90' > ~/.srp.sc +echo 'def bar = 90' > ~/.scala-repl-pp.sc echo 'def baz = 91' > script1.sc echo 'def bam = 92' > script2.sc diff --git a/core/src/main/scala/replpp/package.scala b/core/src/main/scala/replpp/package.scala index 3f0d88d..3f8b9be 100644 --- a/core/src/main/scala/replpp/package.scala +++ b/core/src/main/scala/replpp/package.scala @@ -67,10 +67,11 @@ package object replpp { /** precompile given predef files (if any) and update Config to include the results in the classpath */ def precompilePredefFiles(config: Config): Config = { - if (config.predefFiles.nonEmpty) { + val allPredefFiles = (config.predefFiles :+ globalPredefFile).filter(Files.exists(_)) + if (allPredefFiles.nonEmpty) { val predefClassfilesDir = new SimpleDriver().compileAndGetOutputDir( replpp.compilerArgs(config), - inputFiles = config.predefFiles, + inputFiles = allPredefFiles, verbose = config.verbose ).get config.withAdditionalClasspathEntry(predefClassfilesDir) diff --git a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala index df91991..7ed386b 100644 --- a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala +++ b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala @@ -3,6 +3,7 @@ package replpp.scripting import replpp.{Config, allPredefFiles} import java.nio.file.Files +import scala.util.{Failure, Success} /** * Main entrypoint for ScriptingDriver, i.e. it takes commandline arguments and executes a script on the current JVM. @@ -43,13 +44,12 @@ object NonForkingScriptRunner { scriptArgs = scriptArgs.toArray, verbose = verboseEnabled ).compileAndRun() match { - case Some(exception) => + case Success(_) => // no exception, i.e. all is good + if (verboseEnabled) System.err.println(s"script finished successfully") + case Failure(exception) => System.err.println(s"error during script execution: ${exception.getMessage}") throw exception - case None => // no exception, i.e. all is good - if (verboseEnabled) System.err.println(s"script finished successfully") } } - -} +} \ No newline at end of file diff --git a/core/src/main/scala/replpp/scripting/ScriptingDriver.scala b/core/src/main/scala/replpp/scripting/ScriptingDriver.scala index ea9387e..4602a17 100644 --- a/core/src/main/scala/replpp/scripting/ScriptingDriver.scala +++ b/core/src/main/scala/replpp/scripting/ScriptingDriver.scala @@ -10,6 +10,8 @@ import java.lang.reflect.Method import java.net.URLClassLoader import java.nio.file.{Files, Path, Paths} import scala.language.unsafeNulls +import scala.util.control.NonFatal +import scala.util.{Failure, Try} /** * Runs a given script on the current JVM. @@ -19,9 +21,13 @@ import scala.language.unsafeNulls * because we have a fixed class and method name that ScriptRunner uses when it embeds the script and predef code. * */ class ScriptingDriver(compilerArgs: Array[String], predefFiles: Seq[Path], scriptFile: Path, scriptArgs: Array[String], verbose: Boolean) { - val wrappingResult = WrapForMainArgs(Files.readString(scriptFile)) - val wrappedScript = Files.createTempFile("wrapped-script", ".sc") + private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile)) + private val wrappedScript = Files.createTempFile("wrapped-script", ".sc") + private val tempFiles = Seq.newBuilder[Path] + private var executed = false + Files.writeString(wrappedScript, wrappingResult.fullScript) + tempFiles += wrappedScript if (verbose) { println(s"predefFiles: ${predefFiles.mkString(";")}") @@ -31,27 +37,26 @@ class ScriptingDriver(compilerArgs: Array[String], predefFiles: Seq[Path], scrip println(s"compiler arguments: ${compilerArgs.mkString(",")}") } - // TODO change return type to Try[A]? - def compileAndRun(): Option[Throwable] = { - val inputFiles = wrappedScript +: predefFiles - new SimpleDriver(lineNumberReportingAdjustment = -wrappingResult.linesBeforeWrappedCode) - .compile(compilerArgs, inputFiles, verbose) { (ctx, outDir) => - given Context = ctx - val inheritedClasspath = ctx.settings.classpath.value - val classpathEntries = ClassPath.expandPath(inheritedClasspath, expandStar = true).map(Paths.get(_)) - val mainMethod = lookupMainMethod(outDir, classpathEntries) - try { + def compileAndRun(): Try[Unit] = { + assert(!executed, "scripting driver can only be used once, and this instance has already been used.") + executed = true + val inputFiles = (wrappedScript +: predefFiles).filter(Files.exists(_)) + try { + new SimpleDriver(lineNumberReportingAdjustment = -wrappingResult.linesBeforeWrappedCode) + .compile(compilerArgs, inputFiles, verbose) { (ctx, outDir) => + given Context = ctx + tempFiles += outDir + + val inheritedClasspath = ctx.settings.classpath.value + val classpathEntries = ClassPath.expandPath(inheritedClasspath, expandStar = true).map(Paths.get(_)) + val mainMethod = lookupMainMethod(outDir, classpathEntries) mainMethod.invoke(null, scriptArgs) - None // i.e. no Throwable - this is the 'good case' in the Driver api - } catch { - case e: java.lang.reflect.InvocationTargetException => - System.err.println(s"note: we wrapped the given script in some additional code. Use --verbose to see the full script content") - Some(e.getCause) - } finally { - deleteRecursively(outDir) - Files.deleteIfExists(wrappedScript) } - }.flatten + } catch { + case NonFatal(e) => Failure(e) + } finally { + tempFiles.result().foreach(deleteRecursively) + } } private def lookupMainMethod(outDir: Path, classpathEntries: Seq[Path]): Method = { diff --git a/core/src/main/scala/replpp/util/SimpleDriver.scala b/core/src/main/scala/replpp/util/SimpleDriver.scala index aac2ac5..246e0a0 100644 --- a/core/src/main/scala/replpp/util/SimpleDriver.scala +++ b/core/src/main/scala/replpp/util/SimpleDriver.scala @@ -10,6 +10,7 @@ import replpp.scripting.CompilerError import java.nio.file.{Files, Path} import scala.language.unsafeNulls +import scala.util.Try /** Compiles input files to a temporary directory * @@ -24,20 +25,22 @@ import scala.language.unsafeNulls */ class SimpleDriver(lineNumberReportingAdjustment: Int = 0) extends Driver { - def compileAndGetOutputDir[A](compilerArgs: Array[String], inputFiles: Seq[Path], verbose: Boolean): Option[Path] = + def compileAndGetOutputDir[A](compilerArgs: Array[String], inputFiles: Seq[Path], verbose: Boolean): Try[Path] = compile(compilerArgs, inputFiles, verbose) { (ctx, outDir) => outDir } - - // TODO change return type to `Try[A]?` /** compiles given inputFiles and returns root directory that contains the class and tasty files */ - def compile[A](compilerArgs: Array[String], inputFiles: Seq[Path], verbose: Boolean)(fun: (Context, Path) => A): Option[A] = { + def compile[A](compilerArgs: Array[String], inputFiles: Seq[Path], verbose: Boolean)(fun: (Context, Path) => A): Try[A] = { if (verbose) { println(s"compiler arguments: ${compilerArgs.mkString(",")}") println(s"inputFiles: ${inputFiles.mkString(";")}") } val inputFiles0 = inputFiles.map(pathAsString).toArray - setup(compilerArgs ++ inputFiles0, initCtx.fresh).map { case (toCompile, rootCtx) => + val allArgs = compilerArgs ++ inputFiles0 + Try { + val (toCompile, rootCtx) = setup(allArgs, initCtx.fresh) + .getOrElse(throw CompilerError(s"error during setup with args=`${allArgs.mkString(" ")}`, details should have been reported already on stderr/stdout")) + val outDir = Files.createTempDirectory("scala-repl-pp") given ctx0: Context = { diff --git a/server/src/test/scala/replpp/server/ReplServerTests.scala b/server/src/test/scala/replpp/server/ReplServerTests.scala index 47bbbc7..cd25fed 100644 --- a/server/src/test/scala/replpp/server/ReplServerTests.scala +++ b/server/src/test/scala/replpp/server/ReplServerTests.scala @@ -385,7 +385,7 @@ object Fixture { } def apply[T](predefCode: String = "")(urlToResult: String => T): T = { - val additionalClasspathEntryMaybe = + val additionalClasspathEntryMaybe: Option[Path] = if (predefCode.trim.isEmpty) None else { val predefFile = Files.createTempFile(getClass.getName, "scala") @@ -393,7 +393,7 @@ object Fixture { val predefClassfiles = new SimpleDriver().compileAndGetOutputDir(compilerArgs(additionalClasspathEntryMaybe = None), inputFiles = Seq(predefFile), verbose = false) Files.delete(predefFile) - predefClassfiles + predefClassfiles.toOption } val embeddedRepl = new EmbeddedRepl(compilerArgs(additionalClasspathEntryMaybe))