Skip to content

Commit de0c9df

Browse files
authored
Merge pull request #677 from romanowski/pr655
Convinient way to support nightly versions of compiler + more meaningfull error messages about missconfigured Scala versions
2 parents 15666b6 + 8a3d729 commit de0c9df

File tree

10 files changed

+457
-55
lines changed

10 files changed

+457
-55
lines changed

modules/build/src/main/scala/scala/build/Artifacts.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import scala.build.errors.{
2020
import scala.build.internal.Constants
2121
import scala.build.internal.Constants._
2222
import scala.build.internal.CsLoggerUtil._
23+
import scala.build.internal.ScalaParse.scala2NightlyRegex
2324
import scala.build.internal.Util.ScalaDependencyOps
2425

2526
final case class Artifacts(
@@ -134,12 +135,21 @@ object Artifacts {
134135
Nil
135136
}
136137

138+
val scala2NightlyRepo = Seq(coursier.Repositories.scalaIntegration.root)
139+
137140
val scalaNativeCliDependency =
138141
scalaNativeCliVersion.map(version =>
139142
Seq(dep"org.scala-native:scala-native-cli_2.12:$version")
140143
)
141144

142-
val allExtraRepositories = maybeSnapshotRepo ++ extraRepositories
145+
val isScala2NightlyRequested = scala2NightlyRegex.unapplySeq(params.scalaVersion).isDefined
146+
147+
val allExtraRepositories = {
148+
val baseRepos =
149+
maybeSnapshotRepo ++ extraRepositories
150+
if (isScala2NightlyRequested) baseRepos ++ scala2NightlyRepo
151+
else baseRepos
152+
}
143153

144154
val updatedDependencies =
145155
dependencies ++
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package scala.build.errors
22

3+
import scala.build.errors.ScalaVersionError.getTheGeneralErrorInfo
4+
35
final class InvalidBinaryScalaVersionError(
4-
val binaryVersion: String
5-
) extends ScalaVersionError(s"Cannot find matching Scala version for '$binaryVersion'")
6+
val invalidBinaryVersion: String,
7+
val latestSupportedStableVersions: Seq[String]
8+
) extends ScalaVersionError(s"Cannot find matching Scala version for '$invalidBinaryVersion'\n" +
9+
getTheGeneralErrorInfo(latestSupportedStableVersions))
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package scala.build.errors
22

3+
import scala.build.errors.ScalaVersionError.getTheGeneralErrorInfo
4+
35
final class NoValidScalaVersionFoundError(
4-
val foundVersions: Seq[String]
6+
val foundVersions: Seq[String],
7+
val latestSupportedStableVersions: Seq[String]
58
) extends ScalaVersionError(
6-
s"Cannot find valid Scala version among ${foundVersions.mkString(", ")}"
9+
s"Cannot find a valid matching Scala version among ${foundVersions.mkString(", ")}\n" +
10+
getTheGeneralErrorInfo(latestSupportedStableVersions)
711
)

modules/build/src/main/scala/scala/build/errors/ScalaVersionError.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,12 @@ import scala.build.Position
44

55
class ScalaVersionError(message: String, positions: Seq[Position] = Nil)
66
extends BuildException(message, positions = positions)
7+
8+
object ScalaVersionError {
9+
def getTheGeneralErrorInfo(latestSupportedStableVersions: Seq[String]): String =
10+
s"""You can only choose one of the 3.x, 2.13.x, and 2.12.x. versions.
11+
|The latest supported stable versions are ${latestSupportedStableVersions.mkString(", ")}.
12+
|In addition, you can request the latest Scala 2 and Scala 3 nightly versions by passing 2.nightly, and 3.nightly arguments respectively.
13+
|Specific Scala 2 or Scala 3 nightly versions are also accepted.
14+
|""".stripMargin
15+
}
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package scala.build.errors
22

3+
import scala.build.errors.ScalaVersionError.getTheGeneralErrorInfo
4+
35
final class UnsupportedScalaVersionError(
4-
val binaryVersion: String
5-
) extends ScalaVersionError(s"Unsupported Scala version: $binaryVersion")
6+
val binaryVersion: String,
7+
val latestSupportedStableVersions: Seq[String]
8+
) extends ScalaVersionError(
9+
s"Unsupported Scala version: $binaryVersion" + "\n" + getTheGeneralErrorInfo(
10+
latestSupportedStableVersions
11+
)
12+
)

