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

Include test scope in the REPL when the --test flag is passed #2971

Merged
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 @@ -109,7 +109,7 @@ object Compile extends ScalaCommand[CompileOptions] with BuildCommandHelpers {
None,
logger,
crossBuilds = cross,
buildTests = options.test,
buildTests = options.scope.test,
partial = None,
actionableDiagnostics = actionableDiagnostics,
postAction = () => WatchUtil.printWatchMessage()
Expand All @@ -128,7 +128,7 @@ object Compile extends ScalaCommand[CompileOptions] with BuildCommandHelpers {
None,
logger,
crossBuilds = cross,
buildTests = options.test,
buildTests = options.scope.test,
partial = None,
actionableDiagnostics = actionableDiagnostics
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@ package scala.cli.commands.compile
import caseapp.*
import caseapp.core.help.Help

import scala.cli.commands.shared.{
CrossOptions,
HasSharedOptions,
HelpGroup,
HelpMessages,
SharedOptions,
SharedWatchOptions
}
import scala.cli.commands.shared._
import scala.cli.commands.tags

@HelpMessage(CompileOptions.helpMessage, "", CompileOptions.detailedHelpMessage)
Expand All @@ -31,11 +24,8 @@ final case class CompileOptions(
@Tag(tags.inShortHelp)
printClassPath: Boolean = false,

@Group(HelpGroup.Compilation.toString)
@HelpMessage("Compile test scope")
@Tag(tags.should)
@Tag(tags.inShortHelp)
test: Boolean = false
@Recurse
scope: ScopeOptions = ScopeOptions()
) extends HasSharedOptions
// format: on

Expand Down
148 changes: 81 additions & 67 deletions modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import java.util.zip.ZipFile

import scala.build.EitherCps.{either, value}
import scala.build.*
import scala.build.errors.{BuildException, CantDownloadAmmoniteError, FetchingDependenciesError}
import scala.build.errors.{
BuildException,
CantDownloadAmmoniteError,
FetchingDependenciesError,
MultipleScalaVersionsError
}
import scala.build.input.Inputs
import scala.build.internal.{Constants, Runner}
import scala.build.options.{BuildOptions, JavaOpt, MaybeScalaVersion, Scope}
Expand All @@ -24,6 +29,7 @@ import scala.cli.commands.run.Run.{
}
import scala.cli.commands.run.RunMode
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, SharedOptions}
import scala.cli.commands.util.BuildCommandHelpers
import scala.cli.commands.{ScalaCommand, WatchUtil}
import scala.cli.config.{ConfigDb, Keys}
import scala.cli.packaging.Library
Expand All @@ -33,7 +39,7 @@ import scala.cli.{CurrentParams, ScalaCli}
import scala.jdk.CollectionConverters.*
import scala.util.Properties

object Repl extends ScalaCommand[ReplOptions] {
object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers {
override def group: String = HelpCommandGroup.Main.toString
override def scalaSpecificationLevel = SpecificationLevel.MUST
override def helpFormat: HelpFormat = super.helpFormat
Expand Down Expand Up @@ -117,36 +123,25 @@ object Repl extends ScalaCommand[ReplOptions] {

val directories = Directories.directories

def buildFailed(allowExit: Boolean): Unit = {
System.err.println("Compilation failed")
if (allowExit)
sys.exit(1)
}
def buildCancelled(allowExit: Boolean): Unit = {
System.err.println("Build cancelled")
if (allowExit)
sys.exit(1)
}

def doRunRepl(
buildOptions: BuildOptions,
artifacts: Artifacts,
mainJarOrClassDir: Option[os.Path],
allArtifacts: Seq[Artifacts],
mainJarsOrClassDirs: Seq[os.Path],
allowExit: Boolean,
runMode: RunMode.HasRepl,
buildOpt: Option[Build.Successful]
successfulBuilds: Seq[Build.Successful]
): Unit = {
val res = runRepl(
buildOptions,
programArgs,
artifacts,
mainJarOrClassDir,
directories,
logger,
options = buildOptions,
programArgs = programArgs,
allArtifacts = allArtifacts,
mainJarsOrClassDirs = mainJarsOrClassDirs,
directories = directories,
logger = logger,
allowExit = allowExit,
options.sharedRepl.replDryRun,
runMode,
buildOpt
dryRun = options.sharedRepl.replDryRun,
runMode = runMode,
successfulBuilds = successfulBuilds
)
res match {
case Left(ex) =>
Expand All @@ -156,19 +151,23 @@ object Repl extends ScalaCommand[ReplOptions] {
}
}
def doRunReplFromBuild(
build: Build.Successful,
builds: Seq[Build.Successful],
allowExit: Boolean,
runMode: RunMode.HasRepl,
asJar: Boolean
): Unit =
): Unit = {
doRunRepl(
build.options,
build.artifacts,
Some(if (asJar) Library.libraryJar(build) else build.output),
allowExit,
runMode,
Some(build)
// build options should be the same for both scopes
// combining them may cause for ammonite args to be duplicated, so we're using the main scope's opts
buildOptions = builds.head.options,
allArtifacts = builds.map(_.artifacts),
mainJarsOrClassDirs =
if (asJar) builds.map(Library.libraryJar(_)) else builds.map(_.output),
allowExit = allowExit,
runMode = runMode,
successfulBuilds = builds
)
}

val cross = options.sharedRepl.compileCross.cross.getOrElse(false)
val configDb = ConfigDbUtils.configDb.orExit(logger)
Expand All @@ -178,18 +177,22 @@ object Repl extends ScalaCommand[ReplOptions] {
)

if (inputs.isEmpty) {
val artifacts = initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger)
val allArtifacts =
Seq(initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger)) ++
(if options.sharedRepl.scope.test
then Seq(initialBuildOptions.artifacts(logger, Scope.Test).orExit(logger))
else Nil)
// synchronizing, so that multiple presses to enter (handled by WatchUtil.waitForCtrlC)
// don't try to run repls in parallel
val lock = new Object
def runThing() = lock.synchronized {
doRunRepl(
initialBuildOptions,
artifacts,
None,
buildOptions = initialBuildOptions,
allArtifacts = allArtifacts,
mainJarsOrClassDirs = Seq.empty,
allowExit = !options.sharedRepl.watch.watchMode,
runMode = runMode(options),
buildOpt = None
successfulBuilds = Seq.empty
)
}
runThing()
Expand All @@ -207,22 +210,20 @@ object Repl extends ScalaCommand[ReplOptions] {
None,
logger,
crossBuilds = cross,
buildTests = false,
buildTests = options.sharedRepl.scope.test,
partial = None,
actionableDiagnostics = actionableDiagnostics,
postAction = () => WatchUtil.printWatchMessage()
) { res =>
for (builds <- res.orReport(logger))
builds.main match {
case s: Build.Successful =>
postBuild(builds, allowExit = false) {
successfulBuilds =>
doRunReplFromBuild(
s,
successfulBuilds,
allowExit = false,
runMode = runMode(options),
asJar = options.shared.asJar
)
case _: Build.Failed => buildFailed(allowExit = false)
case _: Build.Cancelled => buildCancelled(allowExit = false)
}
}
try WatchUtil.waitForCtrlC(() => watcher.schedule())
Expand All @@ -237,25 +238,35 @@ object Repl extends ScalaCommand[ReplOptions] {
None,
logger,
crossBuilds = cross,
buildTests = false,
buildTests = options.sharedRepl.scope.test,
partial = None,
actionableDiagnostics = actionableDiagnostics
)
.orExit(logger)
builds.main match {
case s: Build.Successful =>
postBuild(builds, allowExit = false) {
successfulBuilds =>
doRunReplFromBuild(
s,
successfulBuilds,
allowExit = true,
runMode = runMode(options),
asJar = options.shared.asJar
)
case _: Build.Failed => buildFailed(allowExit = true)
case _: Build.Cancelled => buildCancelled(allowExit = true)
}
}
}

def postBuild(builds: Builds, allowExit: Boolean)(f: Seq[Build.Successful] => Unit): Unit = {
if builds.anyBuildFailed then {
System.err.println("Compilation failed")
if allowExit then sys.exit(1)
}
else if builds.anyBuildCancelled then {
System.err.println("Build cancelled")
if allowExit then sys.exit(1)
}
else f(builds.builds.sortBy(_.scope).map(_.asInstanceOf[Build.Successful]))
}

private def maybeAdaptForWindows(args: Seq[String]): Seq[String] =
if (Properties.isWin)
args.map { a =>
Expand All @@ -268,24 +279,28 @@ object Repl extends ScalaCommand[ReplOptions] {
private def runRepl(
options: BuildOptions,
programArgs: Seq[String],
artifacts: Artifacts,
mainJarOrClassDir: Option[os.Path],
allArtifacts: Seq[Artifacts],
mainJarsOrClassDirs: Seq[os.Path],
directories: scala.build.Directories,
logger: Logger,
allowExit: Boolean,
dryRun: Boolean,
runMode: RunMode.HasRepl,
buildOpt: Option[Build.Successful]
successfulBuilds: Seq[Build.Successful]
): Either[BuildException, Unit] = either {

val setupPython = options.notForBloopOptions.python.getOrElse(false)

val cache = options.internal.cache.getOrElse(FileCache())
val shouldUseAmmonite = options.notForBloopOptions.replOptions.useAmmonite

val scalaParams = artifacts.scalaOpt match {
case Some(artifacts) => artifacts.params
case None => ScalaParameters(Constants.defaultScalaVersion)
val scalaParams: ScalaParameters = value {
val distinctScalaParams = allArtifacts.flatMap(_.scalaOpt).map(_.params).distinct
if distinctScalaParams.isEmpty then
Right(ScalaParameters(Constants.defaultScalaVersion))
else if distinctScalaParams.length == 1 then
Right(distinctScalaParams.head)
else Left(MultipleScalaVersionsError(distinctScalaParams.map(_.scalaVersion)))
}

val (scalapyJavaOpts, scalapyExtraEnv) =
Expand All @@ -302,7 +317,7 @@ object Repl extends ScalaCommand[ReplOptions] {
// Putting current dir in PYTHONPATH, see
// https://github.com/VirtusLab/scala-cli/pull/1616#issuecomment-1333283174
// for context.
val dirs = buildOpt.map(_.inputs.workspace).toSeq ++ Seq(os.pwd)
val dirs = successfulBuilds.map(_.inputs.workspace) ++ Seq(os.pwd)
(props0, pythonPathEnv(dirs: _*))
}
else
Expand Down Expand Up @@ -338,15 +353,14 @@ object Repl extends ScalaCommand[ReplOptions] {

// TODO Allow to disable printing the welcome banner and the "Loading..." message in Ammonite.

val rootClasses = mainJarOrClassDir match {
case None => Nil
case Some(dir) if os.isDir(dir) =>
val rootClasses = mainJarsOrClassDirs.flatMap {
case dir if os.isDir(dir) =>
os.list(dir)
.filter(_.last.endsWith(".class"))
.filter(os.isFile(_)) // just in case
.map(_.last.stripSuffix(".class"))
.sorted
case Some(jar) =>
case jar =>
var zf: ZipFile = null
try {
zf = new ZipFile(jar.toIO)
Expand Down Expand Up @@ -396,7 +410,7 @@ object Repl extends ScalaCommand[ReplOptions] {
replArtifacts.replJavaOpts ++
options.javaOptions.javaOpts.toSeq.map(_.value.value) ++
extraProps.toVector.sorted.map { case (k, v) => s"-D$k=$v" },
classPath = mainJarOrClassDir.toSeq ++ replArtifacts.replClassPath,
classPath = mainJarsOrClassDirs ++ replArtifacts.replClassPath,
mainClass = replArtifacts.replMainClass,
args = maybeAdaptForWindows(depClassPathArgs ++ replArgs),
logger = logger,
Expand All @@ -411,8 +425,8 @@ object Repl extends ScalaCommand[ReplOptions] {
value {
ReplArtifacts.default(
scalaParams,
artifacts.userDependencies,
artifacts.extraClassPath,
allArtifacts.flatMap(_.userDependencies).distinct,
allArtifacts.flatMap(_.extraClassPath).distinct,
logger,
cache,
value(options.finalRepositories),
Expand All @@ -427,9 +441,9 @@ object Repl extends ScalaCommand[ReplOptions] {
ReplArtifacts.ammonite(
scalaParams,
options.notForBloopOptions.replOptions.ammoniteVersion(scalaParams.scalaVersion, logger),
artifacts.userDependencies,
artifacts.extraClassPath,
artifacts.extraSourceJars,
allArtifacts.flatMap(_.userDependencies),
allArtifacts.flatMap(_.extraClassPath),
allArtifacts.flatMap(_.extraSourceJars),
value(options.finalRepositories),
logger,
cache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import caseapp.core.help.Help
import scala.cli.commands.shared.{
CrossOptions,
HelpGroup,
ScopeOptions,
SharedJavaOptions,
SharedPythonOptions,
SharedWatchOptions
Expand Down Expand Up @@ -48,7 +49,10 @@ final case class SharedReplOptions(
@Hidden
@Tag(tags.implementation)
@HelpMessage("Don't actually run the REPL, just fetch it")
replDryRun: Boolean = false
replDryRun: Boolean = false,

@Recurse
scope: ScopeOptions = ScopeOptions()
)
// format: on

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package scala.cli.commands.shared

import caseapp.*

import scala.cli.commands.tags

case class ScopeOptions(
@Group(HelpGroup.Compilation.toString)
@HelpMessage("Include test scope")
@Tag(tags.should)
@Tag(tags.inShortHelp)
@Name("testScope")
@Name("withTestScope")
@Name("withTest")
test: Boolean = false
)
object ScopeOptions {
implicit lazy val parser: Parser[ScopeOptions] = Parser.derive
implicit lazy val help: Help[ScopeOptions] = Help.derive
}
Loading
Loading