Skip to content

Scala sh rewrite #13081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 2, 2021
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
./project/scripts/sbt ";scala3-bootstrapped/compile; scala3-bootstrapped/publishLocal; 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"
./project/scripts/bootstrapCmdTests

- name: MiMa
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ community-build/dotty-community-build-deps

# Bloop
.bsp

# Coursier
cs
108 changes: 108 additions & 0 deletions compiler/src/dotty/tools/MainGenericRunner.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package dotty.tools


import scala.annotation.tailrec
import scala.io.Source
import scala.util.Try
import java.net.URLClassLoader

enum ExecuteMode:
case Guess
case Script
case Repl
case Run

case class Settings(
verbose: Boolean = false,
classPath: List[String] = List.empty,
executeMode: ExecuteMode = ExecuteMode.Guess,
exitCode: Int = 0,
residualArgs: List[String] = List.empty,
scriptArgs: List[String] = List.empty,
targetScript: String = "",
) {
def withExecuteMode(em: ExecuteMode): Settings = this.executeMode match
case ExecuteMode.Guess =>
this.copy(executeMode = em)
case _ =>
println(s"execute_mode==[$executeMode], attempted overwrite by [$em]")
this.copy(exitCode = 1)
end withExecuteMode

def withResidualArgs(args: String*): Settings =
this.copy(residualArgs = residualArgs.appendedAll(args.toList))

def withScriptArgs(args: String*): Settings =
this.copy(scriptArgs = scriptArgs.appendedAll(args.toList))

def withTargetScript(file: String): Settings =
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
}

object MainGenericRunner {

@tailrec
def process(args: List[String], settings: Settings): Settings = args match
case Nil =>
settings
case "-repl" :: tail =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which situation is it necessary to explicitly pass -repl?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, never. But dist/bin script provides such option, so we wanted to recreate this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's never used let's just remove it then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which situation is it necessary to explicitly pass -repl?

In dist/bin/scalac, it's needed, but doesn't seem to be needed in dist/bin/scala.

process(tail, settings.withExecuteMode(ExecuteMode.Repl))
case "-run" :: tail =>
process(tail, settings.withExecuteMode(ExecuteMode.Run))
case ("-cp" | "-classpath" | "--classpath") :: cp :: tail =>
process(tail, settings.copy(classPath = settings.classPath.appended(cp)))
case ("-version" | "--version") :: _ =>
settings.copy(
executeMode = ExecuteMode.Repl,
residualArgs = List("-version")
)
case ("-v" | "-verbose" | "--verbose") :: tail =>
process(
tail,
settings.copy(
verbose = true,
residualArgs = settings.residualArgs :+ "-verbose"
)
)
case arg :: tail =>
val line = Try(Source.fromFile(arg).getLines.toList).toOption.flatMap(_.headOption)
if arg.endsWith(".scala") || arg.endsWith(".sc") || (line.nonEmpty && raw"#!.*scala".r.matches(line.get)) then
settings
.withExecuteMode(ExecuteMode.Script)
.withTargetScript(arg)
.withScriptArgs(tail*)
else
process(tail, settings.withResidualArgs(arg))

def main(args: Array[String]): Unit =
val settings = process(args.toList, Settings())
if settings.exitCode != 0 then System.exit(settings.exitCode)
settings.executeMode match
case ExecuteMode.Repl =>
val properArgs =
List("-classpath", settings.classPath.mkString(";")).filter(Function.const(settings.classPath.nonEmpty))
++ settings.residualArgs
repl.Main.main(properArgs.toArray)
case ExecuteMode.Run =>
val properArgs =
List("-classpath", settings.classPath.mkString(";")).filter(Function.const(settings.classPath.nonEmpty))
++ settings.residualArgs
//TODO this is just a java proxy?
case ExecuteMode.Script =>
val properArgs =
List("classpath", settings.classPath.mkString(";")).filter(Function.const(settings.classPath.nonEmpty))
++ settings.residualArgs
++ List("-script", settings.targetScript)
++ settings.scriptArgs
scripting.Main.main(properArgs.toArray)
case ExecuteMode.Guess =>
val properArgs =
List("-classpath", settings.classPath.mkString(";")).filter(Function.const(settings.classPath.nonEmpty))
++ settings.residualArgs
repl.Main.main(properArgs.toArray)
}
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/scripting/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dotty.tools.scripting

