Skip to content

Commit 6326fb7

Browse files
committed
add MainGenericCompiler
1 parent 7fbbeef commit 6326fb7

File tree

6 files changed

+252
-48
lines changed

6 files changed

+252
-48
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package dotty.tools
2+
3+
import scala.language.unsafeNulls
4+
5+
import scala.annotation.tailrec
6+
import scala.io.Source
7+
import scala.util.Try
8+
import java.io.File
9+
import java.lang.Thread
10+
import scala.annotation.internal.sharable
11+
import dotty.tools.dotc.util.ClasspathFromClassloader
12+
import dotty.tools.runner.ObjectRunner
13+
import dotty.tools.dotc.config.Properties.envOrNone
14+
import dotty.tools.io.Jar
15+
import dotty.tools.runner.ScalaClassLoader
16+
import java.nio.file.Paths
17+
import dotty.tools.dotc.config.CommandLineParser
18+
import dotty.tools.scripting.StringDriver
19+
20+
enum CompileMode:
21+
case Guess
22+
case Compile
23+
case Decompile
24+
case PrintTasty
25+
case Script
26+
case Repl
27+
case Run
28+
29+
case class CompileSettings(
30+
verbose: Boolean = false,
31+
classPath: List[String] = List.empty,
32+
compileMode: CompileMode = CompileMode.Guess,
33+
exitCode: Int = 0,
34+
javaArgs: List[String] = List.empty,
35+
javaProps: List[(String, String)] = List.empty,
36+
scalaArgs: List[String] = List.empty,
37+
residualArgs: List[String] = List.empty,
38+
scriptArgs: List[String] = List.empty,
39+
targetScript: String = "",
40+
compiler: Boolean = false,
41+
quiet: Boolean = false,
42+
colors: Boolean = false,
43+
) {
44+
def withCompileMode(em: CompileMode): CompileSettings = this.compileMode match
45+
case CompileMode.Guess =>
46+
this.copy(compileMode = em)
47+
case _ =>
48+
println(s"compile_mode==[$compileMode], attempted overwrite by [$em]")
49+
this.copy(exitCode = 1)
50+
end withCompileMode
51+
52+
def withScalaArgs(args: String*): CompileSettings =
53+
this.copy(scalaArgs = scalaArgs.appendedAll(args.toList.filter(_.nonEmpty)))
54+
55+
def withJavaArgs(args: String*): CompileSettings =
56+
this.copy(javaArgs = javaArgs.appendedAll(args.toList.filter(_.nonEmpty)))
57+
58+
def withJavaProps(args: (String, String)*): CompileSettings =
59+
this.copy(javaProps = javaProps.appendedAll(args.toList))
60+
61+
def withResidualArgs(args: String*): CompileSettings =
62+
this.copy(residualArgs = residualArgs.appendedAll(args.toList.filter(_.nonEmpty)))
63+
64+
def withScriptArgs(args: String*): CompileSettings =
65+
this.copy(scriptArgs = scriptArgs.appendedAll(args.toList.filter(_.nonEmpty)))
66+
67+
def withTargetScript(file: String): CompileSettings =
68+
Try(Source.fromFile(file)).toOption match
69+
case Some(_) => this.copy(targetScript = file)
70+
case None =>
71+
println(s"not found $file")
72+
this.copy(exitCode = 2)
73+
end withTargetScript
74+
75+
def withCompiler: CompileSettings =
76+
this.copy(compiler = true)
77+
78+
def withQuiet: CompileSettings =
79+
this.copy(quiet = true)
80+
81+
def withColors: CompileSettings =
82+
this.copy(colors = true)
83+
84+
def withNoColors: CompileSettings =
85+
this.copy(colors = false)
86+
}
87+
88+
object MainGenericCompiler {
89+
90+
val classpathSeparator = File.pathSeparator
91+
92+
@sharable val javaOption = raw"""-J(.*)""".r
93+
@sharable val javaPropOption = raw"""-D(.+?)=(.?)""".r
94+
@tailrec
95+
def process(args: List[String], settings: CompileSettings): CompileSettings = args match
96+
case Nil =>
97+
settings
98+
case "--" :: tail =>
99+
process(Nil, settings.withResidualArgs(tail.toList*))
100+
case ("-v" | "-verbose" | "--verbose") :: tail =>
101+
process(tail, settings.withScalaArgs("-verbose"))
102+
case ("-q" | "-quiet") :: tail =>
103+
process(tail, settings.withQuiet)
104+
case "-Oshort" :: tail =>
105+
process(tail, settings.withJavaArgs("-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1"))
106+
case "-repl" :: tail =>
107+
process(tail, settings.withCompileMode(CompileMode.Repl))
108+
case "-script" :: targetScript :: tail =>
109+
process(Nil, settings
110+
.withCompileMode(CompileMode.Script)
111+
.withJavaProps("script.path" -> targetScript)
112+
.withTargetScript(targetScript)
113+
.withScriptArgs(tail.toList*))
114+
case "-compile" :: tail =>
115+
process(tail, settings.withCompileMode(CompileMode.Compile))
116+
case "-decompile" :: tail =>
117+
process(tail, settings.withCompileMode(CompileMode.Decompile))
118+
case "-print-tasty" :: tail =>
119+
process(tail, settings.withCompileMode(CompileMode.PrintTasty))
120+
case "-run" :: tail =>
121+
process(tail, settings.withCompileMode(CompileMode.Run))
122+
case "-colors" :: tail =>
123+
process(tail, settings.withColors)
124+
case "-no-colors" :: tail =>
125+
process(tail, settings.withNoColors)
126+
case "-with-compiler" :: tail =>
127+
process(tail, settings.withCompiler)
128+
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
129+
val cpEntries = cp.split(classpathSeparator).toList
130+
val singleEntryClasspath: Boolean = cpEntries.sizeIs == 1
131+
val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic
132+
def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip")))
133+
val (tailargs, newEntries) = if singleEntryClasspath && validGlobbedJar(cpEntries.head) then
134+
// reassemble globbed wildcard classpath
135+
// globdir is wildcard directory for globbed jar files, reconstruct the intended classpath
136+
val cpJars = tail.takeWhile( f => validGlobbedJar(f) )
137+
val remainingArgs = tail.drop(cpJars.size)
138+
(remainingArgs, cpEntries ++ cpJars)
139+
else
140+
(tail, cpEntries)
141+
142+
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
143+
case (o @ javaOption(stripped)) :: tail =>
144+
process(tail, settings.withJavaArgs(stripped))
145+
case (javaPropOption(opt, value)) :: tail =>
146+
process(tail, settings.withJavaProps(opt -> value))
147+
case arg :: tail =>
148+
process(tail, settings.withResidualArgs(arg))
149+
end process
150+
151+
def main(args: Array[String]): Unit =
152+
val settings = process(args.toList, CompileSettings())
153+
if settings.exitCode != 0 then System.exit(settings.exitCode)
154+
155+
def classpathSetting =
156+
if settings.classPath.isEmpty then List()
157+
else List("-classpath", settings.classPath.mkString(classpathSeparator))
158+
159+
def reconstructedArgs() =
160+
classpathSetting ++ settings.scalaArgs ++ settings.residualArgs
161+
162+
def addJavaProps(): Unit =
163+
settings.javaProps.foreach { (k, v) => sys.props(k) = v }
164+
165+
def run(settings: CompileSettings): Unit = settings.compileMode match
166+
case CompileMode.Compile =>
167+
addJavaProps()
168+
val properArgs = reconstructedArgs()
169+
dotty.tools.dotc.Main.main(properArgs.toArray)
170+
case CompileMode.Decompile =>
171+
addJavaProps()
172+
val properArgs = reconstructedArgs()
173+
dotty.tools.dotc.decompiler.Main.main(properArgs.toArray)
174+
case CompileMode.PrintTasty =>
175+
addJavaProps()
176+
val properArgs = reconstructedArgs()
177+
dotty.tools.dotc.core.tasty.TastyPrinter.main(properArgs.toArray)
178+
case CompileMode.Script => // Naive copy from scalac bash script
179+
addJavaProps()
180+
val properArgs =
181+
reconstructedArgs()
182+
++ List("-script", settings.targetScript)
183+
++ settings.scriptArgs
184+
scripting.Main.main(properArgs.toArray)
185+
case CompileMode.Repl | CompileMode.Run =>
186+
addJavaProps()
187+
val properArgs = reconstructedArgs()
188+
repl.Main.main(properArgs.toArray)
189+
case CompileMode.Guess =>
190+
run(settings.withCompileMode(CompileMode.Compile))
191+
end run
192+
193+
run(settings)
194+
end main
195+
}

