From a2f5a2a8817b69ae43b6f993741b25d36b9feaac Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Fri, 18 Aug 2023 17:47:09 +0200 Subject: [PATCH] add suport for hHCR --- .../scala/bloop/bsp/BloopBspServices.scala | 2 +- .../bloop/dap/BloopDebugToolsResolver.scala | 6 +- .../main/scala/bloop/dap/BloopDebuggee.scala | 103 ++++++++++------ .../src/main/scala/bloop/data/Project.scala | 6 + .../bloop/engine/tasks/CompileTask.scala | 20 ++- .../bloop/dap/DebugAdapterConnection.scala | 17 +++ .../scala/bloop/dap/DebugServerSpec.scala | 116 +++++++++++++++++- .../scala/bloop/dap/DebugTestEndpoints.scala | 10 +- project/Dependencies.scala | 4 +- 9 files changed, 222 insertions(+), 62 deletions(-) diff --git a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala index e272f1a958..8f601e810c 100644 --- a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala +++ b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala @@ -635,7 +635,7 @@ final class BloopBspServices( BloopDebuggeeRunner.forTestSuite(projects, testClasses, state, ioScheduler) }) case bsp.DebugSessionParamsDataKind.ScalaAttachRemote => - Right(BloopDebuggeeRunner.forAttachRemote(state, ioScheduler, projects)) + Right(BloopDebuggeeRunner.forAttachRemote(projects, state, ioScheduler)) case dataKind => Left(Response.invalidRequest(s"Unsupported data kind: $dataKind")) } } diff --git a/frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala b/frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala index 8d56ce2fc9..4d23d0edf4 100644 --- a/frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala +++ b/frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala @@ -34,10 +34,10 @@ class BloopDebugToolsResolver(logger: Logger) extends DebugToolsResolver { } } - override def resolveStepFilter(scalaVersion: ScalaVersion): Try[ClassLoader] = { + override def resolveUnpickler(scalaVersion: ScalaVersion): Try[ClassLoader] = { getOrTryUpdate(stepFilterCache, scalaVersion) { - val stepFilterModule = s"${BuildInfo.scala3StepFilterName}_${scalaVersion.binaryVersion}" - val stepFilter = Artifact(BuildInfo.organization, stepFilterModule, BuildInfo.version) + val unpicklerModule = s"${BuildInfo.unpicklerName}_${scalaVersion.binaryVersion}" + val stepFilter = Artifact(BuildInfo.organization, unpicklerModule, BuildInfo.version) val tastyCore = Artifact("org.scala-lang", "tasty-core_3", scalaVersion.value) DependencyResolution .resolveWithErrors(List(stepFilter, tastyCore), logger) diff --git a/frontend/src/main/scala/bloop/dap/BloopDebuggee.scala b/frontend/src/main/scala/bloop/dap/BloopDebuggee.scala index d6e35c89a0..78e38f1496 100644 --- a/frontend/src/main/scala/bloop/dap/BloopDebuggee.scala +++ b/frontend/src/main/scala/bloop/dap/BloopDebuggee.scala @@ -20,22 +20,23 @@ import bloop.testing.DebugLoggingEventHandler import bloop.testing.TestInternals import monix.execution.Scheduler +import io.reactivex.Observable +import java.util.concurrent.Semaphore abstract class BloopDebuggee( initialState: State, ioScheduler: Scheduler, - debugeeScalaVersion: Option[String] + debuggeeScalaVersion: Option[String] ) extends Debuggee { - // The version doesn't matter for project without Scala version (Java only) - val scalaVersion = ScalaVersion(debugeeScalaVersion.getOrElse("2.13.8")) + val scalaVersion = ScalaVersion(debuggeeScalaVersion.getOrElse("2.13.8")) override def run(listener: DebuggeeListener): CancelableFuture[Unit] = { val debugSessionLogger = new DebuggeeLogger(listener, initialState.logger) val task = start(initialState.copy(logger = debugSessionLogger), listener) .map { status => - if (!status.isOk) throw new Exception(s"debugee failed with ${status.name}") + if (!status.isOk) throw new Exception(s"debuggee failed with ${status.name}") } DapCancellableFuture.runAsync(task, ioScheduler) } @@ -51,9 +52,12 @@ private final class MainClassDebugAdapter( val unmanagedEntries: Seq[UnmanagedEntry], env: JdkConfig, initialState: State, - ioScheduler: Scheduler, - scalaVersion: Option[String] -) extends BloopDebuggee(initialState, ioScheduler, scalaVersion) { + ioScheduler: Scheduler +) extends BloopDebuggee(initialState, ioScheduler, project.scalaInstance.map(_.version)) { + + val classesToUpdate: Observable[Seq[String]] = project.classObserver + val compileLocks: Semaphore = project.compileLock + val javaRuntime: Option[JavaRuntime] = JavaRuntime(env.javaHome.underlying) def name: String = s"${getClass.getSimpleName}(${project.name}, ${mainClass.`class`})" def start(state: State, listener: DebuggeeListener): Task[ExitStatus] = { @@ -74,6 +78,11 @@ private final class MainClassDebugAdapter( ) runState.map(_.status) } + + override def compile(): Unit = { + compileLocks.acquire() + compileLocks.release() + } } private final class TestSuiteDebugAdapter( @@ -84,9 +93,18 @@ private final class TestSuiteDebugAdapter( val unmanagedEntries: Seq[UnmanagedEntry], val javaRuntime: Option[JavaRuntime], initialState: State, - ioScheduler: Scheduler, - val debugeeScalaVersion: Option[String] -) extends BloopDebuggee(initialState, ioScheduler, debugeeScalaVersion) { + ioScheduler: Scheduler +) extends BloopDebuggee( + initialState, + ioScheduler, + projects.headOption.flatMap(_.scalaInstance).map(_.version) + ) { + + val classesToUpdate: Observable[Seq[String]] = + projects.map(_.classObserver).fold(Observable.empty[Seq[String]])(_ mergeWith _) + val compileLocks: Seq[Semaphore] = projects.map(_.compileLock) + + override def compile(): Unit = () override def name: String = { val projectsStr = projects.map(_.bspUri).mkString("[", ", ", "]") val selectedTests = testClasses.suites @@ -116,21 +134,41 @@ private final class TestSuiteDebugAdapter( } private final class AttachRemoteDebugAdapter( + projects: Seq[Project], val modules: Seq[Module], val libraries: Seq[Library], val unmanagedEntries: Seq[UnmanagedEntry], val javaRuntime: Option[JavaRuntime], initialState: State, - ioScheduler: Scheduler, - val debugeeScalaVersion: Option[String] -) extends BloopDebuggee(initialState, ioScheduler, debugeeScalaVersion) { + ioScheduler: Scheduler +) extends BloopDebuggee( + initialState, + ioScheduler, + projects.headOption.flatMap(_.scalaInstance).map(_.version) + ) { + override def name: String = s"${getClass.getSimpleName}(${initialState.build.origin})" override def start(state: State, listener: DebuggeeListener): Task[ExitStatus] = Task( ExitStatus.Ok ) + val classesToUpdate: Observable[Seq[String]] = + projects.map(_.classObserver).fold(Observable.empty[Seq[String]])(_ mergeWith _) + val compileLocks: Seq[Semaphore] = projects.map(_.compileLock) + override def compile(): Unit = () } object BloopDebuggeeRunner { + def getEntries( + project: Project, + state: State + ): (Seq[Module], Seq[Library], Seq[UnmanagedEntry]) = { + val dag = state.build.getDagFor(project) + val modules = getModules(dag, state.client) + val libraries = getLibraries(dag) + val unmanagedEntries = + getUnmanagedEntries(project, dag, state.client, modules ++ libraries) + (modules, libraries, unmanagedEntries) + } def forMainClass( projects: Seq[Project], @@ -143,11 +181,7 @@ object BloopDebuggeeRunner { case Seq(project) => project.platform match { case jvm: Platform.Jvm => - val dag = state.build.getDagFor(project) - val modules = getModules(dag, state.client) - val libraries = getLibraries(dag) - val unmanagedEntries = - getUnmanagedEntries(project, dag, state.client, modules ++ libraries) + val (modules, libraries, unmanagedEntries) = getEntries(project, state) Right( new MainClassDebugAdapter( project, @@ -157,8 +191,7 @@ object BloopDebuggeeRunner { unmanagedEntries, jvm.config, state, - ioScheduler, - project.scalaInstance.map(_.version) + ioScheduler ) ) case platform => @@ -178,10 +211,7 @@ object BloopDebuggeeRunner { case Seq() => Left(s"No projects specified for the test suites: [${testClasses.classNames.sorted}]") case Seq(project) if project.platform.isInstanceOf[Platform.Jvm] => - val dag = state.build.getDagFor(project) - val modules = getModules(dag, state.client) - val libraries = getLibraries(dag) - val unmanagedEntries = getUnmanagedEntries(project, dag, state.client, modules ++ libraries) + val (modules, libraries, unmanagedEntries) = getEntries(project, state) val Platform.Jvm(config, _, _, _, _, _) = project.platform val javaRuntime = JavaRuntime(config.javaHome.underlying) Right( @@ -193,12 +223,11 @@ object BloopDebuggeeRunner { unmanagedEntries, javaRuntime, state, - ioScheduler, - project.scalaInstance.map(_.version) + ioScheduler ) ) - case project :: _ => + case _ => Right( new TestSuiteDebugAdapter( projects, @@ -208,8 +237,7 @@ object BloopDebuggeeRunner { Seq.empty, None, state, - ioScheduler, - project.scalaInstance.map(_.version) + ioScheduler ) ) @@ -217,36 +245,33 @@ object BloopDebuggeeRunner { } def forAttachRemote( + projects: Seq[Project], state: State, - ioScheduler: Scheduler, - projects: Seq[Project] + ioScheduler: Scheduler ): Debuggee = { projects match { case Seq(project) if project.platform.isInstanceOf[Platform.Jvm] => - val dag = state.build.getDagFor(project) - val libraries = getLibraries(dag) - val modules = getModules(dag, state.client) - val unmanagedEntries = getUnmanagedEntries(project, dag, state.client, modules ++ libraries) + val (modules, libraries, unmanagedEntries) = getEntries(project, state) val Platform.Jvm(config, _, _, _, _, _) = project.platform val javaRuntime = JavaRuntime(config.javaHome.underlying) new AttachRemoteDebugAdapter( + Seq(project), modules, libraries, unmanagedEntries, javaRuntime, state, - ioScheduler, - project.scalaInstance.map(_.version) + ioScheduler ) case projects => new AttachRemoteDebugAdapter( + projects, Seq.empty, Seq.empty, Seq.empty, None, state, - ioScheduler, - projects.headOption.flatMap(_.scalaInstance).map(_.version) + ioScheduler ) } } diff --git a/frontend/src/main/scala/bloop/data/Project.scala b/frontend/src/main/scala/bloop/data/Project.scala index d5a6716685..2632fb1262 100644 --- a/frontend/src/main/scala/bloop/data/Project.scala +++ b/frontend/src/main/scala/bloop/data/Project.scala @@ -31,6 +31,8 @@ import com.typesafe.config.ConfigSyntax import scalaz.Cord import xsbti.compile.ClasspathOptions import xsbti.compile.CompileOrder +import java.util.concurrent.Semaphore +import io.reactivex.subjects.PublishSubject final case class Project( name: String, @@ -59,6 +61,10 @@ final case class Project( origin: Origin ) { + val compileLock = new Semaphore(1) + + val classObserver = PublishSubject.create[Seq[String]]() + /** The bsp uri associated with this project. */ val bspUri: Bsp.Uri = Bsp.Uri(ProjectUris.toURI(baseDirectory, name)) diff --git a/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala b/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala index 3a8cca306d..25a14a2112 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala @@ -50,6 +50,7 @@ object CompileTask { ): Task[State] = Task.defer { import bloop.data.ClientInfo import bloop.internal.build.BuildInfo + val originUri = state.build.origin val cwd = originUri.getParent val topLevelTargets = Dag.directDependencies(List(dag)).mkString(", ") @@ -93,6 +94,10 @@ object CompileTask { "compile.target" -> project.name ) + project.compileLock.acquire() + + println("compiling") + bundle.prepareSourcesAndInstance match { case Left(earlyResultBundle) => compileProjectTracer.terminate() @@ -190,9 +195,13 @@ object CompileTask { } // Populate the last successful result if result was success - result match { + val compileResult = result match { case s: Compiler.Result.Success => val runningTasks = runPostCompilationTasks(s.backgroundTasks) + val updatedClasses = s.products.generatedRelativeClassFilePaths.keySet + .map(_.drop(1).dropRight(6).replaceAll("/", ".")) + .toSeq // TODO: returns only changed class files + project.classObserver.onNext(updatedClasses) val blockingOnRunningTasks = Task .fromFuture(runningTasks) .executeOn(ExecutionContext.ioScheduler) @@ -221,6 +230,8 @@ object CompileTask { _: Compiler.Result.GlobalError => ResultBundle(result, None, None, CancelableFuture.unit) } + project.compileLock.release() + compileResult } } } @@ -327,12 +338,9 @@ object CompileTask { runIOTasksInParallel(cleanUpTasksToRunInBackground) val runningTasksRequiredForCorrectness = Task.sequence { - results.flatMap { + results.collect { case FinalNormalCompileResult(_, result) => - val tasksAtEndOfBuildCompilation = - Task.fromFuture(result.runningBackgroundTasks) - List(tasksAtEndOfBuildCompilation) - case _ => Nil + Task.fromFuture(result.runningBackgroundTasks) } } diff --git a/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala b/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala index c51ec9edba..754c0bc7aa 100644 --- a/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala +++ b/frontend/src/test/scala/bloop/dap/DebugAdapterConnection.scala @@ -23,6 +23,10 @@ import com.microsoft.java.debug.core.protocol.Responses.VariablesResponseBody import com.microsoft.java.debug.core.protocol.Types.Capabilities import monix.execution.Cancelable import monix.execution.Scheduler +import com.microsoft.java.debug.core.protocol.Responses.RedefineClassesResponse +import java.nio.file.Files +import bloop.io.AbsolutePath +import java.nio.file.Path /** * Manages a connection with a debug adapter. @@ -64,6 +68,8 @@ private[dap] final class DebugAdapterConnection( def setBreakpoints( arguments: SetBreakpointArguments ): Task[SetBreakpointsResponseBody] = { + println(s"Source: ${arguments.source.path}") + println(Files.readString(Path.of(arguments.source.path))) adapter.request(SetBreakpoints, arguments) } @@ -119,6 +125,17 @@ private[dap] final class DebugAdapterConnection( adapter.request(Attach, arguments) } + def stepIn(threadId: Long): Task[Unit] = { + val args = new StepInArguments() + args.threadId = threadId + adapter.request(StepIn, args) + } + + def redefineClasses(): Task[RedefineClassesResponse] = { + val args = new RedefineClassesArguments() + adapter.request(RedefineClasses, args) + } + def close(): Unit = { try socket.close() finally { diff --git a/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala b/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala index aa3ea4d562..5a630bec32 100644 --- a/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala +++ b/frontend/src/test/scala/bloop/dap/DebugServerSpec.scala @@ -6,6 +6,7 @@ import java.net.SocketTimeoutException import java.util.NoSuchElementException import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.TimeUnit.HOURS import scala.collection.mutable import scala.concurrent.Future @@ -45,6 +46,7 @@ import com.microsoft.java.debug.core.protocol.Types import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint import monix.execution.Ack import monix.reactive.Observer +import io.reactivex.Observable object DebugServerSpec extends DebugBspBaseSuite { private val ServerNotListening = new IllegalStateException("Server is not accepting connections") @@ -537,9 +539,9 @@ object DebugServerSpec extends DebugBspBaseSuite { val attachRemoteProcessRunner = BloopDebuggeeRunner.forAttachRemote( + Seq(buildProject), state.compile(project).toTestState.state, - defaultScheduler, - Seq(buildProject) + defaultScheduler ) startDebugServer(attachRemoteProcessRunner) { server => @@ -747,7 +749,7 @@ object DebugServerSpec extends DebugBspBaseSuite { } } - testTask("evaluate expression in attached debuggee", FiniteDuration(120, SECONDS)) { + testTask("evaluate expression in attached debuggee", FiniteDuration(120, HOURS)) { TestUtil.withinWorkspace { workspace => val source = """|/Main.scala |object Main { @@ -780,9 +782,9 @@ object DebugServerSpec extends DebugBspBaseSuite { val attachRemoteProcessRunner = BloopDebuggeeRunner.forAttachRemote( + Seq(buildProject), testState.state, - defaultScheduler, - Seq(buildProject) + defaultScheduler ) startDebugServer(attachRemoteProcessRunner) { server => @@ -886,6 +888,108 @@ object DebugServerSpec extends DebugBspBaseSuite { } } + testTask("Performs Hot Code Replace good", FiniteDuration(120, SECONDS)) { + val source2 = + """|/Main.scala + |object Main { + | def main(args: Array[String]): Unit = { + | println("A") + | new A().m() + | } + |} + | + |class A { + | def m() = { + | println("B") + | } + | def foo = 2 + |} + | + |object B + |""".stripMargin + TestUtil.withinWorkspace { workspace => + val source = + """|/Main.scala + |object Main { + | def main(args: Array[String]): Unit = { + | new A().m() + | } + |} + | + |class A { + | def m() = { + | println("A") + | } + | def foo = 1 + |} + | + |object B + |""".stripMargin + + val logger = new RecordingLogger(ansiCodesSupported = false) + val project = TestProject( + workspace, + "r", + List(source), + scalaVersion = Some("2.12.17") + ) + + loadBspStateWithTask(workspace, List(project), logger) { state => + val testState = state.compile(project).toTestState + val buildProject = testState.getProjectFor(project) + def srcFor(srcName: String) = + buildProject.sources.map(_.resolve(srcName)).find(_.exists).get + val `Main.scala` = srcFor("Main.scala") + val breakpoints = breakpointsArgs(`Main.scala`, 9) + + val attachRemoteProcessRunner = + BloopDebuggeeRunner.forAttachRemote( + Seq(buildProject), + testState.state, + defaultScheduler + ) + + startDebugServer(attachRemoteProcessRunner) { server => + for { + port <- startRemoteProcess(buildProject, testState) + client <- server.startConnection + _ <- client.initialize() + _ <- client.attach("localhost", port) + 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"))) + eval1 <- client.evaluate(topFrame.id, "foo") + _ = writeFile(`Main.scala`, source2) + _ = state.compile(project) + _ <- client.redefineClasses() + _ <- client.stepIn(stopped.threadId) + stopped2 <- client.stopped + stackTrace2 <- client.stackTrace(stopped2.threadId) + topFrame2 <- stackTrace2.stackFrames.headOption + .map(Task.now) + .getOrElse(Task.raiseError(new NoSuchElementException("no frames on the stack"))) + eval2 <- client.evaluate(topFrame2.id, "foo") + _ <- client.continue(stopped.threadId) + _ <- client.exited + _ <- client.terminated + _ <- Task.fromFuture(client.closedPromise.future) + } yield { + assert(client.socket.isClosed) + assertNoDiff(eval1.result, "1") + assertNoDiff(eval1.`type`, "int") + assertNoDiff(eval2.result, "2") + assertNoDiff(eval2.`type`, "int") + } + } + } + } + } + private def startRemoteProcess(buildProject: Project, testState: TestState): Task[Int] = { val attachPort = Promise[Int]() @@ -1017,11 +1121,13 @@ object DebugServerSpec extends DebugBspBaseSuite { override def modules: Seq[Module] = Seq.empty override def libraries: Seq[Library] = Seq.empty override def unmanagedEntries: Seq[UnmanagedEntry] = Seq.empty + override val classesToUpdate = Observable.empty[Seq[String]] override def javaRuntime: Option[JavaRuntime] = None def name: String = "MockRunner" def run(listener: DebuggeeListener): CancelableFuture[Unit] = { DapCancellableFuture.runAsync(task.map(_ => ()), defaultScheduler) } + override def compile(): Unit = () def scalaVersion: ScalaVersion = ScalaVersion("2.12.17") } diff --git a/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala b/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala index 158ac70f81..58ff50199e 100644 --- a/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala +++ b/frontend/src/test/scala/bloop/dap/DebugTestEndpoints.scala @@ -4,12 +4,7 @@ import bloop.dap.DebugTestProtocol._ import com.microsoft.java.debug.core.protocol.Events import com.microsoft.java.debug.core.protocol.Requests._ -import com.microsoft.java.debug.core.protocol.Responses.ContinueResponseBody -import com.microsoft.java.debug.core.protocol.Responses.EvaluateResponseBody -import com.microsoft.java.debug.core.protocol.Responses.ScopesResponseBody -import com.microsoft.java.debug.core.protocol.Responses.SetBreakpointsResponseBody -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._ import com.microsoft.java.debug.core.protocol.Types private[dap] object DebugTestEndpoints { @@ -24,6 +19,9 @@ private[dap] object DebugTestEndpoints { val Variables = new Request[VariablesArguments, VariablesResponseBody]("variables") val Evaluate = new Request[EvaluateArguments, EvaluateResponseBody]("evaluate") val Continue = new Request[ContinueArguments, ContinueResponseBody]("continue") + val RedefineClasses = + new Request[RedefineClassesArguments, RedefineClassesResponse]("redefineClasses") + val StepIn = new Request[StepInArguments, Unit]("stepIn") val ConfigurationDone = new Request[Unit, Unit]("configurationDone") val Exited = new Event[Events.ExitedEvent]("exited") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 404a86e6d2..933f945d9c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -45,7 +45,7 @@ object Dependencies { val asmVersion = "9.5" val snailgunVersion = "0.4.0" val ztExecVersion = "1.12" - val debugAdapterVersion = "3.1.4" + val debugAdapterVersion = "3.2.0-SNAPSHOT" val bloopConfigVersion = "1.5.5" val semanticdbVersion = "4.7.8" val zinc = "org.scala-sbt" %% "zinc" % zincVersion @@ -60,7 +60,7 @@ object Dependencies { val scalazCore = "org.scalaz" %% "scalaz-core" % scalazVersion val coursierInterface = "io.get-coursier" % "interface" % "1.0.18" val coursierInterfaceSubs = "io.get-coursier" % "interface-svm-subs" % "1.0.18" - val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.4.2" + val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0" val shapeless = "com.chuusai" %% "shapeless" % shapelessVersion val caseApp = "com.github.alexarchambault" %% "case-app" % caseAppVersion val sourcecode = "com.lihaoyi" %% "sourcecode" % sourcecodeVersion