From 9f03cf54d19be9231b090e620d72d6f1d513c2d1 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 16 May 2022 17:12:25 +0200 Subject: [PATCH] add MainGenericCompiler --- .../src/dotty/tools/MainGenericCompiler.scala | 196 ++++++++++++++++++ .../src/dotty/tools/MainGenericRunner.scala | 22 +- .../dotc/util/ClasspathFromClassloader.scala | 9 +- .../src/dotty/tools/runner/ObjectRunner.scala | 11 +- .../dotty/tools/runner/ScalaClassLoader.scala | 29 ++- compiler/src/dotty/tools/scripting/Main.scala | 12 +- .../tools/scripting/ScriptingDriver.scala | 22 +- .../dotty/tools/scripting/StringDriver.scala | 8 +- compiler/src/dotty/tools/scripting/Util.scala | 14 +- dist/bin/scalac | 74 ++++--- 10 files changed, 324 insertions(+), 73 deletions(-) create mode 100644 compiler/src/dotty/tools/MainGenericCompiler.scala diff --git a/compiler/src/dotty/tools/MainGenericCompiler.scala b/compiler/src/dotty/tools/MainGenericCompiler.scala new file mode 100644 index 000000000000..119e6294e463 --- /dev/null +++ b/compiler/src/dotty/tools/MainGenericCompiler.scala @@ -0,0 +1,196 @@ +package dotty.tools + +import scala.language.unsafeNulls + +import scala.annotation.tailrec +import scala.io.Source +import scala.util.Try +import java.io.File +import java.lang.Thread +import scala.annotation.internal.sharable +import dotty.tools.dotc.util.ClasspathFromClassloader +import dotty.tools.runner.ObjectRunner +import dotty.tools.dotc.config.Properties.envOrNone +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 + +enum CompileMode: + case Guess + case Compile + case Decompile + case PrintTasty + case Script + case Repl + case Run + +case class CompileSettings( + verbose: Boolean = false, + classPath: List[String] = List.empty, + compileMode: CompileMode = CompileMode.Guess, + exitCode: Int = 0, + javaArgs: List[String] = List.empty, + javaProps: List[(String, String)] = List.empty, + scalaArgs: List[String] = List.empty, + residualArgs: List[String] = List.empty, + scriptArgs: List[String] = List.empty, + targetScript: String = "", + compiler: Boolean = false, + quiet: Boolean = false, + colors: Boolean = false, +) { + def withCompileMode(em: CompileMode): CompileSettings = this.compileMode match + case CompileMode.Guess => + this.copy(compileMode = em) + case _ => + println(s"compile_mode==[$compileMode], attempted overwrite by [$em]") + this.copy(exitCode = 1) + end withCompileMode + + def withScalaArgs(args: String*): CompileSettings = + this.copy(scalaArgs = scalaArgs.appendedAll(args.toList.filter(_.nonEmpty))) + + def withJavaArgs(args: String*): CompileSettings = + this.copy(javaArgs = javaArgs.appendedAll(args.toList.filter(_.nonEmpty))) + + def withJavaProps(args: (String, String)*): CompileSettings = + this.copy(javaProps = javaProps.appendedAll(args.toList)) + + def withResidualArgs(args: String*): CompileSettings = + this.copy(residualArgs = residualArgs.appendedAll(args.toList.filter(_.nonEmpty))) + + def withScriptArgs(args: String*): CompileSettings = + this.copy(scriptArgs = scriptArgs.appendedAll(args.toList.filter(_.nonEmpty))) + + def withTargetScript(file: String): CompileSettings = + Try(Source.fromFile(file)).toOption match + case Some(_) => this.copy(targetScript = file) + case None => + println(s"not found $file") + this.copy(exitCode = 2) + end withTargetScript + + def withCompiler: CompileSettings = + this.copy(compiler = true) + + def withQuiet: CompileSettings = + this.copy(quiet = true) + + def withColors: CompileSettings = + this.copy(colors = true) + + def withNoColors: CompileSettings = + this.copy(colors = false) +} + +object MainGenericCompiler { + + val classpathSeparator = File.pathSeparator + + @sharable val javaOption = raw"""-J(.*)""".r + @sharable val javaPropOption = raw"""-D(.+?)=(.?)""".r + @tailrec + def process(args: List[String], settings: CompileSettings): CompileSettings = args match + case Nil => + settings + case "--" :: tail => + process(Nil, settings.withResidualArgs(tail.toList*)) + case ("-v" | "-verbose" | "--verbose") :: tail => + process(tail, settings.withScalaArgs("-verbose")) + case ("-q" | "-quiet") :: tail => + process(tail, settings.withQuiet) + case "-Oshort" :: tail => + process(tail, settings.withJavaArgs("-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1")) + case "-repl" :: tail => + process(tail, settings.withCompileMode(CompileMode.Repl)) + case "-script" :: targetScript :: tail => + process(Nil, settings + .withCompileMode(CompileMode.Script) + .withJavaProps("script.path" -> targetScript) + .withTargetScript(targetScript) + .withScriptArgs(tail.toList*)) + case "-compile" :: tail => + process(tail, settings.withCompileMode(CompileMode.Compile)) + case "-decompile" :: tail => + process(tail, settings.withCompileMode(CompileMode.Decompile)) + case "-print-tasty" :: tail => + process(tail, settings.withCompileMode(CompileMode.PrintTasty)) + case "-run" :: tail => + process(tail, settings.withCompileMode(CompileMode.Run)) + case "-colors" :: tail => + process(tail, settings.withColors) + case "-no-colors" :: tail => + process(tail, settings.withNoColors) + case "-with-compiler" :: tail => + process(tail, settings.withCompiler) + case ("-cp" | "-classpath" | "--class-path") :: cp :: tail => + val cpEntries = cp.split(classpathSeparator).toList + val singleEntryClasspath: Boolean = cpEntries.sizeIs == 1 + val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic + def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip"))) + val (tailargs, newEntries) = if singleEntryClasspath && validGlobbedJar(cpEntries.head) then + // reassemble globbed wildcard classpath + // globdir is wildcard directory for globbed jar files, reconstruct the intended classpath + val cpJars = tail.takeWhile( f => validGlobbedJar(f) ) + val remainingArgs = tail.drop(cpJars.size) + (remainingArgs, cpEntries ++ cpJars) + else + (tail, cpEntries) + + process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty))) + case (o @ javaOption(stripped)) :: tail => + process(tail, settings.withJavaArgs(stripped)) + case (javaPropOption(opt, value)) :: tail => + process(tail, settings.withJavaProps(opt -> value)) + case arg :: tail => + process(tail, settings.withResidualArgs(arg)) + end process + + def main(args: Array[String]): Unit = + val settings = process(args.toList, CompileSettings()) + if settings.exitCode != 0 then System.exit(settings.exitCode) + + def classpathSetting = + if settings.classPath.isEmpty then List() + else List("-classpath", settings.classPath.mkString(classpathSeparator)) + + def reconstructedArgs() = + classpathSetting ++ settings.scalaArgs ++ settings.residualArgs + + def addJavaProps(): Unit = + settings.javaProps.foreach { (k, v) => sys.props(k) = v } + + def run(settings: CompileSettings): Unit = settings.compileMode match + case CompileMode.Compile => + addJavaProps() + val properArgs = reconstructedArgs() + dotty.tools.dotc.Main.main(properArgs.toArray) + case CompileMode.Decompile => + addJavaProps() + val properArgs = reconstructedArgs() + dotty.tools.dotc.decompiler.Main.main(properArgs.toArray) + case CompileMode.PrintTasty => + addJavaProps() + val properArgs = reconstructedArgs() + dotty.tools.dotc.core.tasty.TastyPrinter.main(properArgs.toArray) + case CompileMode.Script => // Naive copy from scalac bash script + addJavaProps() + val properArgs = + reconstructedArgs() + ++ (if settings.compiler then List("-with-compiler") else Nil) + ++ List("-script", settings.targetScript) + ++ settings.scriptArgs + scripting.Main.main(properArgs.toArray) + case CompileMode.Repl | CompileMode.Run => + addJavaProps() + val properArgs = reconstructedArgs() + repl.Main.main(properArgs.toArray) + case CompileMode.Guess => + run(settings.withCompileMode(CompileMode.Compile)) + end run + + run(settings) + end main +} diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index dca7178c52f5..2806abe58ba9 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -12,8 +12,9 @@ import dotty.tools.dotc.util.ClasspathFromClassloader import dotty.tools.runner.ObjectRunner import dotty.tools.dotc.config.Properties.envOrNone import dotty.tools.io.Jar +import dotty.tools.io.ClassPath import dotty.tools.runner.ScalaClassLoader -import java.nio.file.Paths +import java.nio.file.{Path, Paths} import dotty.tools.dotc.config.CommandLineParser import dotty.tools.scripting.StringDriver @@ -170,7 +171,7 @@ object MainGenericRunner { val newSettings = if arg.startsWith("-") then settings else settings.withPossibleEntryPaths(arg).withModeShouldBePossibleRun process(tail, newSettings.withResidualArgs(arg)) end process - + def main(args: Array[String]): Unit = val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")).filter(_.nonEmpty) val allArgs = scalaOpts ++ args @@ -207,14 +208,18 @@ object MainGenericRunner { 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 { + val bootclasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator) + val adjustCompiler = removeCompiler(bootclasspath) + val userClasspath = settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) + val fullClasspath = (adjustCompiler ++ userClasspath :+ ".").mkString(classpathSeparator) + val classpathEntries: Seq[Path] = ClassPath.expandPath(fullClasspath, expandStar=true).map { Paths.get(_) } + val newClasspath = classpathEntries.map(_.toFile.toURI.toURL) + val res = ObjectRunner.runAndCatch(newClasspath, settings.compiler, settings.targetToRun, settings.residualArgs).flatMap { case ex: ClassNotFoundException if ex.getMessage == settings.targetToRun => val file = settings.targetToRun Jar(file).mainClass match case Some(mc) => - ObjectRunner.runAndCatch(newClasspath :+ File(file).toURI.toURL, mc, settings.residualArgs) + ObjectRunner.runAndCatch(newClasspath :+ File(file).toURI.toURL, settings.compiler, mc, settings.residualArgs) case None => Some(IllegalArgumentException(s"No main class defined in manifest in jar: $file")) case ex => Some(ex) @@ -232,7 +237,7 @@ object MainGenericRunner { 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 - ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs) + ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, settings.compiler, mainClass, settings.scriptArgs) else Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar")) errorFn("", res) @@ -242,6 +247,7 @@ object MainGenericRunner { List("-classpath", settings.classPath.mkString(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty)) ++ settings.residualArgs ++ (if settings.save then List("-save") else Nil) + ++ (if settings.compiler then List("-with-compiler") else Nil) ++ settings.scalaArgs ++ List("-script", settings.targetScript) ++ settings.scriptArgs @@ -253,7 +259,7 @@ object MainGenericRunner { } val cpArgs = if cp.isEmpty then Nil else List("-classpath", cp) val properArgs = cpArgs ++ settings.residualArgs ++ settings.scalaArgs - val driver = StringDriver(properArgs.toArray, settings.targetExpression) + val driver = StringDriver(properArgs.toArray, settings.compiler, settings.targetExpression) driver.compileAndRun(settings.classPath) case ExecuteMode.Guess => diff --git a/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala b/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala index 25def103083d..499ccf8e88a4 100644 --- a/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala +++ b/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala @@ -7,6 +7,8 @@ import java.nio.file.Paths import dotty.tools.repl.AbstractFileClassLoader +import java.nio.file.FileSystemNotFoundException + object ClasspathFromClassloader { /** Attempt to recreate a classpath from a classloader. @@ -27,8 +29,11 @@ object ClasspathFromClassloader { // the classpath coming from the child is added at the _end_ of the // classpath. classpathBuff ++= - cl.getURLs.iterator.map(url => Paths.get(url.toURI).toAbsolutePath.toString) - case _ => + cl.getURLs.iterator.flatMap(url => + try Paths.get(url.toURI).toAbsolutePath.toString :: Nil + catch case _: FileSystemNotFoundException => Nil + ) + case _ => if cl.getClass.getName == classOf[AbstractFileClassLoader].getName then // HACK: We can't just collect the classpath from arbitrary parent // classloaders since the current classloader might intentionally diff --git a/compiler/src/dotty/tools/runner/ObjectRunner.scala b/compiler/src/dotty/tools/runner/ObjectRunner.scala index cb8f9d791dfa..50c634c80a2e 100644 --- a/compiler/src/dotty/tools/runner/ObjectRunner.scala +++ b/compiler/src/dotty/tools/runner/ObjectRunner.scala @@ -11,23 +11,24 @@ import java.util.concurrent.ExecutionException * This is a copy implementation from scala/scala scala.tools.nsc.CommonRunner trait */ trait CommonRunner { - /** Run a given object, specified by name, using a + + /** Run a given object, specified by name, using a * specified classpath and argument list. * * @throws java.lang.ClassNotFoundException * @throws java.lang.NoSuchMethodException * @throws java.lang.reflect.InvocationTargetException */ - def run(urls: Seq[URL], objectName: String, arguments: Seq[String]): Unit = { + def run(urls: Seq[URL], withCompiler: Boolean, objectName: String, arguments: Seq[String]): Unit = { import RichClassLoader._ - ScalaClassLoader.fromURLsParallelCapable(urls).run(objectName, arguments) + ScalaClassLoader.filteringCompiler(urls, withCompiler).run(objectName, arguments) } /** Catches any non-fatal exception thrown by run (in the case of InvocationTargetException, * unwrapping it) and returns it in an Option. */ - def runAndCatch(urls: Seq[URL], objectName: String, arguments: Seq[String]): Option[Throwable] = - try { run(urls, objectName, arguments) ; None } + def runAndCatch(urls: Seq[URL], withCompiler: Boolean, objectName: String, arguments: Seq[String]): Option[Throwable] = + try { run(urls, withCompiler, objectName, arguments) ; None } catch { case NonFatal(e) => Some(rootCause(e)) } private def rootCause(x: Throwable): Throwable = x match { diff --git a/compiler/src/dotty/tools/runner/ScalaClassLoader.scala b/compiler/src/dotty/tools/runner/ScalaClassLoader.scala index bbc028b217c1..6cb617d9f94d 100644 --- a/compiler/src/dotty/tools/runner/ScalaClassLoader.scala +++ b/compiler/src/dotty/tools/runner/ScalaClassLoader.scala @@ -12,6 +12,7 @@ import java.lang.reflect.{ InvocationTargetException, UndeclaredThrowableExcepti import scala.annotation.internal.sharable import scala.annotation.tailrec import scala.util.control.Exception.catching +import java.lang.reflect.Method final class RichClassLoader(private val self: ClassLoader) extends AnyVal { /** Execute an action with this classloader as context classloader. */ @@ -33,7 +34,11 @@ final class RichClassLoader(private val self: ClassLoader) extends AnyVal { val method = clsToRun.getMethod("main", classOf[Array[String]]) if !Modifier.isStatic(method.getModifiers) then throw new NoSuchMethodException(s"$objectName.main is not static") - try asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*)) + run(method, arguments.toArray) + } + + def run(main: Method, arguments: Array[String]): Unit = { + try asContext(main.invoke(null, Array(arguments: AnyRef): _*)) catch unwrapHandler({ case ex => throw ex }) } @@ -59,10 +64,30 @@ object RichClassLoader { } object ScalaClassLoader { + + private val compilerClassPrefixes = List( + "dotty.tools", + "scala.quoted.staging", + "scala.tasty", + ) + def setContext(cl: ClassLoader) = Thread.currentThread.setContextClassLoader(cl) def fromURLsParallelCapable(urls: Seq[URL], parent: ClassLoader | Null = null): URLClassLoader = - new URLClassLoader(urls.toArray, if parent == null then bootClassLoader else parent) + filteringCompiler(urls, withCompiler = true, parent) + + def filteringCompiler(urls: Seq[URL], withCompiler: Boolean, parent: ClassLoader | Null = null): URLClassLoader = + new URLClassLoader(urls.toArray, if parent == null then bootClassLoader else parent) { + ClassLoader.registerAsParallelCapable() + + override def loadClass(name: String, resolve: Boolean): Class[?] = { + if !withCompiler && compilerClassPrefixes.exists(name.startsWith) then + throw new ClassNotFoundException(s"Class $name can not be loaded without the `-with-compiler` flag.") + else + super.loadClass(name, resolve) + } + + } @sharable private[this] val bootClassLoader: ClassLoader = if scala.util.Properties.isJavaAtLeast("9") then diff --git a/compiler/src/dotty/tools/scripting/Main.scala b/compiler/src/dotty/tools/scripting/Main.scala index f5259974f16f..cebf59b7ae7d 100755 --- a/compiler/src/dotty/tools/scripting/Main.scala +++ b/compiler/src/dotty/tools/scripting/Main.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.config.Properties.isWin object Main: /** All arguments before -script are compiler arguments. All arguments afterwards are script arguments.*/ - private def distinguishArgs(args: Array[String]): (Array[String], File, Array[String], Boolean, Boolean) = + private def distinguishArgs(args: Array[String]): (Array[String], File, Array[String], Boolean, Boolean, Boolean) = val (leftArgs, rest) = args.splitAt(args.indexOf("-script")) assert(rest.size >= 2, s"internal error: rest == Array(${rest.mkString(",")})") @@ -20,7 +20,11 @@ object Main: val scriptArgs = rest.drop(2) var saveJar = false var invokeFlag = true // by default, script main method is invoked + var withCompiler = false // by default, script is run without compiler val compilerArgs = leftArgs.filter { + case "-with-compiler" => + withCompiler = true + false case "-save" | "-savecompiled" => saveJar = true false @@ -30,12 +34,12 @@ object Main: case _ => true } - (compilerArgs, file, scriptArgs, saveJar, invokeFlag) + (compilerArgs, file, scriptArgs, saveJar, invokeFlag, withCompiler) end distinguishArgs def main(args: Array[String]): Unit = - val (compilerArgs, scriptFile, scriptArgs, saveJar, invokeFlag) = distinguishArgs(args) - val driver = ScriptingDriver(compilerArgs, scriptFile, scriptArgs) + val (compilerArgs, scriptFile, scriptArgs, saveJar, invokeFlag, withCompiler) = distinguishArgs(args) + val driver = ScriptingDriver(compilerArgs, withCompiler, scriptFile, scriptArgs) try 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) diff --git a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala index 38765ac6dafa..fa7641d43df3 100755 --- a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala +++ b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala @@ -9,8 +9,10 @@ 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.runner.RichClassLoader.given +import dotty.tools.dotc.util.ClasspathFromClassloader -class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver: +class ScriptingDriver(compilerArgs: Array[String], withCompiler: Boolean, scriptFile: File, scriptArgs: Array[String]) extends Driver: def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Unit = val outDir = Files.createTempDirectory("scala3-scripting") outDir.toFile.deleteOnExit() @@ -22,10 +24,19 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: if doCompile(newCompiler, toCompile).hasErrors then throw ScriptingException("Errors encountered during compilation") + def removeCompiler(cp: Array[String]) = + if (!withCompiler) then // Let's remove compiler from the classpath + val compilerLibs = Seq("scala3-compiler", "scala3-interfaces", "tasty-core", "scala-asm", "scala3-staging", "scala3-tasty-inspector") + cp.filterNot(c => compilerLibs.exists(c.contains)) + else + cp + 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 bootclasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(pathsep) + val withCompilerClasspath = removeCompiler(bootclasspath).mkString(pathsep) + val fullClasspath = s"${ctx.settings.classpath.value}${pathsep}${withCompilerClasspath}" + val classpathEntries: Seq[Path] = ClassPath.expandPath(fullClasspath, expandStar=true).map { Paths.get(_) } + val (cl, mainClass, mainMethod) = detectMainClassAndMethod(outDir, withCompiler, classpathEntries, scriptFile.toString) val invokeMain: Boolean = Option(pack) match case Some(func) => @@ -33,7 +44,8 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: case None => true end match - if invokeMain then mainMethod.invoke(null, scriptArgs) + if invokeMain then + cl.run(mainMethod, scriptArgs) catch case e: java.lang.reflect.InvocationTargetException => throw e.getCause diff --git a/compiler/src/dotty/tools/scripting/StringDriver.scala b/compiler/src/dotty/tools/scripting/StringDriver.scala index b8b64994f285..40908c8ea9b6 100755 --- a/compiler/src/dotty/tools/scripting/StringDriver.scala +++ b/compiler/src/dotty/tools/scripting/StringDriver.scala @@ -8,8 +8,9 @@ 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.runner.RichClassLoader.given -class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Driver: +class StringDriver(compilerArgs: Array[String], withCompiler: Boolean, scalaSource: String) extends Driver: override def sourcesRequired: Boolean = false def compileAndRun(classpath: List[String] = Nil): Unit = @@ -31,9 +32,10 @@ class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Dri 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(_) } + // write expanded classpath to java.class.path property, so called script can see it sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep) - val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource) - mainMethod.invoke(null, Array.empty[String]) + val (cl, mainClass, mainMethod) = detectMainClassAndMethod(outDir, withCompiler, classpathEntries, scalaSource) + cl.run(mainMethod, Array.empty[String]) catch case e: java.lang.reflect.InvocationTargetException => throw e.getCause diff --git a/compiler/src/dotty/tools/scripting/Util.scala b/compiler/src/dotty/tools/scripting/Util.scala index 71ae4413de55..cdd362d23166 100755 --- a/compiler/src/dotty/tools/scripting/Util.scala +++ b/compiler/src/dotty/tools/scripting/Util.scala @@ -7,6 +7,9 @@ import java.io.File import java.net.{ URLClassLoader } import java.lang.reflect.{ Modifier, Method } +import dotty.tools.runner.ScalaClassLoader +import dotty.tools.runner.RichClassLoader.given + object Util: def deleteFile(target: File): Unit = @@ -16,11 +19,11 @@ object Util: target.delete() end deleteFile - def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (String, Method) = + def detectMainClassAndMethod(outDir: Path, withCompiler: Boolean, classpathEntries: Seq[Path], srcFile: String): (URLClassLoader, String, Method) = val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL } - val cl = URLClassLoader(classpathUrls.toArray) + val cl = ScalaClassLoader.filteringCompiler(classpathUrls, withCompiler) - def collectMainMethods(target: File, path: String): List[(String, Method)] = + def collectMainMethods(target: File, path: String): List[(URLClassLoader, String, Method)] = val nameWithoutExtension = target.getName.takeWhile(_ != '.') val targetPath = if path.nonEmpty then s"${path}.${nameWithoutExtension}" @@ -32,10 +35,10 @@ object Util: membersMainMethod <- collectMainMethods(packageMember, targetPath) yield membersMainMethod else if target.getName.endsWith(".class") then - val cls = cl.loadClass(targetPath) + val cls = cl.tryToInitializeClass(targetPath).getOrElse(throw new ClassNotFoundException(targetPath)) try val method = cls.getMethod("main", classOf[Array[String]]) - if Modifier.isStatic(method.getModifiers) then List((cls.getName, method)) else Nil + if Modifier.isStatic(method.getModifiers) then List((cl, cls.getName, method)) else Nil catch case _: java.lang.NoSuchMethodException => Nil else Nil @@ -59,4 +62,3 @@ object Util: def pathsep = sys.props("path.separator") end Util - diff --git a/dist/bin/scalac b/dist/bin/scalac index 6ebf0fe70ea8..d490d3d48c12 100644 --- a/dist/bin/scalac +++ b/dist/bin/scalac @@ -30,43 +30,42 @@ source "$PROG_HOME/bin/common" [ -z "$PROG_NAME" ] && PROG_NAME=$CompilerMain -withCompiler=true - while [[ $# -gt 0 ]]; do -case "$1" in - --) shift; for arg; do addResidual "$arg"; done; set -- ;; - -v|-verbose) verbose=true && addScala "-verbose" && shift ;; - -q|-quiet) quiet=true && shift ;; - - # Optimize for short-running applications, see https://github.com/lampepfl/dotty/issues/222 - -Oshort) addJava "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" && shift ;; - -repl) PROG_NAME="$ReplMain" && shift ;; - -script) PROG_NAME="$ScriptingMain" && target_script="$2" && shift && shift - while [[ $# -gt 0 ]]; do addScript "$1" && shift ; done ;; - -compile) PROG_NAME="$CompilerMain" && shift ;; - -decompile) PROG_NAME="$DecompilerMain" && shift ;; - -print-tasty) PROG_NAME="$TastyPrinterMain" && shift ;; - -run) PROG_NAME="$ReplMain" && shift ;; - -colors) colors=true && shift ;; - -no-colors) unset colors && shift ;; - -with-compiler) jvm_cp_args="$PSEP$DOTTY_COMP$PSEP$TASTY_CORE" && shift ;; - - # break out -D and -J options and add them to java_args so - # they reach the JVM in time to do some good. The -D options - # will be available as system properties. - -D*) addJava "$1" && shift ;; - -J*) addJava "${1:2}" && shift ;; - *) addResidual "$1" && shift ;; + case "$1" in + -D*) + # pass to scala as well: otherwise we lose it sometimes when we + # need it, e.g. communicating with a server compiler. + addJava "$1" + addScala "$1" + # respect user-supplied -Dscala.usejavacp + shift + ;; + -J*) + # as with -D, pass to scala even though it will almost + # never be used. + addJava "${1:2}" + addScala "$1" + shift + ;; + -classpath*) + if [ "$1" != "${1##* }" ]; then + # hashbang-combined args "-classpath 'lib/*'" + A=$1 ; shift # consume $1 before adding its substrings back + set -- $A "$@" # split $1 on whitespace and put it back + else + addScala "$1" + shift + fi + ;; + *) + addScala "$1" + shift + ;; esac done compilerJavaClasspathArgs -if [ "$PROG_NAME" == "$ScriptingMain" ]; then - setScriptName="-Dscript.path=$target_script" - scripting_string="-script $target_script ${script_args[@]}" -fi - [ -n "$script_trace" ] && set -x [ -z "${ConEmuPID-}" -o -n "${cygwin-}" ] && export MSYSTEM= PWD= # workaround for #12405 @@ -75,11 +74,10 @@ eval "\"$JAVACMD\"" \ ${JAVA_OPTS:-$default_java_opts} \ "${java_args[@]}" \ "-classpath \"$jvm_cp_args\"" \ - -Dscala.usejavacp=true \ - "$setScriptName" \ - "$PROG_NAME" \ - "${scala_args[@]}" \ - "${residual_args[@]}" \ - "${scripting_string-}" -scala_exit_status=$? + "-Dscala.usejavacp=true" \ + "-Dscala.home=$PROG_HOME" \ + "dotty.tools.MainGenericCompiler" \ + "${scala_args[@]}" +scala_exit_status=$? +onExit