Skip to content

Commit a35a1a0

Browse files
vasilmkdclaude
andcommitted
[project wizard] Fall back to hardcoded Scala/sbt versions when the version download fails with a non-OK HTTP status #SCL-25707 fixed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 361e367 commit a35a1a0

4 files changed

Lines changed: 48 additions & 14 deletions

File tree

sbt/sbt-impl/src/org/jetbrains/sbt/project/template/wizard/ScalaVersionStepLike.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ trait ScalaVersionStepLike extends IndentationSyntaxStepLike with AsynchronousVe
5757
Versions.Scala.loadVersionsWithProgress(indicator)
5858
}
5959
downloadVersionsAsynchronously(isScalaLoading, disposable, scalaDownloadVersions, Versions.Scala.toString) { versions =>
60-
val stringRepresentation = filterAvailableScalaVersions(versions.map(_.presentation))
61-
updateSelectionsAndElementsModelForScala(stringRepresentation)
60+
if (versions.nonEmpty) {
61+
val stringRepresentation = filterAvailableScalaVersions(versions.map(_.presentation))
62+
updateSelectionsAndElementsModelForScala(stringRepresentation)
63+
}
6264
}
6365
}
6466

sbt/sbt-impl/src/org/jetbrains/sbt/project/template/wizard/buildSystem/SbtScalaNewProjectWizardStep.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ final class SbtScalaNewProjectWizardStep(parent: ScalaNewProjectWizardMultiStep)
157157
}
158158

159159
override def setDownloadedSbtVersions(versions: Seq[SbtVersion]): Unit = {
160-
availableSbtVersions.set(versions.toOption)
161-
availableSbtVersionsForScala3.set(Versions.SBT.sbtVersionsForScala3(versions).toOption)
160+
availableSbtVersions.set(versions.toOption.filter(_.nonEmpty))
161+
availableSbtVersionsForScala3.set(Versions.SBT.sbtVersionsForScala3(versions).toOption.filter(_.nonEmpty))
162162
updateSupportedSbtVersionsForSelectedScalaVersion(selections.scalaVersion)
163163
}
164164

scala/scala-impl/src/org/jetbrains/plugins/scala/project/Versions.scala

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jetbrains.plugins.scala.project
22

3+
import com.intellij.openapi.diagnostic.Logger
34
import com.intellij.openapi.progress.{ProcessCanceledException, ProgressIndicator, ProgressManager}
45
import com.intellij.util.concurrency.AppExecutorUtil
56
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
@@ -22,6 +23,8 @@ object Versions {
2223

2324
import Entity._
2425

26+
private val Log: Logger = Logger.getInstance(this.getClass)
27+
2528
sealed abstract class Kind(private[Versions] val entities: List[Entity]) {
2629

2730
@throws[InterruptedException]
@@ -136,17 +139,15 @@ object Versions {
136139
}
137140

138141
val downloadedVersionStringsFutures: Seq[CompletableFuture[(DownloadableEntity, Seq[String])]] = downloadable.zip(httpFutures).map {
139-
case (entity@DownloadableEntity(_, _, hardcodedVersions, versionPattern), future) =>
142+
case (entity@DownloadableEntity(url, _, hardcodedVersions, versionPattern), future) =>
140143
future
141-
.thenApply[Seq[String]] { responseStream =>
142-
val bodyLines = responseStream.body().toList.asScala.toSeq
143-
bodyLines
144-
}
145-
.thenApply[(DownloadableEntity, Seq[String])] { lines =>
146-
val versionStrings =
147-
if (lines.isEmpty) hardcodedVersions
148-
else extractVersions(lines, versionPattern)
149-
144+
.thenApply[(DownloadableEntity, Seq[String])] { response =>
145+
val statusCode = response.statusCode()
146+
val bodyLines = response.body().toList.asScala.toSeq
147+
val versionStrings = extractVersionsFromResponse(statusCode, bodyLines, versionPattern).getOrElse {
148+
Log.warn(s"Failed to extract versions from $url (status code: $statusCode), falling back to hardcoded versions")
149+
hardcodedVersions
150+
}
150151
entity -> versionStrings
151152
}
152153
.whenComplete((_, _) => latch.countDown())
@@ -219,6 +220,17 @@ object Versions {
219220
case pattern(number) => number
220221
}
221222

223+
/**
224+
* @return `None` if the response was not successful (e.g., HTTP 429 from Maven Central, SCL-25707)
225+
* or if no versions could be extracted from the body (e.g., unexpected page format).
226+
* Callers are expected to fall back to the hardcoded versions in this case.
227+
*/
228+
private[project] def extractVersionsFromResponse(statusCode: Int, bodyLines: Seq[String], versionPattern: Regex): Option[Seq[String]] =
229+
statusCode match {
230+
case 200 => Some(extractVersions(bodyLines, versionPattern)).filter(_.nonEmpty)
231+
case _ => None
232+
}
233+
222234
@RequiresBackgroundThread
223235
@throws[ExecutionException]
224236
@throws[InterruptedException]

scala/scala-impl/test/org/jetbrains/plugins/scala/project/VersionsTest.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,24 @@ class VersionsTest {
4646
assertTrue(s"Hardcoded Scala 3 versions should contain $version", hardcodedVersions.contains(version))
4747
}
4848
}
49+
50+
//SCL-25707
51+
@Test
52+
def testExtractVersionsFromResponse(): Unit = {
53+
val pattern = """.+>(\d+\.\d+\.\d+)/<.*""".r // same shape as the Scala version pattern in Versions.Entity
54+
val validBody = Seq(
55+
"""<a href="2.13.15/" title="2.13.15/">2.13.15/</a>""",
56+
"""<a href="3.3.4/" title="3.3.4/">3.3.4/</a>"""
57+
)
58+
59+
assertEquals(Some(Seq("2.13.15", "3.3.4")), Versions.extractVersionsFromResponse(200, validBody, pattern))
60+
// an error response body (e.g., HTTP 429 Too Many Requests from Maven Central) must not be treated as a successful download
61+
assertEquals(None, Versions.extractVersionsFromResponse(429, Seq("<html><body>429 Too Many Requests</body></html>"), pattern))
62+
// redirects are not followed by the HTTP client
63+
assertEquals(None, Versions.extractVersionsFromResponse(301, Seq.empty, pattern))
64+
// a successful response in an unexpected format
65+
assertEquals(None, Versions.extractVersionsFromResponse(200, Seq("<html>totally new layout</html>"), pattern))
66+
// a successful response with an empty body
67+
assertEquals(None, Versions.extractVersionsFromResponse(200, Seq.empty, pattern))
68+
}
4969
}

0 commit comments

Comments
 (0)