diff --git a/modules/build/src/main/scala/scala/build/Artifacts.scala b/modules/build/src/main/scala/scala/build/Artifacts.scala index c9c330e8bf..1ee62ccadc 100644 --- a/modules/build/src/main/scala/scala/build/Artifacts.scala +++ b/modules/build/src/main/scala/scala/build/Artifacts.scala @@ -18,7 +18,6 @@ import scala.build.errors.{ import scala.build.internal.Constants import scala.build.internal.Constants._ import scala.build.internal.CsLoggerUtil._ -import scala.build.internal.ScalaParse.scala2NightlyRegex import scala.build.internal.Util.ScalaDependencyOps final case class Artifacts( @@ -134,22 +133,14 @@ object Artifacts { Nil } - val scala2NightlyRepo = Seq(coursier.Repositories.scalaIntegration.root) - val scalaNativeCliDependency = scalaNativeCliVersion.map { version => import coursier.moduleString Seq(coursier.Dependency(mod"org.scala-native:scala-native-cli_2.12", version)) } - val isScala2NightlyRequested = scala2NightlyRegex.unapplySeq(params.scalaVersion).isDefined - - val allExtraRepositories = { - val baseRepos = - maybeSnapshotRepo ++ extraRepositories - if (isScala2NightlyRequested) baseRepos ++ scala2NightlyRepo - else baseRepos - } + val allExtraRepositories = + maybeSnapshotRepo ++ extraRepositories val internalDependencies = jvmRunnerDependencies.map(Positioned.none(_)) ++ diff --git a/modules/build/src/main/scala/scala/build/errors/ScalaVersionError.scala b/modules/build/src/main/scala/scala/build/errors/ScalaVersionError.scala index fe1bb3e7cf..02df3f6bc6 100644 --- a/modules/build/src/main/scala/scala/build/errors/ScalaVersionError.scala +++ b/modules/build/src/main/scala/scala/build/errors/ScalaVersionError.scala @@ -9,7 +9,8 @@ object ScalaVersionError { def getTheGeneralErrorInfo(latestSupportedStableVersions: Seq[String]): String = s"""You can only choose one of the 3.x, 2.13.x, and 2.12.x. versions. |The latest supported stable versions are ${latestSupportedStableVersions.mkString(", ")}. - |In addition, you can request the latest Scala 2 and Scala 3 nightly versions by passing 2.nightly, and 3.nightly arguments respectively. + |In addition, you can request compilation with the last nightly versions of Scala, + |by passing the 2.nightly, 2.12.nightly, 2.13.nightly, or 3.nightly arguments. |Specific Scala 2 or Scala 3 nightly versions are also accepted. |""".stripMargin } diff --git a/modules/build/src/main/scala/scala/build/options/BuildOptions.scala b/modules/build/src/main/scala/scala/build/options/BuildOptions.scala index 45f63da19e..b7d16705b9 100644 --- a/modules/build/src/main/scala/scala/build/options/BuildOptions.scala +++ b/modules/build/src/main/scala/scala/build/options/BuildOptions.scala @@ -49,9 +49,16 @@ final case class BuildOptions( Seq(s"Scala ${value(scalaParams).scalaVersion}", platform0) } + lazy val scalaVersionIsExotic = scalaParams.exists { scalaParameters => + scalaParameters.scalaVersion.startsWith("2") && scalaParameters.scalaVersion.exists(_.isLetter) + } + def addRunnerDependency: Option[Boolean] = internalDependencies.addRunnerDependencyOpt - .orElse(if (platform.value == Platform.JVM) None else Some(false)) + .orElse { + if (platform.value == Platform.JVM && !scalaVersionIsExotic) None + else Some(false) + } private def scalaLibraryDependencies: Either[BuildException, Seq[AnyDependency]] = either { if (platform.value != Platform.Native && scalaOptions.addScalaLibrary.getOrElse(true)) { @@ -289,13 +296,52 @@ final case class BuildOptions( JavaHome().withCache(jvmCache) } + private val scala2NightlyRepo = Seq(coursier.Repositories.scalaIntegration.root) + def finalRepositories: Seq[String] = - classPathOptions.extraRepositories ++ internal.localRepository.toSeq + scalaParams.map { params => + if (isScala2Nightly(params.scalaVersion)) scala2NightlyRepo else Seq.empty + }.getOrElse(Seq.empty) ++ + classPathOptions.extraRepositories ++ internal.localRepository.toSeq private lazy val maxSupportedStableScalaVersions = latestSupportedStableScalaVersion() private lazy val latestSupportedStableVersions = maxSupportedStableScalaVersions.map(_.repr) + private def getAllMatchingStableVersions(scalaVersionArg: Option[String]): Seq[String] = { + + def isStable(version: String): Boolean = + !version.exists(_.isLetter) + + modules(scalaVersionArg).flatMap(moduleVersions(_).versions.available.filter(isStable)).distinct + } + + private def modules(maybeScalaVersionArg: Option[String]) = { + import coursier.moduleString + def scala2 = mod"org.scala-lang:scala-library" + // No unstable, that *ought* not to be a problem down-the-line…? + def scala3 = mod"org.scala-lang:scala3-library_3" + if (maybeScalaVersionArg.contains("2") || maybeScalaVersionArg.exists(_.startsWith("2."))) + Seq(scala2) + else if (maybeScalaVersionArg.contains("3") || maybeScalaVersionArg.exists(_.startsWith("3."))) + Seq(scala3) + else Seq(scala2, scala3) + } + + private def moduleVersions(mod: Module): Versions.Result = + finalCache.logger.use { + try Versions(finalCache) + .withModule(mod) + .result() + .unsafeRun()(finalCache.ec) + catch { + case NonFatal(e) => throw new Exception(e) + } + } + + private def getAllMatchingVersions(maybeScalaVersionArg: Option[String]): Seq[String] = + modules(maybeScalaVersionArg).flatMap(moduleVersions(_).versions.available).distinct + /** @param scalaVersionArg * the command line, using directive, or default argument passed as scala version * @param scalaBinaryVersionArg @@ -303,87 +349,105 @@ final case class BuildOptions( * @return * Either a BuildException or the calculated (ScalaVersion, ScalaBinaryVersion) tuple */ - private def turnScalaVersionArgToScalaVersions( + private def turnScalaVersionArgToStableScalaVersions( scalaVersionArg: Option[String], scalaBinaryVersionArg: Option[String] ): Either[BuildException, (String, String)] = either { - def isSupportedVersion(version: String): Boolean = - version.startsWith("2.12.") || version.startsWith("2.13.") || version.startsWith("3.") - lazy val allStableVersions = { - val modules = { - import coursier.moduleString - def scala2 = mod"org.scala-lang:scala-library" - // No unstable, that *ought* not to be a problem down-the-line…? - def scala3 = mod"org.scala-lang:scala3-library_3" - if (scalaVersionArg.contains("2") || scalaVersionArg.exists(_.startsWith("2."))) Seq(scala2) - else if (scalaVersionArg.contains("3") || scalaVersionArg.exists(_.startsWith("3."))) - Seq(scala3) - else Seq(scala2, scala3) - } - def isStable(v: String): Boolean = - !v.endsWith("-NIGHTLY") && !v.contains("-RC") - def moduleVersions(mod: Module): Seq[String] = { - val res = finalCache.logger.use { - try Versions(finalCache) - .withModule(mod) - .result() - .unsafeRun()(finalCache.ec) - catch { - case NonFatal(e) => throw new Exception(e) - } - } - res.versions.available.filter(isStable) - } - modules.flatMap(moduleVersions).distinct - } - def matchNewestStableScalaVersion(maybeScalaVersionStringArg: Option[String]) - : Either[ScalaVersionError, String] = - maybeScalaVersionStringArg match { - case Some(scalaVersionStringArg) => - val prefix = - if (Util.isFullScalaVersion(scalaVersionStringArg)) scalaVersionStringArg - else if (scalaVersionStringArg.endsWith(".")) scalaVersionStringArg - else scalaVersionStringArg + "." - val matchingVersions = allStableVersions.filter(_.startsWith(prefix)).map(Version(_)) - if (matchingVersions.isEmpty) - Left(new InvalidBinaryScalaVersionError( + lazy val allStableVersions = getAllMatchingStableVersions(scalaVersionArg) + + val scalaVersion = value(matchNewestStableScalaVersion(scalaVersionArg, allStableVersions)) + val scalaBinaryVersion = scalaBinaryVersionArg.getOrElse(ScalaVersion.binary(scalaVersion)) + (scalaVersion, scalaBinaryVersion) + } + + private def turnScalaVersionArgToNonStableScalaVersions( + scalaVersionArg: Option[String], + scalaBinaryVersionArg: Option[String] + ): Either[BuildException, (String, String)] = either { + + lazy val allStableVersions = getAllMatchingVersions(scalaVersionArg) + + val scalaVersion = value(matchNewestNonStableScalaVersion(scalaVersionArg, allStableVersions)) + val scalaBinaryVersion = scalaBinaryVersionArg.getOrElse(ScalaVersion.binary(scalaVersion)) + (scalaVersion, scalaBinaryVersion) + } + + private def isSupportedVersion(version: String): Boolean = + version.startsWith("2.12.") || version.startsWith("2.13.") || version.startsWith("3.") + + private def matchNewestStableScalaVersion( + maybeScalaVersionStringArg: Option[String], + versionPool: Seq[String] + ): Either[ScalaVersionError, String] = + maybeScalaVersionStringArg match { + case Some(scalaVersionStringArg) => + val prefix = + if (Util.isFullScalaVersion(scalaVersionStringArg)) scalaVersionStringArg + else if (scalaVersionStringArg.endsWith(".")) scalaVersionStringArg + else scalaVersionStringArg + "." + val matchingStableVersions = versionPool.filter(_.startsWith(prefix)).map(Version(_)) + if (matchingStableVersions.isEmpty) + Left(new InvalidBinaryScalaVersionError( + scalaVersionStringArg, + latestSupportedStableVersions + )) + else { + val validMaxVersions = maxSupportedStableScalaVersions + .filter(_.repr.startsWith(prefix)) + val validMatchingVersions = { + val filtered = matchingStableVersions.filter(v => validMaxVersions.exists(v <= _)) + if (filtered.isEmpty) matchingStableVersions + else filtered + }.filter(v => isSupportedVersion(v.repr)) + if (validMatchingVersions.isEmpty) + Left(new UnsupportedScalaVersionError( scalaVersionStringArg, latestSupportedStableVersions )) - else { - val validMaxVersions = maxSupportedStableScalaVersions - .filter(_.repr.startsWith(prefix)) - val validMatchingVersions = { - val filtered = matchingVersions.filter(v => validMaxVersions.exists(v <= _)) - if (filtered.isEmpty) matchingVersions - else filtered - }.filter(v => isSupportedVersion(v.repr)) - if (validMatchingVersions.isEmpty) - Left(new UnsupportedScalaVersionError( - scalaVersionStringArg, - latestSupportedStableVersions - )) - else - Right(validMatchingVersions.max.repr) - } - case None => - val validVersions = allStableVersions - .map(Version(_)) - .filter(v => maxSupportedStableScalaVersions.exists(v <= _)) - if (validVersions.isEmpty) - Left(new NoValidScalaVersionFoundError( - allStableVersions, + else + Right(validMatchingVersions.max.repr) + } + case None => + val validVersions = versionPool + .map(Version(_)) + .filter(v => maxSupportedStableScalaVersions.exists(v <= _)) + if (validVersions.isEmpty) + Left(new NoValidScalaVersionFoundError( + versionPool, + latestSupportedStableVersions + )) + else + Right(validVersions.max.repr) + } + + private def matchNewestNonStableScalaVersion( + maybeScalaVersionStringArg: Option[String], + versionPool: Seq[String] + ): Either[ScalaVersionError, String] = + maybeScalaVersionStringArg match { + case Some(scalaVersionStringArg) => + if (versionPool.contains(scalaVersionStringArg)) + if (isSupportedVersion(scalaVersionStringArg)) + Right(scalaVersionStringArg) + else + Left(new UnsupportedScalaVersionError( + scalaVersionStringArg, latestSupportedStableVersions )) - else - Right(validVersions.max.repr) - } + else + Left(new InvalidBinaryScalaVersionError( + scalaVersionStringArg, + latestSupportedStableVersions + )) - val scalaVersion = value(matchNewestStableScalaVersion(scalaVersionArg)) - val scalaBinaryVersion = scalaBinaryVersionArg.getOrElse(ScalaVersion.binary(scalaVersion)) - (scalaVersion, scalaBinaryVersion) - } + case None => + Left(new NoValidScalaVersionFoundError( + versionPool, + latestSupportedStableVersions + )) + + } private def latestScalaVersionFrom( versions: CoreVersions, @@ -491,22 +555,54 @@ final case class BuildOptions( (scalaVersion, scalaBinaryVersion) } + def computeLatestScalaTwoTwelveNightlyVersions(): Either[BuildException, (String, String)] = + either { + val moduleVersion: Either[ScalaVersionError, String] = { + import coursier.moduleString + def scalaNightly2Module: Module = mod"org.scala-lang:scala-library" + val res = finalCache.logger.use { + Versions(finalCache) + .withModule(scalaNightly2Module) + .withRepositories(Seq(coursier.Repositories.scalaIntegration)) + .result() + .unsafeRun()(finalCache.ec) + }.versions.available + val twoTwelveNightlies = res.filter(_.startsWith("2.12.")).map(Version(_)) + if (twoTwelveNightlies.nonEmpty) Right(twoTwelveNightlies.max.repr) + else Left( + new NoValidScalaVersionFoundError(res, latestSupportedStableVersions) + ) + } + + val scalaVersion = value(moduleVersion) + val scalaBinaryVersion = ScalaVersion.binary(scalaVersion) + (scalaVersion, scalaBinaryVersion) + } + + private def isScala2Nightly(version: String): Boolean = + scala2NightlyRegex.unapplySeq(version).isDefined + lazy val scalaParams: Either[BuildException, ScalaParameters] = either { - def isScala2Nightly(version: String): Boolean = - scala2NightlyRegex.unapplySeq(version).isDefined def isScala3Nightly(version: String): Boolean = version.startsWith("3") && version.endsWith("-NIGHTLY") val (scalaVersion, scalaBinaryVersion) = value { scalaOptions.scalaVersion match { - case Some("3.nightly") => computeLatestScalaThreeNightlyVersions() - case Some("2.nightly") => computeLatestScalaTwoNightlyVersions() + case Some("3.nightly") => computeLatestScalaThreeNightlyVersions() + case Some("2.nightly") => computeLatestScalaTwoNightlyVersions() + case Some("2.13.nightly") => computeLatestScalaTwoNightlyVersions() + case Some("2.12.nightly") => computeLatestScalaTwoTwelveNightlyVersions() case Some(versionString) if isScala3Nightly(versionString) => turnScala3NightlyVersionArgIntoVersion(versionString) case Some(versionString) if isScala2Nightly(versionString) => turnScala2NightlyVersionArgToVersions(versionString) - case _ => turnScalaVersionArgToScalaVersions( + case Some(versionString) if versionString.exists(_.isLetter) => + turnScalaVersionArgToNonStableScalaVersions( + scalaOptions.scalaVersion, + scalaOptions.scalaBinaryVersion + ) + case _ => turnScalaVersionArgToStableScalaVersions( scalaOptions.scalaVersion, scalaOptions.scalaBinaryVersion ) diff --git a/modules/build/src/test/scala/scala/build/tests/BuildOptionsTests.scala b/modules/build/src/test/scala/scala/build/tests/BuildOptionsTests.scala index 98f7eec9ce..b4db7eebb2 100644 --- a/modules/build/src/test/scala/scala/build/tests/BuildOptionsTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/BuildOptionsTests.scala @@ -124,6 +124,20 @@ class BuildOptionsTests extends munit.FunSuite { ) } + test("Scala 3.1.2-RC1 works") { + + val options = BuildOptions( + scalaOptions = ScalaOptions( + scalaVersion = Some("3.1.2-RC1") + ) + ) + val scalaParams = options.scalaParams.orThrow + assert( + scalaParams.scalaVersion == "3.1.2-RC1", + "-S 3.1.2-RC1 argument does not lead to 3.1.2-RC1 build option" + ) + } + test("Scala 2.12.9-bin-1111111 shows No Valid Scala Version Error") { val options = BuildOptions( @@ -171,6 +185,32 @@ class BuildOptionsTests extends munit.FunSuite { ) } + test("-S 2.13.nightly option works") { + val options = BuildOptions( + scalaOptions = ScalaOptions( + scalaVersion = Some("2.13.nightly") + ) + ) + val scalaParams = options.scalaParams.orThrow + assert( + scala2NightlyRegex.unapplySeq(scalaParams.scalaVersion).isDefined, + "-S 2.13.nightly argument does not lead to scala2 nightly build option" + ) + } + + test("-S 2.12.nightly option works") { + val options = BuildOptions( + scalaOptions = ScalaOptions( + scalaVersion = Some("2.12.nightly") + ) + ) + val scalaParams = options.scalaParams.orThrow + assert( + scalaParams.scalaVersion == "2.12.16-bin-586302a", + "-S 2.12.nightly argument does not lead to scala2 nightly build option" + ) + } + test("-S 2.13.9-bin-4505094 option works without repo specification") { val options = BuildOptions( scalaOptions = ScalaOptions( diff --git a/website/docs/commands/compile.md b/website/docs/commands/compile.md index 822e463472..6c128644e3 100644 --- a/website/docs/commands/compile.md +++ b/website/docs/commands/compile.md @@ -68,7 +68,7 @@ scala-cli compile --scala 3 Hello.scala The nightly builds of Scala compiler are unstable ones which are published on a nightly basis. -For using the latest Scala 2 and Scala 3 nightly builds, you should pass `2.nightly` and `3.nightly`, respectively. +To use the latest Scala 2 and Scala 3 nightly builds, pass `2.nightly` and `3.nightly`, respectively. You can also request the last `2.12.nightly` and `2.13.nightly` versions. `2.13.nightly` is the same as `2.nightly`. Scala CLI takes care of fetching the nightly builds of Scala 2 and Scala 3 from different repositories, without you having to pass their addresses as input after the `--repo` flag.