Skip to content

Commit

Permalink
Merge pull request #1369 from Gedochao/backwards-compat-classpat-and-…
Browse files Browse the repository at this point in the history
…main-class-inputs

Make inputs optional when `-classpath` and `--main-class` are passed
  • Loading branch information
Gedochao authored Sep 16, 2022
2 parents bce41cb + fe791de commit 880242e
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 31 deletions.
3 changes: 2 additions & 1 deletion modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ object Build {
def outputOpt: Some[os.Path] = Some(output)
def dependencyClassPath: Seq[os.Path] = sources.resourceDirs ++ artifacts.classPath
def fullClassPath: Seq[os.Path] = Seq(output) ++ dependencyClassPath
def foundMainClasses(): Seq[String] = MainClass.find(output)
def foundMainClasses(): Seq[String] =
MainClass.find(output) ++ options.classPathOptions.extraClassPath.flatMap(MainClass.find)
def retainedMainClass(
mainClasses: Seq[String],
commandString: String,
Expand Down
21 changes: 13 additions & 8 deletions modules/build/src/main/scala/scala/build/Inputs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,11 @@ object Inputs {
directories: Directories,
forcedWorkspace: Option[os.Path],
enableMarkdown: Boolean,
allowRestrictedFeatures: Boolean
allowRestrictedFeatures: Boolean,
extraClasspathWasPassed: Boolean
): Inputs = {

assert(validElems.nonEmpty)
assert(extraClasspathWasPassed || validElems.nonEmpty)

val (inferredWorkspace, inferredNeedsHash, workspaceOrigin) = {
val settingsFiles = projectSettingsFiles(validElems)
Expand Down Expand Up @@ -490,7 +491,8 @@ object Inputs {
acceptFds: Boolean,
forcedWorkspace: Option[os.Path],
enableMarkdown: Boolean,
allowRestrictedFeatures: Boolean
allowRestrictedFeatures: Boolean,
extraClasspathWasPassed: Boolean
): Either[BuildException, Inputs] = {
val validatedArgs: Seq[Either[String, Seq[Element]]] =
validateArgs(args, cwd, download, stdinOpt, acceptFds)
Expand All @@ -504,15 +506,16 @@ object Inputs {
val validElems = validatedArgsAndSnippets.collect {
case Right(elem) => elem
}.flatten
assert(validElems.nonEmpty)
assert(extraClasspathWasPassed || validElems.nonEmpty)

Right(forValidatedElems(
validElems,
baseProjectName,
directories,
forcedWorkspace,
enableMarkdown,
allowRestrictedFeatures
allowRestrictedFeatures,
extraClasspathWasPassed
))
}
else
Expand All @@ -533,10 +536,11 @@ object Inputs {
acceptFds: Boolean = false,
forcedWorkspace: Option[os.Path] = None,
enableMarkdown: Boolean = false,
allowRestrictedFeatures: Boolean
allowRestrictedFeatures: Boolean,
extraClasspathWasPassed: Boolean
): Either[BuildException, Inputs] =
if (
args.isEmpty && scriptSnippetList.isEmpty && scalaSnippetList.isEmpty && javaSnippetList.isEmpty
args.isEmpty && scriptSnippetList.isEmpty && scalaSnippetList.isEmpty && javaSnippetList.isEmpty && !extraClasspathWasPassed
)
defaultInputs().toRight(new InputsException(
"No inputs provided (expected files with .scala, .sc, .java or .md extensions, and / or directories)."
Expand All @@ -555,7 +559,8 @@ object Inputs {
acceptFds,
forcedWorkspace,
enableMarkdown,
allowRestrictedFeatures
allowRestrictedFeatures,
extraClasspathWasPassed
)

def default(): Option[Inputs] =
Expand Down
38 changes: 23 additions & 15 deletions modules/build/src/main/scala/scala/build/internal/MainClass.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scala.build.internal

import org.objectweb.asm
import org.objectweb.asm.ClassReader

object MainClass {

Expand Down Expand Up @@ -36,20 +37,27 @@ object MainClass {
if (foundMainClass) nameOpt else None
}

def findInClass(path: os.Path): Iterator[String] = {
val is = os.read.inputStream(path)
try {
val reader = new ClassReader(is)
val checker = new MainMethodChecker
reader.accept(checker, 0)
checker.mainClassOpt.iterator
}
finally is.close()
}
def find(output: os.Path): Seq[String] =
os.walk(output)
.iterator
.filter(os.isFile(_))
.filter(_.last.endsWith(".class"))
.flatMap { path =>
val is = os.read.inputStream(path)
try {
val reader = new asm.ClassReader(is)
val checker = new MainMethodChecker
reader.accept(checker, 0)
checker.mainClassOpt.iterator
}
finally is.close()
}
.toVector
output match {
case o if os.isFile(o) && o.last.endsWith(".class") =>
findInClass(o).toVector
case o if os.isDir(o) =>
os.walk(o)
.iterator
.filter(os.isFile(_))
.filter(_.last.endsWith(".class"))
.flatMap(findInClass)
.toVector
case _ => Vector.empty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ final case class TestInputs(
tmpDir,
Directories.under(tmpDir / ".data"),
forcedWorkspace = forcedWorkspaceOpt.map(_.resolveFrom(tmpDir)),
allowRestrictedFeatures = true
allowRestrictedFeatures = true,
extraClasspathWasPassed = false
)
res match {
case Left(err) => throw new Exception(err)
Expand Down
3 changes: 2 additions & 1 deletion modules/cli/src/main/scala/scala/cli/commands/Clean.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ object Clean extends ScalaCommand[CleanOptions] {
options.directories.directories,
defaultInputs = () => Inputs.default(),
forcedWorkspace = options.workspace.forcedWorkspaceOpt,
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures,
extraClasspathWasPassed = false
) match {
case Left(message) =>
System.err.println(message)
Expand Down
3 changes: 2 additions & 1 deletion modules/cli/src/main/scala/scala/cli/commands/Default.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class Default(
{
val shouldDefaultToRun =
args.remaining.nonEmpty || options.shared.snippet.executeScript.nonEmpty ||
options.shared.snippet.executeScala.nonEmpty || options.shared.snippet.executeJava.nonEmpty
options.shared.snippet.executeScala.nonEmpty || options.shared.snippet.executeJava.nonEmpty ||
(options.shared.extraJarsAndClasspath.nonEmpty && options.sharedRun.mainClass.mainClass.nonEmpty)
if shouldDefaultToRun then RunOptions.parser else ReplOptions.parser
}.parse(rawArgs) match
case Left(e) => error(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ object SharedOptionsUtil extends CommandHelpers {
scriptSnippetList: List[String],
scalaSnippetList: List[String],
javaSnippetList: List[String],
enableMarkdown: Boolean = false
enableMarkdown: Boolean = false,
extraClasspathWasPassed: Boolean = false
): Either[BuildException, Inputs] = {
val resourceInputs = resourceDirs
.map(os.Path(_, Os.pwd))
Expand All @@ -84,7 +85,8 @@ object SharedOptionsUtil extends CommandHelpers {
acceptFds = !Properties.isWin,
forcedWorkspace = forcedWorkspaceOpt,
enableMarkdown = enableMarkdown,
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures,
extraClasspathWasPassed = extraClasspathWasPassed
)
maybeInputs.map { inputs =>
val forbiddenDirs =
Expand Down Expand Up @@ -367,7 +369,8 @@ object SharedOptionsUtil extends CommandHelpers {
scriptSnippetList = allScriptSnippets,
scalaSnippetList = allScalaSnippets,
javaSnippetList = allJavaSnippets,
enableMarkdown = v.markdown.enableMarkdown
enableMarkdown = v.markdown.enableMarkdown,
extraClasspathWasPassed = v.extraJarsAndClasspath.nonEmpty
)

def allScriptSnippets: List[String] = v.snippet.scriptSnippet ++ v.snippet.executeScript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class DefaultTests extends ScalaCliSuite {
test("running scala-cli with no args should default to repl") {
TestInputs.empty.fromRoot { root =>
val res = os.proc(TestUtil.cli, "--repl-dry-run").call(cwd = root, mergeErrIntoOut = true)
expect(res.out.trim() == "Dry run, not running REPL.")
expect(res.out.trim() == replDryRunOutput)
}
}
test("running scala-cli with no args should not accept run-only options") {
Expand Down Expand Up @@ -94,11 +94,66 @@ class DefaultTests extends ScalaCliSuite {
}
}

test("default to the run sub-command if -classpath and --main-class are passed") {
val expectedOutput = "Hello"
val mainClassName = "Main"
TestInputs(
os.rel / s"$mainClassName.scala" -> s"""object $mainClassName extends App { println("$expectedOutput") }"""
).fromRoot { (root: os.Path) =>
val compilationOutputDir = os.rel / "compilationOutput"
// first, precompile to an explicitly specified output directory with -d
os.proc(
TestUtil.cli,
".",
"-d",
compilationOutputDir
).call(cwd = root)

// next, run while relying on the pre-compiled class instead of passing inputs
val runRes = os.proc(
TestUtil.cli,
"--main-class",
mainClassName,
"-classpath",
(os.rel / compilationOutputDir).toString
).call(cwd = root)
expect(runRes.out.trim == expectedOutput)
}
}

test("default to the repl sub-command if -classpath is passed, but --main-class isn't") {
val expectedOutput = "Hello"
val mainClassName = "Main"
TestInputs(
os.rel / s"$mainClassName.scala" -> s"""object $mainClassName extends App { println("$expectedOutput") }"""
).fromRoot { (root: os.Path) =>
val compilationOutputDir = os.rel / "compilationOutput"
// first, precompile to an explicitly specified output directory with -d
os.proc(
TestUtil.cli,
".",
"-d",
compilationOutputDir
).call(cwd = root)

// next, run the repl while relying on the pre-compiled classes
val runRes = os.proc(
TestUtil.cli,
"--repl-dry-run",
"-classpath",
(os.rel / compilationOutputDir).toString
).call(cwd = root, mergeErrIntoOut = true)
expect(runRes.out.trim == replDryRunOutput)
}
}

private def unrecognizedArgMessage(argName: String) =
s"""
|Unrecognized argument: $argName
|
|To list all available options, run
| ${Console.BOLD}${TestUtil.detectCliPath} --help${Console.RESET}
|""".stripMargin.trim

private lazy val replDryRunOutput = "Dry run, not running REPL."
}
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,34 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
}
}

test("run main class from -classpath even when no explicit inputs are passed") {
val expectedOutput = "Hello"
TestInputs(
os.rel / "Main.scala" -> s"""object Main extends App { println("$expectedOutput") }"""
).fromRoot { (root: os.Path) =>
val compilationOutputDir = os.rel / "compilationOutput"
// first, precompile to an explicitly specified output directory with -d
os.proc(
TestUtil.cli,
"compile",
".",
"-d",
compilationOutputDir,
extraOptions
).call(cwd = root)

// next, run while relying on the pre-compiled class instead of passing inputs
val runRes = os.proc(
TestUtil.cli,
"run",
"-classpath",
(os.rel / compilationOutputDir).toString,
extraOptions
).call(cwd = root)
expect(runRes.out.trim == expectedOutput)
}
}

if (actualScalaVersion.startsWith("3"))
test("should throw exception for code compiled by scala 3.1.3") {
val exceptionMsg = "Throw exception in Scala"
Expand Down

0 comments on commit 880242e

Please sign in to comment.