-
Notifications
You must be signed in to change notification settings - Fork 315
/
MtagsResolver.scala
216 lines (191 loc) Β· 6.45 KB
/
MtagsResolver.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package scala.meta.internal.metals
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.duration._
import scala.util.control.NonFatal
import scala.meta.internal.metals.BuildInfo
import scala.meta.internal.semver.SemVer
trait MtagsResolver {
/**
* Try and resolve mtags module for a given version of Scala.
* @return information to use and load the presentation compiler implementation
*/
def resolve(scalaVersion: String): Option[MtagsBinaries]
/**
* Check if a given Scala version is supported in Metals.
*
* @param version Scala version to check
*/
def isSupportedScalaVersion(version: String): Boolean =
resolve(version).isDefined
/**
* Check if this version of Scala is supported in a previous
* binary compatible Metals version. Needed for the doctor.
* @param version scala version to check
*/
def isSupportedInOlderVersion(version: String): Boolean
}
object MtagsResolver {
def default(): MtagsResolver = new Default
/**
* Map of removed Scala versions since 0.11.10.
* Points to the last Metals version that supported it.
*/
private val removedScalaVersions = Map(
"2.13.1" -> "0.11.10",
"2.13.2" -> "0.11.10",
"2.13.3" -> "0.11.12",
"2.13.4" -> "1.0.1",
"2.13.5" -> "1.2.2",
"2.12.9" -> "0.11.10",
"2.12.10" -> "0.11.12",
"2.12.11" -> "1.2.2",
"3.0.0" -> "0.11.10",
"3.0.1" -> "0.11.10",
"3.0.2" -> "0.11.12",
"3.2.2-RC1" -> "0.11.10",
"3.3.0-RC1" -> "0.11.10",
"3.3.0-RC2" -> "0.11.11",
"3.3.0-RC3" -> "0.11.12",
"3.3.0-RC4" -> "0.11.12",
"3.3.0-RC5" -> "0.11.12",
"3.3.0-RC6" -> "0.11.12",
"3.3.1-RC1" -> "0.11.12",
"3.3.1-RC2" -> "0.11.12",
"3.3.1-RC3" -> "0.11.12",
"3.3.1-RC4" -> "1.0.0",
"3.3.1-RC5" -> "1.0.0",
"3.3.1-RC6" -> "1.0.1",
"3.3.1-RC7" -> "1.0.1",
"3.3.0" -> "1.2.2",
"3.3.2-RC1" -> "1.2.2",
"3.3.2-RC2" -> "1.2.2",
"3.3.2-RC3" -> "1.2.2",
)
class Default extends MtagsResolver {
private val firstScala3PCVersion = BuildInfo.firstScala3PCVersion
private val states =
new ConcurrentHashMap[String, State]()
def hasStablePresentationCompiler(scalaVersion: String): Boolean =
SemVer.isCompatibleVersion(
firstScala3PCVersion,
scalaVersion,
)
def isSupportedInOlderVersion(version: String): Boolean =
removedScalaVersions.contains(version)
def resolve(scalaVersion: String): Option[MtagsBinaries] = {
if (hasStablePresentationCompiler(scalaVersion))
resolve(
scalaVersion,
original = None,
resolveType = ResolveType.StablePC,
)
else
resolve(
scalaVersion,
original = None,
resolveType = ResolveType.Regular,
)
}
private object ResolveType extends Enumeration {
val Regular, StablePC = Value
}
/**
* Resolving order is following:
* 1. Built-in mtags matching scala version
* 2. If presentation compiler is available we resolve it otherwise we resolve mtags.
*/
private def resolve(
scalaVersion: String,
original: Option[String],
resolveType: ResolveType.Value,
): Option[MtagsBinaries] = {
def fetch(fetchResolveType: ResolveType.Value, tries: Int = 5): State =
try {
val metalsVersion = removedScalaVersions.getOrElse(
scalaVersion,
BuildInfo.metalsVersion,
)
if (metalsVersion != BuildInfo.metalsVersion) {
scribe.warn(
s"$scalaVersion is no longer supported in the current Metals versions, using the last known supported version $metalsVersion"
)
}
val jars = fetchResolveType match {
case ResolveType.StablePC =>
Embedded.downloadScala3PresentationCompiler(scalaVersion)
case _ => Embedded.downloadMtags(scalaVersion, metalsVersion)
}
State.Success(
MtagsBinaries.Artifacts(
scalaVersion,
jars,
fetchResolveType == ResolveType.StablePC,
)
)
} catch {
case NonFatal(_) if tries > 0 =>
fetch(fetchResolveType, tries - 1)
case NonFatal(e) =>
State.Failure(System.currentTimeMillis(), e)
}
def shouldResolveAgain(failure: State.Failure): Boolean = {
(System
.currentTimeMillis() - failure.lastTryMillis) > 5.minutes.toMillis
}
def logResolution(state: State): State = {
state match {
case _: State.Success =>
val msg = resolveType match {
case ResolveType.Regular => s"Resolved mtags for $scalaVersion"
case ResolveType.StablePC =>
s"Resolved Scala 3 presentation compiler for $scalaVersion"
}
scribe.debug(msg)
case fail: State.Failure =>
val errorMsg = resolveType match {
case ResolveType.Regular =>
s"Failed to resolve mtags for $scalaVersion"
case ResolveType.StablePC =>
s"Failed to resolve Scala 3 presentation compiler for $scalaVersion"
}
scribe.error(errorMsg, fail.exception)
case _ =>
}
state
}
// The metals_2.12 artifact depends on mtags_2.12.x where "x" matches
// `mtags.BuildInfo.scalaCompilerVersion`. In the case when
// `info.getScalaVersion == mtags.BuildInfo.scalaCompilerVersion` then we
// skip fetching the mtags module from Maven.
if (MtagsBinaries.isBuildIn(scalaVersion)) {
Some(MtagsBinaries.BuildIn)
} else {
val computed = states.compute(
original.getOrElse(scalaVersion),
(_, value) => {
value match {
case null => logResolution(fetch(resolveType))
case succ: State.Success => succ
case failure: State.Failure if shouldResolveAgain(failure) =>
logResolution(fetch(resolveType))
case failure: State.Failure =>
failure
}
},
)
computed match {
case State.Success(v) => Some(v)
case _: State.Failure => None
}
}
}
sealed trait State
object State {
case class Success(v: MtagsBinaries.Artifacts) extends State
case class Failure(
lastTryMillis: Long,
exception: Throwable,
) extends State
}
}
}