Skip to content

Commit ba74614

Browse files
committed
Add a launcher option allowing to override the default Scala version
1 parent 81acfaf commit ba74614

File tree

11 files changed

+137
-34
lines changed

11 files changed

+137
-34
lines changed

modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,25 +158,23 @@ case object ScriptPreprocessor extends Preprocessor {
158158
* code wrapper compatible with provided BuildOptions
159159
*/
160160
def getScriptWrapper(buildOptions: BuildOptions): CodeWrapper = {
161-
val scalaVersionOpt = for {
162-
maybeScalaVersion <- buildOptions.scalaOptions.scalaVersion
163-
scalaVersion <- maybeScalaVersion.versionOpt
164-
} yield scalaVersion
161+
val effectiveScalaVersion =
162+
buildOptions.scalaOptions.scalaVersion.flatMap(_.versionOpt)
163+
.orElse(buildOptions.scalaOptions.defaultScalaVersion)
164+
.getOrElse(Constants.defaultScalaVersion)
165165

166166
def objectCodeWrapperForScalaVersion =
167167
// AppObjectWrapper only introduces the 'main.sc' restriction when used in Scala 3, there's no gain in using it with Scala 3
168-
if (scalaVersionOpt.exists(_.startsWith("2")))
169-
AppCodeWrapper
170-
else
171-
ObjectCodeWrapper
168+
if effectiveScalaVersion.startsWith("2") then AppCodeWrapper
169+
else ObjectCodeWrapper
172170

173171
buildOptions.scriptOptions.forceObjectWrapper match {
174172
case Some(true) => objectCodeWrapperForScalaVersion
175173
case _ =>
176174
buildOptions.scalaOptions.platform.map(_.value) match {
177-
case Some(_: Platform.JS.type) => objectCodeWrapperForScalaVersion
178-
case _ if scalaVersionOpt.exists(_.startsWith("2")) => AppCodeWrapper
179-
case _ => ClassCodeWrapper
175+
case Some(_: Platform.JS.type) => objectCodeWrapperForScalaVersion
176+
case _ if effectiveScalaVersion.startsWith("2") => AppCodeWrapper
177+
case _ => ClassCodeWrapper
180178
}
181179
}
182180
}

modules/cli/src/main/scala/scala/cli/ScalaCli.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ object ScalaCli {
5353
private def isGraalvmNativeImage: Boolean =
5454
sys.props.contains("org.graalvm.nativeimage.imagecode")
5555

56+
private var defaultScalaVersion: Option[String] = None
57+
58+
def getDefaultScalaVersion: String = defaultScalaVersion.getOrElse(Constants.defaultScalaVersion)
59+
5660
private def partitionArgs(args: Array[String]): (Array[String], Array[String]) = {
5761
val systemProps = args.takeWhile(_.startsWith("-D"))
5862
(systemProps, args.drop(systemProps.size))
@@ -237,6 +241,8 @@ object ScalaCli {
237241
&& sys.props.get("scala-cli.kind").exists(_.startsWith("jvm")) =>
238242
JavaLauncherCli.runAndExit(args)
239243
case None =>
244+
if launcherOpts.cliUserScalaVersion.nonEmpty then
245+
defaultScalaVersion = launcherOpts.cliUserScalaVersion
240246
if launcherOpts.powerOptions.power then
241247
isSipScala = false
242248
args0.toArray

modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
3838
private val globalOptionsAtomic: AtomicReference[GlobalOptions] =
3939
new AtomicReference(GlobalOptions.default)
4040

41-
private def globalOptions: GlobalOptions = globalOptionsAtomic.get()
41+
private def globalOptions: GlobalOptions = globalOptionsAtomic.get()
42+
protected def defaultScalaVersion: String = ScalaCli.getDefaultScalaVersion
4243

4344
def sharedOptions(t: T): Option[SharedOptions] = // hello borked unused warning
4445
None
@@ -133,7 +134,7 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
133134
.toOption
134135
.flatten
135136
.map(_.scalaVersion)
136-
.getOrElse(Constants.defaultScalaVersion)
137+
.getOrElse(defaultScalaVersion)
137138
val (fromIndex, completions) = cache.logger.use {
138139
coursier.complete.Complete(cache)
139140
.withInput(prefix)
@@ -313,13 +314,20 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
313314
* change this behaviour.
314315
*/
315316
def buildOptions(options: T): Option[BuildOptions] =
316-
sharedOptions(options).map(shared => shared.buildOptions().orExit(shared.logger))
317+
sharedOptions(options)
318+
.map(shared => shared.buildOptions().orExit(shared.logger))
317319

318320
protected def buildOptionsOrExit(options: T): BuildOptions =
319-
buildOptions(options).getOrElse {
320-
sharedOptions(options).foreach(_.logger.debug("build options could not be initialized"))
321-
sys.exit(1)
322-
}
321+
buildOptions(options)
322+
.map(bo =>
323+
bo.copy(scalaOptions =
324+
bo.scalaOptions.copy(defaultScalaVersion = Some(defaultScalaVersion))
325+
)
326+
)
327+
.getOrElse {
328+
sharedOptions(options).foreach(_.logger.debug("build options could not be initialized"))
329+
sys.exit(1)
330+
}
323331
override def shouldSuppressExperimentalFeatureWarnings: Boolean =
324332
globalOptions.globalSuppress.suppressExperimentalFeatureWarning
325333
.orElse {

modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
166166
}
167167

168168
def finalBuildOptions(options: PackageOptions): BuildOptions = {
169-
val finalBuildOptions = options.finalBuildOptions.orExit(options.shared.logger)
169+
val initialOptions = options.finalBuildOptions.orExit(options.shared.logger)
170+
val finalBuildOptions = initialOptions.copy(scalaOptions =
171+
initialOptions.scalaOptions.copy(defaultScalaVersion = Some(defaultScalaVersion))
172+
)
170173
val buildOptions = finalBuildOptions.copy(
171174
javaOptions = finalBuildOptions.javaOptions.copy(
172175
javaOpts =

modules/cli/src/main/scala/scala/cli/commands/version/Version.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import caseapp.core.help.HelpFormat
55

66
import scala.build.Logger
77
import scala.build.internal.Constants
8-
import scala.cli.CurrentParams
98
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup}
109
import scala.cli.commands.update.Update
1110
import scala.cli.commands.{CommandUtils, ScalaCommand, SpecificationLevel}
1211
import scala.cli.config.PasswordOption
1312
import scala.cli.util.ArgHelpers.*
13+
import scala.cli.{CurrentParams, ScalaCli}
1414

1515
object Version extends ScalaCommand[VersionOptions] {
1616
override def group: String = HelpCommandGroup.Miscellaneous.toString
@@ -34,7 +34,7 @@ object Version extends ScalaCommand[VersionOptions] {
3434
else None
3535
}
3636
if options.cliVersion then println(Constants.version)
37-
else if options.scalaVersion then println(Constants.defaultScalaVersion)
37+
else if options.scalaVersion then println(ScalaCli.getDefaultScalaVersion)
3838
else {
3939
println(versionInfo)
4040
if !options.offline then
@@ -51,5 +51,5 @@ object Version extends ScalaCommand[VersionOptions] {
5151
val version = Constants.version
5252
val detailedVersionOpt = Constants.detailedVersion.filter(_ != version).fold("")(" (" + _ + ")")
5353
s"""$fullRunnerName version: $version$detailedVersionOpt
54-
|Scala version (default): ${Constants.defaultScalaVersion}""".stripMargin
54+
|Scala version (default): ${ScalaCli.getDefaultScalaVersion}""".stripMargin
5555
}

modules/cli/src/main/scala/scala/cli/launcher/LauncherOptions.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scala.cli.launcher
33
import caseapp.*
44

55
import scala.cli.commands.shared.HelpGroup
6-
import scala.cli.commands.tags
6+
import scala.cli.commands.{Constants, tags}
77

88
@HelpMessage("Run another Scala CLI version")
99
final case class LauncherOptions(
@@ -19,6 +19,15 @@ final case class LauncherOptions(
1919
@Hidden
2020
@Tag(tags.implementation)
2121
cliScalaVersion: Option[String] = None,
22+
@Group(HelpGroup.Launcher.toString)
23+
@HelpMessage(
24+
s"The default version of Scala used when processing user inputs (current default: ${Constants.defaultScalaVersion}). Can be overridden with --scala-version. "
25+
)
26+
@ValueDescription("version")
27+
@Hidden
28+
@Tag(tags.implementation)
29+
@Name("cliDefaultScalaVersion")
30+
cliUserScalaVersion: Option[String] = None,
2231
@Recurse
2332
powerOptions: PowerOptions = PowerOptions()
2433
)

modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import scala.util.{Properties, Using}
1414

1515
abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersionArgs {
1616
_: TestScalaVersion =>
17-
private lazy val extraOptions = scalaVersionArgs ++ TestUtil.extraOptions
17+
protected lazy val extraOptions: Seq[String] = scalaVersionArgs ++ TestUtil.extraOptions
1818

1919
def maybeUseBash(cmd: os.Shellable*)(cwd: os.Path = null): os.CommandResult = {
2020
val res = os.proc(cmd*).call(cwd = cwd, check = false)

modules/integration/src/test/scala/scala/cli/integration/PackageTestsDefault.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ class PackageTestsDefault extends PackageTestDefinitions with TestDefault {
1313
|""".stripMargin
1414
)
1515
inputs.fromRoot { root =>
16-
val runRes = os.proc(TestUtil.cli, "run", "--native", ".")
16+
val runRes = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions)
1717
.call(cwd = root)
1818
val runOutput = runRes.out.trim().linesIterator.filter(!_.startsWith("[info] ")).toVector
1919
expect(runOutput == Seq("Hello"))
2020

21-
val packageRes = os.proc(TestUtil.cli, "--power", "package", "--native", ".", "-o", "hello")
22-
.call(cwd = root, mergeErrIntoOut = true)
21+
val packageRes =
22+
os.proc(TestUtil.cli, "--power", "package", "--native", ".", "-o", "hello", extraOptions)
23+
.call(cwd = root, mergeErrIntoOut = true)
2324
val packageOutput = packageRes.out.trim()
2425
val topPackageOutput = packageOutput.linesIterator.takeWhile(!_.startsWith("Wrote ")).toVector
2526
// no compilation or Scala Native pipeline output, as this should just re-use what the run command wrote

modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,84 @@ class SipScalaTests extends ScalaCliSuite {
513513
expect(res.err.trim().contains(expectedError))
514514
}
515515
}
516+
517+
for {
518+
sv <- Seq(Constants.scala212, Constants.scala213, Constants.scala3NextRc)
519+
code =
520+
if (sv.startsWith("3")) "println(dotty.tools.dotc.config.Properties.simpleVersionString)"
521+
else "println(scala.util.Properties.versionNumberString)"
522+
anotherVersion =
523+
if (sv.startsWith("3")) Constants.scala3Lts
524+
else "2.13.7"
525+
} {
526+
test(
527+
s"default Scala version overridden with $sv by a launcher parameter is respected when running a script"
528+
) {
529+
TestInputs(os.rel / "simple.sc" -> code)
530+
.fromRoot { root =>
531+
val r = os.proc(
532+
TestUtil.cli,
533+
"--cli-default-scala-version",
534+
sv,
535+
"run",
536+
"simple.sc",
537+
"--with-compiler"
538+
)
539+
.call(cwd = root)
540+
expect(r.out.trim() == sv)
541+
}
542+
}
543+
test(
544+
s"default Scala version overridden with $sv by a launcher parameter is overridable by -S"
545+
) {
546+
TestInputs(os.rel / "simple.sc" -> code)
547+
.fromRoot { root =>
548+
val r = os.proc(
549+
TestUtil.cli,
550+
"--cli-default-scala-version",
551+
sv,
552+
"run",
553+
"simple.sc",
554+
"--with-compiler",
555+
"-S",
556+
anotherVersion
557+
)
558+
.call(cwd = root)
559+
expect(r.out.trim() == anotherVersion)
560+
}
561+
}
562+
563+
test(
564+
s"default Scala version overridden with $sv by a launcher parameter is respected when printing Scala version"
565+
) {
566+
TestInputs.empty.fromRoot { root =>
567+
val r =
568+
os.proc(TestUtil.cli, "--cli-default-scala-version", sv, "version", "--scala-version")
569+
.call(cwd = root)
570+
expect(r.out.trim() == sv)
571+
}
572+
}
573+
574+
test(
575+
s"default Scala version overridden with $sv by a launcher parameter is respected when printing versions"
576+
) {
577+
TestInputs.empty.fromRoot { root =>
578+
val r = os.proc(TestUtil.cli, "--cli-default-scala-version", sv, "version")
579+
.call(cwd = root)
580+
expect(r.out.trim().contains(sv))
581+
}
582+
}
583+
}
584+
585+
test(s"default Scala version override launcher option can only be passed once") {
586+
TestInputs.empty.fromRoot { root =>
587+
val (sv1, sv2) = (Constants.scala212, Constants.scala213)
588+
val launcherOpt = "--cli-default-scala-version"
589+
val r = os.proc(TestUtil.cli, launcherOpt, sv1, launcherOpt, sv2, "version")
590+
.call(cwd = root, check = false, stderr = os.Pipe)
591+
expect(r.exitCode == 1)
592+
expect(r.err.trim().contains(launcherOpt))
593+
expect(r.err.trim().contains("already specified"))
594+
}
595+
}
516596
}

modules/options/src/main/scala/scala/build/options/BuildOptions.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,6 @@ final case class BuildOptions(
245245
def javaHomeLocation(): Positioned[os.Path] =
246246
javaOptions.javaHomeLocation(archiveCache, finalCache, internal.verbosityOrDefault)
247247

248-
// used when downloading fails
249-
private def defaultStableScalaVersions =
250-
Seq(defaultScala212Version, defaultScala213Version, defaultScalaVersion)
251-
252248
def javaHome(): Positioned[JavaHomeInfo] = javaCommand0
253249

254250
lazy val javaHomeManager =
@@ -315,7 +311,8 @@ final case class BuildOptions(
315311
val defaultVersions = Set(
316312
Constants.defaultScalaVersion,
317313
Constants.defaultScala212Version,
318-
Constants.defaultScala213Version
314+
Constants.defaultScala213Version,
315+
scalaOptions.defaultScalaVersion.getOrElse(Constants.defaultScalaVersion)
319316
)
320317

321318
val svOpt: Option[String] = scalaOptions.scalaVersion match {
@@ -379,7 +376,7 @@ final case class BuildOptions(
379376
}
380377
Some(sv)
381378

382-
case None => Some(Constants.defaultScalaVersion)
379+
case None => Some(scalaOptions.defaultScalaVersion.getOrElse(Constants.defaultScalaVersion))
383380
}
384381

385382
svOpt match {

0 commit comments

Comments
 (0)