I recently switched a Scala project’s build from SBT to Gradle and was disappointed to find that Gradle has no support for launching the Scala REPL.
My workaround:
- Let Gradle compile and output the classpath
- Launch the REPL separately
At first this felt like a kludge around a deficiency in Gradle, but now I think this approach may actually make more sense than SBT’s conflation of build tool and REPL. The build tool doesn’t need to be responsible for everything.
Launching the REPL from the scala-compiler jar
Normally you’d launch the REPL using the scala
script provided by the
standard Scala installation. But I’d prefer to let Gradle download the
appropriate version of Scala for the project rather than requiring
developers to install it themselves. Gradle can help us with this because
the artifact org.scala-lang:scala-compiler
in the Maven Central repo
contains the Scala REPL.
The main
method that launches the REPL belongs to a class with the
(rather non-obvious) name scala.tools.nsc.MainGenericRunner
.
Thus we need to run
java -Dscala.usejavacp=true \
-classpath "$CLASSPATH" \
scala.tools.nsc.MainGenericRunner
where $CLASSPATH
includes the scala-compiler
jar.
Fetching the jar with Gradle
To let Gradle provide the scala-compiler artifact for us, add it
as a classpath
dependency to buildscript
.
buildscript {
dependencies {
classpath "org.scala-lang:scala-compiler:${scalaVersion}"
}
repositories {
mavenCentral()
}
}
Then add a function to look up the filesystem path of this artifact, which we’ll use later when assembling the full classpath for the REPL session.
def scalaPath = buildscript.configurations.classpath.find {
it.name == "scala-compiler-${scalaVersion}.jar"
}
Generating the classpath
Getting the classpath for your project and its dependencies in Gradle is pretty simple.
def classpath = sourceSets.main.runtimeClasspath.asPath
Combined with the path of the Scala compiler, the result is the full classpath that we’ll use for launching the REPL.
task printClasspath << {
println "${scalaPath}:${classpath}"
}
With this task defined, call gradle printClasspath --quiet
to set the
classpath in the startup script.
java -Dscala.usejavacp=true \
-classpath "$(gradle printClasspath --quiet)" \
scala.tools.nsc.MainGenericRunner
Initial REPL commands
SBT has a useful setting that lets you specify Scala expressions that run automatically when the REPL starts. This tends to save you the trouble of repeating a lot of imports every time you start a session.
initialCommands in console := "..."
You can accomplish this using the -i
option on the Scala REPL, which
loads commands from a file. In this example, the file containing initial
commands is named repl.scala
.
java -Dscala.usejavacp=true \
-classpath "$(gradle printClasspath --quiet)" \
scala.tools.nsc.MainGenericRunner \
-i repl.scala
Full example
build.gradle
project(':repl') {
def scalaVersion = '2.11.7'
// Dependencies on any other projects that should be
// accessible from the REPL context.
dependencies {
compile project(':example_project_1')
compile project(':example_project_2')
}
// Require the scala-compiler jar
buildscript {
dependencies {
classpath "org.scala-lang:scala-compiler:${scalaVersion}"
}
repositories {
mavenCentral()
}
}
// The path of the scala-compiler jar
def scalaPath = buildscript.configurations.classpath.find {
it.name == "scala-compiler-${scalaVersion}.jar"
}
// The classpath of this project and its dependencies
def classpath = sourceSets.main.runtimeClasspath.asPath
// Prints the classpath needed to launch the REPL
task printClasspath << {
println "${scalaPath}:${classpath}"
}
}
repl.sh
#!/bin/bash
gradle :repl:compileScala && \
java -Dscala.usejavacp=true \
-classpath "$(gradle :repl:printClasspath --quiet)" \
scala.tools.nsc.MainGenericRunner \
-i repl.scala
repl.scala
myproject.repl.init()
import myproject.repl._