-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
Dotc.scala
153 lines (130 loc) · 5.54 KB
/
Dotc.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package scala.tools.tastytest
import scala.util.{Try, Success, Failure}
import scala.util.control.NonFatal
import scala.reflect.internal.util.ScalaClassLoader
import scala.reflect.runtime.ReflectionUtils
import java.lang.reflect.{Modifier, Method}
import ClasspathOps._
import java.io.OutputStream
import java.io.BufferedReader
import java.io.PrintWriter
object Dotc extends Script.Command {
final case class ClassLoader private (val parent: ScalaClassLoader)
def initClassloader(): Try[Dotc.ClassLoader] =
Try(Dotc.ClassLoader(ScalaClassLoader.fromURLs(Classpaths.dottyCompiler.asURLs)))
def processIn(op: Dotc.ClassLoader => Int): Int = {
Dotc.initClassloader() match {
case Success(cl) => op(cl)
case Failure(err) =>
println(red(s"could not initialise Scala 3 classpath: $err"))
1
}
}
def loadClass(name: String)(implicit cl: Dotc.ClassLoader) =
Class.forName(name, true, cl.parent)
def invokeStatic(method: Method, args: Seq[Any])(implicit cl: Dotc.ClassLoader) = {
assert(Modifier.isStatic(method.getModifiers), s"$method is not static!")
invoke(method, null, args)
}
def invokeStatic(
className: String,
methodName: String,
args: Seq[(Class[_], Any)],
)(implicit cl: Dotc.ClassLoader): Try[Object] = {
val cls = loadClass(className)
val (tpes, provided) = args.unzip
val method = cls.getMethod(methodName, tpes:_*)
Try {
invokeStatic(method, provided)
}
}
def invoke(method: Method, obj: AnyRef, args: Seq[Any])(implicit cl: Dotc.ClassLoader) = {
inClassloader[AnyRef] {
method.invoke(obj, args.toArray:_*)
}
}
def inClassloader[T](op: => T)(implicit cl: Dotc.ClassLoader): T = {
try cl.parent.asContext[T] {
op
}
catch {
case NonFatal(ex) => throw ReflectionUtils.unwrapThrowable(ex)
}
}
def processMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Boolean] =
processMethodImpl(className)(args, None)
private def makeConsoleReporter(stream: OutputStream)(implicit cl: Dotc.ClassLoader): Try[AnyRef] = Try {
val consoleReporterCls = loadClass("dotty.tools.dotc.reporting.ConsoleReporter")
val ctor = consoleReporterCls.getConstructor(classOf[BufferedReader], classOf[PrintWriter])
val pwriter = new PrintWriter(stream, true)
inClassloader[AnyRef] {
ctor.newInstance(Console.in, pwriter)
}
}
private def processMethodImpl(className: String)(args: Seq[String], writer: Option[OutputStream])(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
val reporterCls = loadClass("dotty.tools.dotc.reporting.Reporter")
val Reporter_hasErrors = reporterCls.getMethod("hasErrors")
val processArgs: Try[Seq[(Class[_], Any)]] = {
writer match {
case Some(stream) =>
val callbackCls = loadClass("dotty.tools.dotc.interfaces.CompilerCallback")
for (myReporter <- makeConsoleReporter(stream)) yield
Seq(classOf[Array[String]] -> args.toArray, reporterCls -> myReporter, callbackCls -> null)
case _ =>
Try(Seq(classOf[Array[String]] -> args.toArray))
}
}
for {
args <- processArgs
reporter <- invokeStatic(className, "process", args)
} yield {
val hasErrors = invoke(Reporter_hasErrors, reporter, Seq.empty).asInstanceOf[Boolean]
!hasErrors
}
}
def mainMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Unit] = {
val mainArgs = Seq(classOf[Array[String]] -> args.toArray)
for (_ <- invokeStatic(className, "main", mainArgs)) yield ()
}
def dotcVersion(implicit cl: Dotc.ClassLoader): String = {
val compilerPropertiesClass = loadClass("dotty.tools.dotc.config.Properties")
val Properties_simpleVersionString = compilerPropertiesClass.getMethod("simpleVersionString")
invokeStatic(Properties_simpleVersionString, Seq.empty).asInstanceOf[String]
}
def dotc(out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] =
dotcImpl(None, out, classpath, additionalSettings, sources:_*)
def dotc(writer: OutputStream, out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] =
dotcImpl(Some(writer), out, classpath, additionalSettings, sources:_*)
def dotcImpl(writer: Option[OutputStream], out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
if (sources.isEmpty) {
Success(true)
}
else {
val libraryDeps = Classpaths.dottyLibrary ++ Classpaths.scalaReflect
val args = Seq(
"-d", out,
"-classpath", libraryDeps.mkString(classpath + Files.classpathSep, Files.classpathSep, ""),
"-deprecation",
"-Xfatal-warnings",
"-color:never",
) ++ additionalSettings ++ sources
if (TastyTest.verbose) {
println(yellow(s"Invoking dotc (version $dotcVersion) with args: $args"))
}
processMethodImpl("dotty.tools.dotc.Main")(args, writer)
}
}
val commandName: String = "dotc"
val describe: String = s"$commandName <out: Directory> <src: File>"
def process(args: String*): Int = {
if (args.length < 2) {
println(red(s"please provide at least two arguments in sub-command: $describe"))
return 1
}
val Seq(out, src, additional @ _*) = args: @unchecked
Dotc.processIn { implicit scala3classloader =>
val success = dotc(out, out, additional, src).get
if (success) 0 else 1
}
}
}