compiler/src/dotty/tools/runner/ScalaClassLoader.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import java.lang.reflect.{ InvocationTargetException, UndeclaredThrowableExcepti
1212
import scala.annotation.internal.sharable
1313
import scala.annotation.tailrec
1414
import scala.util.control.Exception.catching
15+
import java.lang.reflect.Method
1516

1617
final class RichClassLoader(private val self: ClassLoader) extends AnyVal {
1718
/** Execute an action with this classloader as context classloader. */
@@ -33,7 +34,11 @@ final class RichClassLoader(private val self: ClassLoader) extends AnyVal {
3334
val method = clsToRun.getMethod("main", classOf[Array[String]])
3435
if !Modifier.isStatic(method.getModifiers) then
3536
throw new NoSuchMethodException(s"$objectName.main is not static")
36-
try asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*))
37+
run(method, arguments.toArray)
38+
}
39+
40+
def run(main: Method, arguments: Array[String]): Unit = {
41+
try asContext(main.invoke(null, Array(arguments: AnyRef): _*))
3742
catch unwrapHandler({ case ex => throw ex })
3843
}
3944

compiler/src/dotty/tools/scripting/ScriptingDriver.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dotty.tools.dotc.Driver
99
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx }
1010
import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
1111
import Util.*
12+
import dotty.tools.runner.RichClassLoader.given
1213

