Skip to content

Commit baa9825

Browse files
authored
Merge pull request #217 from tpasternak/bloop-compiles-against-right-jvm
Bloop compile to correct JVM version
2 parents 5c35eed + ae51120 commit baa9825

File tree

28 files changed

+424
-160
lines changed

28 files changed

+424
-160
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ jobs:
4242
if-no-files-found: error
4343
retention-days: 2
4444
- name: Unit tests
45-
run: ./mill -i unitTests
45+
run: |
46+
./mill -i unitTests
47+
./mill -i bloop-rifle._.test
4648
- name: JVM integration tests
4749
run: ./mill -i integration.jvm.test
4850

build.sc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ class TestRunner(val crossScalaVersion: String) extends CrossSbtModule with Scal
485485
}
486486

487487
class BloopRifle(val crossScalaVersion: String) extends CrossSbtModule with ScalaCliPublishModule
488+
with HasTests
488489
with ScalaCliScalafixModule {
489490
def scalacOptions = T {
490491
super.scalacOptions() ++ Seq("-Ywarn-unused", "-deprecation")
@@ -517,6 +518,8 @@ class BloopRifle(val crossScalaVersion: String) extends CrossSbtModule with Scal
517518
PathRef(dest)
518519
}
519520
def generatedSources = super.generatedSources() ++ Seq(constantsFile())
521+
522+
object test extends Tests with ScalaCliScalafixModule
520523
}
521524

522525
class TastyLib(val crossScalaVersion: String) extends CrossSbtModule with ScalaCliPublishModule

modules/bloop-rifle/src/main/scala/scala/build/bloop/BloopServer.scala

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,37 @@ package scala.build.bloop
33
import ch.epfl.scala.bsp4j
44
import org.eclipse.lsp4j.jsonrpc
55

6-
import java.io.IOException
6+
import java.io.{File, IOException}
77
import java.net.{ConnectException, Socket}
8-
import java.nio.file.{Files, Path}
8+
import java.nio.file.{Files, Path, Paths}
99
import java.util.concurrent.{Future => JFuture, ScheduledExecutorService, TimeoutException}
1010

1111
import scala.annotation.tailrec
1212
import scala.build.bloop.bloop4j.BloopExtraBuildParams
13+
import scala.build.blooprifle.BloopRifleConfig.{AtLeast, Strict}
14+
import scala.build.blooprifle._
1315
import scala.build.blooprifle.internal.Constants
14-
import scala.build.blooprifle.{BloopRifle, BloopRifleConfig, BloopRifleLogger, BspConnection}
1516
import scala.concurrent.Await
16-
import scala.concurrent.duration.{Duration, FiniteDuration}
17+
import scala.concurrent.duration._
1718
import scala.jdk.CollectionConverters._
1819

1920
trait BloopServer {
2021
def server: BuildServer
2122

2223
def shutdown(): Unit
24+
25+
def jvmVersion: String
26+
27+
def bloopVersion: String
2328
}
2429

