diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38258f9430..e04c83ac83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: - uses: coursier/setup-action@v1.3.0 with: jvm: "temurin:17" + apps: sbt - name: Tests run: | .github/setup-test-projects.sh &&\ @@ -64,6 +65,7 @@ jobs: - uses: coursier/setup-action@v1.3.0 with: jvm: "temurin:17" + apps: sbt - name: Compile and test main projects # Only running the tests in 2.12 for now. Many test fixtures need # to be updated for 2.13. diff --git a/backend/src/main/scala/bloop/Compiler.scala b/backend/src/main/scala/bloop/Compiler.scala index ab299e9fdb..1bb7f94aff 100644 --- a/backend/src/main/scala/bloop/Compiler.scala +++ b/backend/src/main/scala/bloop/Compiler.scala @@ -8,7 +8,7 @@ import java.util.concurrent.Executor import scala.collection.mutable import scala.concurrent.Promise -import scala.util.control.NonFatal +import scala.util.Try import bloop.io.AbsolutePath import bloop.io.ParallelOps diff --git a/backend/src/main/scala/bloop/ScalaInstance.scala b/backend/src/main/scala/bloop/ScalaInstance.scala index b88e202274..13a0ef2e35 100644 --- a/backend/src/main/scala/bloop/ScalaInstance.scala +++ b/backend/src/main/scala/bloop/ScalaInstance.scala @@ -23,7 +23,8 @@ final class ScalaInstance private ( val organization: String, val name: String, override val version: String, - override val allJars: Array[File] + override val allJars: Array[File], + val bridgeJarsOpt: Option[Seq[File]] = None ) extends xsbti.compile.ScalaInstance { override lazy val loaderCompilerOnly: ClassLoader = @@ -153,6 +154,15 @@ object ScalaInstance { private[ScalaInstance] final val ScalacCompilerName = "scala-compiler" + def apply( + scalaOrg: String, + scalaName: String, + scalaVersion: String, + allJars: Seq[AbsolutePath], + logger: Logger + ): ScalaInstance = + apply(scalaOrg, scalaName, scalaVersion, allJars, logger, None) + /** * Reuses all jars to create an Scala instance if and only if all of them exist. * @@ -170,7 +180,8 @@ object ScalaInstance { scalaName: String, scalaVersion: String, allJars: Seq[AbsolutePath], - logger: Logger + logger: Logger, + bridgeJarsOpt: Option[Seq[AbsolutePath]] ): ScalaInstance = { val jarsKey = allJars.map(_.underlying).sortBy(_.toString).toList if (allJars.nonEmpty) { @@ -179,7 +190,13 @@ object ScalaInstance { DebugFilter.Compilation ) jarsKey.foreach(p => logger.debug(s" => $p")(DebugFilter.Compilation)) - new ScalaInstance(scalaOrg, scalaName, scalaVersion, allJars.map(_.toFile).toArray) + new ScalaInstance( + scalaOrg, + scalaName, + scalaVersion, + allJars.map(_.toFile).toArray, + bridgeJarsOpt.map(_.map(_.toFile)) + ) } val nonExistingJars = allJars.filter(j => !Files.exists(j.underlying)) diff --git a/backend/src/main/scala/sbt/internal/inc/BloopComponentCompiler.scala b/backend/src/main/scala/sbt/internal/inc/BloopComponentCompiler.scala index afd4ef72ca..3be9edd926 100644 --- a/backend/src/main/scala/sbt/internal/inc/BloopComponentCompiler.scala +++ b/backend/src/main/scala/sbt/internal/inc/BloopComponentCompiler.scala @@ -116,7 +116,11 @@ object BloopComponentCompiler { */ private def compiledBridge(bridgeSources: ModuleID, scalaInstance: ScalaInstance): File = { val raw = new RawCompiler(scalaInstance, ClasspathOptionsUtil.auto, logger) - val zinc = new BloopComponentCompiler(raw, manager, bridgeSources, logger) + val bridgeJarsOpt = scalaInstance match { + case b: _root_.bloop.ScalaInstance => b.bridgeJarsOpt + case _ => None + } + val zinc = new BloopComponentCompiler(raw, manager, bridgeSources, bridgeJarsOpt, logger) logger.debug(s"Getting $bridgeSources for Scala ${scalaInstance.version}")( DebugFilter.Compilation ) @@ -229,6 +233,7 @@ private[inc] class BloopComponentCompiler( compiler: RawCompiler, manager: BloopComponentManager, bridgeSources: ModuleID, + bridgeJarsOpt: Option[Seq[File]], logger: BloopLogger ) { implicit val debugFilter: DebugFilter.Compilation.type = DebugFilter.Compilation @@ -257,23 +262,25 @@ private[inc] class BloopComponentCompiler( IO.withTemporaryDirectory { _ => val shouldResolveSources = bridgeSources.explicitArtifacts.exists(_.`type` == "src") - val allArtifacts = BloopDependencyResolution.resolveWithErrors( - List( - BloopDependencyResolution - .Artifact(bridgeSources.organization, bridgeSources.name, bridgeSources.revision) - ), - logger, - resolveSources = shouldResolveSources, - List( - coursierapi.MavenRepository.of( - "https://scala-ci.typesafe.com/artifactory/scala-integration/" + val allArtifacts = bridgeJarsOpt.map(_.map(_.toPath)).getOrElse { + BloopDependencyResolution.resolveWithErrors( + List( + BloopDependencyResolution + .Artifact(bridgeSources.organization, bridgeSources.name, bridgeSources.revision) + ), + logger, + resolveSources = shouldResolveSources, + List( + coursierapi.MavenRepository.of( + "https://scala-ci.typesafe.com/artifactory/scala-integration/" + ) ) - ) - ) match { - case Right(paths) => paths.map(_.underlying).toVector - case Left(t) => - val msg = s"Couldn't retrieve module $bridgeSources" - throw new InvalidComponent(msg, t) + ) match { + case Right(paths) => paths.map(_.underlying).toVector + case Left(t) => + val msg = s"Couldn't retrieve module $bridgeSources" + throw new InvalidComponent(msg, t) + } } if (!shouldResolveSources) { @@ -286,7 +293,7 @@ private[inc] class BloopComponentCompiler( if (!HydraSupport.isEnabled(compiler.scalaInstance)) (bridgeSources.name, sources) else { val hydraBridgeModule = HydraSupport.getModuleForBridgeSources(compiler.scalaInstance) - mergeBloopAndHydraBridges(sources, hydraBridgeModule) match { + mergeBloopAndHydraBridges(sources.toVector, hydraBridgeModule) match { case Right(mergedHydraBridgeSourceJar) => (hydraBridgeModule.name, mergedHydraBridgeSourceJar) case Left(error) => diff --git a/build.sc b/build.sc index 3d64c217ba..82cd072bbf 100644 --- a/build.sc +++ b/build.sc @@ -32,7 +32,7 @@ object Dependencies { def asm = ivy"org.ow2.asm:asm:$asmVersion" def asmUtil = ivy"org.ow2.asm:asm-util:$asmVersion" - def bloopConfig = ivy"ch.epfl.scala::bloop-config:1.5.5" + def bloopConfig = ivy"ch.epfl.scala::bloop-config:2.0.0" def brave = ivy"io.zipkin.brave:brave:5.18.1" def bsp4j = ivy"ch.epfl.scala:bsp4j:2.1.1" def bsp4s = ivy"ch.epfl.scala::bsp4s:2.1.1" @@ -61,7 +61,7 @@ object Dependencies { def munit = ivy"org.scalameta::munit:0.7.29" def nailgun = ivy"io.github.alexarchambault.bleep:nailgun-server:1.0.7" def osLib = ivy"com.lihaoyi::os-lib:0.9.0" - def pprint = ivy"com.lihaoyi::pprint:0.8.1" + def pprint = ivy"com.lihaoyi::pprint:0.9.0" def sbtTestAgent = ivy"org.scala-sbt:test-agent:1.9.9" def sbtTestInterface = ivy"org.scala-sbt:test-interface:1.0" def scalaDebugAdapter = ivy"ch.epfl.scala::scala-debug-adapter:4.0.3" @@ -72,12 +72,12 @@ object Dependencies { def scalaJsSbtTestAdapter1 = ivy"org.scala-js::scalajs-sbt-test-adapter:$scalaJs1Version" def scalaJsLogging1 = ivy"org.scala-js::scalajs-logging:1.1.1" def scalaNativeTools04 = ivy"org.scala-native::tools:0.4.17" - def scalaNativeTools05 = ivy"org.scala-native::tools:0.5.0-RC2" + def scalaNativeTools05 = ivy"org.scala-native::tools:0.5.1" def scalazCore = ivy"org.scalaz::scalaz-core:7.3.8" def snailgun = ivy"io.github.alexarchambault.scala-cli.snailgun::snailgun-core:0.4.1-sc2" - def sourcecode = ivy"com.lihaoyi::sourcecode:0.3.1" + def sourcecode = ivy"com.lihaoyi::sourcecode:0.4.1" def svm = ivy"org.graalvm.nativeimage:svm:$graalvmVersion" - def utest = ivy"com.lihaoyi::utest:0.8.2" + def utest = ivy"com.lihaoyi::utest:0.8.3" def xxHashLibrary = ivy"net.jpountz.lz4:lz4:1.3.0" def zinc = ivy"org.scala-sbt::zinc:1.9.5" def zipkinSender = ivy"io.zipkin.reporter2:zipkin-sender-urlconnection:2.17.2" diff --git a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala index 7c0550b121..f7010888e0 100644 --- a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala +++ b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala @@ -130,6 +130,7 @@ final class BloopBspServices( .requestAsync(endpoints.BuildTarget.sources)(p => schedule(sources(p))) .requestAsync(endpoints.BuildTarget.inverseSources)(p => schedule(inverseSources(p))) .requestAsync(endpoints.BuildTarget.resources)(p => schedule(resources(p))) + .requestAsync(endpoints.BuildTarget.outputPaths)(p => schedule(outputPaths(p))) .requestAsync(endpoints.BuildTarget.scalacOptions)(p => schedule(scalacOptions(p))) .requestAsync(endpoints.BuildTarget.javacOptions)(p => schedule(javacOptions(p))) .requestAsync(endpoints.BuildTarget.compile)(p => schedule(compile(p))) @@ -311,7 +312,7 @@ final class BloopBspServices( dependencySourcesProvider = Some(true), dependencyModulesProvider = Some(true), resourcesProvider = Some(true), - outputPathsProvider = None, + outputPathsProvider = Some(true), buildTargetChangedProvider = Some(false), jvmTestEnvironmentProvider = Some(true), jvmRunEnvironmentProvider = Some(true), @@ -1204,6 +1205,39 @@ final class BloopBspServices( } } + def outputPaths( + request: bsp.OutputPathsParams + ): BspEndpointResponse[bsp.OutputPathsResult] = { + def outputPaths( + projects: Seq[ProjectMapping], + state: State + ): BspResult[bsp.OutputPathsResult] = { + + val response = bsp.OutputPathsResult( + projects.iterator.map { + case (target, project) => + val outputPathItems = + List( + bsp.OutputPathItem(bsp.Uri(project.out.toBspUri), bsp.OutputPathItemKind.Directory) + ) + bsp.OutputPathsItem(target, outputPathItems) + }.toList + ) + + Task.now((state, Right(response))) + } + + ifInitialized(None) { (state: State, logger: BspServerLogger) => + mapToProjects(request.targets, state) match { + case Left(error) => + // Log the mapping error to the user via a log event + an error status code + logger.error(error) + Task.now((state, Right(bsp.OutputPathsResult(Nil)))) + case Right(mappings) => outputPaths(mappings, state) + } + } + } + def dependencyModules( request: bsp.DependencyModulesParams ): BspEndpointResponse[bsp.DependencyModulesResult] = { diff --git a/frontend/src/main/scala/bloop/bsp/ProjectUris.scala b/frontend/src/main/scala/bloop/bsp/ProjectUris.scala index ac4d99cf24..594ecaf4f2 100644 --- a/frontend/src/main/scala/bloop/bsp/ProjectUris.scala +++ b/frontend/src/main/scala/bloop/bsp/ProjectUris.scala @@ -12,19 +12,16 @@ import bloop.engine.State import bloop.io.AbsolutePath object ProjectUris { + private val queryPrefix = "id=" def getProjectDagFromUri(projectUri: String, state: State): Either[String, Option[Project]] = { if (projectUri.isEmpty) Left("URI cannot be empty.") else { - val query = Try(new URI(projectUri).getRawQuery().split("&").map(_.split("="))).toEither - query match { - case Left(_) => + Try(new URI(projectUri).getQuery()).toEither match { + case Right(query) if query.startsWith(queryPrefix) => + val projectName = query.stripPrefix(queryPrefix) + Right(state.build.getProjectFor(projectName)) + case _ => Left(s"URI '${projectUri}' has invalid format. Example: ${ProjectUris.Example}") - case Right(parsed) => - parsed.headOption match { - case Some(Array("id", projectName)) => Right(state.build.getProjectFor(projectName)) - case _ => - Left(s"URI '${projectUri}' has invalid format. Example: ${ProjectUris.Example}") - } } } } @@ -39,7 +36,7 @@ object ProjectUris { existingUri.getHost, existingUri.getPort, existingUri.getPath, - s"id=${id}", + s"$queryPrefix${id}", existingUri.getFragment ) } diff --git a/frontend/src/main/scala/bloop/data/ClientInfo.scala b/frontend/src/main/scala/bloop/data/ClientInfo.scala index 478985dc6f..ff27c766fc 100644 --- a/frontend/src/main/scala/bloop/data/ClientInfo.scala +++ b/frontend/src/main/scala/bloop/data/ClientInfo.scala @@ -11,11 +11,11 @@ import scala.collection.mutable import scala.util.control.NonFatal import scala.util.matching.Regex +import bloop.ClientClassesObserver import bloop.io.AbsolutePath import bloop.io.Filenames import bloop.io.Paths import bloop.util.UUIDUtil -import bloop.ClientClassesObserver sealed trait ClientInfo { diff --git a/frontend/src/main/scala/bloop/data/Project.scala b/frontend/src/main/scala/bloop/data/Project.scala index cabb75d403..f3736ff6f6 100644 --- a/frontend/src/main/scala/bloop/data/Project.scala +++ b/frontend/src/main/scala/bloop/data/Project.scala @@ -290,7 +290,14 @@ object Project { else { val scalaJars = scala.jars.map(AbsolutePath.apply) Some( - ScalaInstance(scala.organization, scala.name, scala.version, scalaJars, logger) + ScalaInstance( + scala.organization, + scala.name, + scala.version, + scalaJars, + logger, + scala.bridgeJars.map(_.map(AbsolutePath(_))) + ) ) } } diff --git a/frontend/src/main/scala/bloop/engine/tasks/compilation/CompileBundle.scala b/frontend/src/main/scala/bloop/engine/tasks/compilation/CompileBundle.scala index 89e695c540..d9c09d3244 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/compilation/CompileBundle.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/compilation/CompileBundle.scala @@ -2,6 +2,7 @@ package bloop.engine.tasks.compilation import scala.concurrent.Promise +import bloop.ClientClassesObserver import bloop.CompileOutPaths import bloop.Compiler import bloop.ScalaInstance @@ -23,7 +24,6 @@ import bloop.tracing.BraveTracer import monix.reactive.Observable import sbt.internal.inc.PlainVirtualFileConverter -import bloop.ClientClassesObserver sealed trait CompileBundle diff --git a/frontend/src/main/scala/bloop/engine/tasks/toolchains/ScalaNativeToolchain.scala b/frontend/src/main/scala/bloop/engine/tasks/toolchains/ScalaNativeToolchain.scala index 1b51af487c..b1ea756f97 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/toolchains/ScalaNativeToolchain.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/toolchains/ScalaNativeToolchain.scala @@ -3,6 +3,7 @@ package bloop.engine.tasks.toolchains import java.lang.reflect.InvocationTargetException import java.nio.file.Path +import scala.concurrent.Future import scala.util.Try import bloop.DependencyResolution @@ -14,7 +15,6 @@ import bloop.internal.build.BuildInfo import bloop.io.AbsolutePath import bloop.logging.Logger import bloop.task.Task -import scala.concurrent.Future final class ScalaNativeToolchain private (classLoader: ClassLoader) { diff --git a/frontend/src/test/resources/cross-test-build-scala-native-0.5/project/plugins.sbt b/frontend/src/test/resources/cross-test-build-scala-native-0.5/project/plugins.sbt index cf1c2ba4a5..2fccee8e53 100644 --- a/frontend/src/test/resources/cross-test-build-scala-native-0.5/project/plugins.sbt +++ b/frontend/src/test/resources/cross-test-build-scala-native-0.5/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.0.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.0-RC2") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.1") val pluginVersion = sys.props.getOrElse( "bloopVersion", diff --git a/frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala b/frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala index 544f662e50..8967258483 100644 --- a/frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala +++ b/frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala @@ -349,6 +349,19 @@ abstract class BspBaseSuite extends BaseSuite with BspClientTest { TestUtil.await(FiniteDuration(5, "s"))(resourcesTask) } + def requestOutputPaths(project: TestProject): bsp.OutputPathsResult = { + val outputPathsTask = { + client0 + .request(endpoints.BuildTarget.outputPaths, bsp.OutputPathsParams(List(project.bspId))) + .map { + case RpcFailure(_, error) => fail(s"Received error ${error}") + case RpcSuccess(resources, _) => resources + } + } + + TestUtil.await(FiniteDuration(5, "s"))(outputPathsTask) + } + def requestDependencyModules(project: TestProject): bsp.DependencyModulesResult = { val dependencyModulesTask = { client0 diff --git a/frontend/src/test/scala/bloop/bsp/BspProtocolSpec.scala b/frontend/src/test/scala/bloop/bsp/BspProtocolSpec.scala index 51ec49ae91..b965675f4c 100644 --- a/frontend/src/test/scala/bloop/bsp/BspProtocolSpec.scala +++ b/frontend/src/test/scala/bloop/bsp/BspProtocolSpec.scala @@ -516,6 +516,37 @@ class BspProtocolSpec( } } + test("outputPaths request works") { + TestUtil.withinWorkspace { workspace => + val logger = new RecordingLogger(ansiCodesSupported = false) + loadBspBuildFromResources("cross-test-build-scalajs-0.6", workspace, logger) { build => + val mainProject = build.projectFor("test-project") + val testProject = build.projectFor("test-project-test") + val mainJsProject = build.projectFor("test-projectJS") + val testJsProject = build.projectFor("test-projectJS-test") + val rootMain = build.projectFor("cross-test-build-scalajs-0-6") + val rootTest = build.projectFor("cross-test-build-scalajs-0-6-test") + + def checkOutputPaths(project: TestProject): Unit = { + val outputPathsResult = build.state.requestOutputPaths(project) + assert(outputPathsResult.items.size == 1) + val outputPathsItem = outputPathsResult.items.head + assert(outputPathsItem.target == project.bspId) + val outputPaths = outputPathsItem.outputPaths.map(_.uri.toPath) + val expectedOutputPaths = List(project.config.out.toAbsolutePath()) + assert(outputPaths == expectedOutputPaths) + } + + checkOutputPaths(mainProject) + checkOutputPaths(testProject) + checkOutputPaths(mainJsProject) + checkOutputPaths(testJsProject) + checkOutputPaths(rootMain) + checkOutputPaths(rootTest) + } + } + } + test("dependency modules request works") { TestUtil.withinWorkspace { workspace => val logger = new RecordingLogger(ansiCodesSupported = false) diff --git a/frontend/src/test/scala/bloop/bsp/TestConstants.scala b/frontend/src/test/scala/bloop/bsp/TestConstants.scala index f3699b62e6..5c41c575fc 100644 --- a/frontend/src/test/scala/bloop/bsp/TestConstants.scala +++ b/frontend/src/test/scala/bloop/bsp/TestConstants.scala @@ -18,6 +18,7 @@ object TestConstants { "dependencySourcesProvider": true, "dependencyModulesProvider": true, "resourcesProvider": true, + "outputPathsProvider":true, "buildTargetChangedProvider": false, "jvmRunEnvironmentProvider": true, "jvmTestEnvironmentProvider": true, diff --git a/frontend/src/test/scala/bloop/util/TestProject.scala b/frontend/src/test/scala/bloop/util/TestProject.scala index b55ba3a387..d8638e1a12 100644 --- a/frontend/src/test/scala/bloop/util/TestProject.scala +++ b/frontend/src/test/scala/bloop/util/TestProject.scala @@ -160,7 +160,8 @@ abstract class BaseTestProject { scalacOptions, allJars.map(_.underlying).toList, None, - Some(setup) + Some(setup), + None ) val javacConfig = Config.Java(javacOptions)