1314
class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver:
1415
def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Unit =
@@ -25,15 +26,16 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs:
2526
try
2627
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
2728
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
28-
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString)
29+
val (cl, mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString)
2930
val invokeMain: Boolean =
3031
Option(pack) match
3132
case Some(func) =>
3233
func(outDir, classpathEntries, mainClass)
3334
case None =>
3435
true
3536
end match
36-
if invokeMain then mainMethod.invoke(null, scriptArgs)
37+
if invokeMain then
38+
cl.run(mainMethod, scriptArgs)
3739
catch
3840
case e: java.lang.reflect.InvocationTargetException =>
3941
throw e.getCause

compiler/src/dotty/tools/scripting/StringDriver.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.dotc.Driver
88
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx }
99
import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
1010
import Util.*
11+
import dotty.tools.runner.RichClassLoader.given
1112

1213
class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Driver:
1314
override def sourcesRequired: Boolean = false
@@ -32,8 +33,8 @@ class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Dri
3233
val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}"
3334
val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) }
3435
sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep)
35-
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource)
36-
mainMethod.invoke(null, Array.empty[String])
36+
val (cl, mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource)
37+
cl.run(mainMethod, Array.empty[String])
3738
catch
3839
case e: java.lang.reflect.InvocationTargetException =>
3940
throw e.getCause

compiler/src/dotty/tools/scripting/Util.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import java.io.File
77
import java.net.{ URLClassLoader }
88
import java.lang.reflect.{ Modifier, Method }
99

10+
import dotty.tools.runner.ScalaClassLoader
11+
import dotty.tools.runner.RichClassLoader.given
12+
1013
object Util:
1114

1215
def deleteFile(target: File): Unit =
@@ -16,11 +19,11 @@ object Util:
1619
target.delete()
1720
end deleteFile
1821

19-
def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (String, Method) =
22+
def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (URLClassLoader, String, Method) =
2023
val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL }
21-
val cl = URLClassLoader(classpathUrls.toArray)
24+
val cl = ScalaClassLoader.fromURLsParallelCapable(classpathUrls)
2225

23-
def collectMainMethods(target: File, path: String): List[(String, Method)] =
26+
def collectMainMethods(target: File, path: String): List[(URLClassLoader, String, Method)] =
2427
val nameWithoutExtension = target.getName.takeWhile(_ != '.')
2528
val targetPath =
2629
if path.nonEmpty then s"${path}.${nameWithoutExtension}"
@@ -32,10 +35,10 @@ object Util:
3235
membersMainMethod <- collectMainMethods(packageMember, targetPath)
3336
yield membersMainMethod
3437
else if target.getName.endsWith(".class") then
35-
val cls = cl.loadClass(targetPath)
38+
val cls = cl.tryToInitializeClass(targetPath).getOrElse(throw new ClassNotFoundException(targetPath))
3639
try
3740
val method = cls.getMethod("main", classOf[Array[String]])
38-
if Modifier.isStatic(method.getModifiers) then List((cls.getName, method)) else Nil
41+
if Modifier.isStatic(method.getModifiers) then List((cl, cls.getName, method)) else Nil
3942
catch
4043
case _: java.lang.NoSuchMethodException => Nil
4144
else Nil
@@ -59,4 +62,3 @@ object Util:
5962
def pathsep = sys.props("path.separator")
6063

