Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Bloop Java Home to that of Metals #3871

Merged
merged 10 commits into from
May 25, 2022
219 changes: 139 additions & 80 deletions metals/src/main/scala/scala/meta/internal/metals/BloopServers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import scala.util.Success
import scala.util.Try

import scala.meta.internal.bsp.BuildChange
import scala.meta.internal.metals.BloopJsonUpdateCause.BloopJsonUpdateCause
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.io.AbsolutePath
Expand Down Expand Up @@ -120,29 +121,35 @@ final class BloopServers(
shutdownServer()
reconnect().ignoreValue
case _ =>
Future.successful(())
Future.unit
}
} else {
Future.successful(())
Future.unit
}
}

private def writeJVMPropertiesToBloopGlobalJsonFile(
bloopGlobalJsonFilePath: AbsolutePath,
bloopCreatedByMetalsFilePath: AbsolutePath,
requestedBloopJvmProperties: List[String],
maybeBloopJvmProperties: Option[List[String]],
maybeJavaHome: Option[String]
): Try[Unit] = Try {

val javaOptionsString =
s"\"javaOptions\": [${requestedBloopJvmProperties.map(property => s"\"$property\"").mkString(", ")}]"
val javaOptionsString = maybeBloopJvmProperties
.map { bloopJvmProperties =>
s"\"javaOptions\": [${bloopJvmProperties.map(property => s"\"$property\"").mkString(", ")}]"
}
.getOrElse("")