import java.io.File
import java.nio.file.{Path, Paths}
import dotty.tools.dotc.config.Properties.isWin
import dotty.tools.dotc.config.Properties.isWin

/** Main entry point to the Scripting execution engine */
object Main:
Expand Down Expand Up @@ -87,7 +87,7 @@ object Main:
case s if s.startsWith("./") => s.drop(2)
case s => s
}

// convert to absolute path relative to cwd.
def absPath: String = norm match
case str if str.isAbsolute => norm
Expand Down
79 changes: 79 additions & 0 deletions compiler/test/dotty/tools/coursier/CoursierScalaTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package dotty
package tools
package scripting

import java.io.File
import java.nio.file.{Path, Paths, Files}
import scala.sys.process._

import org.junit.Test
import org.junit.BeforeClass

import vulpix.TestConfiguration

import dotty.tools.absPath
import scala.collection.mutable.ListBuffer

class CoursierScalaTests:

// classpath tests are managed by scripting.ClasspathTests.scala
def testFiles = scripts("/scripting").filter { ! _.getName.startsWith("classpath") }

// Cannot run tests in parallel, more info here: https://stackoverflow.com/questions/6345660/java-executing-bash-script-error-26-text-file-busy
@Test def allTests =
def scriptArgs() =
val scriptPath = scripts("/scripting").find(_.getName == "showArgs.sc").get.absPath
val testScriptArgs = Seq("a", "b", "c", "-repl", "-run", "-script", "-debug")

val args = scriptPath +: testScriptArgs
val output = CoursierScalaTests.csCmd(args*)
val expectedOutput = List(
"arg 0:[a]",
"arg 1:[b]",
"arg 2:[c]",
"arg 3:[-repl]",
"arg 4:[-run]",
"arg 5:[-script]",
"arg 6:[-debug]",
)
for (line, expect) <- output zip expectedOutput do
printf("expected: %-17s\nactual : %s\n", expect, line)
assert(output == expectedOutput)
scriptArgs()

def version() =
val output = CoursierScalaTests.csCmd("-version")
assert(output.mkString("\n").contains(sys.env("DOTTY_BOOTSTRAPPED_VERSION")))
version()

def emptyArgsEqualsRepl() =
val output = CoursierScalaTests.csCmd()
assert(output.mkString("\n").contains("Unable to create a system terminal")) // Scala attempted to create REPL so we can assume it is working
emptyArgsEqualsRepl()

def repl() =
val output = CoursierScalaTests.csCmd("-repl")
assert(output.mkString("\n").contains("Unable to create a system terminal")) // Scala attempted to create REPL so we can assume it is working
repl()


object CoursierScalaTests:

def execCmd(command: String, options: String*): List[String] =
val cmd = (command :: options.toList).toSeq.mkString(" ")
val out = new ListBuffer[String]
cmd.!(ProcessLogger(out += _, out += _))
out.toList


def csCmd(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 "dotty.tools.MainGenericRunner" --property "scala.usejavacp=true"""" +: newOptions)*)

/** Get coursier script */
@BeforeClass def setup(): Unit =
val ver = execCmd("uname").head.replace('L', 'l').replace('D', 'd')
execCmd("curl", s"-fLo cs https://git.io/coursier-cli-$ver") #&& execCmd("chmod", "+x cs")

6 changes: 6 additions & 0 deletions compiler/test/dotty/tools/utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def scripts(path: String): Array[File] = {
dir.listFiles
}

extension (f: File) def absPath =
f.getAbsolutePath.replace('\\', '/')

extension (str: String) def dropExtension =
str.reverse.dropWhile(_ != '.').drop(1).reverse

private def withFile[T](file: File)(action: Source => T): T =
resource(Source.fromFile(file, UTF_8.name))(action)

Expand Down
1 change: 1 addition & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ object Build {
// Avoid various sbt craziness involving classloaders and parallelism
run / fork := true,
Test / fork := true,
Test / envVars := Map("DOTTY_BOOTSTRAPPED_VERSION" -> dottyVersion),
Test / parallelExecution := false,

outputStrategy := Some(StdoutOutput),
Expand Down