Skip to content
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

Make -d and -classpath options backwards compatible with the scala command #1340

Merged
merged 5 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,9 @@ final case class CompileOptions(
cross: CrossOptions = CrossOptions(),

@Name("p")
@Name("classpath")
@Name("printClasspath")
@HelpMessage("Print the resulting class path")
classPath: Boolean = false,

@Name("output-directory")
@HelpMessage("Copy compilation results to output directory using either relative or absolute path")
@ValueDescription("/example/path")
output: Option[String] = None,
printClassPath: Boolean = false,

@HelpMessage("Compile test scope")
test: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ final case class ScalacOptions(
@HelpMessage("Add a scalac option")
@ValueDescription("option")
@Name("scala-opt")
@Name("scala-option")
@Name("O")
scalacOption: List[String] = Nil
)
Expand All @@ -21,7 +22,7 @@ final case class ScalacOptions(
object ScalacOptions {

private val scalacOptionsArg = Arg("scalacOption")
.withExtraNames(Seq(Name("scala-opt"), Name("O")))
.withExtraNames(Seq(Name("scala-opt"), Name("O"), Name("scala-option")))
.withValueDescription(Some(ValueDescription("option")))
.withHelpMessage(Some(HelpMessage(
"Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`."
Expand All @@ -40,6 +41,12 @@ object ScalacOptions {
val ScalacPrintOptions: Set[String] =
scalacOptionsPurePrefixes ++ Set("-help", "-Xshow-phases", "-Vphases")

/** This includes all the scalac options which are redirected to native Scala CLI options. */
val ScalaCliRedirectedOptions = Set(
"-classpath", // redirected to --extra-jars
"-d" // redirected to --compilation-output
)

private val scalacOptionsArgument: Argument[List[String]] =
new Argument[List[String]] {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ final case class SharedDependencyOptions(
@Group("Dependency")
@HelpMessage("Add dependencies")
@Name("dep")
@Name("d")
dependency: List[String] = Nil,

@Group("Dependency")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@ final case class SharedOptions(
markdown: MarkdownOptions = MarkdownOptions(),

@Group("Java")
@HelpMessage("Add extra JARs in the class path")
@HelpMessage("Add extra JARs and compiled classes to the class path")
@ValueDescription("paths")
@Name("jar")
@Name("jars")
@Name("extraJar")
@Name("class")
@Name("extraClass")
@Name("classes")
@Name("extraClasses")
@Name("-classpath")
@Name("classpath")
@Name("classPath")
@Name("extraClassPath")
extraJars: List[String] = Nil,

@Group("Java")
Expand Down Expand Up @@ -109,6 +117,15 @@ final case class SharedOptions(

@Hidden
strictBloopJsonCheck: Option[Boolean] = None,

@Name("output-directory")
@Name("d")
@Name("destination")
@Name("compileOutput")
@Name("compileOut")
@HelpMessage("Copy compilation results to output directory using either relative or absolute path")
@ValueDescription("/example/path")
compilationOutput: Option[String] = None,
)
// format: on

Expand Down
15 changes: 6 additions & 9 deletions modules/cli/src/main/scala/scala/cli/commands/Compile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ import java.io.File
import scala.build.options.Scope
import scala.build.{Build, BuildThreads, Builds, Os}
import scala.cli.CurrentParams
import scala.cli.commands.util.BuildCommandHelpers
import scala.cli.commands.util.CommonOps.SharedDirectoriesOptionsOps
import scala.cli.commands.util.SharedOptionsUtil._
import scala.cli.config.{ConfigDb, Keys}

object Compile extends ScalaCommand[CompileOptions] {
object Compile extends ScalaCommand[CompileOptions] with BuildCommandHelpers {
override def group = "Main"
override def sharedOptions(options: CompileOptions): Option[SharedOptions] = Some(options.shared)

def outputPath(options: CompileOptions): Option[os.Path] =
options.output.filter(_.nonEmpty).map(p => os.Path(p, Os.pwd))

def run(options: CompileOptions, args: RemainingArgs): Unit = {
maybePrintGroupHelp(options)
maybePrintSimpleScalacOutput(options, options.shared.buildOptions())
Expand All @@ -36,8 +34,8 @@ object Compile extends ScalaCommand[CompileOptions] {
Update.checkUpdateSafe(logger)

val cross = options.cross.cross.getOrElse(false)
if (options.classPath && cross) {
System.err.println(s"Error: cannot specify both --class-path and --cross")
if (options.printClassPath && cross) {
System.err.println(s"Error: cannot specify both --print-class-path and --cross")
sys.exit(1)
}

Expand Down Expand Up @@ -66,13 +64,12 @@ object Compile extends ScalaCommand[CompileOptions] {
build <- builds.get(Scope.Test).orElse(builds.get(Scope.Main))
s <- build.successfulOpt
} yield s
if (options.classPath)
if (options.printClassPath)
for (s <- successulBuildOpt) {
val cp = s.fullClassPath.map(_.toString).mkString(File.pathSeparator)
println(cp)
}
for (output <- outputPath(options); s <- successulBuildOpt)
os.copy.over(s.output, output)
successulBuildOpt.foreach(_.copyOutput(options.shared))
}
}

Expand Down
2 changes: 2 additions & 0 deletions modules/cli/src/main/scala/scala/cli/commands/Package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
) { res =>
res.orReport(logger).map(_.main).foreach {
case s: Build.Successful =>
s.copyOutput(options.shared)
val mtimeDestPath = doPackage(
logger = logger,
outputOpt = options.output.filter(_.nonEmpty),
Expand Down Expand Up @@ -124,6 +125,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
.orExit(logger)
builds.main match {
case s: Build.Successful =>
s.copyOutput(options.shared)
val res0 = doPackage(
logger = logger,
outputOpt = options.output.filter(_.nonEmpty),
Expand Down
2 changes: 2 additions & 0 deletions modules/cli/src/main/scala/scala/cli/commands/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
)
.orReport(logger)
.flatten
s.copyOutput(options.shared)
if (options.sharedRun.watch.restart)
processOpt = maybeProcess
else
Expand Down Expand Up @@ -251,6 +252,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
.orExit(logger)
builds.main match {
case s: Build.Successful =>
s.copyOutput(options.shared)
val res = maybeRun(
s,
allowTerminate = true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package scala.cli.commands.util

import scala.build.errors.MainClassError
import scala.build.{Build, Logger}
import scala.cli.commands.ScalaCommand
import scala.build.{Build, Logger, Os}
import scala.cli.commands.{ScalaCommand, SharedOptions}

trait BuildCommandHelpers { self: ScalaCommand[_] =>
import scala.cli.commands.util.ScalacOptionsUtil.*
extension (successfulBuild: Build.Successful) {
def retainedMainClass(
logger: Logger,
Expand All @@ -15,5 +16,15 @@ trait BuildCommandHelpers { self: ScalaCommand[_] =>
self.argvOpt.map(_.mkString(" ")).getOrElse(actualFullCommand),
logger
)

/** -O -d defaults to --compile-output; if both are defined, --compile-output takes precedence
*/
def copyOutput(sharedOptions: SharedOptions): Unit =
sharedOptions.compilationOutput.filter(_.nonEmpty)
.orElse(sharedOptions.scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-d"))
.filter(_.nonEmpty)
.map(os.Path(_, Os.pwd)).foreach(output =>
os.copy.over(successfulBuild.output, output, createFolders = true)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package scala.cli.commands.util

import scala.build.options.{ScalacOpt, ShadowingSeq}
import scala.cli.commands.ScalacOptions

object ScalacOptionsUtil {
Gedochao marked this conversation as resolved.
Show resolved Hide resolved
extension (opts: List[String]) {
def toScalacOptShadowingSeq: ShadowingSeq[ScalacOpt] =
ShadowingSeq.from(opts.filter(_.nonEmpty).map(ScalacOpt(_)))

}

extension (opts: ShadowingSeq[ScalacOpt]) {
def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] =
opts.filterKeys(_.key.exists(f))
def filterNonRedirected: ShadowingSeq[ScalacOpt] =
opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_))
def getScalacOption(key: String): Option[String] =
opts.get(ScalacOpt(key)).headOption.map(_.value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import scala.build.options as bo
import scala.cli.ScalaCli
import scala.cli.commands.ScalaJsOptions
import scala.cli.commands.util.CommonOps.*
import scala.cli.commands.util.ScalacOptionsUtil.*
import scala.cli.commands.util.SharedCompilationServerOptionsUtil.*
import scala.cli.config.{ConfigDb, Keys}
import scala.concurrent.ExecutionContextExecutorService
Expand Down Expand Up @@ -169,12 +170,11 @@ object SharedOptionsUtil extends CommandHelpers {
scalaBinaryVersion = scalaBinaryVersion.map(_.trim).filter(_.nonEmpty),
addScalaLibrary = scalaLibrary.orElse(java.map(!_)),
generateSemanticDbs = semanticDb,
scalacOptions = ShadowingSeq.from(
scalac.scalacOption
.filter(_.nonEmpty)
.map(ScalacOpt(_))
.map(Positioned.commandLine)
),
scalacOptions = scalac
.scalacOption
.toScalacOptShadowingSeq
.filterNonRedirected
.map(Positioned.commandLine),
compilerPlugins =
SharedOptionsUtil.parseDependencies(
dependencies.compilerPlugin.map(Positioned.none),
Expand All @@ -199,7 +199,7 @@ object SharedOptionsUtil extends CommandHelpers {
runJmh = if (enableJmh) Some(true) else None
),
classPathOptions = bo.ClassPathOptions(
extraClassPath = extraJars
extraClassPath = extraJarsAndClasspath
.flatMap(_.split(File.pathSeparator).toSeq)
.filter(_.nonEmpty)
.map(os.Path(_, os.pwd)),
Expand Down Expand Up @@ -228,6 +228,9 @@ object SharedOptionsUtil extends CommandHelpers {
)
}

def extraJarsAndClasspath: List[String] =
extraJars ++ scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-classpath")

def globalInteractiveWasSuggested: Option[Boolean] =
configDb.getOrNone(Keys.globalInteractiveWasSuggested, logger)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String])
test("copy compile output") {
mainAndTestInputs.fromRoot { root =>
val tempOutput = root / "output"
os.proc(TestUtil.cli, "compile", "--output", tempOutput, extraOptions, ".").call(cwd = root)
os.proc(TestUtil.cli, "compile", "--compile-output", tempOutput, extraOptions, ".").call(cwd =
root
)
checkIfCompileOutputIsCopied("Main", tempOutput)
}
}
Expand All @@ -107,9 +109,9 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String])
TestUtil.cli,
"compile",
"--test",
"--output",
"--compile-output",
tempOutput,
"--class-path",
"--print-class-path",
extraOptions,
"."
).call(cwd =
Expand Down Expand Up @@ -416,7 +418,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String])
os.proc(
TestUtil.cli,
"compile",
"--class-path",
"--print-class-path",
extraOptions,
"."
).call(cwd = root)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
// format: off
val cmd = Seq[os.Shellable](
TestUtil.cli, "compile", extraOptions,
"--class-path", ".",
"--print-class-path", ".",
if (inlineDelambdafy) Seq("-Ydelambdafy:inline") else Nil
)
// format: on
Expand Down Expand Up @@ -2219,6 +2219,82 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
}
}

test("-classpath allows to run with scala-cli compile -d option pre-compiled classes") {
val preCompileDir = "PreCompileDir"
val preCompiledInput = "Message.scala"
val runDir = "RunDir"
val mainInput = "Main.scala"
val expectedOutput = "Hello"
TestInputs(
os.rel / preCompileDir / preCompiledInput -> "case class Message(value: String)",
os.rel / runDir / mainInput -> s"""object Main extends App { println(Message("$expectedOutput").value) }"""
).fromRoot { (root: os.Path) =>
val preCompileOutputDir = os.rel / "outParentDir" / "out"

// first, precompile to an explicitly specified output directory with -d
os.proc(
TestUtil.cli,
"compile",
preCompiledInput,
"-d",
preCompileOutputDir.toString,
extraOptions
).call(cwd = root / preCompileDir)

// next, run while relying on the pre-compiled class, specifying the path with -classpath
val runRes = os.proc(
TestUtil.cli,
"run",
mainInput,
"-classpath",
(os.rel / os.up / preCompileDir / preCompileOutputDir).toString,
extraOptions
).call(cwd = root / runDir)
expect(runRes.out.trim == expectedOutput)
}
}

test("-O -classpath allows to run with scala-cli compile -O -d option pre-compiled classes") {
val preCompileDir = "PreCompileDir"
val preCompiledInput = "Message.scala"
val runDir = "RunDir"
val mainInput = "Main.scala"
val expectedOutput = "Hello"
TestInputs(
os.rel / preCompileDir / preCompiledInput -> "case class Message(value: String)",
os.rel / runDir / mainInput -> s"""object Main extends App { println(Message("$expectedOutput").value) }"""
).fromRoot { (root: os.Path) =>
val preCompileOutputDir = os.rel / "outParentDir" / "out"

// first, precompile to an explicitly specified output directory with -O -d
val compileRes = os.proc(
TestUtil.cli,
"compile",
preCompiledInput,
"-O",
"-d",
"-O",
preCompileOutputDir.toString,
extraOptions
).call(cwd = root / preCompileDir, stderr = os.Pipe)
expect(!compileRes.err.trim.contains("Warning: Flag -d set repeatedly"))

// next, run while relying on the pre-compiled class, specifying the path with -O -classpath
val runRes = os.proc(
TestUtil.cli,
"run",
mainInput,
"-O",
"-classpath",
"-O",
(os.rel / os.up / preCompileDir / preCompileOutputDir).toString,
extraOptions
).call(cwd = root / runDir, stderr = os.Pipe)
expect(!runRes.err.trim.contains("Warning: Flag -classpath set repeatedly"))
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
Loading