modules/build/src/main/scala/scala/build/internal/ScalaParse.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ object ScalaParse {
88

99
import Scala._
1010

11+
val scala2NightlyRegex = raw"""2\.(\d+)\.(\d+)-bin-[a-f0-9]*""".r
12+
1113
// from https://github.com/com-lihaoyi/Ammonite/blob/0f0d597f04e62e86cbf76d3bd16deb6965331470/amm/compiler/src/main/scala/ammonite/compiler/Parsers.scala#L162-L176
1214
def formatFastparseError(fileName: String, rawCode: String, f: Parsed.Failure) = {
1315

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

Lines changed: 184 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package scala.build.options
2-
32
import coursier.cache.{ArchiveCache, FileCache}
4-
import coursier.core.Version
3+
import coursier.core.{Version, Versions => CoreVersions}
54
import coursier.jvm.{JavaHome, JvmCache, JvmIndex}
65
import coursier.util.{Artifact, Task}
6+
import coursier.{Module, Versions}
77
import dependency._
88

99
import java.math.BigInteger
@@ -13,15 +13,10 @@ import java.security.MessageDigest
1313

1414
import scala.build.EitherCps.{either, value}
1515
import scala.build.blooprifle.VersionUtil.parseJavaVersion
16-
import scala.build.errors.{
17-
BuildException,
18-
Diagnostic,
19-
InvalidBinaryScalaVersionError,
20-
NoValidScalaVersionFoundError,
21-
UnsupportedScalaVersionError
22-
}
16+
import scala.build.errors._
2317
import scala.build.internal.Constants._
2418
import scala.build.internal.CsLoggerUtil._
19+
import scala.build.internal.ScalaParse.scala2NightlyRegex
2520
import scala.build.internal.{OsLibc, StableScalaVersion, Util}
2621
import scala.build.options.validation.BuildOptionsRule
2722
import scala.build.{Artifacts, Logger, Os, Position, Positioned}
@@ -230,7 +225,7 @@ final case class BuildOptions(
230225
private def defaultStableScalaVersions =
231226
Seq(defaultScala212Version, defaultScala213Version, defaultScalaVersion)
232227

233-
private def latestSupportedScalaVersion(): Seq[Version] = {
228+
private def latestSupportedStableScalaVersion(): Seq[Version] = {
234229

235230
val cache = finalCache.withMessage("Getting list of Scala CLI-supported Scala versions")
236231
val supportedScalaVersionsUrl = scalaOptions.scalaVersionsUrl
@@ -298,30 +293,42 @@ final case class BuildOptions(
298293
def finalRepositories: Seq[String] =
299294
classPathOptions.extraRepositories ++ internal.localRepository.toSeq
300295

301-
private def computeScalaVersions(
302-
scalaVersion: Option[String],
303-
scalaBinaryVersion: Option[String]
296+
private lazy val maxSupportedStableScalaVersions = latestSupportedStableScalaVersion()
297+
298+
private lazy val latestSupportedStableVersions = maxSupportedStableScalaVersions.map(_.repr)
299+
300+
/** @param scalaVersionArg
301+
* the command line, using directive, or default argument passed as scala version
302+
* @param scalaBinaryVersionArg
303+
* the command line, using directive, or default argument passed as scala Binary version
304+
* @return
305+
* Either a BuildException or the calculated (ScalaVersion, ScalaBinaryVersion) tuple
306+
*/
307+
private def turnScalaVersionArgToScalaVersions(
308+
scalaVersionArg: Option[String],
309+
scalaBinaryVersionArg: Option[String]
304310
): Either[BuildException, (String, String)] = either {
305-
lazy val allVersions = {
311+
def isSupportedVersion(version: String): Boolean =
312+
version.startsWith("2.12.") || version.startsWith("2.13.") || version.startsWith("3.")
313+
lazy val allStableVersions = {
306314
import coursier._
307-
import scala.concurrent.ExecutionContext.{global => ec}
308315
val modules = {
309316
def scala2 = mod"org.scala-lang:scala-library"
310317
// No unstable, that *ought* not to be a problem down-the-line…?
311318
def scala3 = mod"org.scala-lang:scala3-library_3"
312-
if (scalaVersion.contains("2") || scalaVersion.exists(_.startsWith("2."))) Seq(scala2)
313-
else if (scalaVersion.contains("3") || scalaVersion.exists(_.startsWith("3."))) Seq(scala3)
319+
if (scalaVersionArg.contains("2") || scalaVersionArg.exists(_.startsWith("2."))) Seq(scala2)
320+
else if (scalaVersionArg.contains("3") || scalaVersionArg.exists(_.startsWith("3.")))
321+
Seq(scala3)
314322
else Seq(scala2, scala3)
315323
}
316324
def isStable(v: String): Boolean =
317325
!v.endsWith("-NIGHTLY") && !v.contains("-RC")
318326
def moduleVersions(mod: Module): Seq[String] = {
319-
val cache = finalCache.withMessage("Getting Scala version list")
320-
val res = cache.logger.use {
321-
try Versions(cache)
327+
val res = finalCache.logger.use {
328+
try Versions(finalCache)
322329
.withModule(mod)
323330
.result()
324-
.unsafeRun()(ec)
331+
.unsafeRun()(finalCache.ec)
325332
catch {
326333
case NonFatal(e) => throw new Exception(e)
327334
}
@@ -330,53 +337,187 @@ final case class BuildOptions(
330337
}
331338
modules.flatMap(moduleVersions).distinct
332339
}
333-
def matchNewestScalaVersion(sv: Option[String]) = {
334-
lazy val maxSupportedScalaVersions = latestSupportedScalaVersion()
335340

336-
sv match {
337-
case Some(sv0) =>
338-
val prefix = if (sv0.endsWith(".")) sv0 else sv0 + "."
339-
val matchingVersions = allVersions.filter(_.startsWith(prefix)).map(Version(_))
341+
def matchNewestStableScalaVersion(maybeScalaVersionStringArg: Option[String])
342+
: Either[ScalaVersionError, String] =
343+
maybeScalaVersionStringArg match {
344+
case Some(scalaVersionStringArg) =>
345+
val prefix =
346+
if (Util.isFullScalaVersion(scalaVersionStringArg)) scalaVersionStringArg
347+
else if (scalaVersionStringArg.endsWith(".")) scalaVersionStringArg
348+
else scalaVersionStringArg + "."
349+
val matchingVersions = allStableVersions.filter(_.startsWith(prefix)).map(Version(_))
340350
if (matchingVersions.isEmpty)
341-
Left(new InvalidBinaryScalaVersionError(sv0))
351+
Left(new InvalidBinaryScalaVersionError(
352+
scalaVersionStringArg,
353+
latestSupportedStableVersions
354+
))
342355
else {
343-
val validMaxVersions = maxSupportedScalaVersions
356+
val validMaxVersions = maxSupportedStableScalaVersions
344357
.filter(_.repr.startsWith(prefix))
345358
val validMatchingVersions = {
346359
val filtered = matchingVersions.filter(v => validMaxVersions.exists(v <= _))
347360
if (filtered.isEmpty) matchingVersions
348361
else filtered
349-
}
362+
}.filter(v => isSupportedVersion(v.repr))
350363
if (validMatchingVersions.isEmpty)
351-
Left(new UnsupportedScalaVersionError(sv0))
364+
Left(new UnsupportedScalaVersionError(
365+
scalaVersionStringArg,
366+
latestSupportedStableVersions
367+
))
352368
else
353369
Right(validMatchingVersions.max.repr)
354370
}
355371
case None =>
356-
val validVersions = allVersions
372+
val validVersions = allStableVersions
357373
.map(Version(_))
358-
.filter(v => maxSupportedScalaVersions.exists(v <= _))
374+
.filter(v => maxSupportedStableScalaVersions.exists(v <= _))
359375
if (validVersions.isEmpty)
360-
Left(new NoValidScalaVersionFoundError(allVersions))
376+
Left(new NoValidScalaVersionFoundError(
377+
allStableVersions,
378+
latestSupportedStableVersions
379+
))
361380
else
362381
Right(validVersions.max.repr)
363382
}
383+
384+
val scalaVersion = value(matchNewestStableScalaVersion(scalaVersionArg))
385+
val scalaBinaryVersion = scalaBinaryVersionArg.getOrElse(ScalaVersion.binary(scalaVersion))
386+
(scalaVersion, scalaBinaryVersion)
387+
}
388+
389+
private def latestScalaVersionFrom(
390+
versions: CoreVersions,
391+
desc: String
392+
): Either[scala.build.errors.ScalaVersionError, String] =
393+
versions.latest(coursier.core.Latest.Release) match {
394+
case Some(versionString) => Right(versionString)
395+
case None =>
396+
val msg =
397+
s"Unable to find matching version for $desc in available version: ${versions.available.mkString(", ")}. " +
398+
"This error may indicate a network or other problem accessing repository."
399+
Left(new ScalaVersionError(msg))
364400
}
365-
val maybeSv = scalaVersion match {
366-
case None => matchNewestScalaVersion(None)
367-
case Some(sv0) =>
368-
if (Util.isFullScalaVersion(sv0)) Right(sv0)
369-
else matchNewestScalaVersion(Some(sv0))
401+
402+
/** @return
403+
* Either a BuildException or the calculated (ScalaVersion, ScalaBinaryVersion) tuple
404+
*/
405+
private def computeLatestScalaThreeNightlyVersions(): Either[BuildException, (String, String)] =
406+
either {
407+
import coursier.Versions
408+
import coursier._
409+
410+
val moduleVersion: Either[ScalaVersionError, String] = {
411+
def scala3 = mod"org.scala-lang:scala3-library_3"
412+
val res = finalCache.logger.use {
413+
Versions(finalCache)
414+
.withModule(scala3)
415+
.result()
416+
.unsafeRun()(finalCache.ec)
417+
}
418+
latestScalaVersionFrom(res.versions, "latest Scala 3 nightly build")
419+
}
420+
421+
val scalaVersion = value(moduleVersion)
422+
val scalaBinaryVersion = ScalaVersion.binary(scalaVersion)
423+
(scalaVersion, scalaBinaryVersion)
370424
}
371425

372-
val sv = value(maybeSv)
373-
val sbv = scalaBinaryVersion.getOrElse(ScalaVersion.binary(sv))
374-
(sv, sbv)
426+
/** @return
427+
* Either a BuildException or the calculated (ScalaVersion, ScalaBinaryVersion) tuple
428+
*/
429+
private def computeLatestScalaTwoNightlyVersions(): Either[BuildException, (String, String)] =
430+
either {
431+
import coursier.Versions
432+
import coursier._
433+
434+
val moduleVersion: Either[ScalaVersionError, String] = {
435+
def scalaNightly2Module: Module = mod"org.scala-lang:scala-library"
436+
val res = finalCache.logger.use {
437+
Versions(finalCache)
438+
.withModule(scalaNightly2Module)
439+
.withRepositories(Seq(coursier.Repositories.scalaIntegration))
440+
.result()
441+
.unsafeRun()(finalCache.ec)
442+
}
443+
latestScalaVersionFrom(res.versions, "latest Scala 2 nightly build")
444+
}
445+
446+
val scalaVersion = value(moduleVersion)
447+
val scalaBinaryVersion = ScalaVersion.binary(scalaVersion)
448+
(scalaVersion, scalaBinaryVersion)
449+
}
450+
451+
private def turnScala2NightlyVersionArgToVersions(versionString: String)
452+
: Either[BuildException, (String, String)] = either {
453+
454+
val moduleVersion: Either[ScalaVersionError, String] = {
455+
import coursier._
456+
def scalaNightly2Module: Module = mod"org.scala-lang:scala-library"
457+
val res = finalCache.logger.use {
458+
Versions(finalCache)
459+
.withModule(scalaNightly2Module)
460+
.withRepositories(Seq(coursier.Repositories.scalaIntegration))
461+
.result()
462+
.unsafeRun()(finalCache.ec)
463+
}
464+
if (res.versions.available.contains(versionString)) Right(versionString)
465+
else
466+
Left(
467+
new NoValidScalaVersionFoundError(res.versions.available, latestSupportedStableVersions)
468+
)
469+
}
470+
471+
val scalaVersion = value(moduleVersion)
472+
val scalaBinaryVersion = ScalaVersion.binary(scalaVersion)
473+
(scalaVersion, scalaBinaryVersion)
474+
}
475+
476+
private def turnScala3NightlyVersionArgIntoVersion(versionString: String)
477+
: Either[BuildException, (String, String)] = either {
478+
val moduleVersion: Either[ScalaVersionError, String] = {
479+
import coursier._
480+
def scala3 = mod"org.scala-lang:scala3-library_3"
481+
val res = finalCache.logger.use {
482+
Versions(finalCache)
483+
.withModule(scala3)
484+
.result()
485+
.unsafeRun()(finalCache.ec)
486+
}
487+
if (res.versions.available.contains(versionString)) Right(versionString)
488+
else
489+
Left(
490+
new NoValidScalaVersionFoundError(res.versions.available, latestSupportedStableVersions)
491+
)
492+
}
493+
494+
val scalaVersion = value(moduleVersion)
495+
val scalaBinaryVersion = ScalaVersion.binary(scalaVersion)
496+
(scalaVersion, scalaBinaryVersion)
375497
}
376498

377499
lazy val scalaParams: Either[BuildException, ScalaParameters] = either {
500+
def isScala2Nightly(version: String): Boolean =
501+
scala2NightlyRegex.unapplySeq(version).isDefined
502+
def isScala3Nightly(version: String): Boolean =
503+
version.startsWith("3") && version.endsWith("-NIGHTLY")
504+
378505
val (scalaVersion, scalaBinaryVersion) =
379-
value(computeScalaVersions(scalaOptions.scalaVersion, scalaOptions.scalaBinaryVersion))
506+
value {
507+
scalaOptions.scalaVersion match {
508+
case Some("3.nightly") => computeLatestScalaThreeNightlyVersions()
509+
case Some("2.nightly") => computeLatestScalaTwoNightlyVersions()
510+
case Some(versionString) if isScala3Nightly(versionString) =>
511+
turnScala3NightlyVersionArgIntoVersion(versionString)
512+
case Some(versionString) if isScala2Nightly(versionString) =>
513+
turnScala2NightlyVersionArgToVersions(versionString)
514+
case _ => turnScalaVersionArgToScalaVersions(
515+
scalaOptions.scalaVersion,
516+
scalaOptions.scalaBinaryVersion
517+
)
518+
}
519+
}
520+
380521
val maybePlatformSuffix = platform.value match {
381522
case Platform.JVM => None
382523
case Platform.JS => Some(scalaJsOptions.platformSuffix)

0 commit comments

Comments
 (0)