Skip to content

Commit e716a55

Browse files
authored
Merge pull request #3969 from tgodzik/fix-windows-escaping
bugfix: Escape java home path on windows
2 parents 5606385 + 6ed9732 commit e716a55

File tree

3 files changed

+110
-126
lines changed

3 files changed

+110
-126
lines changed

metals/src/main/scala/scala/meta/internal/metals/BloopServers.scala

Lines changed: 94 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ final class BloopServers(
4949

5050
import BloopServers._
5151

52+
private val bloopJsonPath: Option[AbsolutePath] =
53+
getBloopFilePath(fileName = "bloop.json")
54+
private val bloopLockFile: Option[AbsolutePath] =
55+
getBloopFilePath(fileName = "created_by_metals.lock")
56+
57+
private def bloopLastModifiedTime: Long = bloopJsonPath
58+
.flatMap(path => Try(path.toNIO.toFile().lastModified()).toOption)
59+
.getOrElse(0L)
60+
5261
def shutdownServer(): Boolean = {
5362
val dummyIn = new ByteArrayInputStream(new Array(0))
5463
val cli = new BloopgunCli(
@@ -78,7 +87,8 @@ final class BloopServers(
7887
workspace,
7988
client,
8089
languageClient,
81-
() => connectToLauncher(bloopVersion, config.bloopPort),
90+
() =>
91+
connectToLauncher(bloopVersion, config.bloopPort, userConfiguration),
8292
tables.dismissedNotifications.ReconnectBsp,
8393
config,
8494
name
@@ -129,56 +139,41 @@ final class BloopServers(
129139
}
130140

131141
private def writeJVMPropertiesToBloopGlobalJsonFile(
132-
bloopGlobalJsonFilePath: AbsolutePath,
133-
bloopCreatedByMetalsFilePath: AbsolutePath,
134-
maybeBloopJvmProperties: Option[List[String]],
142+
maybeBloopJvmProperties: List[String],
135143
maybeJavaHome: Option[String]
136144
): Try[Unit] = Try {
137-
138-
val javaOptionsString = maybeBloopJvmProperties
139-
.map { bloopJvmProperties =>
140-
s"\"javaOptions\": [${bloopJvmProperties.map(property => s"\"$property\"").mkString(", ")}]"
141-
}
142-
.getOrElse("")
143-
144-
val jvmPropertiesString = maybeJavaHome
145-
.map { javaHome =>
146-
if (javaOptionsString.isEmpty)
147-
s"{ \"javaHome\": \"$javaHome\"}"
148-
else
149-
s"""|{
150-
| $javaOptionsString,
151-
| \"javaHome\": \"$javaHome\"
152-
|}""".stripMargin
153-
}
154-
.getOrElse(s"{$javaOptionsString}")
155-
bloopGlobalJsonFilePath.writeText(jvmPropertiesString)
156-
bloopCreatedByMetalsFilePath.writeText(
157-
bloopGlobalJsonFilePath.toNIO.toFile.lastModified().toString
158-
)
145+
if (maybeJavaHome.isDefined || maybeBloopJvmProperties.nonEmpty) {
146+
val javaOptionsField =
147+
if (maybeBloopJvmProperties.nonEmpty)
148+
Some(
149+
"javaOptions" -> ujson.Arr(
150+
maybeBloopJvmProperties.map(opt => ujson.Str(opt.trim())): _*
151+
)
152+
)
153+
else None
154+
val fields: List[(String, ujson.Value)] =
155+
List(
156+
maybeJavaHome.map(v => "javaHome" -> ujson.Str(v.trim())),
157+
javaOptionsField
158+
).flatten
159+
val obj = ujson.Obj.from(fields)
160+
val jvmPropertiesString = ujson.write(obj)
161+
162+
bloopJsonPath.foreach(_.writeText(jvmPropertiesString))
163+
bloopLockFile.foreach(_.writeText(bloopLastModifiedTime.toString()))
164+
}
159165
}
160166

161-
private def getBloopGlobalJsonLastModifiedByMetalsTime(
162-
bloopCreatedByMetalsFilePath: AbsolutePath
163-
): Long = Try {
164-
bloopCreatedByMetalsFilePath.readText.toLong
165-
}.getOrElse(0)
166-
167167
private def processUserPreferenceForBloopJvmProperties(
168168
messageActionItem: MessageActionItem,
169-
bloopGlobalJsonFilePath: AbsolutePath,
170-
bloopCreatedByMetalsFilePath: AbsolutePath,
171-
maybeBloopJvmProperties: Option[List[String]],
169+
maybeBloopJvmProperties: List[String],
172170
maybeJavaHome: Option[String],
173171
reconnect: () => Future[BuildChange]
174172
): Future[Unit] = {
175-
messageActionItem match {
176-
177-
case item
173+
(messageActionItem, bloopJsonPath) match {
174+
case (item, _)
178175
if item == Messages.BloopGlobalJsonFilePremodified.applyAndRestart =>
179176
writeJVMPropertiesToBloopGlobalJsonFile(
180-
bloopGlobalJsonFilePath,
181-
bloopCreatedByMetalsFilePath,
182177
maybeBloopJvmProperties,
183178
maybeJavaHome
184179
) match {
@@ -188,70 +183,65 @@ final class BloopServers(
188183
reconnect().ignoreValue
189184
}
190185

191-
case item
186+
case (item, Some(bloopPath))
192187
if item == Messages.BloopGlobalJsonFilePremodified.openGlobalJsonFile =>
193188
val position = new Position(0, 0)
194189
val range = new org.eclipse.lsp4j.Range(position, position)
195190
val command = ClientCommands.GotoLocation.toExecuteCommandParams(
196191
ClientCommands.WindowLocation(
197-
bloopGlobalJsonFilePath.toURI.toString,
192+
bloopPath.toURI.toString,
198193
range
199194
)
200195
)
201196
Future.successful(languageClient.metalsExecuteClientCommand(command))
202197

203-
case item
204-
if item == Messages.BloopGlobalJsonFilePremodified.useGlobalFile =>
205-
Future.unit
198+
case _ => Future.unit
199+
206200
}
207201
}
208202

209203
private def updateBloopGlobalJsonFileThenRestart(
210-
bloopGlobalJsonFilePath: AbsolutePath,
211-
bloopCreatedByMetalsFilePath: AbsolutePath,
212-
maybeBloopJvmProperties: Option[List[String]],
204+
maybeBloopJvmProperties: List[String],
213205
maybeJavaHome: Option[String],
214-
bloopJsonUpdateCause: BloopJsonUpdateCause,
215-
reconnect: () => Future[BuildChange]
206+
reconnect: () => Future[BuildChange],
207+
bloopJsonUpdateCause: BloopJsonUpdateCause
216208
): Future[Unit] = {
217-
writeJVMPropertiesToBloopGlobalJsonFile(
218-
bloopGlobalJsonFilePath,
219-
bloopCreatedByMetalsFilePath,
220-
maybeBloopJvmProperties,
221-
maybeJavaHome
222-
) match {
223-
case Failure(exception) => Future.failed(exception)
224-
case Success(_) =>
225-
languageClient
226-
.showMessageRequest(
227-
Messages.BloopJvmPropertiesChange.params(bloopJsonUpdateCause)
228-
)
229-
.asScala
230-
.flatMap {
231-
case messageActionItem
232-
if messageActionItem == Messages.BloopJvmPropertiesChange.reconnect =>
209+
languageClient
210+
.showMessageRequest(
211+
Messages.BloopJvmPropertiesChange.params(bloopJsonUpdateCause)
212+
)
213+
.asScala
214+
.flatMap {
215+
case messageActionItem
216+
if messageActionItem == Messages.BloopJvmPropertiesChange.reconnect =>
217+
writeJVMPropertiesToBloopGlobalJsonFile(
218+
maybeBloopJvmProperties,
219+
maybeJavaHome
220+
) match {
221+
case Failure(exception) => Future.failed(exception)
222+
case Success(_) =>
233223
shutdownServer()
234224
reconnect().ignoreValue
235-
case _ =>
236-
Future.unit
237225
}
238-
}
226+
case _ =>
227+
Future.unit
228+
}
239229

240230
}
241231

242232
private def getBloopFilePath(fileName: String): Option[AbsolutePath] = {
243-
Try {
233+
sys.props.get("user.home").map { home =>
244234
AbsolutePath(
245235
Paths
246-
.get(System.getProperty("user.home"))
236+
.get(home)
247237
.resolve(s".bloop/$fileName")
248238
)
249-
}.toOption
239+
}
250240
}
251241

252242
private def maybeLoadBloopGlobalJsonFile(
253243
bloopGlobalJsonFilePath: AbsolutePath
254-
): (Option[String], Option[List[String]]) = {
244+
): (Option[String], List[String]) = {
255245

256246
val maybeLinkedHashMap =
257247
bloopGlobalJsonFilePath.readTextOpt.map(ujson.read(_)).flatMap(_.objOpt)
@@ -267,16 +257,9 @@ final class BloopServers(
267257
javaOptionsValue <- linkedHashMap.get("javaOptions")
268258
javaOptionsValueArray <- javaOptionsValue.arrOpt
269259
} yield javaOptionsValueArray.flatMap(_.strOpt).toList
270-
(maybeJavaHome, maybeJavaOptions)
260+
(maybeJavaHome, maybeJavaOptions.getOrElse(Nil))
271261
}
272262

273-
private def getBloopGlobalJsonLastModifiedTime(
274-
bloopGlobalJsonFilePath: AbsolutePath
275-
): Long =
276-
Try {
277-
bloopGlobalJsonFilePath.toNIO.toFile.lastModified()
278-
}.getOrElse(0)
279-
280263
/**
281264
* First we check if the user requested to update the Bloop JVM
282265
* properties through the extension.
@@ -304,31 +287,26 @@ final class BloopServers(
304287
maybeRequestedMetalsJavaHome: Option[String],
305288
reconnect: () => Future[BuildChange]
306289
): Future[Unit] = {
307-
308290
val result =
309-
for { // if bloopGlobalJsonFilePath is defined
310-
bloopGlobalJsonFilePath <- getBloopFilePath(fileName = "bloop.json")
311-
bloopCreatedByMetalsFilePath <- getBloopFilePath(fileName =
312-
"created_by_metals.lock"
313-
)
291+
for {
292+
bloopPath <- bloopJsonPath
314293
(maybeBloopGlobalJsonJavaHome, maybeBloopGlobalJsonJvmProperties) =
315-
maybeLoadBloopGlobalJsonFile(bloopGlobalJsonFilePath)
316-
294+
maybeLoadBloopGlobalJsonFile(bloopPath)
317295
bloopJsonUpdateCause <-
318296
if (
319-
maybeRequestedBloopJvmProperties != maybeBloopGlobalJsonJvmProperties
320-
&& maybeRequestedBloopJvmProperties.nonEmpty
297+
maybeRequestedBloopJvmProperties
298+
.exists(requested =>
299+
requested != maybeBloopGlobalJsonJvmProperties
300+
)
321301
) Some(BloopJsonUpdateCause.JVM_OPTS)
322302
else if (maybeRequestedMetalsJavaHome != maybeBloopGlobalJsonJavaHome)
323303
Some(BloopJsonUpdateCause.JAVA_HOME)
324304
else None
325-
maybeBloopJvmProperties = maybeRequestedBloopJvmProperties.orElse(
305+
maybeBloopJvmProperties = maybeRequestedBloopJvmProperties.getOrElse(
326306
maybeBloopGlobalJsonJvmProperties
327307
)
328308
} yield updateBloopJvmProperties(
329309
maybeBloopJvmProperties,
330-
bloopGlobalJsonFilePath,
331-
bloopCreatedByMetalsFilePath,
332310
maybeRequestedMetalsJavaHome,
333311
reconnect,
334312
bloopJsonUpdateCause
@@ -340,20 +318,16 @@ final class BloopServers(
340318
}
341319

342320
private def updateBloopJvmProperties(
343-
maybeBloopJvmProperties: Option[List[String]],
344-
bloopGlobalJsonFilePath: AbsolutePath,
345-
bloopCreatedByMetalsFilePath: AbsolutePath,
321+
maybeBloopJvmProperties: List[String],
346322
maybeJavaHome: Option[String],
347323
reconnect: () => Future[BuildChange],
348324
bloopJsonUpdateCause: BloopJsonUpdateCause
349-
): Future[Unit] = { // the properties are updated
325+
): Future[Unit] = {
326+
val lockFileTime = bloopLockFile
327+
.flatMap(file => Try(file.readText.toLong).toOption)
328+
.getOrElse(0L)
350329
if (
351-
bloopGlobalJsonFilePath.exists &&
352-
getBloopGlobalJsonLastModifiedTime(
353-
bloopGlobalJsonFilePath
354-
) > getBloopGlobalJsonLastModifiedByMetalsTime(
355-
bloopCreatedByMetalsFilePath
356-
)
330+
bloopJsonPath.exists(_.exists) && bloopLastModifiedTime > lockFileTime
357331
) {
358332
// the global json file was previously modified by the user through other means;
359333
// therefore overwriting it requires user input
@@ -365,8 +339,6 @@ final class BloopServers(
365339
.flatMap {
366340
processUserPreferenceForBloopJvmProperties(
367341
_,
368-
bloopGlobalJsonFilePath,
369-
bloopCreatedByMetalsFilePath,
370342
maybeBloopJvmProperties,
371343
maybeJavaHome,
372344
reconnect
@@ -384,12 +356,10 @@ final class BloopServers(
384356
// hence it can get created or overwritten by Metals with no worries
385357
// about overriding the user preferred settings
386358
updateBloopGlobalJsonFileThenRestart(
387-
bloopGlobalJsonFilePath,
388-
bloopCreatedByMetalsFilePath,
389359
maybeBloopJvmProperties,
390360
maybeJavaHome,
391-
bloopJsonUpdateCause,
392-
reconnect
361+
reconnect,
362+
bloopJsonUpdateCause
393363
) andThen {
394364
case Failure(exception) =>
395365
languageClient.showMessage(
@@ -403,8 +373,21 @@ final class BloopServers(
403373

404374
private def connectToLauncher(
405375
bloopVersion: String,
406-
bloopPort: Option[Int]
376+
bloopPort: Option[Int],
377+
userConfiguration: UserConfiguration
407378
): Future[SocketConnection] = {
379+
// we should set up Java before running Bloop in order to not restart it
380+
bloopJsonPath match {
381+
case Some(bloopPath) if !bloopPath.exists =>
382+
// we want to use the same java version as Metals, so it's ok to use java.home
383+
val metalsJavaHome =
384+
userConfiguration.javaHome.orElse(sys.props.get("java.home"))
385+
writeJVMPropertiesToBloopGlobalJsonFile(
386+
userConfiguration.bloopJvmProperties.getOrElse(Nil),
387+
metalsJavaHome
388+
)
389+
case _ =>
390+
}
408391
val launcherInOutPipe = Pipe.open()
409392
val launcherIn = new QuietInputStream(
410393
Channels.newInputStream(launcherInOutPipe.source()),

metals/src/main/scala/scala/meta/internal/metals/Messages.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ object Messages {
352352

353353
object BloopJvmPropertiesChange {
354354
def reconnect: MessageActionItem =
355-
new MessageActionItem("Restart Bloop")
355+
new MessageActionItem("Apply and restart Bloop")
356356

357357
def notNow: MessageActionItem =
358358
new MessageActionItem("Not now")

metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,20 +1306,21 @@ class MetalsLanguageServer(
13061306
val restartBuildServer = bspSession
13071307
.map { session =>
13081308
if (session.main.isBloop) {
1309-
bloopServers.ensureDesiredVersion(
1310-
userConfig.currentBloopVersion,
1311-
session.version,
1312-
userConfig.bloopVersion.nonEmpty,
1313-
old.bloopVersion.isDefined,
1314-
() => autoConnectToBuildServer
1315-
)
1316-
1317-
bloopServers.ensureDesiredJvmSettings(
1318-
userConfig.bloopJvmProperties,
1319-
userConfig.javaHome,
1320-
() => autoConnectToBuildServer()
1321-
)
1322-
1309+
bloopServers
1310+
.ensureDesiredVersion(
1311+
userConfig.currentBloopVersion,
1312+
session.version,
1313+
userConfig.bloopVersion.nonEmpty,
1314+
old.bloopVersion.isDefined,
1315+
() => autoConnectToBuildServer
1316+
)
1317+
.flatMap { _ =>
1318+
bloopServers.ensureDesiredJvmSettings(
1319+
userConfig.bloopJvmProperties,
1320+
userConfig.javaHome,
1321+
() => autoConnectToBuildServer()
1322+
)
1323+
}
13231324
} else if (
13241325
userConfig.ammoniteJvmProperties != old.ammoniteJvmProperties && buildTargets.allBuildTargetIds
13251326
.exists(Ammonite.isAmmBuildTarget)

0 commit comments

Comments
 (0)