Skip to content

Commit

Permalink
add MainGenericCompiler
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed May 17, 2022
1 parent 7fbbeef commit 6326fb7
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 48 deletions.
195 changes: 195 additions & 0 deletions compiler/src/dotty/tools/MainGenericCompiler.scala
@@ -0,0 +1,195 @@
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()
++ 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
}
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/runner/ScalaClassLoader.scala
Expand Up @@ -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. */
Expand All @@ -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 })
}

Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/scripting/ScriptingDriver.scala
Expand Up @@ -9,6 +9,7 @@ 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 ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver:
def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Unit =
Expand All @@ -25,15 +26,16 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs:
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 (cl, mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString)
val invokeMain: Boolean =
Option(pack) match
case Some(func) =>
func(outDir, classpathEntries, mainClass)
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
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/scripting/StringDriver.scala
Expand Up @@ -8,6 +8,7 @@ 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:
override def sourcesRequired: Boolean = false
Expand All @@ -32,8 +33,8 @@ class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Dri
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(_) }
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, classpathEntries, scalaSource)
cl.run(mainMethod, Array.empty[String])
catch
case e: java.lang.reflect.InvocationTargetException =>
throw e.getCause
Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/scripting/Util.scala
Expand Up @@ -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 =
Expand All @@ -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, classpathEntries: Seq[Path], srcFile: String): (URLClassLoader, String, Method) =
val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL }
val cl = URLClassLoader(classpathUrls.toArray)
val cl = ScalaClassLoader.fromURLsParallelCapable(classpathUrls)

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}"
Expand All @@ -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
Expand All @@ -59,4 +62,3 @@ object Util:
def pathsep = sys.props("path.separator")

end Util

73 changes: 36 additions & 37 deletions dist/bin/scalac
Expand Up @@ -30,43 +30,44 @@ source "$PROG_HOME/bin/common"

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

withCompiler=true
compileMode="guess"

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

Expand All @@ -75,11 +76,9 @@ 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=$?

0 comments on commit 6326fb7

Please sign in to comment.