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_library
whosesources
param is the*.java
files; one of its dependencies should be... - a
scala_library
whosesources
param is the*.scala
files and whosejava_sources
is 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