diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 712df11ca03b..9eabac4eee9a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -111,7 +111,7 @@ jobs: - name: Cmd Tests run: | - ./project/scripts/sbt ";scala3-bootstrapped/compile; scala3-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test;sjsCompilerTests/test ;sbt-test/scripted scala2-compat/* ;configureIDE ;stdlib-bootstrapped/test:run ;stdlib-bootstrapped-tasty-tests/test; scala3-compiler-bootstrapped/scala3CompilerCoursierTest:test" + ./project/scripts/sbt ";dist/pack; scala3-bootstrapped/compile; scala3-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test;sjsCompilerTests/test ;sbt-test/scripted scala2-compat/* ;configureIDE ;stdlib-bootstrapped/test:run ;stdlib-bootstrapped-tasty-tests/test; scala3-compiler-bootstrapped/scala3CompilerCoursierTest:test" ./project/scripts/bootstrapCmdTests - name: MiMa diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index 2e634645fddc..fbc947ce175a 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -157,7 +157,7 @@ object MainGenericRunner { process(tail, newSettings.withResidualArgs(arg)) def main(args: Array[String]): Unit = - val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")) + val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")).filter(_.nonEmpty) val allArgs = scalaOpts ++ args val settings = process(allArgs.toList, Settings()) if settings.exitCode != 0 then System.exit(settings.exitCode) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 13adb0165c77..e9f5ea07a353 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -73,6 +73,9 @@ trait AllScalaSettings extends CommonScalaSettings, PluginSettings, VerboseSetti ) val wikiSyntax: Setting[Boolean] = BooleanSetting("-Xwiki-syntax", "Retains the Scala2 behavior of using Wiki Syntax in Scaladoc.") + + val jvmargs = PrefixSetting("-J", "-J", "Pass directly to the runtime system.") + val defines = PrefixSetting("-Dproperty=value", "-D", "Pass -Dproperty=value directly to the runtime system.") end AllScalaSettings /** Settings shared by compiler and scaladoc */ diff --git a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala index 2e616eeea631..d8a14f72ebfa 100644 --- a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala +++ b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala @@ -60,6 +60,13 @@ class CoursierScalaTests: assertTrue(output.mkString("\n").endsWith("scriptPath.sc")) scriptPath() + def scriptEnvDashJDashD() = + val scriptPath = scripts("/scripting").find(_.getName == "envtest.sc").get.absPath + val args = scriptPath + val output = CoursierScalaTests.csScalaCmd("-J-Dkey=World", args) + assertEquals(output.mkString("\n"), "Hello World") + scriptEnvDashJDashD() + def version() = val output = CoursierScalaTests.csScalaCmd("-version") assertTrue(output.mkString("\n").contains(sys.env("DOTTY_BOOTSTRAPPED_VERSION"))) @@ -75,6 +82,11 @@ class CoursierScalaTests: assertEquals(output.mkString("\n"), "Hello") run() + def runDashJDashD() = + val output = CoursierScalaTests.csScalaCmd("-J-Dkey=World", "-classpath", scripts("/run").head.getParentFile.getParent, "-run", "run.envtest") + assertEquals(output.mkString("\n"), "Hello World") + runDashJDashD() + def notOnlyOptionsEqualsRun() = val output = CoursierScalaTests.csScalaCmd("-classpath", scripts("/run").head.getParentFile.getParent, "run.myfile") assertEquals(output.mkString("\n"), "Hello") @@ -147,10 +159,12 @@ object CoursierScalaTests: csCmd("dotty.tools.dotc.Main", options*) private def csCmd(entry: String, options: String*): List[String] = - val newOptions = options match - case Nil => options - case _ => "--" +: options - execCmd("./cs", (s"""launch "org.scala-lang:scala3-compiler_3:${sys.env("DOTTY_BOOTSTRAPPED_VERSION")}" --main-class "$entry" --property "scala.usejavacp=true"""" +: newOptions)*) + val (jOpts, args) = options.partition(_.startsWith("-J")) + val newOptions = args match + case Nil => args + case _ => "--" +: args + val newJOpts = jOpts.map(s => s"--java-opt ${s.stripPrefix("-J")}").mkString(" ") + execCmd("./cs", (s"""launch "org.scala-lang:scala3-compiler_3:${sys.env("DOTTY_BOOTSTRAPPED_VERSION")}" $newJOpts --main-class "$entry" --property "scala.usejavacp=true"""" +: newOptions)*) /** Get coursier script */ @BeforeClass def setup(): Unit = diff --git a/compiler/test-coursier/run/envtest.scala b/compiler/test-coursier/run/envtest.scala new file mode 100644 index 000000000000..bf416c9519ce --- /dev/null +++ b/compiler/test-coursier/run/envtest.scala @@ -0,0 +1,4 @@ +package run + +object envtest extends App: + println("Hello " + sys.props("key")) diff --git a/compiler/test-resources/scripting/classpathReport.sc b/compiler/test-resources/scripting/classpathReport.sc index e7b2f7067a1f..2a240d2fc3a0 100755 --- a/compiler/test-resources/scripting/classpathReport.sc +++ b/compiler/test-resources/scripting/classpathReport.sc @@ -1,4 +1,4 @@ -#!dist/target/pack/bin/scala -classpath dist/target/pack/lib/* +#!/usr/bin/env -S bin/scala -classpath 'dist/target/pack/lib/*' import java.nio.file.Paths diff --git a/compiler/test-resources/scripting/envtest.sc b/compiler/test-resources/scripting/envtest.sc new file mode 100755 index 000000000000..b2fde1b32339 --- /dev/null +++ b/compiler/test-resources/scripting/envtest.sc @@ -0,0 +1,2 @@ +def main(args: Array[String]): Unit = + println("Hello " + util.Properties.propOrNull("key")) diff --git a/compiler/test-resources/scripting/envtest.scala b/compiler/test-resources/scripting/envtest.scala new file mode 100644 index 000000000000..bf416c9519ce --- /dev/null +++ b/compiler/test-resources/scripting/envtest.scala @@ -0,0 +1,4 @@ +package run + +object envtest extends App: + println("Hello " + sys.props("key")) diff --git a/compiler/test-resources/scripting/unglobClasspath.sc b/compiler/test-resources/scripting/unglobClasspath.sc index 796697cdedf2..d05ff78435b3 100755 --- a/compiler/test-resources/scripting/unglobClasspath.sc +++ b/compiler/test-resources/scripting/unglobClasspath.sc @@ -1,4 +1,4 @@ -#!bin/scala -classpath 'dist/target/pack/lib/*' +#!/usr/bin/env -S bin/scala -classpath 'dist/target/pack/lib/*' // won't compile unless the hashbang line sets classpath import org.jline.terminal.Terminal diff --git a/compiler/test/dotty/tools/scripting/BashScriptsTests.scala b/compiler/test/dotty/tools/scripting/BashScriptsTests.scala index 061350e86e33..759430a26817 100644 --- a/compiler/test/dotty/tools/scripting/BashScriptsTests.scala +++ b/compiler/test/dotty/tools/scripting/BashScriptsTests.scala @@ -7,6 +7,7 @@ import java.nio.file.{Path, Paths, Files} import scala.sys.process._ import org.junit.Test +import org.junit.Assert.assertEquals import vulpix.TestConfiguration @@ -28,13 +29,13 @@ class BashScriptsTests: printf("scalac path: [%s]\n", scalacPath) lazy val expectedOutput = List( - "arg 0:[a]", - "arg 1:[b]", - "arg 2:[c]", - "arg 3:[-repl]", - "arg 4:[-run]", - "arg 5:[-script]", - "arg 6:[-debug]", + "arg 0:[a]", + "arg 1:[b]", + "arg 2:[c]", + "arg 3:[-repl]", + "arg 4:[-run]", + "arg 5:[-script]", + "arg 6:[-debug]", ) lazy val testScriptArgs = Seq( "a", "b", "c", "-repl", "-run", "-script", "-debug" @@ -56,6 +57,38 @@ class BashScriptsTests: if fail then assert(stdout == expectedOutput) + /* verify `dist/bin/scala` with -J setting */ + @Test def verifyScJProperty = + val commandline = Seq(scalaPath, "-J-Dkey=World", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ") + val (validTest, exitCode, stdout, stderr) = bashCommand(commandline) + assertEquals(stdout.mkString("/n"), "Hello World") + + /* verify `dist/bin/scala` with -J setting */ + @Test def verifyScalaJProperty = + val commandline = Seq(scalaPath, "-J-Dkey=World3", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ") + val (validTest, exitCode, stdout, stderr) = bashCommand(commandline) + assertEquals(stdout.mkString("/n"), "Hello World3") + + /* verify `dist/bin/scala` with -D setting */ + @Test def verifyScDProperty = + val commandline = Seq(scalaPath, "-Dkey=World3", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ") + val (validTest, exitCode, stdout, stderr) = bashCommand(commandline) + assertEquals(stdout.mkString("/n"), "Hello World3") + + /* verify `dist/bin/scala` with -D setting */ + @Test def verifyScalaDProperty = + val commandline = Seq(scalaPath, "-Dkey=World4", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ") + val (validTest, exitCode, stdout, stderr) = bashCommand(commandline) + assertEquals(stdout.mkString("/n"), "Hello World4") + + /* verify `dist/bin/scala` with -D setting */ + @Test def saveAndRunWithDProperty = + val commandline = Seq(scalaPath, "-save", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ") + val (_, _, _, _) = bashCommand(commandline) + val commandline2 = Seq(scalaPath, "-Dkey=World5", testFiles.find(_.getName == "envtest.jar").get.absPath).mkString(" ") + val (validTest, exitCode, stdout, stderr) = bashCommand(commandline2) + assertEquals(stdout.mkString("/n"), "Hello World5") + /* verify `dist/bin/scala` non-interference with command line args following script name */ @Test def verifyScalaArgs = val commandline = (Seq("SCALA_OPTS= ", scalaPath, showArgsScript) ++ testScriptArgs).mkString(" ") @@ -73,7 +106,7 @@ class BashScriptsTests: assert(stdout == expectedOutput) /* - * verify that scriptPath.sc sees a valid script.path property, + * verify that scriptPath.sc sees a valid script.path property, * and that it's value is the path to "scriptPath.sc". */ @Test def verifyScriptPathProperty = @@ -134,6 +167,7 @@ class BashScriptsTests: def exists: Boolean = s.toPath.toFile.exists def name: String = s.toFile.getName def dropExtension: String = s.reverse.dropWhile(_ != '.').drop(1).reverse + def parent(up: Int): String = s.norm.split("/").reverse.drop(up).reverse.mkString("/") } extension(p: Path) { @@ -168,7 +202,7 @@ class BashScriptsTests: if scalacPath.isFile then scalacPath.replaceAll("/bin/scalac", "") else envOrElse("SCALA_HOME", "").norm - lazy val javaHome = envOrElse("JAVA_HOME", "").norm + lazy val javaHome = whichJava.parent(2) lazy val testEnvPairs = List( ("JAVA_HOME", javaHome), @@ -176,14 +210,12 @@ class BashScriptsTests: ("PATH", adjustedPath), ).filter { case (name, valu) => valu.nonEmpty } - lazy val whichBash: String = - var whichBash = "" - if osname.startsWith("windows") then - whichBash = which("bash.exe") - else - whichBash = which("bash") + lazy val whichBash: String = whichExe("bash") + lazy val whichJava: String = whichExe("java") - whichBash + def whichExe(basename: String): String = + val exeName = if (osname.toLowerCase.startsWith("windows")) s"$basename.exe" else basename + which(exeName) def bashCommand(cmdstr: String, additionalEnvPairs: List[(String, String)] = Nil): (Boolean, Int, Seq[String], Seq[String]) = { var (stdout, stderr) = (List.empty[String], List.empty[String]) @@ -192,7 +224,7 @@ class BashScriptsTests: val envPairs = testEnvPairs ++ additionalEnvPairs val proc = Process(cmd, None, envPairs *) val exitVal = proc ! ProcessLogger ( - (out: String) => stdout ::= out, + (out: String) => stdout ::= out, (err: String) => stderr ::= err ) val validTest = exitVal == 0 && ! stderr.exists(_.contains("Permission denied")) diff --git a/compiler/test/dotty/tools/scripting/ClasspathTests.scala b/compiler/test/dotty/tools/scripting/ClasspathTests.scala index 82b6affe0c02..d755789543a3 100755 --- a/compiler/test/dotty/tools/scripting/ClasspathTests.scala +++ b/compiler/test/dotty/tools/scripting/ClasspathTests.scala @@ -41,7 +41,7 @@ class ClasspathTests: cmd.foreach { printf("[%s]\n", _) } - // test script reports the classpath it sees + // test script reports the classpath it sees val scriptOutput = exec(cmd:_*) val scriptCwd = findTaggedLine("cwd", scriptOutput) printf("script ran in directory [%s]\n", scriptCwd) @@ -49,7 +49,7 @@ class ClasspathTests: val hashbangClasspathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct val packlibJars = listJars(s"$scriptCwd/$packLibDir").sorted.distinct - + printf("%d jar files in dist/target/pack/lib\n", packlibJars.size) printf("%d test script jars in classpath\n", hashbangClasspathJars.size) @@ -78,7 +78,7 @@ class ClasspathTests: cmd.foreach { printf("[%s]\n", _) } - // test script reports the classpath it sees + // test script reports the classpath it sees val scriptOutput = exec(cmd:_*) val scriptCp = findTaggedLine("unglobbed classpath", scriptOutput) val classpathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct diff --git a/dist/bin/scala b/dist/bin/scala index 5ae7761b69c5..8a87d6d0d8a0 100755 --- a/dist/bin/scala +++ b/dist/bin/scala @@ -28,10 +28,34 @@ fi source "$PROG_HOME/bin/common" +while [[ $# -gt 0 ]]; do + 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 + ;; + *) + addScala "$1" + shift + ;; + esac +done + # exec here would prevent onExit from being called, leaving terminal in unusable state compilerJavaClasspathArgs [ -z "${ConEmuPID-}" -o -n "${cygwin-}" ] && export MSYSTEM= PWD= # workaround for #12405 -eval "\"$JAVACMD\"" "-Dscala.home=$PROG_HOME" "-classpath \"$jvm_cp_args\"" "dotty.tools.MainGenericRunner" "-classpath \"$jvm_cp_args\"" "$@" +eval "\"$JAVACMD\"" "${java_args[@]}" "-Dscala.home=$PROG_HOME" "-classpath \"$jvm_cp_args\"" "dotty.tools.MainGenericRunner" "-classpath \"$jvm_cp_args\"" "${scala_args[@]}" scala_exit_status=$?