-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Scala sh rewrite #13081
Changes from 4 commits
4b2ac53
2c46c3e
630579b
216b70b
72c279f
0c340bc
df72930
12eeb9f
f3a72f2
314c3c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,3 +96,6 @@ community-build/dotty-community-build-deps | |
|
||
# Bloop | ||
.bsp | ||
|
||
# Coursier | ||
cs |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package dotty.tools | ||
|
||
|
||
import scala.annotation.tailrec | ||
import scala.io.Source | ||
import scala.util.Try | ||
import java.net.URLClassLoader | ||
import sys.process._ | ||
import java.io.File | ||
import java.lang.Thread | ||
|
||
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 = "", | ||
areWithCompiler: Boolean = false, | ||
) { | ||
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 | ||
|
||
def withCompiler: Settings = | ||
this.copy(areWithCompiler = true) | ||
} | ||
|
||
object MainGenericRunner { | ||
|
||
final val classpathSeparator = ":" | ||
|
||
@tailrec | ||
def process(args: List[String], settings: Settings): Settings = args match | ||
case Nil => | ||
settings | ||
case "-repl" :: tail => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In which situation is it necessary to explicitly pass -repl? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, never. But There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's never used let's just remove it then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In |
||
process(tail, settings.withExecuteMode(ExecuteMode.Repl)) | ||
case "-run" :: tail => | ||
process(tail, settings.withExecuteMode(ExecuteMode.Run)) | ||
case ("-cp" | "-classpath" | "--classpath") :: cp :: tail => | ||
BarkingBad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 "-with-compiler" :: tail => | ||
process(tail, settings.withCompiler) | ||
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(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty)) | ||
++ settings.residualArgs | ||
repl.Main.main(properArgs.toArray) | ||
case ExecuteMode.Run => | ||
val properArgs = | ||
val newClasspath = settings.classPath ++ getClasspath :+ "." | ||
List("-classpath", newClasspath.mkString(classpathSeparator)).filter(Function.const(newClasspath.nonEmpty)) | ||
++ settings.residualArgs | ||
s"java ${properArgs.mkString(" ")}".! // For now we collect classpath that coursier provides for convenience | ||
case ExecuteMode.Script => | ||
val properArgs = | ||
List("-classpath", settings.classPath.mkString(classpathSeparator)).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(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty)) | ||
++ settings.residualArgs | ||
repl.Main.main(properArgs.toArray) | ||
|
||
|
||
private def getClasspath(cl: ClassLoader): Array[String] = cl match | ||
case null => Array() | ||
case u: URLClassLoader => u.getURLs.map(_.toURI.toString) ++ getClasspath(cl.getParent) | ||
case cl if cl.getClass.getName == "jdk.internal.loader.ClassLoaders$AppClassLoader" => | ||
// Required with JDK >= 9 | ||
sys.props.getOrElse("java.class.path", "") | ||
.split(File.pathSeparator) | ||
.filter(_.nonEmpty) | ||
case _ => getClasspath(cl.getParent) | ||
|
||
private def getClasspath: List[String] = | ||
getClasspath(Thread.currentThread().getContextClassLoader).toList | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
object myfile extends App: | ||
println("Hello") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
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() | ||
|
||
def run() = | ||
val output = CoursierScalaTests.csCmd("-run", "-classpath", scripts("/run").head.getParent, "myfile") | ||
assert(output.mkString("\n") == "Hello") | ||
run() | ||
|
||
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") | ||
|
Uh oh!
There was an error while loading. Please reload this page.