2530
object BloopServer {
26-
2731
private case class BloopServerImpl(
2832
server: BuildServer,
2933
listeningFuture: JFuture[Void],
30-
socket: Socket
34+
socket: Socket,
35+
jvmVersion: String,
36+
bloopVersion: String
3137
) extends BloopServer {
3238
def shutdown(): Unit = {
3339
// Close the jsonrpc thread listening to input messages
@@ -37,31 +43,77 @@ object BloopServer {
3743
}
3844
}
3945

46+
private case class ResolvedBloopParameters(
47+
bloopVersion: BloopVersion,
48+
jvmRelease: Int,
49+
javaPath: String
50+
)
51+
52+
private def resolveBloopInfo(
53+
bloopInfo: BloopServerRuntimeInfo,
54+
config: BloopRifleConfig
55+
): ResolvedBloopParameters = {
56+
val bloopV: BloopVersion = config.retainedBloopVersion match {
57+
case AtLeast(version) =>
58+
val ord = Ordering.fromLessThan[BloopVersion](_ isOlderThan _)
59+
Seq(bloopInfo.bloopVersion, version).max(ord)
60+
case Strict(version) => version
61+
}
62+
val jvmV = List(bloopInfo.jvmVersion, config.minimumBloopJvm).max
63+
val bloopInfoJava = Paths.get(bloopInfo.javaHome, "bin", "java").toString()
64+
val expectedJava = if (jvmV >= bloopInfo.jvmVersion) config.javaPath else bloopInfoJava
65+
ResolvedBloopParameters(bloopV, jvmV, expectedJava)
66+
}
67+
4068
private def ensureBloopRunning(
4169
config: BloopRifleConfig,
4270
startServerChecksPool: ScheduledExecutorService,
4371
logger: BloopRifleLogger
44-
): Unit = {
45-
46-
val isBloopRunning = BloopRifle.check(config, logger, startServerChecksPool)
47-
48-
logger.debug(
49-
if (isBloopRunning) s"Bloop is running on ${config.host}:${config.port}"
50-
else s"No bloop daemon found on ${config.host}:${config.port}"
51-
)
52-
53-
if (!isBloopRunning) {
54-
logger.debug("Starting bloop server")
55-
val serverStartedFuture = BloopRifle.startServer(
72+
): BloopServerRuntimeInfo = {
73+
val workdir = new File(".").getCanonicalFile.toPath
74+
def startBloop(bloopVersion: String, bloopJava: String) = {
75+
logger.info(s"Starting bloop $bloopVersion on $bloopJava")
76+
val fut = BloopRifle.startServer(
5677
config,
5778
startServerChecksPool,
58-
logger
79+
logger,
80+
bloopVersion,
81+
bloopJava
5982
)
60-
61-
Await.result(serverStartedFuture, Duration.Inf)
62-
logger.debug("Bloop server started")
83+
Await.result(fut, 30.seconds)
84+
}
85+
def exitBloop() = BloopRifle.exit(config, workdir, logger)
86+
87+
val bloopInfo =
88+
BloopRifle.getCurrentBloopVersion(config, logger, workdir, startServerChecksPool)
89+
val isRunning = BloopRifle.check(config, logger, startServerChecksPool)
90+
val ResolvedBloopParameters(expectedBloopVersion, expectedBloopJvmRelease, javaPath) =
91+
bloopInfo match {
92+
case Left(error) =>
93+
error match {
94+
case BloopNotRunning =>
95+
case ParsingFailed(bloopAboutOutput) =>
96+
logger.info(s"Failed to parse output of 'bloop about':\n$bloopAboutOutput")
97+
}
98+
ResolvedBloopParameters(
99+
config.retainedBloopVersion.version,
100+
config.minimumBloopJvm,
101+
config.javaPath
102+
)
103+
case Right(value) => resolveBloopInfo(value, config)
104+
}
105+
val bloopVersionIsOk = bloopInfo.exists(_.bloopVersion == expectedBloopVersion)
106+
val bloopJvmIsOk = bloopInfo.exists(_.jvmVersion == expectedBloopJvmRelease)
107+
val isOk = bloopVersionIsOk && bloopJvmIsOk
108+
109+
if (!isOk) {
110+
logger.info(s"Bloop currently running: $bloopInfo")
111+
if (isRunning) exitBloop()
112+
startBloop(expectedBloopVersion.raw, javaPath)
63113
}
64114

115+
BloopRifle.getCurrentBloopVersion(config, logger, workdir, startServerChecksPool)
116+
.getOrElse(throw new RuntimeException("Fatal error, could not spawn Bloop"))
65117
}
66118

67119
private def connect(
@@ -99,9 +151,9 @@ object BloopServer {
99151
logger: BloopRifleLogger,
100152
period: FiniteDuration,
101153
timeout: FiniteDuration
102-
): (BspConnection, Socket) = {
154+
): (BspConnection, Socket, BloopServerRuntimeInfo) = {
103155

104-
ensureBloopRunning(config, threads.startServerChecks, logger)
156+
val bloopInfo = ensureBloopRunning(config, threads.startServerChecks, logger)
105157

106158
logger.debug("Opening BSP connection with bloop")
107159
Files.createDirectories(workspace.resolve(".scala/.bloop"))
@@ -116,7 +168,7 @@ object BloopServer {
116168

117169
logger.debug(s"Connected to Bloop via BSP at ${conn.address}")
118170

119-
(conn, socket)
171+
(conn, socket, bloopInfo)
120172
}
121173

122174
def buildServer(
@@ -130,7 +182,8 @@ object BloopServer {
130182
logger: BloopRifleLogger
131183
): BloopServer = {
132184

133-
val (conn, socket) = bsp(config, workspace, threads, logger, config.period, config.timeout)
185+
val (conn, socket, bloopInfo) =
186+
bsp(config, workspace, threads, logger, config.period, config.timeout)
134187

135188
logger.debug(s"Connected to Bloop via BSP at ${conn.address}")
136189

@@ -172,7 +225,7 @@ object BloopServer {
172225
}
173226

174227
server.onBuildInitialized()
175-
BloopServerImpl(server, f, socket)
228+
BloopServerImpl(server, f, socket, bloopInfo.jvmVersion.toString(), bloopInfo.bloopVersion.raw)
176229
}
177230

178231
def withBuildServer[T](
@@ -206,5 +259,4 @@ object BloopServer {
206259
}
207260
// format: on
208261
}
209-
210262
}

modules/bloop-rifle/src/main/scala/scala/build/blooprifle/BloopRifle.scala

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
package scala.build.blooprifle
22

3-
import java.io.{
4-
ByteArrayOutputStream,
5-
File,
6-
FileInputStream,
7-
FileOutputStream,
8-
InputStream,
9-
OutputStream
10-
}
3+
import java.io.{ByteArrayOutputStream, FileInputStream, FileOutputStream, InputStream, OutputStream}
114
import java.nio.file.Path
125
import java.util.concurrent.ScheduledExecutorService
136

@@ -37,14 +30,7 @@ object BloopRifle {
3730
config.port,
3831
logger
3932
)
40-
check() && {
41-
!BloopRifle.shutdownBloopIfVersionIncompatible(
42-
config,
43-
logger,
44-
new File(".").getCanonicalFile.toPath,
45-
scheduler
46-
)
47-
}
33+
check()
4834
}
4935

5036
/** Starts a new bloop server.
@@ -58,15 +44,17 @@ object BloopRifle {
5844
def startServer(
5945
config: BloopRifleConfig,
6046
scheduler: ScheduledExecutorService,
61-
logger: BloopRifleLogger
47+
logger: BloopRifleLogger,
48+
version: String,
49+
bloopJava: String
6250
): Future[Unit] =
63-
config.classPath() match {
51+
config.classPath(version) match {
6452
case Left(ex) => Future.failed(new Exception("Error getting Bloop class path", ex))
6553
case Right(cp) =>
6654
Operations.startServer(
6755
config.host,
6856
config.port,
69-
config.javaPath,
57+
bloopJava,
7058
config.javaOpts,
7159
cp.map(_.toPath),
7260
scheduler,
@@ -188,53 +176,63 @@ object BloopRifle {
188176

189177
def nullInputStream() = new FileInputStream(Util.devNull)
190178

191-
def extractVersionFromBloopAbout(stdoutFromBloopAbout: String): Option[String] =
192-
stdoutFromBloopAbout.split("\n").find(_.startsWith("bloop v")).map(
193-
_.split(" ")(1).trim().drop(1)
179+
// Probably we should implement an endpoint in Bloop to get this
180+
// information in better form. I'm not sure error here should be escalated or ignored.
181+
private def extractVersionFromBloopAbout(stdoutFromBloopAbout: String)
182+
: Option[BloopServerRuntimeInfo] = {
183+
184+
val bloopVersionRegex = "bloop v(.*)\\s".r
185+
val bloopJvmRegex = "Running on Java JDK v([0-9._A-Za-z]+) [(](.*)[)]".r
186+
187+
for {
188+
bloopVersion <- bloopVersionRegex.findFirstMatchIn(stdoutFromBloopAbout).map(_.group(1))
189+
bloopJvmVersion <- bloopJvmRegex.findFirstMatchIn(stdoutFromBloopAbout).map(_.group(1))
190+
javaHome <- bloopJvmRegex.findFirstMatchIn(stdoutFromBloopAbout).map(_.group(2))
191+
jvmRelease <- VersionUtil.jvmRelease(bloopJvmVersion)
192+
} yield BloopServerRuntimeInfo(
193+
bloopVersion = BloopVersion(bloopVersion),
194+
jvmVersion = jvmRelease,
195+
javaHome = javaHome
194196
)
197+
}
195198

196199
def getCurrentBloopVersion(
197200
config: BloopRifleConfig,
198201
logger: BloopRifleLogger,
199202
workdir: Path,
200203
scheduler: ScheduledExecutorService
201-
) = {
202-
val bufferedOStream = new ByteArrayOutputStream(100000)
203-
Operations.about(
204-
config.host,
205-
config.port,
206-
workdir,
207-
nullInputStream(),
208-
bufferedOStream,
209-
nullOutputStream(),
210-
logger,
211-
scheduler
212-
)
213-
extractVersionFromBloopAbout(new String(bufferedOStream.toByteArray))
214-
}
204+
): Either[BloopAboutFailure, BloopServerRuntimeInfo] = {
205+
val isRunning = BloopRifle.check(config, logger, scheduler)
215206

216-
/** Sometimes we need some minimal requirements for Bloop version. This method kills Bloop if its
217-
* version is unsupported.
218-
* @returns
219-
* true if the 'exit' command has actually been sent to Bloop
220-
*/
221-
def shutdownBloopIfVersionIncompatible(
222-
config: BloopRifleConfig,
223-
logger: BloopRifleLogger,
224-
workDir: Path,
225-
scheduler: ScheduledExecutorService
226-
): Boolean = {
227-
val currentBloopVersion = getCurrentBloopVersion(config, logger, workDir, scheduler)
228-
val isOk = config.acceptBloopVersion.forall { f =>
229-
currentBloopVersion.forall(f(_))
230-
}
231-
if (isOk)
232-
logger.debug("No need to restart Bloop")
233-
else {
234-
logger.debug(s"Shutting down unsupported Bloop $currentBloopVersion.")
235-
val retCode = exit(config, workDir, logger)
236-
logger.debug(s"Bloop exit code: $retCode")
207+
if (isRunning) {
208+
val bufferedOStream = new ByteArrayOutputStream(100000)
209+
Operations.about(
210+
config.host,
211+
config.port,
212+
workdir,
213+
nullInputStream(),
214+
bufferedOStream,
215+
nullOutputStream(),
216+
logger,
217+
scheduler
218+
)
219+
val bloopAboutOutput = new String(bufferedOStream.toByteArray)
220+
extractVersionFromBloopAbout(bloopAboutOutput) match {
221+
case Some(value) => Right(value)
222+
case None => Left(ParsingFailed(bloopAboutOutput))
223+
}
237224
}
238-
!isOk
225+
else
226+
Left(BloopNotRunning)
239227
}
240228
}
229+
230+
sealed trait BloopAboutFailure
231+
case object BloopNotRunning extends BloopAboutFailure
232+
case class ParsingFailed(bloopAboutOutput: String) extends BloopAboutFailure
233+
234+
case class BloopServerRuntimeInfo(
235+
bloopVersion: BloopVersion,
236+
jvmVersion: Int,
237+
javaHome: String
238+
)

0 commit comments

Comments
 (0)