Skip to content

Commit df72930

Browse files
committed
Add support for scala opts, change the way scala program is run by scala main runner
1 parent 0c340bc commit df72930

File tree

3 files changed

+305
-18
lines changed

3 files changed

+305
-18
lines changed

compiler/src/dotty/tools/MainGenericRunner.scala

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import sys.process._
99
import java.io.File
1010
import java.lang.Thread
1111
import scala.annotation.internal.sharable
12+
import dotty.tools.dotc.util.ClasspathFromClassloader
13+
import dotty.tools.runner.ObjectRunner
14+
import dotty.tools.dotc.config.Properties.envOrNone
1215

1316
enum ExecuteMode:
1417
case Guess
@@ -107,7 +110,9 @@ object MainGenericRunner {
107110
process(tail, newSettings.withResidualArgs(arg))
108111

109112
def main(args: Array[String]): Unit =
110-
val settings = process(args.toList, Settings())
113+
val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" "))
114+
val allArgs = scalaOpts ++ args
115+
val settings = process(allArgs.toList, Settings())
111116
if settings.exitCode != 0 then System.exit(settings.exitCode)
112117

113118
def run(mode: ExecuteMode): Unit = mode match
@@ -117,11 +122,9 @@ object MainGenericRunner {
117122
++ settings.residualArgs
118123
repl.Main.main(properArgs.toArray)
119124
case ExecuteMode.Run =>
120-
val properArgs =
121-
val newClasspath = settings.classPath ++ getClasspath :+ "."
122-
List("-classpath", newClasspath.mkString(classpathSeparator)).filter(Function.const(newClasspath.nonEmpty))
123-
++ settings.residualArgs
124-
s"java ${settings.javaArgs.mkString(" ")} ${properArgs.mkString(" ")}".! // For now we collect classpath that coursier provides for convenience
125+
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
126+
val newClasspath = (settings.classPath ++ scalaClasspath :+ ".").map(File(_).toURI.toURL)
127+
errorFn("", ObjectRunner.runAndCatch(newClasspath, settings.residualArgs.head, settings.residualArgs.drop(1)))
125128
case ExecuteMode.Script =>
126129
val properArgs =
127130
List("-classpath", settings.classPath.mkString(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty))
@@ -139,16 +142,10 @@ object MainGenericRunner {
139142

140143
run(settings.executeMode)
141144

142-
private def getClasspath(cl: ClassLoader): Array[String] = cl match
143-
case null => Array()
144-
case u: URLClassLoader => u.getURLs.map(_.toURI.toString) ++ getClasspath(cl.getParent)
145-
case cl if cl.getClass.getName == "jdk.internal.loader.ClassLoaders$AppClassLoader" =>
146-
// Required with JDK >= 9
147-
sys.props.getOrElse("java.class.path", "")
148-
.split(File.pathSeparator)
149-
.filter(_.nonEmpty)
150-
case _ => getClasspath(cl.getParent)
151-
152-
private def getClasspath: List[String] =
153-
getClasspath(Thread.currentThread().getContextClassLoader).toList
145+
146+
def errorFn(str: String, e: Option[Throwable] = None, isFailure: Boolean = true): Boolean = {
147+
if (str.nonEmpty) Console.err.println(str)
148+
e.foreach(_.printStackTrace())
149+
!isFailure
150+
}
154151
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dotty.tools
2+
package runner
3+
4+
import java.net.URL
5+
import scala.util.control.NonFatal
6+
import java.lang.reflect.InvocationTargetException
7+
import java.lang.reflect.UndeclaredThrowableException
8+
import java.util.concurrent.ExecutionException
9+
10+
/**
11+
* This is a copy implementation from scala/scala scala.tools.nsc.CommonRunner trait
12+
*/
13+
trait CommonRunner {
14+
/** Run a given object, specified by name, using a
15+
* specified classpath and argument list.
16+
*
17+
* @throws java.lang.ClassNotFoundException
18+
* @throws java.lang.NoSuchMethodException
19+
* @throws java.lang.reflect.InvocationTargetException
20+
*/
21+
def run(urls: Seq[URL], objectName: String, arguments: Seq[String]): Unit = {
22+
import RichClassLoader._
23+
ScalaClassLoader.fromURLsParallelCapable(urls).run(objectName, arguments)
24+
}
25+
26+
/** Catches any non-fatal exception thrown by run (in the case of InvocationTargetException,
27+
* unwrapping it) and returns it in an Option.
28+
*/
29+
def runAndCatch(urls: Seq[URL], objectName: String, arguments: Seq[String]): Option[Throwable] =
30+
try { run(urls, objectName, arguments) ; None }
31+
catch { case NonFatal(e) => Some(rootCause(e)) }
32+
33+
private def rootCause(x: Throwable): Throwable = x match {
34+
case _: InvocationTargetException |
35+
_: ExceptionInInitializerError |
36+
_: UndeclaredThrowableException |
37+
_: ExecutionException
38+
if x.getCause != null =>
39+
rootCause(x.getCause)
40+
case _ => x
41+
}
42+
}
43+
44+
/** An object that runs another object specified by name.
45+
*
46+
* @author Lex Spoon
47+
*/
48+
object ObjectRunner extends CommonRunner
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package dotty.tools
2+
package runner
3+
4+
import java.lang.invoke.{MethodHandles, MethodType}
5+
6+
import scala.language.implicitConversions
7+
import java.lang.{ClassLoader => JClassLoader}
8+
import java.lang.reflect.Modifier
9+
import java.net.{URLClassLoader => JURLClassLoader}
10+
import java.net.URL
11+
12+
import scala.annotation.tailrec
13+
import scala.util.control.Exception.catching
14+
import scala.reflect.{ClassTag, classTag}
15+
import java.lang.reflect.InvocationTargetException
16+
import java.lang.reflect.UndeclaredThrowableException
17+
import dotty.tools.repl.AbstractFileClassLoader
18+
import dotty.tools.io.AbstractFile
19+
import dotty.tools.io.Streamable
20+
import scala.annotation.internal.sharable
21+
22+
trait HasClassPath {
23+
def classPathURLs: Seq[URL]
24+
}
25+
26+
final class RichClassLoader(private val self: JClassLoader) extends AnyVal {
27+
/** Executing an action with this classloader as context classloader */
28+
def asContext[T](action: => T): T = {
29+
val saved = Thread.currentThread.getContextClassLoader
30+
try { ScalaClassLoader.setContext(self) ; action }
31+
finally ScalaClassLoader.setContext(saved)
32+
}
33+
34+
/** Load and link a class with this classloader */
35+
def tryToLoadClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, initialize = false)
36+
/** Load, link and initialize a class with this classloader */
37+
def tryToInitializeClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, initialize = true)
38+
39+
private def tryClass[T <: AnyRef](path: String, initialize: Boolean): Option[Class[T]] =
40+
catching(classOf[ClassNotFoundException], classOf[SecurityException]) opt
41+
Class.forName(path, initialize, self).asInstanceOf[Class[T]]
42+
43+
/** Create an instance of a class with this classloader */
44+
def create(path: String): AnyRef =
45+
tryToInitializeClass[AnyRef](path).map(_.getConstructor().newInstance()).orNull
46+
47+
/** Create an instance with ctor args, or invoke errorFn before throwing. */
48+
def create[T <: AnyRef : ClassTag](path: String, errorFn: String => Unit)(args: AnyRef*): T = {
49+
def fail(msg: String) = error(msg, new IllegalArgumentException(msg))
50+
def error(msg: String, e: Throwable) = { errorFn(msg) ; throw e }
51+
try {
52+
val clazz = Class.forName(path, /*initialize =*/ true, /*loader =*/ self)
53+
if (classTag[T].runtimeClass isAssignableFrom clazz) {
54+
val ctor = {
55+
val maybes = clazz.getConstructors filter (c => c.getParameterCount == args.size &&
56+
(c.getParameterTypes zip args).forall { case (k, a) => k isAssignableFrom a.getClass })
57+
if (maybes.size == 1) maybes.head
58+
else fail(s"Constructor must accept arg list (${args map (_.getClass.getName) mkString ", "}): ${path}")
59+
}
60+
(ctor.newInstance(args: _*)).asInstanceOf[T]
61+
} else {
62+
errorFn(s"""Loader for ${classTag[T]}: [${show(classTag[T].runtimeClass.getClassLoader)}]
63+
|Loader for ${clazz.getName}: [${show(clazz.getClassLoader)}]""".stripMargin)
64+
fail(s"Not a ${classTag[T]}: ${path}")
65+
}
66+
} catch {
67+
case e: ClassNotFoundException =>
68+
error(s"Class not found: ${path}", e)
69+
case e @ (_: LinkageError | _: ReflectiveOperationException) =>
70+
error(s"Unable to create instance: ${path}: ${e.toString}", e)
71+
}
72+
}
73+
74+
/** The actual bytes for a class file, or an empty array if it can't be found. */
75+
def classBytes(className: String): Array[Byte] = classAsStream(className) match {
76+
case null => Array()
77+
case stream => Streamable.bytes(stream)
78+
}
79+
80+
/** An InputStream representing the given class name, or null if not found. */
81+
def classAsStream(className: String) = self.getResourceAsStream {
82+
if (className endsWith ".class") className
83+
else s"${className.replace('.', '/')}.class" // classNameToPath
84+
}
85+
86+
/** Run the main method of a class to be loaded by this classloader */
87+
def run(objectName: String, arguments: Seq[String]): Unit = {
88+
val clsToRun = tryToInitializeClass(objectName) getOrElse (
89+
throw new ClassNotFoundException(objectName)
90+
)
91+
val method = clsToRun.getMethod("main", classOf[Array[String]])
92+
if (!Modifier.isStatic(method.getModifiers))
93+
throw new NoSuchMethodException(objectName + ".main is not static")
94+
95+
try asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*)) // !!! : AnyRef shouldn't be necessary
96+
catch unwrapHandler({ case ex => throw ex })
97+
}
98+
99+
@tailrec
100+
def unwrapThrowable(x: Throwable): Throwable = x match {
101+
case _: InvocationTargetException | // thrown by reflectively invoked method or constructor
102+
_: ExceptionInInitializerError | // thrown when running a static initializer (e.g. a scala module constructor)
103+
_: UndeclaredThrowableException | // invocation on a proxy instance if its invocation handler's `invoke` throws an exception
104+
_: ClassNotFoundException | // no definition for a class instantiated by name
105+
_: NoClassDefFoundError // the definition existed when the executing class was compiled, but can no longer be found
106+
if x.getCause != null =>
107+
unwrapThrowable(x.getCause)
108+
case _ => x
109+
}
110+
// Transforms an exception handler into one which will only receive the unwrapped
111+
// exceptions (for the values of wrap covered in unwrapThrowable.)
112+
def unwrapHandler[T](pf: PartialFunction[Throwable, T]): PartialFunction[Throwable, T] =
113+
pf.compose({ case ex => unwrapThrowable(ex) })
114+
115+
def show(cl: ClassLoader): String = {
116+
import scala.reflect.Selectable.reflectiveSelectable
117+
118+
@tailrec
119+
def isAbstractFileClassLoader(clazz: Class[_]): Boolean = {
120+
if (clazz == null) return false
121+
if (clazz == classOf[AbstractFileClassLoader]) return true
122+
isAbstractFileClassLoader(clazz.getSuperclass)
123+
}
124+
def inferClasspath(cl: ClassLoader): String = cl match {
125+
case cl: java.net.URLClassLoader if cl.getURLs != null =>
126+
(cl.getURLs mkString ",")
127+
case cl if cl != null && isAbstractFileClassLoader(cl.getClass) =>
128+
cl.asInstanceOf[{val root: AbstractFile}].root.canonicalPath
129+
case null =>
130+
val loadBootCp = (flavor: String) => scala.util.Properties.propOrNone(flavor + ".boot.class.path")
131+
loadBootCp("sun") orElse loadBootCp("java") getOrElse "<unknown>"
132+
case _ =>
133+
"<unknown>"
134+
}
135+
cl match {
136+
case null => s"primordial classloader with boot classpath [${inferClasspath(cl)}]"
137+
case _ => s"$cl of type ${cl.getClass} with classpath [${inferClasspath(cl)}] and parent being ${show(cl.getParent)}"
138+
}
139+
}
140+
}
141+
142+
object RichClassLoader {
143+
implicit def wrapClassLoader(loader: ClassLoader): RichClassLoader = new RichClassLoader(loader)
144+
}
145+
146+
/** A wrapper around java.lang.ClassLoader to lower the annoyance
147+
* of java reflection.
148+
*/
149+
trait ScalaClassLoader extends JClassLoader {
150+
private def wrap = new RichClassLoader(this)
151+
/** Executing an action with this classloader as context classloader */
152+
def asContext[T](action: => T): T = wrap.asContext(action)
153+
154+
/** Load and link a class with this classloader */
155+
def tryToLoadClass[T <: AnyRef](path: String): Option[Class[T]] = wrap.tryToLoadClass[T](path)
156+
/** Load, link and initialize a class with this classloader */
157+
def tryToInitializeClass[T <: AnyRef](path: String): Option[Class[T]] = wrap.tryToInitializeClass(path)
158+
159+
/** Create an instance of a class with this classloader */
160+
def create(path: String): AnyRef = wrap.create(path)
161+
162+
/** Create an instance with ctor args, or invoke errorFn before throwing. */
163+
def create[T <: AnyRef : ClassTag](path: String, errorFn: String => Unit)(args: AnyRef*): T =
164+
wrap.create[T](path, errorFn)(args: _*)
165+
166+
/** The actual bytes for a class file, or an empty array if it can't be found. */
167+
def classBytes(className: String): Array[Byte] = wrap.classBytes(className)
168+
169+
/** An InputStream representing the given class name, or null if not found. */
170+
def classAsStream(className: String) = wrap.classAsStream(className)
171+
172+
/** Run the main method of a class to be loaded by this classloader */
173+
def run(objectName: String, arguments: Seq[String]): Unit = wrap.run(objectName, arguments)
174+
}
175+
176+
177+
/** Methods for obtaining various classloaders.
178+
* appLoader: the application classloader. (Also called the java system classloader.)
179+
* extLoader: the extension classloader.
180+
* bootLoader: the boot classloader.
181+
* contextLoader: the context classloader.
182+
*/
183+
object ScalaClassLoader {
184+
/** Returns loaders which are already ScalaClassLoaders unaltered,
185+
* and translates java.net.URLClassLoaders into scala URLClassLoaders.
186+
* Otherwise creates a new wrapper.
187+
*/
188+
implicit def apply(cl: JClassLoader): ScalaClassLoader = cl match {
189+
case cl: ScalaClassLoader => cl
190+
case cl: JURLClassLoader => new URLClassLoader(cl.getURLs.toSeq, cl.getParent)
191+
case _ => new JClassLoader(cl) with ScalaClassLoader
192+
}
193+
def contextLoader = apply(Thread.currentThread.getContextClassLoader)
194+
def appLoader = apply(JClassLoader.getSystemClassLoader)
195+
def setContext(cl: JClassLoader) = Thread.currentThread.setContextClassLoader(cl)
196+
197+
class URLClassLoader(urls: Seq[URL], parent: JClassLoader)
198+
extends JURLClassLoader(urls.toArray, parent)
199+
with ScalaClassLoader
200+
with HasClassPath {
201+
private[this] var classloaderURLs: Seq[URL] = urls
202+
def classPathURLs: Seq[URL] = classloaderURLs
203+
204+
/** Override to widen to public */
205+
override def addURL(url: URL) = {
206+
classloaderURLs :+= url
207+
super.addURL(url)
208+
}
209+
override def close(): Unit = {
210+
super.close()
211+
classloaderURLs = null
212+
}
213+
}
214+
215+
def fromURLs(urls: Seq[URL], parent: ClassLoader = null): URLClassLoader = {
216+
new URLClassLoader(urls, if (parent == null) bootClassLoader else parent)
217+
}
218+
219+
def fromURLsParallelCapable(urls: Seq[URL], parent: ClassLoader = null): JURLClassLoader = {
220+
new JURLClassLoader(urls.toArray, if (parent == null) bootClassLoader else parent)
221+
}
222+
223+
/** True if supplied class exists in supplied path */
224+
def classExists(urls: Seq[URL], name: String): Boolean =
225+
(fromURLs(urls) tryToLoadClass name).isDefined
226+
227+
/** Finding what jar a clazz or instance came from */
228+
def originOfClass(x: Class[_]): Option[URL] =
229+
Option(x.getProtectionDomain.getCodeSource) flatMap (x => Option(x.getLocation))
230+
231+
@sharable private[this] val bootClassLoader: ClassLoader = {
232+
if (!scala.util.Properties.isJavaAtLeast("9")) null
233+
else {
234+
try {
235+
MethodHandles.lookup().findStatic(classOf[ClassLoader], "getPlatformClassLoader", MethodType.methodType(classOf[ClassLoader])).invoke().asInstanceOf[ClassLoader]
236+
} catch {
237+
case _: Throwable =>
238+
null
239+
}
240+
}
241+
}
242+
}

0 commit comments

Comments
 (0)