Skip to content

Commit 83b90d8

Browse files
authored
Merge pull request #15212 from dotty-staging/main-generic-compiler
implement scalac script in Scala
2 parents ee9cc8f + a117aff commit 83b90d8

File tree

5 files changed

+267
-54
lines changed

5 files changed

+267
-54
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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 "-repl" :: tail =>
105+
process(tail, settings.withCompileMode(CompileMode.Repl))
106+
case "-script" :: targetScript :: tail =>
107+
process(Nil, settings
108+
.withCompileMode(CompileMode.Script)
109+
.withJavaProps("script.path" -> targetScript)
110+
.withTargetScript(targetScript)
111+
.withScriptArgs(tail*))
112+
case "-compile" :: tail =>
113+
process(tail, settings.withCompileMode(CompileMode.Compile))
114+
case "-decompile" :: tail =>
115+
process(tail, settings.withCompileMode(CompileMode.Decompile))
116+
case "-print-tasty" :: tail =>
117+
process(tail, settings.withCompileMode(CompileMode.PrintTasty))
118+
case "-run" :: tail =>
119+
process(tail, settings.withCompileMode(CompileMode.Run))
120+
case "-colors" :: tail =>
121+
process(tail, settings.withColors)
122+
case "-no-colors" :: tail =>
123+
process(tail, settings.withNoColors)
124+
case "-with-compiler" :: tail =>
125+
process(tail, settings.withCompiler)
126+
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
127+
val (tailargs, newEntries) = MainGenericRunner.processClasspath(cp, tail)
128+
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
129+
case "-Oshort" :: tail =>
130+
// Nothing is to be done here. Request that the user adds the relevant flags manually.
131+
// i.e this has no effect when MainGenericRunner is invoked programatically.
132+
val addTC="-XX:+TieredCompilation"
133+
val tStopAtLvl="-XX:TieredStopAtLevel=1"
134+
println(s"ignoring deprecated -Oshort flag, please add `-J$addTC` and `-J$tStopAtLvl` flags manually")
135+
process(tail, settings)
136+
case javaOption(stripped) :: tail =>
137+
process(tail, settings.withJavaArgs(stripped))
138+
case javaPropOption(opt, value) :: tail =>
139+
process(tail, settings.withJavaProps(opt -> value))
140+
case arg :: tail =>
141+
process(tail, settings.withResidualArgs(arg))
142+
end process
143+
144+
def main(args: Array[String]): Unit =
145+
val settings = process(args.toList, CompileSettings())
146+
if settings.exitCode != 0 then System.exit(settings.exitCode)
147+
148+
def classpathSetting =
149+
if settings.classPath.isEmpty then List()
150+
else List("-classpath", settings.classPath.mkString(classpathSeparator))
151+
152+
def reconstructedArgs() =
153+
classpathSetting ++ settings.scalaArgs ++ settings.residualArgs
154+
155+
def addJavaProps(): Unit =
156+
settings.javaProps.foreach { (k, v) => sys.props(k) = v }
157+
158+
def run(settings: CompileSettings): Unit = settings.compileMode match
159+
case CompileMode.Compile =>
160+
addJavaProps()
161+
val properArgs = reconstructedArgs()
162+
dotty.tools.dotc.Main.main(properArgs.toArray)
163+
case CompileMode.Decompile =>
164+
addJavaProps()
165+
val properArgs = reconstructedArgs()
166+
dotty.tools.dotc.decompiler.Main.main(properArgs.toArray)
167+
case CompileMode.PrintTasty =>
168+
addJavaProps()
169+
val properArgs = reconstructedArgs()
170+
dotty.tools.dotc.core.tasty.TastyPrinter.main(properArgs.toArray)
171+
case CompileMode.Script => // Naive copy from scalac bash script
172+
addJavaProps()
173+
val properArgs =
174+
reconstructedArgs()
175+
++ List("-script", settings.targetScript)
176+
++ settings.scriptArgs
177+
scripting.Main.main(properArgs.toArray)
178+
case CompileMode.Repl | CompileMode.Run =>
179+
addJavaProps()
180+
val properArgs = reconstructedArgs()
181+
repl.Main.main(properArgs.toArray)
182+
case CompileMode.Guess =>
183+
run(settings.withCompileMode(CompileMode.Compile))
184+
end run
185+
186+
run(settings)
187+
end main
188+
}

