Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class KotlinJsIntegrationTest extends AnyFunSuite with Matchers {

test("KotlinJsCompilerConfig: default configuration") {
val config = KotlinJsCompilerConfig.Default
config.kotlinVersion shouldBe "2.3.0"
config.kotlinVersion shouldBe bleep.model.Versions.Kotlin23
config.moduleKind shouldBe KotlinJsCompilerConfig.ModuleKind.CommonJS
config.outputMode shouldBe KotlinJsCompilerConfig.OutputMode.JsExecutable
config.sourceMap shouldBe true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class KotlinNativeIntegrationTest extends AnyFunSuite with Matchers {

test("KotlinNativeCompilerConfig: default configuration") {
val config = KotlinNativeCompilerConfig.Default
config.kotlinVersion shouldBe "2.3.0"
config.kotlinVersion shouldBe bleep.model.Versions.Kotlin23
config.outputKind shouldBe KotlinNativeCompilerConfig.OutputKind.Executable
config.debuggable shouldBe true
config.optimized shouldBe false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ object KotlinJsCompilerConfig {

/** Default configuration. */
val Default: KotlinJsCompilerConfig = KotlinJsCompilerConfig(
kotlinVersion = "2.3.0",
kotlinVersion = bleep.model.Versions.Kotlin23,
moduleKind = ModuleKind.CommonJS,
moduleName = "main",
outputMode = OutputMode.JsExecutable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ object KotlinNativeCompilerConfig {

/** Default configuration. */
val Default: KotlinNativeCompilerConfig = KotlinNativeCompilerConfig(
kotlinVersion = "2.3.0",
kotlinVersion = bleep.model.Versions.Kotlin23,
target = Target.hostTarget,
outputKind = OutputKind.Executable,
debuggable = true,
Expand Down
2 changes: 1 addition & 1 deletion bleep-bsp/src/scala/bleep/analysis/ProjectCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ object KotlinProjectCompiler extends ProjectCompiler {
*/
object JavacProjectCompiler extends ProjectCompiler {
// Use Scala 3.8.3 to match bleep-bsp (from template-scala-3)
private val zincScalaVersion = "3.8.3"
private val zincScalaVersion = bleep.model.Versions.ZincScala

def supports(language: ProjectLanguage): Boolean = language match {
case _: ProjectLanguage.JavaOnly => true
Expand Down
4 changes: 2 additions & 2 deletions bleep-bsp/src/scala/bleep/analysis/ZincBridge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -872,9 +872,9 @@ object ZincBridge {
// scala3-sbt-bridge is a Java artifact, not a Scala artifact (no _3 suffix)
s"org.scala-lang:scala3-sbt-bridge:$scalaVersion"
} else if (scalaVersion.startsWith("2.13")) {
s"org.scala-sbt::compiler-bridge:1.10.4"
s"org.scala-sbt::compiler-bridge:${bleep.model.Versions.CompilerBridge}"
} else if (scalaVersion.startsWith("2.12")) {
s"org.scala-sbt::compiler-bridge:1.10.4"
s"org.scala-sbt::compiler-bridge:${bleep.model.Versions.CompilerBridge}"
} else {
throw new IllegalArgumentException(s"Unsupported Scala version: $scalaVersion")
}
Expand Down
77 changes: 52 additions & 25 deletions bleep-bsp/src/scala/bleep/bsp/MultiWorkspaceBspServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -506,15 +506,15 @@ class MultiWorkspaceBspServer(

// Set up rewrites for IDE clients (SemanticDB support for goto-definition, find-references, etc.)
val rewrites: List[bleep.rewrites.BuildRewrite] = if (isIdeClient) {
val sdVersion = semanticDbVersionFromIde.getOrElse("4.15.2")
val sdVersion = semanticDbVersionFromIde.getOrElse(model.Versions.SemanticdbScalac)
logger.info(s"IDE client '${params.displayName}' detected, applying semanticDb rewrite with version $sdVersion")
List(new bleep.rewrites.semanticDb(sdVersion))
} else Nil
activeRewrites.set(rewrites)

// Resolve Java semanticdb plugin for IDE clients
if (isIdeClient) {
val javaSDVersion = javaSemanticDbVersionFromIde.getOrElse("0.10.0")
val javaSDVersion = javaSemanticDbVersionFromIde.getOrElse(model.Versions.SemanticdbJavac)
logger.info(s"Resolving Java semanticdb plugin: com.sourcegraph:semanticdb-javac:$javaSDVersion")
resolveJavaSemanticdbPlugin(javaSDVersion)
}
Expand Down Expand Up @@ -2761,7 +2761,7 @@ class MultiWorkspaceBspServer(

val testRunnerClasses = testRunnerFromBuild match {
case Some(path) => List(path)
case None => fetchTestRunnerViaCoursier(started)
case None => fetchTestRunnerViaCoursier(started, dependencyClasspath)
}

(classesDir :: resourceDirs) ++ dependencyClasspath ++ testRunnerClasses
Expand All @@ -2773,13 +2773,14 @@ class MultiWorkspaceBspServer(
*
* 1. `bleep-test-runner` itself — see [[fetchBleepTestRunnerOnly]] for how its version is chosen (`${BLEEP_VERSION}` for a `dev` build so it
* short-circuits to BleepDevDeps class dirs, otherwise pinned to this server's version).
* 2. Four hardcoded external test-framework deps (test-interface, jupiter-interface, junit-platform-launcher, junit-vintage-engine). These are stable
* across every workspace and every bleep version, so we cache them in a process-wide atomic — see
* `MultiWorkspaceBspServer.cachedExternalTestRunnerJars`. Without this, every inner-bleep `commands.test` re-runs Coursier for the same four deps;
* 2. Four external test-framework deps (test-interface, jupiter-interface, junit-platform-launcher, junit-vintage-engine). The junit pair is resolved at
* the junit-platform version already on the project's classpath when there is one — modern junit hard-fails on any version skew between the launcher
* and engine jars ("OutputDirectoryCreator not available") — and at bleep's default otherwise. Cached per (resolver, platform version) — see
* `MultiWorkspaceBspServer.cachedExternalTestRunnerJars`. Without the cache, every inner-bleep `commands.test` re-runs Coursier for the same four deps;
* under CI's contention that's enough to trip the suite-idle timeout in #580.
*/
private def fetchTestRunnerViaCoursier(started: Started): List[Path] = {
val externalJars = MultiWorkspaceBspServer.fetchExternalTestRunnerDeps(started)
private def fetchTestRunnerViaCoursier(started: Started, dependencyClasspath: List[Path]): List[Path] = {
val externalJars = MultiWorkspaceBspServer.fetchExternalTestRunnerDeps(started, dependencyClasspath)
val testRunnerJars = fetchBleepTestRunnerOnly(started)
if (testRunnerJars.isEmpty)
throw new RuntimeException("bleep-test-runner resolution returned no jars")
Expand Down Expand Up @@ -4052,40 +4053,66 @@ object MultiWorkspaceBspServer {
/** Enable debug logging to stderr (for development only) */
val DebugLogging: Boolean = sys.env.get("BLEEP_BSP_DEBUG").contains("true")

/** Hardcoded external test-framework dependencies that bleep-test-runner needs at runtime — same versions for every workspace and every bleep version. */
private val externalTestRunnerDeps: SortedSet[model.Dep] = SortedSet[model.Dep](
model.Dep.Java("org.scala-sbt", "test-interface", "1.0"),
model.Dep.Java("net.aichler", "jupiter-interface", "0.11.1"),
model.Dep.Java("org.junit.platform", "junit-platform-launcher", "1.9.1"),
model.Dep.Java("org.junit.vintage", "junit-vintage-engine", "5.9.1")
)
/** External test-framework dependencies that bleep-test-runner needs at runtime.
*
* The junit-platform launcher and vintage engine are version-sensitive: junit hard-fails when the launcher and the engine jars on the classpath disagree
* ("OutputDirectoryCreator not available … unaligned versions"), in both directions. So when the project's own classpath already carries a junit-platform
* (any project using junit-jupiter or kotest does), the injected pair is resolved at THAT version; only a project with no junit-platform at all gets bleep's
* defaults, which are then internally consistent by construction.
*/
private def externalTestRunnerDeps(junitPlatformVersion: Option[String]): SortedSet[model.Dep] = {
val launcherVersion = junitPlatformVersion.getOrElse(model.Versions.JunitPlatformLauncher)
// junit-platform 1.x pairs with jupiter/vintage 5.x at the same minor.patch; from junit 6 the version lines are unified.
val engineVersion = junitPlatformVersion match {
case Some(v) if v.startsWith("1.") => "5" + v.stripPrefix("1")
case Some(v) => v
case None => model.Versions.JunitVintageEngine
}
SortedSet[model.Dep](
model.Dep.Java("org.scala-sbt", "test-interface", model.Versions.TestInterface),
model.Dep.Java("net.aichler", "jupiter-interface", model.Versions.JupiterInterface),
// jupiter-interface drags in a stale junit-jupiter-engine (5.9.1) transitively; pinning the engine here makes coursier evict it to the aligned
// version, otherwise it NoSuchMethodErrors against the matched junit-platform-commons during discovery.
model.Dep.Java("org.junit.jupiter", "junit-jupiter-engine", engineVersion),
model.Dep.Java("org.junit.platform", "junit-platform-launcher", launcherVersion),
model.Dep.Java("org.junit.vintage", "junit-vintage-engine", engineVersion)
)
}

private val JunitPlatformEngineJar = "junit-platform-engine-(\\d[\\w.\\-]*)\\.jar".r

/** The junit-platform version already on the project's test classpath, detected from the engine jar's filename. */
private def detectJunitPlatformVersion(classpath: List[Path]): Option[String] =
classpath.iterator.map(_.getFileName.toString).collectFirst { case JunitPlatformEngineJar(version) => version }

/** Per-resolver memoization of the [[externalTestRunnerDeps]] resolution.
/** Per-(resolver, junit-platform version) memoization of the [[externalTestRunnerDeps]] resolution.
*
* The four deps don't change across workspaces or bleep versions, so resolving them once per resolver instance avoids re-running Coursier on every
* inner-bleep `commands.test`. Without this cache, each test workspace's [[InProcessBspServer]] (a fresh [[MultiWorkspaceBspServer]] per `commands.test`
* call) re-fetches the same artifacts; under CI's CPU contention with two parallel test JVMs that's enough to trip the 120 s suite-idle timeout in #580.
* Resolving once per key avoids re-running Coursier on every inner-bleep `commands.test`. Without this cache, each test workspace's [[InProcessBspServer]]
* (a fresh [[MultiWorkspaceBspServer]] per `commands.test` call) re-fetches the same artifacts; under CI's CPU contention with two parallel test JVMs that's
* enough to trip the 120 s suite-idle timeout in #580.
*
* Keyed by resolver-instance identity (not process-wide) so two BSP servers configured with different resolver settings — different mirrors, repositories,
* credentials — don't share jars resolved against the wrong config. Same resolver instance reused across calls within a server still hits the cache.
*/
private val cachedExternalTestRunnerJars: java.util.concurrent.ConcurrentHashMap[CoursierResolver, List[Path]] =
new java.util.concurrent.ConcurrentHashMap[CoursierResolver, List[Path]]()
private val cachedExternalTestRunnerJars: java.util.concurrent.ConcurrentHashMap[(CoursierResolver, Option[String]), List[Path]] =
new java.util.concurrent.ConcurrentHashMap[(CoursierResolver, Option[String]), List[Path]]()

private def fetchExternalTestRunnerDeps(started: Started): List[Path] = {
private def fetchExternalTestRunnerDeps(started: Started, dependencyClasspath: List[Path]): List[Path] = {
val resolver = started.resolver
val cached = cachedExternalTestRunnerJars.get(resolver)
val junitPlatformVersion = detectJunitPlatformVersion(dependencyClasspath)
val key = (resolver, junitPlatformVersion)
val cached = cachedExternalTestRunnerJars.get(key)
if (cached != null) return cached
val result = resolver.force(
externalTestRunnerDeps,
externalTestRunnerDeps(junitPlatformVersion),
model.VersionCombo.Jvm(model.VersionScala.Scala3),
libraryVersionSchemes = SortedSet.empty[model.LibraryVersionScheme],
context = "resolving bleep-test-runner external deps",
model.IgnoreEvictionErrors.No
)
// putIfAbsent: identical resolver from two threads is harmless (Coursier's disk cache handles
// concurrent downloads, and the jars resolved against the same config are identical).
val existing = cachedExternalTestRunnerJars.putIfAbsent(resolver, result.jars)
val existing = cachedExternalTestRunnerJars.putIfAbsent(key, result.jars)
if (existing != null) existing else result.jars
}
}
2 changes: 1 addition & 1 deletion bleep-cli/src/scala-3/bleep/mcp/BleepMcpServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class BleepMcpServer(initialStarted: Started) extends McpServer[IO] {
userPaths = started.pre.userPaths,
resolver = resolver,
logger = started.logger,
extraServerClasspath = Seq.empty
javaSemanticdbVersion = bsp.SetupBleepBsp.DefaultJavaSemanticdbVersion
)
case _: bsp.BspServerClasspathSource.InProcess =>
Left(new BleepException.Text("MCP server does not support in-process BSP mode"))
Expand Down
18 changes: 2 additions & 16 deletions bleep-cli/src/scala/bleep/bsp/BspProxy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import cats.effect.unsafe.implicits.global
import io.circe.Json

import java.io.{ByteArrayOutputStream, InputStream}
import java.nio.file.Path

/** `bleep bsp` — what an IDE launches, per the `.bsp/bleep.json` descriptor.
*
Expand All @@ -27,7 +26,7 @@ object BspProxy {
*/
private val KnownIdeClients = Set("Metals", "IntelliJ", "IntelliJ-BSP")

private val DefaultSemanticDbVersion = "4.15.2"
private val DefaultSemanticDbVersion = model.Versions.SemanticdbScalac

def run(pre: Prebootstrapped): ExitCode = {
val config = BleepConfigOps.loadOrDefault(pre.userPaths).orThrow
Expand Down Expand Up @@ -74,7 +73,7 @@ object BspProxy {
userPaths = pre.userPaths,
resolver = started.resolver,
logger = pre.logger,
extraServerClasspath = resolveExtraClasspath(javaSemanticdbVersion, pre.logger)
javaSemanticdbVersion = javaSemanticdbVersion.getOrElse(SetupBleepBsp.DefaultJavaSemanticdbVersion)
).orThrow

val program = BspRifle.ensureRunningAndConnect(bspConfig, pre.logger).use { connection =>
Expand Down Expand Up @@ -232,17 +231,4 @@ object BspProxy {
else new String(bytes, "US-ASCII")
}

/** Resolve semanticdb-javac for the BSP server classpath, when the IDE asked for Java semanticDB. */
private def resolveExtraClasspath(javaSemanticdbVersion: Option[String], logger: ryddig.Logger): Seq[Path] =
javaSemanticdbVersion match {
case Some(version) =>
logger.info(s"Resolving semanticdb-javac $version for BSP server classpath")
import coursier._
val dep = Dependency(Module(Organization("com.sourcegraph"), ModuleName("semanticdb-javac")), version)
val jars = Fetch().addDependencies(dep).run().map(_.toPath).toSeq
logger.info(s"Resolved ${jars.size} JARs for semanticdb-javac")
jars
case None =>
Seq.empty
}
}
9 changes: 5 additions & 4 deletions bleep-cli/src/scala/bleep/commands/BuildCreateNew.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ object BuildCreateNew {
dependsOn = model.JsonSet(model.ProjectName(name)),
isTestProject = Some(true),
platform = Some(jvmPlatform(None)),
dependencies = model.JsonSet(model.Dep.Java("org.junit.jupiter", "junit-jupiter", "5.10.1"))
dependencies = model.JsonSet(model.Dep.Java("org.junit.jupiter", "junit-jupiter", model.Versions.JunitJupiter))
)
val mainSrc =
s"""package $mainPkg;
Expand Down Expand Up @@ -201,9 +201,10 @@ object BuildCreateNew {
isTestProject = Some(true),
kotlin = Some(kotlin),
platform = Some(jvmPlatform(None)),
// kotest only — the generated test uses FunSpec/shouldBe, and kotest brings its own junit-platform. Adding junit-jupiter here would drag in a
// different junit-platform line and junit hard-fails when the launcher and engine jars disagree.
dependencies = model.JsonSet(
model.Dep.Java("io.kotest", "kotest-runner-junit5-jvm", "5.8.0"),
model.Dep.Java("org.junit.jupiter", "junit-jupiter", "5.10.1")
model.Dep.Java("io.kotest", "kotest-runner-junit5-jvm", model.Versions.Kotest)
)
)
val mainSrc =
Expand Down Expand Up @@ -284,7 +285,7 @@ object BuildCreateNew {
val testProj = mainProj.copy(
dependsOn = model.JsonSet(model.ProjectName(name)),
isTestProject = Some(true),
dependencies = model.JsonSet(model.Dep.Scala("org.scalameta", "munit", "1.0.0")),
dependencies = model.JsonSet(model.Dep.Scala("org.scalameta", "munit", model.Versions.Munit)),
platform = Some(platformFor(mainPlatform, None))
)

Expand Down
2 changes: 1 addition & 1 deletion bleep-cli/src/scala/bleep/commands/Fmt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object Fmt {
/** Scala formatting support via scalafmt */
object ScalaFmt {
val defaultConfig: String =
"""version=3.11.1
s"""version=${model.Versions.Scalafmt}
|maxColumn = 160
|runner.dialect = scala213
|""".stripMargin
Expand Down
4 changes: 2 additions & 2 deletions bleep-cli/src/scala/bleep/mavenimport/buildFromMavenPom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ object buildFromMavenPom {
// Scala test libs (scalatest, munit, etc.) already implement sbt test-interface.
// JUnit Jupiter requires an external adapter; JUnit 4 needs junit-interface.
private val testAdapterDeps: Map[String, model.Dep] = Map(
"com.github.sbt.junit.JupiterFramework" -> model.Dep.Java("net.aichler", "jupiter-interface", "0.11.1"),
"com.novocode.junit.JUnitFramework" -> model.Dep.Java("com.github.sbt", "junit-interface", "0.13.3")
"com.github.sbt.junit.JupiterFramework" -> model.Dep.Java("net.aichler", "jupiter-interface", model.Versions.JupiterInterface),
"com.novocode.junit.JUnitFramework" -> model.Dep.Java("com.github.sbt", "junit-interface", model.Versions.JunitInterface)
)

// Repos that are default and shouldn't be included
Expand Down
2 changes: 1 addition & 1 deletion bleep-core/src/scala/bleep/BspQuery.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object BspQuery {
userPaths = started.pre.userPaths,
resolver = resolver,
logger = started.logger,
extraServerClasspath = Seq.empty
javaSemanticdbVersion = bsp.SetupBleepBsp.DefaultJavaSemanticdbVersion
) match {
case Left(err) => return Left(err)
case Right(config) => (BspRifle.ensureRunningAndConnect(config, started.logger), config.traceFile)
Expand Down
Loading
Loading