
replpp.scripting.ScriptingDriver.scala Maven / Gradle / Ivy
package replpp.scripting
import dotty.tools.dotc.Driver
import dotty.tools.dotc.core.Contexts
import dotty.tools.dotc.core.Contexts.{Context, ctx}
import dotty.tools.io.{ClassPath, Directory, PlainDirectory}
import replpp.pathSeparator
import replpp.scripting.ScriptingDriver.*
import java.io.File
import java.lang.reflect.{Method, Modifier}
import java.net.URLClassLoader
import java.nio.file.{Files, Path, Paths}
import scala.language.unsafeNulls
/**
* Runs a given script on the current JVM.
*
* Similar to dotty.tools.scripting.ScriptingDriver, but simpler and faster.
* Main difference: we don't (need to) recursively look for main method entrypoints in the entire classpath,
* 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], scriptFile: File, scriptArgs: Array[String], verbose: Boolean) extends Driver {
if (verbose) {
println(s"full script content (including wrapper code) -> $scriptFile:")
println(os.read(os.Path(scriptFile.getAbsolutePath)))
println(s"script arguments: ${scriptArgs.mkString(",")}")
println(s"compiler arguments: ${compilerArgs.mkString(",")}")
}
def compileAndRun(): Option[Throwable] = {
setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh).flatMap { case (toCompile, rootCtx) =>
val outDir = os.temp.dir(prefix = "scala3-scripting", deleteOnExit = false)
given Context = {
val ctx = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, new PlainDirectory(Directory(outDir.toNIO)))
if (verbose) {
ctx.setSetting(rootCtx.settings.help, true)
.setSetting(rootCtx.settings.XshowPhases, true)
.setSetting(rootCtx.settings.Vhelp, true)
.setSetting(rootCtx.settings.Vprofile, true)
.setSetting(rootCtx.settings.explain, true)
} else ctx
}
if (doCompile(newCompiler, toCompile).hasErrors) {
val msgAddonMaybe = if (verbose) "" else " - try `--verbose` for more output"
Some(ScriptingException(s"Errors encountered during compilation$msgAddonMaybe"))
} else {
val classpath = s"${outDir.toNIO.toAbsolutePath.toString}$pathSeparator${ctx.settings.classpath.value}"
val classpathEntries = ClassPath.expandPath(classpath, expandStar = true).map(Paths.get(_))
val mainMethod = lookupMainMethod(outDir.toNIO, classpathEntries)
try {
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 => Some(e.getCause)
} finally os.remove.all(outDir)
}
}
}
private def lookupMainMethod(outDir: Path, classpathEntries: Seq[Path]): Method = {
val classpathUrls = (classpathEntries :+ outDir).map(_.toUri.toURL)
val clazz = URLClassLoader(classpathUrls.toArray).loadClass(MainClassName)
clazz.getMethod(MainMethodName, classOf[Array[String]])
}
}
object ScriptingDriver {
val MainClassName = "ScalaReplPP"
val MainMethodName = "main"
}
case class ScriptingException(msg: String) extends RuntimeException(msg)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy