Skip to content

Commit 6c0a15e

Browse files
committed
changes to dist/bin/scala script:
1. pass @<argsFile> and -color:* options to compiler 2. add -save|-savecompiled option 3. recognize scripts with #!.*scala regardless of extension 4. if -save is specified and <scriptPath>.jar file is newer than <scriptPath> execute it (no compile) 5. set -Dscript.path for script execution paths (script and .jar) changes to dotty.tools.scripting package: 1. additional compiler args splitting and filtering 2. renamed detectMainMethod to detectMainClassAndMethod, returns both main class name and reflect.Method object 3. on -save option: a. if compile is successful, create same-name jar file in <scriptPath> parent directory b. "java.class.path" appended to context classpath with deduplication c. write "Main-Class" and "Class-Path" to jar manifest added new tests to verify the following: 1. one line and multi-line hash bang sections are ignored by compiler 2. main class name in stack dump is as expected when main class is declared in script 3. main class name in stack dump is as expected when main class is not declared in script 4. script.path property matches scriptFile.absPath 5. verify that with -save option jar file with expected name is generated 6. verify that without -save option, no jar file is generated 7. generated jar file is executable via "java -jar <scriptFile>.jar"
1 parent c95f4af commit 6c0a15e

File tree

9 files changed

+142
-55
lines changed

9 files changed

+142
-55
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ object Main:
6565
import java.util.jar.Attributes.Name
6666
val cpString:String = cpPaths.distinct.mkString(" ")
6767
val manifestAttributes:Seq[(Name, String)] = Seq(
68-
(Name.MANIFEST_VERSION, "1.0.0"),
68+
(Name.MANIFEST_VERSION, "1.0"),
6969
(Name.MAIN_CLASS, mainClassName),
7070
(Name.CLASS_PATH, cpString),
7171
)

compiler/test-resources/scripting/hashBang.scala renamed to compiler/test-resources/scripting/hashBang.sc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22
# comment
33
STUFF=nada
44
!#
5-
5+
// everything above this point should be ignored by the compiler
66
def main(args: Array[String]): Unit =
7+
args.zipWithIndex.foreach { case (arg,i) => printf("arg %d: [%s]\n",i,arg) }
78
System.err.printf("mainClassFromStack: %s\n",mainFromStack)
89
assert(mainFromStack.contains("hashBang"),s"fromStack[$mainFromStack]")
910

1011
lazy val mainFromStack:String = {
1112
val result = new java.io.StringWriter()
1213
new RuntimeException("stack").printStackTrace(new java.io.PrintWriter(result))
1314
val stack = result.toString.split("[\r\n]+").toList
14-
//for( s <- stack ){ System.err.printf("[%s]\n",s) }
15+
if verbose then for( s <- stack ){ System.err.printf("[%s]\n",s) }
1516
stack.filter { str => str.contains(".main(") }.map {
16-
_.replaceAll(".*[(]","").
17-
replaceAll("\\.main\\(.*","").
18-
replaceAll(".scala.*","")
17+
// derive main class name from stack when main object is NOT declared in source
18+
_.replaceAll("[.].*","").
19+
replaceAll("\\s+at\\s+","")
1920
}.distinct.take(1).mkString("")
2021
}
22+
23+
lazy val verbose = Option(System.getenv("DOTC_VERBOSE")) match
24+
case None => false
25+
case _ => true
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env scala
2+
export STUFF=nada
3+
#lots of other stuff that isn't valid scala
4+
!#
5+
// everything above this point should be ignored by the compiler
6+
object Zoo {
7+
def main(args: Array[String]): Unit =
8+
args.zipWithIndex.foreach { case (arg,i) => printf("arg %d: [%s]\n",i,arg) }
9+
printf("mainClassFromStack: %s\n",mainClassFromStack)
10+
assert(mainClassFromStack == "Zoo",s"fromStack[$mainClassFromStack]")
11+
12+
lazy val mainClassFromStack:String = {
13+
val result = new java.io.StringWriter()
14+
new RuntimeException("stack").printStackTrace(new java.io.PrintWriter(result))
15+
val stack = result.toString.split("[\r\n]+").toList
16+
if verbose then for( s <- stack ){ System.err.printf("[%s]\n",s) }
17+
val shortStack = stack.filter { str => str.contains(".main(") && ! str.contains("$") }.map {
18+
// derive main class name from stack when main object is declared in source
19+
_.replaceAll("[.].*","").
20+
replaceAll("\\s+at\\s+","")
21+
}
22+
// for( s <- shortStack ){ System.err.printf("[%s]\n",s) }
23+
shortStack.take(1).mkString("|")
24+
}
25+
26+
lazy val verbose = Option(System.getenv("DOTC_VERBOSE")) match
27+
case None => false
28+
case _ => true
29+
}

compiler/test-resources/scripting/mainClassOnStack.scala

Lines changed: 0 additions & 21 deletions
This file was deleted.

compiler/test-resources/scripting/scriptName.scala

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import java.nio.file.Paths
2+
3+
object ScriptParent {
4+
def main(args: Array[String]): Unit = {
5+
args.zipWithIndex.foreach { case (arg,i) => printf("arg %d: [%s]\n",i,arg) }
6+
val scriptName = Option(sys.props("script.path")) match {
7+
case None =>
8+
printf("no script.path property\n")
9+
case Some(script) =>
10+
val p = Paths.get(script).toAbsolutePath.toFile.getParent
11+
printf("parentDir: [%s]\n",p)
12+
}
13+
}
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env scala
2+
3+
def main(args: Array[String]): Unit =
4+
args.zipWithIndex.foreach { case (arg,i) => printf("arg %d: [%s]\n",i,arg) }
5+
val path = Option(sys.props("script.path")) match {
6+
case None => printf("no script.path property is defined\n")
7+
case Some(path) =>
8+
printf("script.path: %s\n",path)
9+
assert(path.endsWith("scriptPath.sc"),s"actual path [$path]")
10+
}

compiler/test/dotty/tools/scripting/ScriptingTests.scala

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,94 @@ import org.junit.Test
99
import vulpix.TestConfiguration
1010

1111

12-
/** Runs all tests contained in `compiler/test-resources/repl/` */
12+
/** Runs all tests contained in `compiler/test-resources/scripting/` */
1313
class ScriptingTests:
1414
extension (str: String) def dropExtension =
1515
str.reverse.dropWhile(_ != '.').drop(1).reverse
1616

17-
@Test def scriptingTests =
18-
val testFiles = scripts("/scripting")
17+
def testFiles = scripts("/scripting")
1918

20-
val argss: Map[String, Array[String]] = (
21-
for
22-
argFile <- testFiles
23-
if argFile.getName.endsWith(".args")
24-
name = argFile.getName.dropExtension
25-
scriptArgs = readLines(argFile).toArray
26-
yield name -> scriptArgs).toMap
19+
def script2jar(scriptFile: File) =
20+
val jarName = s"${scriptFile.getName.dropExtension}.jar"
21+
File(scriptFile.getParent,jarName)
2722

23+
def showScriptUnderTest(scriptFile: File): Unit =
24+
printf("===> test script name [%s]\n",scriptFile.getName)
25+
26+
27+
val argss: Map[String, Array[String]] = (
28+
for
29+
argFile <- testFiles
30+
if argFile.getName.endsWith(".args")
31+
name = argFile.getName.dropExtension
32+
scriptArgs = readLines(argFile).toArray
33+
yield name -> scriptArgs).toMap
34+
35+
def scalaFilesWithArgs(extension: String) = (
2836
for
2937
scriptFile <- testFiles
30-
if scriptFile.getName.endsWith(".scala")
38+
if scriptFile.getName.endsWith(extension)
3139
name = scriptFile.getName.dropExtension
3240
scriptArgs = argss.getOrElse(name, Array.empty[String])
33-
do
41+
yield scriptFile -> scriptArgs).toList.sortBy { (file,args) => file.getName }
42+
43+
@Test def scriptingDriverTests =
44+
45+
for (scriptFile,scriptArgs) <- scalaFilesWithArgs(".scala") do
46+
showScriptUnderTest(scriptFile)
47+
val unexpectedJar = script2jar(scriptFile)
48+
unexpectedJar.delete
49+
50+
sys.props("script.path") = scriptFile.absPath
3451
ScriptingDriver(
3552
compilerArgs = Array(
36-
"-classpath", TestConfiguration.basicClasspath),
53+
"-classpath", TestConfiguration.basicClasspath
54+
),
3755
scriptFile = scriptFile,
3856
scriptArgs = scriptArgs
39-
).compileAndRun()
57+
).compileAndRun { (path:java.nio.file.Path,classpath:String, mainClass:String) =>
58+
printf("mainClass from ScriptingDriver: %s\n",mainClass)
59+
}
60+
assert(! unexpectedJar.exists, s"not expecting jar file: ${unexpectedJar.absPath}")
61+
62+
@Test def scriptingMainTests =
63+
for (scriptFile,scriptArgs) <- scalaFilesWithArgs(".sc") do
64+
showScriptUnderTest(scriptFile)
65+
val unexpectedJar = script2jar(scriptFile)
66+
unexpectedJar.delete
67+
68+
sys.props("script.path") = scriptFile.absPath
69+
val mainArgs: Array[String] = Array(
70+
"-classpath", TestConfiguration.basicClasspath.toString,
71+
"-script", scriptFile.toString,
72+
) ++ scriptArgs
73+
74+
Main.main(mainArgs)
75+
assert(! unexpectedJar.exists, s"not expecting jar file: ${unexpectedJar.absPath}")
76+
77+
@Test def scriptingJarTest =
78+
for (scriptFile,scriptArgs) <- scalaFilesWithArgs(".sc") do
79+
showScriptUnderTest(scriptFile)
80+
val expectedJar = script2jar(scriptFile)
81+
expectedJar.delete
82+
83+
sys.props("script.path") = scriptFile.absPath
84+
val mainArgs: Array[String] = Array(
85+
"-classpath", TestConfiguration.basicClasspath.toString,
86+
"-save",
87+
"-script", scriptFile.toString,
88+
) ++ scriptArgs
89+
90+
Main.main(mainArgs)
91+
92+
printf("===> test script jar name [%s]\n",expectedJar.getName)
93+
assert(expectedJar.exists)
94+
95+
import scala.sys.process._
96+
val cmd = Array("java",s"-Dscript.path=${scriptFile.getName}","-jar",expectedJar.absPath)
97+
++ scriptArgs
98+
Process(cmd).lazyLines_!.foreach { println }
99+
100+
extension(f: File){
101+
def absPath = f.getAbsolutePath.replace('\\','/')
102+
}

dist/bin/scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ if [ $execute_script == true ]; then
115115
fi
116116
target_jar="${target_script%.*}.jar"
117117
jar_found=false
118-
setScriptName="-Dscript.name=${target_script##*/}"
118+
setScriptName="-Dscript.path=$target_script"
119119
[[ $save_compiled == true && -f "$target_jar" ]] && jar_found=true
120120
if [[ $jar_found == true && "$target_jar" -nt "$target_script" ]]; then
121121
java $setScriptName -jar "$target_jar" "${script_args[@]}"

0 commit comments

Comments
 (0)