diff --git a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala index aa12a4566e..3be2751390 100644 --- a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala +++ b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala @@ -594,7 +594,7 @@ final class BloopBspServices( filters => BloopDebuggeeRunner.forTestSuite(projects, filters, state, ioScheduler) ) case bsp.DebugSessionParamsDataKind.ScalaAttachRemote => - Right(BloopDebuggeeRunner.forAttachRemote(state, ioScheduler)) + Right(BloopDebuggeeRunner.forAttachRemote(state, ioScheduler, projects)) case dataKind => Left(JsonRpcResponse.invalidRequest(s"Unsupported data kind: $dataKind")) } } @@ -1102,7 +1102,7 @@ final class BloopBspServices( val sourceJars = project.resolution.toList.flatMap { res => res.modules.flatMap { m => m.artifacts.iterator - .filter(a => a.classifier.toList.contains("sources")) + .filter(a => a.classifier.contains("sources")) .map(a => bsp.Uri(AbsolutePath(a.path).toBspUri)) .toList } diff --git a/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala b/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala index 064d02b7be..9ab4cde2f1 100644 --- a/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala +++ b/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala @@ -1,21 +1,23 @@ package bloop.dap +import bloop.ScalaInstance import bloop.cli.ExitStatus import bloop.data.{JdkConfig, Platform, Project} -import bloop.engine.State +import bloop.engine.{Build, State} import bloop.engine.tasks.{RunMode, Tasks} +import bloop.logging.Logger import bloop.testing.{LoggingEventHandler, TestInternals} import ch.epfl.scala.bsp.ScalaMainClass -import ch.epfl.scala.debugadapter.{CancelableFuture, DebuggeeRunner, DebuggeeListener} +import ch.epfl.scala.debugadapter._ import monix.eval.Task import monix.execution.Scheduler -import xsbti.compile.analysis.SourceInfo -import java.nio.file.Path +import java.net.URLClassLoader +import scala.collection.mutable +import scala.annotation.tailrec abstract class BloopDebuggeeRunner(initialState: State, ioScheduler: Scheduler) extends DebuggeeRunner { - private lazy val allAnalysis = initialState.results.allAnalysis override def run(listener: DebuggeeListener): CancelableFuture[Unit] = { val debugSessionLogger = new DebuggeeLogger(listener, initialState.logger) @@ -28,31 +30,18 @@ abstract class BloopDebuggeeRunner(initialState: State, ioScheduler: Scheduler) } protected def start(state: State): Task[ExitStatus] - - override def classFilesMappedTo( - origin: Path, - lines: Array[Int], - columns: Array[Int] - ): List[Path] = { - def isInfoEmpty(info: SourceInfo) = info == sbt.internal.inc.SourceInfos.emptyInfo - - val originFile = origin.toFile - val foundClassFiles = allAnalysis.collectFirst { - case analysis if !isInfoEmpty(analysis.infos.get(originFile)) => - analysis.relations.products(originFile).iterator.map(_.toPath).toList - } - - foundClassFiles.toList.flatten - } } private final class MainClassDebugAdapter( project: Project, mainClass: ScalaMainClass, + val classPathEntries: Seq[ClassPathEntry], + val evaluationClassLoader: Option[ClassLoader], env: JdkConfig, initialState: State, ioScheduler: Scheduler ) extends BloopDebuggeeRunner(initialState, ioScheduler) { + val javaRuntime = JavaRuntime(env.javaHome.underlying) def name: String = s"${getClass.getSimpleName}(${project.name}, ${mainClass.`class`})" def start(state: State): Task[ExitStatus] = { val workingDir = state.commonOptions.workingPath @@ -78,6 +67,9 @@ private final class MainClassDebugAdapter( private final class TestSuiteDebugAdapter( projects: Seq[Project], filters: List[String], + val classPathEntries: Seq[ClassPathEntry], + val javaRuntime: Option[JavaRuntime], + val evaluationClassLoader: Option[ClassLoader], initialState: State, ioScheduler: Scheduler ) extends BloopDebuggeeRunner(initialState, ioScheduler) { @@ -103,8 +95,13 @@ private final class TestSuiteDebugAdapter( } } -private final class AttachRemoteDebugAdapter(initialState: State, ioScheduler: Scheduler) - extends BloopDebuggeeRunner(initialState, ioScheduler) { +private final class AttachRemoteDebugAdapter( + val classPathEntries: Seq[ClassPathEntry], + val javaRuntime: Option[JavaRuntime], + val evaluationClassLoader: Option[ClassLoader], + initialState: State, + ioScheduler: Scheduler +) extends BloopDebuggeeRunner(initialState, ioScheduler) { override def name: String = s"${getClass.getSimpleName}(${initialState.build.origin})" override def start(state: State): Task[ExitStatus] = Task(ExitStatus.Ok) } @@ -121,11 +118,22 @@ object BloopDebuggeeRunner { case Seq(project) => project.platform match { case jvm: Platform.Jvm => - Right(new MainClassDebugAdapter(project, mainClass, jvm.config, state, ioScheduler)) + val classPathEntries = getClassPathEntries(state.build, project) + val evaluationClassLoader = getEvaluationClassLoader(project, state, ioScheduler) + Right( + new MainClassDebugAdapter( + project, + mainClass, + classPathEntries, + evaluationClassLoader, + jvm.config, + state, + ioScheduler + ) + ) case platform => Left(s"Unsupported platform: ${platform.getClass.getSimpleName}") } - case projects => Left(s"Multiple projects specified for main class [$mainClass]: $projects") } } @@ -138,10 +146,157 @@ object BloopDebuggeeRunner { ): Either[String, DebuggeeRunner] = { projects match { case Seq() => Left(s"No projects specified for the test suites: [${filters.sorted}]") - case projects => Right(new TestSuiteDebugAdapter(projects, filters, state, ioScheduler)) + case Seq(project) if project.platform.isInstanceOf[Platform.Jvm] => + val Platform.Jvm(config, _, _, _, _, _) = project.platform + val classPathEntries = getClassPathEntries(state.build, project) + val javaRuntime = JavaRuntime(config.javaHome.underlying) + val evaluationClassLoader = getEvaluationClassLoader(project, state, ioScheduler) + Right( + new TestSuiteDebugAdapter( + projects, + filters, + classPathEntries, + javaRuntime, + evaluationClassLoader, + state, + ioScheduler + ) + ) + case _ => + Right( + new TestSuiteDebugAdapter(projects, filters, Seq.empty, None, None, state, ioScheduler) + ) + } + } + + def forAttachRemote( + state: State, + ioScheduler: Scheduler, + projects: Seq[Project] + ): DebuggeeRunner = { + projects match { + case Seq(project) if project.platform.isInstanceOf[Platform.Jvm] => + val Platform.Jvm(config, _, _, _, _, _) = project.platform + val classPathEntries = getClassPathEntries(state.build, project) + val javaRuntime = JavaRuntime(config.javaHome.underlying) + val evaluationClassLoader = getEvaluationClassLoader(project, state, ioScheduler) + new AttachRemoteDebugAdapter( + classPathEntries, + javaRuntime, + evaluationClassLoader, + state, + ioScheduler + ) + case _ => new AttachRemoteDebugAdapter(Seq.empty, None, None, state, ioScheduler) } } - def forAttachRemote(state: State, ioScheduler: Scheduler): DebuggeeRunner = - new AttachRemoteDebugAdapter(state, ioScheduler) + private def getClassPathEntries(build: Build, project: Project): Seq[ClassPathEntry] = { + val allProjects = getAllDepsRecursively(build, project) + getLibraries(allProjects) ++ getClassDirectories(allProjects) + } + + private def getEvaluationClassLoader( + project: Project, + state: State, + ioScheduler: Scheduler + ): Option[ClassLoader] = { + project.scalaInstance + .flatMap(getEvaluationClassLoader(_, state.logger, ioScheduler)) + } + + private def getEvaluationClassLoader( + scalaInstance: ScalaInstance, + logger: Logger, + ioScheduler: Scheduler + ): Option[ClassLoader] = { + import ch.epfl.scala.debugadapter.BuildInfo._ + import coursier._ + val scalaVersion = scalaInstance.version + val module = s"${expressionCompilerName}_$scalaVersion" + val expressionCompilerDep = Dependency( + Module( + Organization(expressionCompilerOrganization), + ModuleName(module) + ), + expressionCompilerVersion + ) + val resolution = Fetch() + .addDependencies(expressionCompilerDep) + .either()(ioScheduler) + + resolution match { + case Left(error) => + logger.warn( + s"Failed fetching $expressionCompilerOrganization:$module:$expressionCompilerVersion" + ) + logger.warn(error.getMessage) + logger.warn(s"Expression evaluation will not work.") + None + case Right(files) => + val expressionCompilerJar = files + .find(_.getName.startsWith(expressionCompilerName)) + .map(_.toURI.toURL) + val evaluationClassLoader = + new URLClassLoader(expressionCompilerJar.toArray, scalaInstance.loader) + Some(evaluationClassLoader) + } + } + + private def getAllDepsRecursively(build: Build, project: Project): Seq[Project] = { + @tailrec def tailApply(projects: Seq[Project], acc: Set[Project]): Seq[Project] = { + if (projects.isEmpty) acc.toSeq + else { + val dependencies = projects + .flatMap(_.dependencies) + .flatMap(build.getProjectFor) + .distinct + .filterNot(acc.contains) + tailApply(dependencies, acc ++ dependencies) + } + } + tailApply(Seq(project), Set(project)) + } + + private def getLibraries(allProjects: Seq[Project]): Seq[ClassPathEntry] = { + allProjects + .flatMap(_.resolution) + .flatMap(_.modules) + .distinct + .flatMap { module => + for { + classJar <- module.artifacts.find(_.classifier.isEmpty) + sourceJar <- module.artifacts.find(_.classifier.contains("sources")) + } yield { + val sourceEntry = SourceJar(sourceJar.path) + ClassPathEntry(classJar.path, Seq(sourceEntry)) + } + } + .distinct + } + + private def getClassDirectories(allProjects: Seq[Project]): Seq[ClassPathEntry] = { + allProjects.map { project => + val sourceBuffer = mutable.Buffer.empty[SourceEntry] + for (sourcePath <- project.sources) { + if (sourcePath.isDirectory) { + sourceBuffer += SourceDirectory(sourcePath.underlying) + } else { + sourceBuffer += StandaloneSourceFile( + sourcePath.underlying, + sourcePath.underlying.getFileName.toString + ) + } + } + for (glob <- project.sourcesGlobs) { + glob.walkThrough { file => + sourceBuffer += StandaloneSourceFile( + file.underlying, + file.toRelative(glob.directory).toString + ) + } + } + ClassPathEntry(project.out.underlying, sourceBuffer.toSeq) + } + } } diff --git a/frontend/src/main/scala/bloop/data/Project.scala b/frontend/src/main/scala/bloop/data/Project.scala index 2385e943be..4374478de1 100644 --- a/frontend/src/main/scala/bloop/data/Project.scala +++ b/frontend/src/main/scala/bloop/data/Project.scala @@ -1,34 +1,20 @@ package bloop.data -import java.net.URI - -import bloop.io.AbsolutePath -import bloop.logging.{DebugFilter, Logger} import bloop.ScalaInstance import bloop.bsp.ProjectUris -import bloop.config.Config -import bloop.engine.{Build, Dag} -import bloop.engine.caches.SemanticDBCache +import bloop.config.{Config, ConfigCodecs} +import bloop.engine.Dag import bloop.engine.tasks.toolchains.{JvmToolchain, ScalaJsToolchain, ScalaNativeToolchain} -import bloop.io.ByteHasher -import java.nio.charset.StandardCharsets -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.PathMatcher -import java.nio.file.Path -import java.nio.file.FileVisitor -import java.nio.file.FileVisitResult -import java.nio.file.Files -import java.nio.file.FileVisitOption -import java.nio.file.SimpleFileVisitor - -import scala.util.Try -import scala.collection.mutable +import bloop.io.{AbsolutePath, ByteHasher} +import bloop.logging.{DebugFilter, Logger} import ch.epfl.scala.{bsp => Bsp} +import monix.eval.Task import xsbti.compile.{ClasspathOptions, CompileOrder} -import bloop.config.ConfigCodecs +import java.nio.charset.StandardCharsets +import scala.collection.mutable +import scala.util.Try import scala.util.control.NonFatal -import monix.eval.Task final case class Project( name: String, @@ -92,23 +78,7 @@ final case class Project( def allSourceFilesAndDirectories: Task[List[AbsolutePath]] = Task { val buf = mutable.ListBuffer.empty[AbsolutePath] buf ++= sources - sourcesGlobs.foreach { glob => - if (Files.isDirectory(glob.directory.underlying)) { - Files.walkFileTree( - glob.directory.underlying, - java.util.EnumSet.of(FileVisitOption.FOLLOW_LINKS), - glob.walkDepth, - new SimpleFileVisitor[Path] { - override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { - if (glob.matches(file)) { - buf += AbsolutePath(file) - } - FileVisitResult.CONTINUE - } - } - ) - } - } + for (glob <- sourcesGlobs) glob.walkThrough(buf += _) buf.result() } diff --git a/frontend/src/main/scala/bloop/data/SourcesGlobs.scala b/frontend/src/main/scala/bloop/data/SourcesGlobs.scala index 3ced64e3fe..6846d3e12d 100644 --- a/frontend/src/main/scala/bloop/data/SourcesGlobs.scala +++ b/frontend/src/main/scala/bloop/data/SourcesGlobs.scala @@ -1,16 +1,13 @@ package bloop.data -import java.nio.file.FileSystems -import java.nio.file.PathMatcher -import java.nio.file.Path - -import scala.util.Properties -import scala.util.control.NonFatal - import bloop.config.Config import bloop.io.AbsolutePath import bloop.logging.Logger +import java.nio.file._ +import java.nio.file.attribute.BasicFileAttributes +import scala.util.control.NonFatal + case class SourcesGlobs( directory: AbsolutePath, walkDepth: Int, @@ -27,6 +24,22 @@ case class SourcesGlobs( } matchesList(includes) && !matchesList(excludes) } + + def walkThrough(f: AbsolutePath => Unit): Unit = { + if (directory.isDirectory) { + val _ = Files.walkFileTree( + directory.underlying, + java.util.EnumSet.of(FileVisitOption.FOLLOW_LINKS), + walkDepth, + new SimpleFileVisitor[Path] { + override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { + if (matches(file)) f(AbsolutePath(file)) + FileVisitResult.CONTINUE + } + } + ) + } + } } object SourcesGlobs { diff --git a/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala b/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala index eeb5e58c4c..1cc58626e9 100644 --- a/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala +++ b/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala @@ -18,6 +18,7 @@ import com.microsoft.java.debug.core.protocol.Responses.ContinueResponseBody import com.microsoft.java.debug.core.protocol.Responses.ScopesResponseBody import com.microsoft.java.debug.core.protocol.Responses.StackTraceResponseBody import com.microsoft.java.debug.core.protocol.Responses.VariablesResponseBody +import com.microsoft.java.debug.core.protocol.Responses.EvaluateResponseBody /** * Manages a connection with a debug adapter. @@ -80,6 +81,13 @@ private[dap] final class DebugAdapterConnection( adapter.request(Variables, arguments) } + def evaluate(frameId: Int, expression: String): Task[EvaluateResponseBody] = { + val arguments = new EvaluateArguments + arguments.frameId = frameId + arguments.expression = expression + adapter.request(Evaluate, arguments) + } + def stopped: Task[Events.StoppedEvent] = { adapter.next(StoppedEvent) } diff --git a/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala b/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala index 95dab8a629..7121cb3893 100644 --- a/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala +++ b/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala @@ -1,40 +1,29 @@ package bloop.dap -import java.net.{ConnectException, SocketException, SocketTimeoutException} -import java.util.NoSuchElementException -import java.util.concurrent.TimeUnit.{MILLISECONDS, SECONDS} import bloop.cli.ExitStatus -import bloop.logging.RecordingLogger +import bloop.data.Platform +import bloop.engine.{ExecutionContext, State} +import bloop.engine.tasks.{RunMode, Tasks} +import bloop.io.Environment.lineSeparator +import bloop.logging.LoggerAction.LogInfoMessage +import bloop.logging.{Logger, LoggerAction, ObservedLogger, RecordingLogger} +import bloop.reporter.ReporterAction import bloop.util.{TestProject, TestUtil} import ch.epfl.scala.bsp.ScalaMainClass -import monix.eval.Task -import monix.execution.Ack - -import scala.collection.mutable -import scala.concurrent.duration.{Duration, FiniteDuration} -import scala.concurrent.{Promise, TimeoutException} -import bloop.engine.ExecutionContext -import bloop.io.Environment.lineSeparator - +import ch.epfl.scala.debugadapter._ import com.microsoft.java.debug.core.protocol.Requests.SetBreakpointArguments import com.microsoft.java.debug.core.protocol.Types import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint - -import java.nio.file.Path -import bloop.logging.Logger -import bloop.logging.NoopLogger +import monix.eval.Task +import monix.execution.Ack import monix.reactive.Observer -import bloop.reporter.ReporterAction -import bloop.logging.LoggerAction -import bloop.logging.LoggerAction.LogInfoMessage -import bloop.logging.ObservedLogger -import scala.concurrent.Future -import bloop.data.Platform -import bloop.engine.tasks.Tasks -import bloop.engine.tasks.RunMode -import bloop.engine.State -import ch.epfl.scala.debugadapter.{CancelableFuture, DebugServer, DebuggeeRunner, DebuggeeListener} +import java.net.{ConnectException, SocketException, SocketTimeoutException} +import java.util.NoSuchElementException +import java.util.concurrent.TimeUnit.{MILLISECONDS, SECONDS} +import scala.collection.mutable +import scala.concurrent.duration.{Duration, FiniteDuration} +import scala.concurrent.{Future, Promise, TimeoutException} object DebugServerSpec extends DebugBspBaseSuite { private val ServerNotListening = new IllegalStateException("Server is not accepting connections") @@ -625,7 +614,8 @@ object DebugServerSpec extends DebugBspBaseSuite { val attachRemoteProcessRunner = BloopDebuggeeRunner.forAttachRemote( state.compile(project).toTestState.state, - defaultScheduler + defaultScheduler, + Seq(buildProject) ) startDebugServer(attachRemoteProcessRunner) { server => @@ -661,6 +651,73 @@ object DebugServerSpec extends DebugBspBaseSuite { } } + test("evaluate expression") { + TestUtil.withinWorkspace { workspace => + val source = """|/Main.scala + |object Main { + | def main(args: Array[String]): Unit = { + | val foo = new Foo + | println(foo) + | } + |} + | + |class Foo { + | override def toString = "foo" + |} + |""".stripMargin + + val logger = new RecordingLogger(ansiCodesSupported = false) + val project = TestProject(workspace, "r", List(source)) + + loadBspState(workspace, List(project), logger) { state => + val runner = mainRunner(project, state) + + val buildProject = state.toTestState.getProjectFor(project) + def srcFor(srcName: String) = + buildProject.sources.map(_.resolve(srcName)).find(_.exists).get + val `Main.scala` = srcFor("Main.scala") + + val breakpoints = { + val arguments = new SetBreakpointArguments() + val breakpoint1 = new SourceBreakpoint() + breakpoint1.line = 4 + arguments.source = new Types.Source(`Main.scala`.syntax, 0) + arguments.sourceModified = false + arguments.breakpoints = Array(breakpoint1) + arguments + } + + startDebugServer(runner) { server => + val test = for { + client <- server.startConnection + _ <- client.initialize() + _ <- client.launch() + _ <- client.initialized + breakpoints <- client.setBreakpoints(breakpoints) + _ = assert(breakpoints.breakpoints.forall(_.verified)) + _ <- client.configurationDone() + stopped <- client.stopped + stackTrace <- client.stackTrace(stopped.threadId) + topFrame <- stackTrace.stackFrames.headOption + .map(Task.now) + .getOrElse(Task.raiseError(new NoSuchElementException("no frames on the stack"))) + evaluation <- client.evaluate(topFrame.id, "1 + 1") + _ <- client.continue(stopped.threadId) + _ <- client.exited + _ <- client.terminated + _ <- Task.fromFuture(client.closedPromise.future) + } yield { + assert(client.socket.isClosed) + assertNoDiff(evaluation.`type`, "int") + assertNoDiff(evaluation.result, "2") + } + + TestUtil.await(FiniteDuration(60, SECONDS), ExecutionContext.ioScheduler)(test) + } + } + } + } + private def portListeningLogger(underlying: Logger, listener: Int => Unit): Logger = { val listeningObserver = new Observer[Either[ReporterAction, LoggerAction]] { override def onNext(elem: Either[ReporterAction, LoggerAction]): Future[Ack] = elem match { @@ -724,16 +781,13 @@ object DebugServerSpec extends DebugBspBaseSuite { def startDebugServer(task: Task[ExitStatus])(f: TestServer => Any): Unit = { val runner = new DebuggeeRunner { + override def classPathEntries: Seq[ClassPathEntry] = Seq.empty + override def javaRuntime: Option[JavaRuntime] = None + override def evaluationClassLoader: Option[ClassLoader] = None def name: String = "MockRunner" def run(listener: DebuggeeListener): CancelableFuture[Unit] = { DapCancellableFuture.runAsync(task.map(_ => ()), defaultScheduler) } - - def classFilesMappedTo( - origin: Path, - lines: Array[Int], - columns: Array[Int] - ): List[Path] = Nil } startDebugServer( diff --git a/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala b/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala index f63bcc0ee1..f489e3aa7a 100644 --- a/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala +++ b/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala @@ -8,6 +8,7 @@ import com.microsoft.java.debug.core.protocol.Responses.ContinueResponseBody import com.microsoft.java.debug.core.protocol.Responses.ScopesResponseBody import com.microsoft.java.debug.core.protocol.Responses.StackTraceResponseBody import com.microsoft.java.debug.core.protocol.Responses.VariablesResponseBody +import com.microsoft.java.debug.core.protocol.Responses.EvaluateResponseBody private[dap] object DebugTestEndpoints { val Initialize = new Request[InitializeArguments, Types.Capabilities]("initialize") @@ -19,6 +20,7 @@ private[dap] object DebugTestEndpoints { val StackTrace = new Request[StackTraceArguments, StackTraceResponseBody]("stackTrace") val Scopes = new Request[ScopesArguments, ScopesResponseBody]("scopes") val Variables = new Request[VariablesArguments, VariablesResponseBody]("variables") + val Evaluate = new Request[EvaluateArguments, EvaluateResponseBody]("evaluate") val Continue = new Request[ContinueArguments, ContinueResponseBody]("continue") val ConfigurationDone = new Request[Unit, Unit]("configurationDone") diff --git a/launcher/src/test/scala/bloop/launcher/LauncherBaseSuite.scala b/launcher/src/test/scala/bloop/launcher/LauncherBaseSuite.scala index 46f1f08348..832e95c4e4 100644 --- a/launcher/src/test/scala/bloop/launcher/LauncherBaseSuite.scala +++ b/launcher/src/test/scala/bloop/launcher/LauncherBaseSuite.scala @@ -2,7 +2,7 @@ package bloop.launcher import bloop.io.Paths import bloop.io.AbsolutePath -import bloop.io.Environment.{lineSeparator, LineSplitter} +import bloop.io.Environment.LineSplitter import bloop.testing.BaseSuite import bloop.bloopgun.core.Shell import bloop.bloopgun.util.Environment diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 87f52a85f7..78ff146006 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -55,7 +55,7 @@ object Dependencies { val asmVersion = "7.0" val snailgunVersion = "0.4.0" val ztExecVersion = "1.11" - val debugAdapterVersion = "1.1.3" + val debugAdapterVersion = "2.0.2" import sbt.librarymanagement.syntax.stringToOrganization val zinc = "ch.epfl.scala" %% "zinc" % zincVersion