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 whose sources param is the *.java files; one of its dependencies should be...
  • a scala_library whose sources param is the *.scala files and whose java_sources is the above java_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
Generated by publish_docs from dist/markdown/html/examples/src/scala/org/pantsbuild/example/README.html 2022-12-03T01:09:00.356460