compiler/src/dotty/tools/MainGenericRunner.scala

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ object MainGenericRunner {
100100

101101
val classpathSeparator = File.pathSeparator
102102

103+
def processClasspath(cp: String, tail: List[String]): (List[String], List[String]) =
104+
val cpEntries = cp.split(classpathSeparator).toList
105+
val singleEntryClasspath: Boolean = cpEntries.take(2).size == 1
106+
val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic
107+
def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip")))
108+
if singleEntryClasspath && validGlobbedJar(cpEntries.head) then
109+
// reassemble globbed wildcard classpath
110+
// globdir is wildcard directory for globbed jar files, reconstruct the intended classpath
111+
val cpJars = tail.takeWhile( f => validGlobbedJar(f) )
112+
val remainingArgs = tail.drop(cpJars.size)
113+
(remainingArgs, cpEntries ++ cpJars)
114+
else
115+
(tail, cpEntries)
116+
103117
@sharable val javaOption = raw"""-J(.*)""".r
104118
@sharable val scalaOption = raw"""@.*""".r
105119
@sharable val colorOption = raw"""-color:.*""".r
@@ -110,21 +124,8 @@ object MainGenericRunner {
110124
case "-run" :: fqName :: tail =>
111125
process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
112126
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
113-
val cpEntries = cp.split(classpathSeparator).toList
114-
val singleEntryClasspath: Boolean = cpEntries.take(2).size == 1
115-
val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic
116-
def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip")))
117-
val (tailargs, newEntries) = if singleEntryClasspath && validGlobbedJar(cpEntries.head) then
118-
// reassemble globbed wildcard classpath
119-
// globdir is wildcard directory for globbed jar files, reconstruct the intended classpath
120-
val cpJars = tail.takeWhile( f => validGlobbedJar(f) )
121-
val remainingArgs = tail.drop(cpJars.size)
122-
(remainingArgs, cpEntries ++ cpJars)
123-
else
124-
(tail, cpEntries)
125-
127+
val (tailargs, newEntries) = processClasspath(cp, tail)
126128
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
127-
128129
case ("-version" | "--version") :: _ =>
129130
settings.copy(
130131
executeMode = ExecuteMode.Repl,
@@ -170,7 +171,7 @@ object MainGenericRunner {
170171
val newSettings = if arg.startsWith("-") then settings else settings.withPossibleEntryPaths(arg).withModeShouldBePossibleRun
171172
process(tail, newSettings.withResidualArgs(arg))
172173
end process
173-
174+
174175
def main(args: Array[String]): Unit =
175176
val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")).filter(_.nonEmpty)
176177
val allArgs = scalaOpts ++ args

dist/bin/scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ while [[ $# -gt 0 ]]; do
3333
-D*)
3434
# pass to scala as well: otherwise we lose it sometimes when we
3535
# need it, e.g. communicating with a server compiler.
36+
# respect user-supplied -Dscala.usejavacp
3637
addJava "$1"
3738
addScala "$1"
38-
# respect user-supplied -Dscala.usejavacp
3939
shift
4040
;;
4141
-J*)
@@ -47,7 +47,7 @@ while [[ $# -gt 0 ]]; do
4747
;;
4848
-classpath*)
4949
if [ "$1" != "${1##* }" ]; then
50-
# hashbang-combined args "-classpath 'lib/*'"
50+
# -classpath and its value have been supplied in a single string e.g. "-classpath 'lib/*'"
5151
A=$1 ; shift # consume $1 before adding its substrings back
5252
set -- $A "$@" # split $1 on whitespace and put it back
5353
else

dist/bin/scalac

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

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

33-
withCompiler=true
34-
3533
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-
34+
case "$1" in
35+
--)
36+
# pass all remaining arguments to scala, e.g. to avoid interpreting them here as -D or -J
37+
while [[ $# -gt 0 ]]; do addScala "$1" && shift ; done
38+
;;
39+
-script)
40+
# pass all remaining arguments to scala, e.g. to avoid interpreting them here as -D or -J
41+
while [[ $# -gt 0 ]]; do addScala "$1" && shift ; done
42+
;;
4143
# 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 ;;
44+
-Oshort)
45+
addScala "-Oshort" && \
46+
addJava "-XX:+TieredCompilation" && addJava "-XX:TieredStopAtLevel=1" && shift ;;
47+
-D*)
48+
# pass to scala as well: otherwise we lose it sometimes when we
49+
# need it, e.g. communicating with a server compiler.
50+
# respect user-supplied -Dscala.usejavacp
51+
addJava "$1"
52+
addScala "$1"
53+
shift
54+
;;
55+
-J*)
56+
# as with -D, pass to scala even though it will almost
57+
# never be used.
58+
addJava "${1:2}"
59+
addScala "$1"
60+
shift
61+
;;
62+
-classpath*)
63+
if [ "$1" != "${1##* }" ]; then
64+
# -classpath and its value have been supplied in a single string e.g. "-classpath 'lib/*'"
65+
A=$1 ; shift # consume $1 before adding its substrings back
66+
set -- $A "$@" # split $1 on whitespace and put it back
67+
else
68+
addScala "$1"
69+
shift
70+
fi
71+
;;
72+
*)
73+
addScala "$1"
74+
shift
75+
;;
6076
esac
6177
done
6278

6379
compilerJavaClasspathArgs
6480

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

@@ -75,11 +86,10 @@ eval "\"$JAVACMD\"" \
7586
${JAVA_OPTS:-$default_java_opts} \
7687
"${java_args[@]}" \
7788
"-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=$?
89+
"-Dscala.usejavacp=true" \
90+
"-Dscala.home=$PROG_HOME" \
91+
"dotty.tools.MainGenericCompiler" \
92+
"${scala_args[@]}"
8593

94+
scala_exit_status=$?
95+
onExit

project/scripts/bootstrappedOnlyCmdTests

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,26 @@ echo "testing sbt scalac -decompile from file"
4747
./bin/scalac -decompile -color:never "$OUT/$TASTY" > "$tmp"
4848
grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tmp"
4949

50+
# check that `sbt scalac -print-tasty` runs
51+
echo "testing sbt scalac -print-tasty from file"
52+
./bin/scalac -print-tasty -color:never "$OUT/$TASTY" > "$tmp"
53+
grep -qe "118: STRINGconst 32 \[hello world\]" "$tmp"
54+
5055
echo "testing loading tasty from .tasty file in jar"
5156
clear_out "$OUT"
5257
./bin/scalac -d "$OUT/out.jar" "$SOURCE"
5358
./bin/scalac -decompile -color:never "$OUT/out.jar" > "$tmp"
5459
grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tmp"
5560

61+
echo "testing printing tasty from .tasty file in jar"
62+
./bin/scalac -print-tasty -color:never "$OUT/out.jar" > "$tmp"
63+
grep -qe "118: STRINGconst 32 \[hello world\]" "$tmp"
64+
65+
echo "testing -script from scalac"
66+
clear_out "$OUT"
67+
./bin/scalac -script "$SOURCE" > "$tmp"
68+
test "$EXPECTED_OUTPUT" = "$(cat "$tmp")"
69+
5670
echo "testing sbt scalac with suspension"
5771
clear_out "$OUT"
5872
"$SBT" "scala3-compiler-bootstrapped/scalac -d $OUT tests/pos-macros/macros-in-same-project-1/Bar.scala tests/pos-macros/macros-in-same-project-1/Foo.scala" > "$tmp"

0 commit comments

Comments
 (0)