Skip to content

Commit 0d79305

Browse files
Use the newest scala version if is not specified (#403)
* Use the newest scala version if is not specified * If downloading configuration files is fail use default from deps.sc * Handle more errors that can happen when choosing a Scala version * Update docs * Fix - scalafmt Co-authored-by: Alexandre Archambault <[email protected]>
1 parent 72bdba0 commit 0d79305

File tree

16 files changed

+224
-21
lines changed

16 files changed

+224
-21
lines changed

build.sc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class Build(val crossScalaVersion: String)
184184
Deps.scalaparse,
185185
Deps.shapeless,
186186
Deps.swoval,
187+
Deps.upickle,
187188
Deps.usingDirectives
188189
)
189190

@@ -246,6 +247,8 @@ class Build(val crossScalaVersion: String)
246247
| def defaultScalafmtVersion = "${Deps.scalafmtCli.dep.version}"
247248
|
248249
| def defaultScalaVersion = "${Scala.defaultUser}"
250+
| def defaultScala212Version = "${Scala.scala212}"
251+
| def defaultScala213Version = "${Scala.scala213}"
249252
|}
250253
|""".stripMargin
251254
if (!os.isFile(dest) || os.read(dest) != code)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package scala.build.errors
22

33
final class InvalidBinaryScalaVersionError(
44
val binaryVersion: String
5-
) extends BuildException(s"Cannot find matching Scala version for '$binaryVersion'")
5+
) extends ScalaVersionError(s"Cannot find matching Scala version for '$binaryVersion'")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scala.build.errors
2+
3+
final class NoValidScalaVersionFoundError(
4+
val foundVersions: Seq[String]
5+
) extends ScalaVersionError(
6+
s"Cannot find valid Scala version among ${foundVersions.mkString(", ")}"
7+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package scala.build.errors
2+
3+
import scala.build.Position
4+
5+
class ScalaVersionError(message: String, positions: Seq[Position] = Nil)
6+
extends BuildException(message, positions = positions)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package scala.build.errors
2+
3+
final class UnsupportedScalaVersionError(
4+
val binaryVersion: String
5+
) extends ScalaVersionError(s"Unsupported Scala version: $binaryVersion")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package scala.build.internal
2+
3+
import upickle.default.{ReadWriter, macroRW}
4+
5+
case class StableScalaVersion(scalaCliVersion: String, supportedScalaVersions: Seq[String])
6+
7+
object StableScalaVersion {
8+
implicit lazy val jsonCodec: ReadWriter[StableScalaVersion] = macroRW
9+
}

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

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package scala.build.options
22

33
import coursier.cache.{ArchiveCache, FileCache}
4+
import coursier.core.Version
45
import coursier.jvm.{JavaHome, JvmCache, JvmIndex}
6+
import coursier.util.{Artifact, Task}
57
import dependency._
68

79
import java.math.BigInteger
@@ -11,9 +13,15 @@ import java.security.MessageDigest
1113

1214
import scala.build.EitherCps.{either, value}
1315
import scala.build.blooprifle.VersionUtil.parseJavaVersion
14-
import scala.build.errors.{BuildException, Diagnostic, InvalidBinaryScalaVersionError}
16+
import scala.build.errors.{
17+
BuildException,
18+
Diagnostic,
19+
InvalidBinaryScalaVersionError,
20+
NoValidScalaVersionFoundError,
21+
UnsupportedScalaVersionError
22+
}
1523
import scala.build.internal.Constants._
16-
import scala.build.internal.{Constants, OsLibc, Util}
24+
import scala.build.internal.{OsLibc, StableScalaVersion, Util}
1725
import scala.build.options.validation.BuildOptionsRule
1826
import scala.build.{Artifacts, Logger, Os, Position, Positioned}
1927
import scala.util.Properties
@@ -166,6 +174,52 @@ final case class BuildOptions(
166174
}
167175
}
168176

177+
// used when downloading fails
178+
private def defaultStableScalaVersions =
179+
Seq(defaultScala212Version, defaultScala213Version, defaultScalaVersion)
180+
181+
private def latestSupportedScalaVersion(): Seq[Version] = {
182+
183+
val supportedScalaVersionsUrl = scalaOptions.scalaVersionsUrl
184+
185+
val task = {
186+
val art = Artifact(supportedScalaVersionsUrl).withChanging(true)
187+
finalCache.file(art).run.flatMap {
188+
case Left(e) => Task.fail(e)
189+
case Right(f) =>
190+
Task.delay {
191+
val content = os.read(os.Path(f, Os.pwd))
192+
upickle.default.read[Seq[StableScalaVersion]](content)
193+
}
194+
}
195+
}
196+
197+
val scalaCliVersion = version
198+
val launchersTask = finalCache.logger.using(task)
199+
200+
// If an error occurred while downloading stable versions,
201+
// it uses stable scala versions from Deps.sc
202+
val supportedScalaVersions =
203+
launchersTask.attempt.unsafeRun()(finalCache.ec) match {
204+
case Left(_) =>
205+
// FIXME Log the exception
206+
defaultStableScalaVersions
207+
case Right(versions) =>
208+
versions
209+
.find(_.scalaCliVersion == scalaCliVersion)
210+
.map(_.supportedScalaVersions)
211+
.getOrElse {
212+
// FIXME Log that: logger.debug(s"Couldn't find Scala CLI version $scalaCliVersion in $versions")
213+
defaultStableScalaVersions
214+
}
215+
}
216+
217+
supportedScalaVersions
218+
.map(Version(_))
219+
.sorted
220+
.reverse
221+
}
222+
169223
def javaHome(): JavaHomeInfo = javaCommand0
170224

171225
private lazy val javaHomeManager = {
@@ -186,7 +240,6 @@ final case class BuildOptions(
186240
scalaVersion: Option[String],
187241
scalaBinaryVersion: Option[String]
188242
): Either[BuildException, (String, String)] = either {
189-
import coursier.core.Version
190243
lazy val allVersions = {
191244
import coursier._
192245
import scala.concurrent.ExecutionContext.{global => ec}
@@ -211,19 +264,43 @@ final case class BuildOptions(
211264
}
212265
modules.flatMap(moduleVersions).distinct
213266
}
214-
val maybeSv = scalaVersion match {
215-
case None => Right(Constants.defaultScalaVersion)
216-
case Some(sv0) =>
217-
if (Util.isFullScalaVersion(sv0)) Right(sv0)
218-
else {
267+
def matchNewestScalaVersion(sv: Option[String]) = {
268+
lazy val maxSupportedScalaVersions = latestSupportedScalaVersion()
269+
270+
sv match {
271+
case Some(sv0) =>
219272
val prefix = if (sv0.endsWith(".")) sv0 else sv0 + "."
220273
val matchingVersions = allVersions.filter(_.startsWith(prefix))
221274
if (matchingVersions.isEmpty)
222275
Left(new InvalidBinaryScalaVersionError(sv0))
276+
else {
277+
val validMaxVersions = maxSupportedScalaVersions
278+
.filter(_.repr.startsWith(prefix))
279+
val validMatchingVersions = matchingVersions
280+
.map(Version(_))
281+
.filter(v => validMaxVersions.exists(v <= _))
282+
if (validMatchingVersions.isEmpty)
283+
Left(new UnsupportedScalaVersionError(sv0))
284+
else
285+
Right(validMatchingVersions.max.repr)
286+
}
287+
case None =>
288+
val validVersions = allVersions
289+
.map(Version(_))
290+
.filter(v => maxSupportedScalaVersions.exists(v <= _))
291+
if (validVersions.isEmpty)
292+
Left(new NoValidScalaVersionFoundError(allVersions))
223293
else
224-
Right(matchingVersions.map(Version(_)).max.repr)
225-
}
294+
Right(validVersions.max.repr)
295+
}
296+
}
297+
val maybeSv = scalaVersion match {
298+
case None => matchNewestScalaVersion(None)
299+
case Some(sv0) =>
300+
if (Util.isFullScalaVersion(sv0)) Right(sv0)
301+
else matchNewestScalaVersion(Some(sv0))
226302
}
303+
227304
val sv = value(maybeSv)
228305
val sbv = scalaBinaryVersion.getOrElse(ScalaVersion.binary(sv))
229306
(sv, sbv)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ final case class ScalaOptions(
1313
extraScalaVersions: Set[String] = Set.empty,
1414
compilerPlugins: Seq[Positioned[AnyDependency]] = Nil,
1515
platform: Option[Positioned[Platform]] = None,
16-
extraPlatforms: Map[Platform, Positioned[Unit]] = Map.empty
16+
extraPlatforms: Map[Platform, Positioned[Unit]] = Map.empty,
17+
supportedScalaVersionsUrl: Option[String] = None
1718
) {
19+
20+
lazy val scalaVersionsUrl = supportedScalaVersionsUrl.getOrElse(
21+
"https://raw.githubusercontent.com/VirtusLab/scala-cli/master/scala-version.scala"
22+
)
23+
1824
def normalize: ScalaOptions = {
1925
var opt = this
2026
for (sv <- opt.scalaVersion if opt.extraScalaVersions.contains(sv))

modules/build/src/test/scala/scala/build/tests/BuildOptionsTests.scala

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package scala.build.tests
22

33
import com.eed3si9n.expecty.Expecty.{assert => expect}
4+
import dependency.ScalaParameters
45

5-
import scala.build.options.{BuildOptions, BuildRequirements}
6+
import scala.build.Ops._
7+
import scala.build.internal.Constants._
8+
import scala.build.options.{BuildOptions, BuildRequirements, ScalaOptions}
9+
import scala.util.Random
610

711
class BuildOptionsTests extends munit.FunSuite {
812

@@ -24,4 +28,74 @@ class BuildOptionsTests extends munit.FunSuite {
2428
)
2529
}
2630

31+
val expectedScalaVersions = Seq(
32+
Some("3") -> defaultScalaVersion,
33+
None -> defaultScalaVersion,
34+
Some("2.13") -> defaultScala213Version,
35+
Some("2.12") -> defaultScala212Version,
36+
Some("2") -> defaultScala213Version,
37+
Some("2.13.2") -> "2.13.2",
38+
Some("3.0.1") -> "3.0.1"
39+
)
40+
41+
for ((prefix, expectedScalaVersion) <- expectedScalaVersions)
42+
test(
43+
s"use expected default scala version for prefix scala version: ${prefix.getOrElse("empty")}"
44+
) {
45+
val options = BuildOptions(
46+
scalaOptions = ScalaOptions(
47+
scalaVersion = prefix,
48+
scalaBinaryVersion = None,
49+
supportedScalaVersionsUrl =
50+
Some(
51+
Random.alphanumeric.take(10).mkString("")
52+
) // invalid url, it should use defaults from Deps.sc
53+
)
54+
)
55+
val scalaParams = options.scalaParams.orThrow
56+
57+
val expectedScalaParams = ScalaParameters(expectedScalaVersion)
58+
59+
expect(scalaParams == expectedScalaParams)
60+
}
61+
62+
val expectedScalaConfVersions = Seq(
63+
Some("3") -> "3.0.1",
64+
None -> "3.0.1",
65+
Some("2.13") -> "2.13.4",
66+
Some("2.12") -> "2.12.13",
67+
Some("2") -> "2.13.4",
68+
Some("2.13.2") -> "2.13.2"
69+
)
70+
71+
val confFile = s"""[
72+
| {
73+
| "scalaCliVersion": "$version",
74+
| "supportedScalaVersions": ["3.0.1", "2.13.4", "2.12.13"]
75+
| }
76+
|]""".stripMargin
77+
78+
for ((prefix, expectedScalaVersion) <- expectedScalaConfVersions)
79+
test(s"use expected scala version from conf file, prefix: ${prefix.getOrElse("empty")}") {
80+
TestInputs.withTmpDir("conf-scala-versions") { dirPath =>
81+
82+
val confFilePath = dirPath / "conf-file.json"
83+
os.write(confFilePath, confFile)
84+
85+
val options = BuildOptions(
86+
scalaOptions = ScalaOptions(
87+
scalaVersion = prefix,
88+
scalaBinaryVersion = None,
89+
supportedScalaVersionsUrl = Some(s"file://${confFilePath.toString()}")
90+
)
91+
)
92+
93+
val scalaParams = options.scalaParams.orThrow
94+
val expectedScalaParams = ScalaParameters(expectedScalaVersion)
95+
96+
expect(scalaParams == expectedScalaParams)
97+
}
98+
99+
}
100+
27101
}

modules/build/src/test/scala/scala/build/tests/TestInputs.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package scala.build.tests
22

33
import java.nio.charset.StandardCharsets
4-
5-
import scala.build.{Build, BuildThreads, Directories, Inputs}
64
import scala.build.blooprifle.BloopRifleConfig
5+
import scala.build.{Build, BuildThreads, Directories, Inputs}
76
import scala.build.errors.BuildException
87
import scala.build.options.BuildOptions
98
import scala.util.control.NonFatal
@@ -51,7 +50,7 @@ object TestInputs {
5150
def apply(files: (os.RelPath, String)*): TestInputs =
5251
TestInputs(files, Nil)
5352

54-
private def withTmpDir[T](prefix: String)(f: os.Path => T): T = {
53+
def withTmpDir[T](prefix: String)(f: os.Path => T): T = {
5554
val tmpDir = os.temp.dir(prefix = prefix)
5655
try f(tmpDir)
5756
finally tryRemoveAll(tmpDir)

0 commit comments

Comments
 (0)