Scala Support
Examples
The sample code examples/src/scala/org/pantsbuild/example/hello/welcome/ shows how to define a library of Scala code.
Its BUILD file looks like that for a Java library, but contains a
scala_library target with .scala sources:
scala_library( dependencies=[ 'examples/src/java/org/pantsbuild/example/hello/greet:greet', 'examples/src/resources/org/pantsbuild/example/hello', ], provides = scala_artifact( org='org.pantsbuild.example.hello', name='welcome', repo=public, ), )
There's a sample test in
examples/tests/scala/org/pantsbuild/example/hello/welcome.
It's a junit_tests with .scala sources.
junit_tests( dependencies=[ 'examples/src/scala/org/pantsbuild/example/hello/welcome:welcome', '3rdparty:junit', '3rdparty:scalatest', ], )
Scala/Java Circular Dependencies
Scala code and Java code can depend on each other. As long as the dependencies aren't circular,
scala_library targets can depend on java_library targets and vice versa. If the dependencies
are circular, you can set up targets to compile all of this code together. Assuming your *.java
and *scala files are in separate directories, you can have:
- a
java_librarywhosesourcesparam is the*.javafiles; one of its dependencies should be... - a
scala_librarywhosesourcesparam is the*.scalafiles and whosejava_sourcesis the abovejava_library.
Do not put the java_library in the scala_library's dependencies or Pants will error out in its
circular dependencies check. Instead, put the java_library in java_sources to work around this
check.
The scala_with_java_sources
example shows how this works:
scala_library( sources = ['Greet.scala'], java_sources=[ 'examples/src/java/org/pantsbuild/example/java_sources', ], )
The referred-to
java_sources
java_library has this java_library in its dependencies:
java_library( dependencies = [ 'examples/src/scala/org/pantsbuild/example/scala_with_java_sources', ], )
(If your circularly-referencing *.scala and *.java files are in the same directory, you don't
need separate java_library and scala_library targets. Instead, use
scala_library(sources=['*.scala', '*.java'],...).)
Scala Version
You can override the default version of the entire Scala toolchain with the single
--scala-version option. You can set that option to one of the supported Scala versions
(currently "2.10" or "2.11"), or to the special value "custom".
If you choose a custom version, you must use the --scala-runtime-spec,
--scala-repl-spec and --scala-suffix-version options to provide
information about your custom Scala version. The first two of these default to the target
addresses //:scala-library and //:scala-repl respectively, so you can simply define those
targets (in the root BUILD.tools file by convention) to point to the relevant JARs.
Scala 3rdparty Jars
You can depend on Scala-specific 3rdparty jars using the scala_jar symbol. This will append e.g. _2.12 to the name field of the jar. The version string used is whatever the --scala-version is set to, or, if it is "custom", then the --scala-suffix-version.
Scala REPL
To bring up Scala's interactive console, use Pants'
repl goal.
In the resulting console, you can import any Scala or Java code from the Pants invocation's
targets and their dependencies.
$ ./pants repl examples/src/scala/org/pantsbuild/example/hello/welcome
...much build output...
15:08:13 00:11 [resources]
15:08:13 00:11 [prepare]
Invalidated 1 target containing 1 payload file.
15:08:13 00:11 [repl]
15:08:13 00:11 [python-repl]
15:08:13 00:11 [scala-repl]
15:08:13 00:11 [bootstrap-scala-repl]
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_60).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import org.pantsbuild.example.hello.welcome
import org.pantsbuild.example.hello.welcome
scala> val folks = List("Abel", "Baker", "Charlie", "Delta")
folks: List[java.lang.String] = List(Abel, Baker, Charlie, Delta)
scala> org.pantsbuild.example.hello.welcome.WelcomeEverybody(folks)
res0: Seq[String] = List(Hello, Abel!, Hello, Baker!, Hello, Charlie!, Hello, Delta!)
scala> exit
warning: there were 1 deprecation warnings; re-run with -deprecation for details
Waiting for background workers to finish.
SUCCESS
$
Testing
Scala tests are run using the junit_tests BUILD target. Both Junit and ScalaTest tests are
supported by default. Most other scala test frameworks support running with JUnit via a base
class/trait or via a @RunWith annotation; so you can use
junit_tests for your scala tests as well.
Scala Code Coverage: Scoverage
Code coverage reports for Scala projects can be generated with the help of Scoverage. To generate a Scoverage report, run the test command with the following additional option(s):
./pants --scoverage-enable-scoverage test ${TARGETS}
As an example, scoverage reports for the example project can be generated as:
./pants --scoverage-enable-scoverage test examples/tests/scala/org/pantsbuild/example/hello/welcome
Scoverage supports the following options:
--scoverage-enable-scoverage (required)
Specifies whether to generate scoverage reports for scala test targets.
Default value is False. If True, implies --test-junit-coverage-processor=scoverage.
--scoverage-report-target-filters (optional)
Regex patterns passed to scoverage report generator, specifying which targets should be
included in reports. All targets matching any of the patterns will be
included when generating reports. If no targets are specified, all
targets are included, which would be the same as specifying ".*" as a filter.
--scoverage-blacklist-targets (optional; troubleshooting)
Scoverage works by instrumenting targets at compile time. However, some targets cannot be
instrumented at all as doing so results in exceeding JVM code size limits. Thus, if you receive
the following error when compiling with scoverage:
Could not write class ${CLASS NAME} because it exceeds JVM code size limits. Method ${METHOD NAME} code too large! ... much build output ... FAILURE: Compilation failure: Failed jobs: compile(${FAILED TARGET NAME})
You can prevent instrumenting the failed target by running the command as follows:
./pants --scoverage-enable-scoverage --scoverage-blacklist-targets='["${FAILED TARGET NAME}"]' ${TEST TARGET}
Formatting and Linting
scalafmt and scalafix are installed by default in both the fmt and lint goals, but
"semantic" scalafix rewrites are disabled by default since most deployments will prefer that the
lint goal run quickly. Semantic rewrites introduce a dependency on compilation, and require an
additional compiler plugin.
Usage
To run a particular scalafix rule on targets, pass:
./pants fmt.scalafix --rules=ProcedureSyntax ${TARGETS}
Enabling semantic rewrites
Enabling scalafix semantic rewrites involves using a compiler plugin, and passing the
--semantic flag to the scalafix task.
In a BUILD file at the root of the repo, define the semanticdb compiler plugin:
SCALA_REV=2.11.12 jar_library( name = 'scalac-plugin-dep', jars = [jar(org='org.scalameta', name='semanticdb-scalac_{}'.format(SCALA_REV), rev='2.0.1')], )
Note that the explicit full scala version string (2.11.12) is required for the semanticdb jar we use here, which is why we can't just use scala_jar (which would just append e.g. _2.12 to the name).
Then, reference it from pants.toml to load the plugin, and enable semantic rewrites to require
compilation for fmt and lint:
[compile.rsc] args = [ # The `-S` prefix here indicates that zinc should pass this option to scalac rather than # to javac (`-C` prefix). '-S-Yrangepos', ] [scala] scalac_plugins = [ 'semanticdb', ] [lint.scalafix] semantic = true [fmt.scalafix] semantic = true