6164
end Util
62-

dist/bin/scalac

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,44 @@ source "$PROG_HOME/bin/common"
3030

3131
[ -z "$PROG_NAME" ] && PROG_NAME=$CompilerMain
3232

33-
withCompiler=true
33+
compileMode="guess"
3434

3535
while [[ $# -gt 0 ]]; do
36-
case "$1" in
37-
--) shift; for arg; do addResidual "$arg"; done; set -- ;;
38-
-v|-verbose) verbose=true && addScala "-verbose" && shift ;;
39-
-q|-quiet) quiet=true && shift ;;
40-
41-
# Optimize for short-running applications, see https://github.com/lampepfl/dotty/issues/222
42-
-Oshort) addJava "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" && shift ;;
43-
-repl) PROG_NAME="$ReplMain" && shift ;;
44-
-script) PROG_NAME="$ScriptingMain" && target_script="$2" && shift && shift
45-
while [[ $# -gt 0 ]]; do addScript "$1" && shift ; done ;;
46-
-compile) PROG_NAME="$CompilerMain" && shift ;;
47-
-decompile) PROG_NAME="$DecompilerMain" && shift ;;
48-
-print-tasty) PROG_NAME="$TastyPrinterMain" && shift ;;
49-
-run) PROG_NAME="$ReplMain" && shift ;;
50-
-colors) colors=true && shift ;;
51-
-no-colors) unset colors && shift ;;
52-
-with-compiler) jvm_cp_args="$PSEP$DOTTY_COMP$PSEP$TASTY_CORE" && shift ;;
53-
54-
# break out -D and -J options and add them to java_args so
55-
# they reach the JVM in time to do some good. The -D options
56-
# will be available as system properties.
57-
-D*) addJava "$1" && shift ;;
58-
-J*) addJava "${1:2}" && shift ;;
59-
*) addResidual "$1" && shift ;;
36+
case "$1" in
37+
-D*)
38+
# pass to scala as well: otherwise we lose it sometimes when we
39+
# need it, e.g. communicating with a server compiler.
40+
addJava "$1"
41+
addScala "$1"
42+
# respect user-supplied -Dscala.usejavacp
43+
shift
44+
;;
45+
-J*)
46+
# as with -D, pass to scala even though it will almost
47+
# never be used.
48+
addJava "${1:2}"
49+
addScala "$1"
50+
shift
51+
;;
52+
-classpath*)
53+
if [ "$1" != "${1##* }" ]; then
54+
# hashbang-combined args "-classpath 'lib/*'"
55+
A=$1 ; shift # consume $1 before adding its substrings back
56+
set -- $A "$@" # split $1 on whitespace and put it back
57+
else
58+
addScala "$1"
59+
shift
60+
fi
61+
;;
62+
*)
63+
addScala "$1"
64+
shift
65+
;;
6066
esac
6167
done
6268

6369
compilerJavaClasspathArgs
6470

65-
if [ "$PROG_NAME" == "$ScriptingMain" ]; then
66-
setScriptName="-Dscript.path=$target_script"
67-
scripting_string="-script $target_script ${script_args[@]}"
68-
fi
69-
7071
[ -n "$script_trace" ] && set -x
7172
[ -z "${ConEmuPID-}" -o -n "${cygwin-}" ] && export MSYSTEM= PWD= # workaround for #12405
7273

@@ -75,11 +76,9 @@ eval "\"$JAVACMD\"" \
7576
${JAVA_OPTS:-$default_java_opts} \
7677
"${java_args[@]}" \
7778
"-classpath \"$jvm_cp_args\"" \
78-
-Dscala.usejavacp=true \
79-
"$setScriptName" \
80-
"$PROG_NAME" \
81-
"${scala_args[@]}" \
82-
"${residual_args[@]}" \
83-
"${scripting_string-}"
84-
scala_exit_status=$?
79+
"-Dscala.usejavacp=true" \
80+
"-Dscala.home=$PROG_HOME" \
81+
"dotty.tools.MainGenericCompiler" \
82+
"${scala_args[@]}"
8583

84+
scala_exit_status=$?

0 commit comments

Comments
 (0)