val jvmPropertiesString = maybeJavaHome
.map { javaHome =>
s"""|{
| $javaOptionsString,
| \"javaHome\": \"$javaHome\"
|}""".stripMargin
if (javaOptionsString.isEmpty)
s"{ \"javaHome\": \"$javaHome\"}"
else
s"""|{
| $javaOptionsString,
| \"javaHome\": \"$javaHome\"
|}""".stripMargin
}
.getOrElse(s"{$javaOptionsString}")
bloopGlobalJsonFilePath.writeText(jvmPropertiesString)
Expand All @@ -161,7 +168,7 @@ final class BloopServers(
messageActionItem: MessageActionItem,
bloopGlobalJsonFilePath: AbsolutePath,
bloopCreatedByMetalsFilePath: AbsolutePath,
requestedBloopJvmProperties: List[String],
maybeBloopJvmProperties: Option[List[String]],
maybeJavaHome: Option[String],
reconnect: () => Future[BuildChange]
): Future[Unit] = {
Expand All @@ -172,7 +179,7 @@ final class BloopServers(
writeJVMPropertiesToBloopGlobalJsonFile(
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
requestedBloopJvmProperties,
maybeBloopJvmProperties,
maybeJavaHome
) match {
case Failure(exception) => Future.failed(exception)
Expand All @@ -195,28 +202,29 @@ final class BloopServers(

case item
if item == Messages.BloopGlobalJsonFilePremodified.useGlobalFile =>
Future.successful(())
Future.unit
}
}

private def updateBloopGlobalJsonFileThenRestart(
bloopGlobalJsonFilePath: AbsolutePath,
bloopCreatedByMetalsFilePath: AbsolutePath,
requestedBloopJvmProperties: List[String],
maybeBloopJvmProperties: Option[List[String]],
maybeJavaHome: Option[String],
bloopJsonUpdateCause: BloopJsonUpdateCause,
reconnect: () => Future[BuildChange]
): Future[Unit] = {
writeJVMPropertiesToBloopGlobalJsonFile(
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
requestedBloopJvmProperties,
maybeBloopJvmProperties,
maybeJavaHome
) match {
case Failure(exception) => Future.failed(exception)
case Success(_) =>
languageClient
.showMessageRequest(
Messages.BloopJvmPropertiesChange.params()
Messages.BloopJvmPropertiesChange.params(bloopJsonUpdateCause)
)
.asScala
.flatMap {
Expand All @@ -225,7 +233,7 @@ final class BloopServers(
shutdownServer()
reconnect().ignoreValue
case _ =>
Future.successful(())
Future.unit
}
}

Expand All @@ -241,6 +249,27 @@ final class BloopServers(
}.toOption
}

private def maybeLoadBloopGlobalJsonFile(
bloopGlobalJsonFilePath: AbsolutePath
): (Option[String], Option[List[String]]) = {

val maybeLinkedHashMap =
bloopGlobalJsonFilePath.readTextOpt.map(ujson.read(_)).flatMap(_.objOpt)

val maybeJavaHome = for {
linkedHashMap <- maybeLinkedHashMap
javaHomeValue <- linkedHashMap.get("javaHome")
javaHomeStr <- javaHomeValue.strOpt
} yield javaHomeStr

val maybeJavaOptions = for {
linkedHashMap <- maybeLinkedHashMap
javaOptionsValue <- linkedHashMap.get("javaOptions")
javaOptionsValueArray <- javaOptionsValue.arrOpt
} yield javaOptionsValueArray.flatMap(_.strOpt).toList
(maybeJavaHome, maybeJavaOptions)
}

private def getBloopGlobalJsonLastModifiedTime(
bloopGlobalJsonFilePath: AbsolutePath
): Long =
Expand All @@ -264,9 +293,6 @@ final class BloopServers(
*
* @param maybeRequestedBloopJvmProperties Bloop JVM Properties requested
* through the Metals Extension settings
* @param maybeRunningBloopJvmProperties
* @param maybeJavaHome Metals' `javaHome`, which Bloop
* should also preferebly use
* @param reconnect function to connect back to the
* build server.
* @return `Future.successful` if the purpose is achieved or `Future.failure`
Expand All @@ -275,77 +301,104 @@ final class BloopServers(
*/
def ensureDesiredJvmSettings(
maybeRequestedBloopJvmProperties: Option[List[String]],
zmerr marked this conversation as resolved.
Show resolved Hide resolved
maybeRunningBloopJvmProperties: Option[List[String]],
maybeJavaHome: Option[String],
maybeRequestedMetalsJavaHome: Option[String],
reconnect: () => Future[BuildChange]
): Future[Unit] = {

val result =
for { // if the requestedBloopJvmProperties and bloopGlobalJsonFilePath are defined
requestedBloopJvmProperties <- maybeRequestedBloopJvmProperties
for { // if bloopGlobalJsonFilePath is defined
bloopGlobalJsonFilePath <- getBloopFilePath(fileName = "bloop.json")
bloopCreatedByMetalsFilePath <- getBloopFilePath(fileName =
"created_by_metals.lock"
)
} yield
if (
maybeRequestedBloopJvmProperties != maybeRunningBloopJvmProperties &&
!(requestedBloopJvmProperties.isEmpty && maybeRunningBloopJvmProperties.isEmpty)
) { // the properties are updated
(maybeBloopGlobalJsonJavaHome, maybeBloopGlobalJsonJvmProperties) =
maybeLoadBloopGlobalJsonFile(bloopGlobalJsonFilePath)

bloopJsonUpdateCause <-
if (
bloopGlobalJsonFilePath.exists &&
getBloopGlobalJsonLastModifiedTime(
bloopGlobalJsonFilePath
) > getBloopGlobalJsonLastModifiedByMetalsTime(
bloopCreatedByMetalsFilePath
)
) {
// the global json file was previously modified by the user through other means;
// therefore overwriting it requires user input
languageClient
.showMessageRequest(
Messages.BloopGlobalJsonFilePremodified.params()
)
.asScala
.flatMap {
processUserPreferenceForBloopJvmProperties(
_,
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
requestedBloopJvmProperties,
maybeJavaHome,
reconnect
) andThen {
case Failure(exception) =>
languageClient.showMessage(
MessageType.Error,
exception.getMessage
)
case Success(_) => Future.successful()
}
}
} else {
// bloop global json file did not exist; or it was last modified by metals;
// hence it can get created or overwritten by Metals with no worries
// about overriding the user preferred settings
updateBloopGlobalJsonFileThenRestart(
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
requestedBloopJvmProperties,
maybeJavaHome,
reconnect
) andThen {
case Failure(exception) =>
languageClient.showMessage(
MessageType.Error,
exception.getMessage
)
case Success(_) => Future.successful()
}
}
} else Future.successful(())
maybeRequestedBloopJvmProperties != maybeBloopGlobalJsonJvmProperties
zmerr marked this conversation as resolved.
Show resolved Hide resolved
&& maybeRequestedBloopJvmProperties.nonEmpty
) Some(BloopJsonUpdateCause.JVM_OPTS)
else if (maybeRequestedMetalsJavaHome != maybeBloopGlobalJsonJavaHome)
Some(BloopJsonUpdateCause.JAVA_HOME)
else None
maybeBloopJvmProperties = maybeRequestedBloopJvmProperties.orElse(
maybeBloopGlobalJsonJvmProperties
)
} yield updateBloopJvmProperties(
maybeBloopJvmProperties,
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
maybeRequestedMetalsJavaHome,
reconnect,
bloopJsonUpdateCause
)

result.getOrElse(Future.successful(()))
result.getOrElse {
Future.unit
}
}

private def updateBloopJvmProperties(
maybeBloopJvmProperties: Option[List[String]],
bloopGlobalJsonFilePath: AbsolutePath,
bloopCreatedByMetalsFilePath: AbsolutePath,
maybeJavaHome: Option[String],
reconnect: () => Future[BuildChange],
bloopJsonUpdateCause: BloopJsonUpdateCause
): Future[Unit] = { // the properties are updated
if (
bloopGlobalJsonFilePath.exists &&
getBloopGlobalJsonLastModifiedTime(
bloopGlobalJsonFilePath
) > getBloopGlobalJsonLastModifiedByMetalsTime(
bloopCreatedByMetalsFilePath
)
) {
// the global json file was previously modified by the user through other means;
// therefore overwriting it requires user input
languageClient
.showMessageRequest(
Messages.BloopGlobalJsonFilePremodified.params(bloopJsonUpdateCause)
)
.asScala
.flatMap {
processUserPreferenceForBloopJvmProperties(
_,
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
maybeBloopJvmProperties,
maybeJavaHome,
reconnect
) andThen {
case Failure(exception) =>
languageClient.showMessage(
MessageType.Error,
exception.getMessage
)
case Success(_) => Future.unit
}
}
} else {
// bloop global json file did not exist; or it was last modified by metals;
// hence it can get created or overwritten by Metals with no worries
// about overriding the user preferred settings
updateBloopGlobalJsonFileThenRestart(
bloopGlobalJsonFilePath,
bloopCreatedByMetalsFilePath,
maybeBloopJvmProperties,
maybeJavaHome,
bloopJsonUpdateCause,
reconnect
) andThen {
case Failure(exception) =>
languageClient.showMessage(
MessageType.Error,
exception.getMessage
)
case Success(_) => Future.unit
}
}
}

private def connectToLauncher(
Expand Down Expand Up @@ -424,6 +477,12 @@ final class BloopServers(
}
}

object BloopJsonUpdateCause extends Enumeration {
type BloopJsonUpdateCause = Value
val JAVA_HOME: Value = Value("Metals Java Home")
val JVM_OPTS: Value = Value("Bloop JVM Properties")
}

object BloopServers {
val name = "Bloop"
}
Loading