第二十五章. Scala 插件

Chapter 25. The Scala Plugin

Scala 插件扩展了 Java 插件,以添加对 Scala 项目的支持。它可以处理 Scala 代码,以及混合的 Scala 和 Java 代码,甚至纯 Java 代码(尽管我们不一定建议将其用于后者)。该插件支持联合编译,它允许自由地混合和匹配 Scala 和 Java 代码以及各自的依赖。例如,一个 Scala 类可以扩展一个 Java 类,而这个 Java 类也可以扩展自一个 Scala 类。这样一来,我们就能够在项目中使用最适合的语言,并在需要时用其他语言重写其中的任何类。
The Scala plugin extends the Java plugin to add support for Scala projects. It can deal with Scala code, mixed Scala and Java code, and even pure Java code (although we don't necessarily recommend to use it for the latter). The plugin supports joint compilation, which allows to freely mix and match Scala and Java code, with dependencies in both directions. For example, a Scala class can extend a Java class that in turn extends a Scala class. This makes it possible to use the best language for the job, and to rewrite any class in the other language if needed.

25.1. 用法

25.1. Usage

要使用 Scala 插件,请在构建脚本中包含以下语句:
To use the Scala plugin, include in your build script:

示例 25.1. 使用 Scala 插件 - Example 25.1. Using the Scala plugin

build.gradle

apply plugin: 'scala'

25.2. 任务

25.2. Tasks

Scala 的插件向项目中添加了以下任务。
The Scala plugin adds the following tasks to the project.

表 25.1. Scala 插件——任务 - Table 25.1. Scala plugin - tasks

任务名称
Task name
依赖于
Depends on
类型
Type
描述
Description
compileScala CompileJava ScalaCompile 编译 Scala 的生产源文件。
Compiles production Scala source files.
compileTestScala compileTestJava ScalaCompile 编译 Scala 的测试源文件。
Compiles test Scala source files.
compileSourceSetScala compileSourceSetJava ScalaCompile 编译给定源集里的 Scala 源文件。
Compiles the given source set's Scala source files.
scaladoc - ScalaDoc 为 Scala 生产源文件生成 API 文档。
Generates API documentation for the production Scala source files.

Scala 插件向 Java 插件所加入的任务添加了以下的依赖。
The Scala plugin adds the following dependencies to tasks added by the Java plugin.

表 25.2. Scala 插件——额外的任务依赖 - Table 25.2. Scala plugin - additional task dependencies

任务名称
Task name
依赖于
Depends on
classes compileScala
testClasses compileTestScala
sourceSetClasses compileSourceSetScala

图 25.1. Scala 插件——任务 - Figure 25.1. Scala plugin - tasks

Scala 插件——任务

25.3. 项目布局

25.3. Project layout

Scala 插件假定项目的布局如下所示。所有的 Scala 源码目录都可以包含 Scala Java 代码。 Java 源码目录可能只包含 Java 源代码。 这些目录都不需要一定存在或者包含有内容。Scala 插件只会简单地进行编译,而不管它发现什么。
The Scala plugin assumes the project layout shown below. All the Scala source directories can contain Scala and Java code. The Java source directories may only contain Java source code. None of these directories need to exist or have anything in them; the Scala plugin will simply compile whatever it finds.

表 25.3. Scala 插件——项目布局 - Table 25.3. Scala plugin - project layout

目录
Directory
意义
Meaning
src/main/java Java 生产源代码
Production Java source
src/main/resources 生产资源
Production resources
src/main/scala Scala 生产源代码。也可能包含联合编译的 Java 源代码。
Production Scala sources. May also contain Java sources for joint compilation.
src/test/java Java 测试源代码
Test Java source
src/test/resources 测试资源
Test resources
src/test/scala Scala 测试源代码。也可能包含联合编译的 Java 源代码。
Test Scala sources. May also contain Java sources for joint compilation.
src/sourceSet/java 给定源集的 Java 源代码
Java source for the given source set
src/sourceSet/resources 给定源集的资源
Resources for the given source set
src/sourceSet/scala 给定源集的 Scala 源代码。也可能包含联合编译的 Java 源代码。
Scala sources for the given source set. May also contain Java sources for joint compilation.

25.3.1. 更改项目布局

25.3.1. Changing the project layout

和 Java 插件一样,Scala 插件允许为 Scala 的生产和测试的源文件配置自定义位置。
Just like the Java plugin, the Scala plugin allows to configure custom locations for Scala production and test sources.

示例 25.2. 自定义 Scala 源文件布局 - Example 25.2. Custom Scala source layout

build.gradle

sourceSets {
    main {
        scala {
            srcDirs = ['src/scala']
        }
    }
    test {
        scala {
            srcDirs = ['test/scala']
        }
    }
}

25.4. 依赖管理

25.4. Dependency management

Scala项目需要声明一个 scala-library 依赖。这个依赖将被用于编译和运行时的类路径。它也将分别用于获取 Scala 编译器和 Scaladoc 工具。 [12]
Scala projects need to declare a scala-library dependency. This dependency will then be used on compile and runtime class paths. It will also be used to get hold of the Scala compiler and Scaladoc tool, respectively. [12]

如果 Scala 用于生产代码, scala-library 依赖应该添加到 compile 的配置中:
If Scala is used for production code, the scala-library dependency should be added to the compile configuration:

示例 25.3. 为生产代码定义一个 Scala 依赖 - Example 25.3. Declaring a Scala dependency for production code

build.gradle

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.11.1'
}

如果 Scala 仅用于测试代码,scala-library 依赖应被添加到 testCompile 配置中:
If Scala is only used for test code, the scala-library dependency should be added to the testCompile configuration:

示例 25.4. 为测试代码声明一个 Scala 依赖 - Example 25.4. Declaring a Scala dependency for test code

build.gradle

dependencies {
    testCompile "org.scala-lang:scala-library:2.11.1"
}

25.5. scalaClasspath 的自动配置

25.5. Automatic configuration of scalaClasspath

ScalaCompileScalaDoc 任务以两种方式使用 Scala:在它们的 classpath 以及它们的 scalaClasspath 上。前者用于定位源码所引用的类,并且通常包含 scala-library 库以及其他库。后者用于分别加载和执行 Scala 编译器和 Scaladoc 工具,并且应该只包含 scala-compiler 库及其依赖。
ScalaCompile and ScalaDoc tasks consume Scala in two ways: on their classpath, and on their scalaClasspath. The former is used to locate classes referenced by the source code, and will typically contain scala-library along with other libraries. The latter is used to load and execute the Scala compiler and Scaladoc tool, respectively, and should only contain the scala-compiler library and its dependencies.

除非显式地配置一个任务的 scalaClasspath,否则 Scala(基础)插件会尝试以如下方式从任务的 classpath 推断出它:
Unless a task's scalaClasspath is configured explicitly, the Scala (base) plugin will try to infer it from the task's classpath. This is done as follows:

  • 如果在 classpath 中找到 scala-library Jar ,并且该项目已经在至少一个仓库中声明了它,那么相应的 scala-compiler 的仓库依赖将添加到 scalaClasspath 中。
    If a scala-library Jar is found on classpath, and the project has at least one repository declared, a corresponding scala-compiler repository dependency will be added to scalaClasspath.
  • 否则,该任务将执行失败,并提示无法推断 scalaClasspath
    Otherwise, execution of the task will fail with a message saying that scalaClasspath could not be inferred.

25.6. 约定属性

25.6. Convention properties

Scala 插件不会向项目添加任何的约定属性。
The Scala plugin does not add any convention properties to the project.

27.5. 源集属性

25.7. Source set properties

Scala 插件向项目中的每个源集添加了以下约定属性。你可以在构建脚本中把它们当成是源集对象的属性一样去使用(请参阅《第 21.3 节,“公约”》)。
The Scala plugin adds the following convention properties to each source set in the project. You can use these properties in your build script as though they were properties of the source set object (see Section 21.3, “Conventions”).

表 25.4. Scala 插件——源集属性 - Table 25.4. Scala plugin - source set properties

属性名称
Property name
类型
Type
默认值
Default value
描述
Description
scala SourceDirectorySet (只读)
SourceDirectorySet (read-only)
不为 null
Not null
这个源码集的 Scala 源文件。包含所有在 Scala 源目录中找到的 .scala .java 文件,并排除所有其他类型的文件。
The Scala source files of this source set. Contains all .scala and .java files found in the Scala source directories, and excludes all other types of files.
scala.srcDirs Set<File>。可以使用《第 16.5 节,“指定一组输入文件”》中所讲到的任何一种方法来设置。
Set<File>. Can set using anything described in Section 16.5, “Specifying a set of input files”.
[projectDir/src/name/scala] 该源目录包含此源集的 Scala 源文件。可能还包含用于联合编译的 Java 源文件。
The source directories containing the Scala source files of this source set. May also contain Java source files for joint compilation.
allScala FileTree(只读)
FileTree (read-only)
不为 null
Not null
此源码集的所有 Scala 源文件。仅包含在 Scala 源目录中找到的 .scala 文件。
All Scala source files of this source set. Contains only the .scala files found in the Scala source directories.

这些属性由一个 ScalaSourceSet 类型的约定对象提供。
These convention properties are provided by a convention object of type ScalaSourceSet.

Scala 插件还修改了一些源集的属性:
The Scala plugin also modifies some source set properties:

表 25.5. Scala 插件——源集属性 - Table 25.5. Scala plugin - source set properties

属性名称
Property name
修改的内容
Change
allJava 添加在 Scala 源目录中找到的所有 .java 文件。
Adds all .java files found in the Scala source directories.
allSource 添加在 Scala 的源目录中找到的所有源文件。
Adds all source files found in the Scala source directories.

25.8. Fast Scala Compiler

25.8. Fast Scala Compiler

Scala 插件包含了对 fsc,即 Fast Scala Compiler 的支持。fsc 在单独的守护进程中运行,可以显著地提高编译速度。
The Scala plugin includes support for fsc, the Fast Scala Compiler. fsc runs in a separate daemon process and can speed up compilation significantly.

示例 25.5. 启用 Fast Scala Compiler - Example 25.5. Enabling the Fast Scala Compiler

build.gradle

compileScala {
    scalaCompileOptions.useCompileDaemon = true

    // optionally specify host and port of the daemon:
    scalaCompileOptions.daemonServer = "localhost:4243"
}


注意,每当 fsc 的编译类路径的内容有变化时,它都需要重新启动。(它检测到编译类路径本身的变化。)这使得它不太适合多项目构建。

Note that fsc expects to be restarted whenever the contents of its compile class path change. (It does detect changes to the compile class path itself.) This makes it less suitable for multi-project builds.

25.9. 在外部进程中编译

25.9. Compiling in external process

scalaCompileOptions.fork 被设为 true 的时候,汇编将在外部进程中进行。fork 的详细情况取决于所使用的编译器。基于 Ant 的编译器(scalaCompileOptions.useAnt = true)将为每一个 ScalaCompile 任务 fork 一个新的进程,并且默认情况下不 fork。基于 Zinc 的编译器(scalaCompileOptions.useAnt = false)则是使用 Gradle 编译器守护进程,并且默认情况下也是这样。
When scalaCompileOptions.fork is set to true, compilation will take place in an external process. The details of forking depend on which compiler is used. The Ant based compiler (scalaCompileOptions.useAnt = true) will fork a new process for every ScalaCompile task, and does not fork by default. The Zinc based compiler (scalaCompileOptions.useAnt = false) will leverage the Gradle compiler daemon, and does so by default.

外部进程默认使用 JVM 的默认内存设置。如果要调整内存设置,请根据需要配置 scalaCompileOptions.forkOptions
Memory settings for the external process default to the JVM's defaults. To adjust memory settings, configure scalaCompileOptions.forkOptions as needed:

示例 25.6. 调整内存设置 - Example 25.6. Adjusting memory settings

build.gradle

tasks.withType(ScalaCompile) {
    configure(scalaCompileOptions.forkOptions) {
        memoryMaximumSize = '1g'
        jvmArgs = ['-XX:MaxPermSize=512m']
    }
}


25.10. 增量编译

25.10. Incremental compilation

通过仅编译源代码自上次编译后发生更改的类以及受这些更改影响的类,增量编译可以显著减少 Scala 的编译时间。它对于频繁编译小量增量代码时特别有效,而我们开发时经常要这样做。
By compiling only classes whose source code has changed since the previous compilation, and classes affected by these changes, incremental compilation can significantly reduce Scala compilation time. It is particularly effective when frequently compiling small code increments, as is often done at development time.

Scala 插件现在支持通过集成 Zinc 的增量编译,它是 sbt 的增量 Scala 编译器的一个单独的版本。如果要把 ScalaCompile 任务从默认的基于 Ant 的编译器切换到新的基于 Zinc 的编译器,需要把 scalaCompileOptions.useAnt 设置为 false
The Scala plugin now supports incremental compilation by integrating with Zinc, a standalone version of sbt's incremental Scala compiler. To switch the ScalaCompile task from the default Ant based compiler to the new Zinc based compiler, set scalaCompileOptions.useAnt to false:

示例 25.7. 激活基于 Zinc 的编译器 - Example 25.7. Activating the Zinc based compiler

build.gradle

tasks.withType(ScalaCompile) {
    scalaCompileOptions.useAnt = false
}


除非在 API 文档中另有说明,否则基于 Zinc 的编译器支持与基于 Ant 的编译器完全相同的配置选项。但是请注意,Zinc 编译器需要 Java 6 或更高的版本。这意味着 Gradle 本身也必须使用 Java 6 或更高版本来运行。
Except where noted in theAPI documentation, the Zinc based compiler supports exactly the same configuration options as the Ant based compiler. Note, however, that the Zinc compiler requires Java 6 or higher to run. This means that Gradle itself has to be run with Java 6 or higher.

Scala 插件添加了一个名为 Zinc 的配置,以解析 Zinc 库及其依赖。如果要覆盖 Gradle 默认使用的 Zinc 版本,请添加一个显式的 Zinc 依赖(例如 zinc "com.typesafe.zinc:zinc:0.1.4")。无论使用 Zinc 的哪一个版本,Zinc 总是会使用在 scalaTools配置上找到的 Scala 编译器。
The Scala plugin adds a configuration named zinc to resolve the Zinc library and its dependencies. To override the Zinc version that Gradle uses by default, add an explicit Zinc dependency (for example zinc "com.typesafe.zinc:zinc:0.1.4"). Regardless of which Zinc version is used, Zinc will always use the Scala compiler found on the scalaTools configuration.

就像 Gradle 上基于 Ant 的编译器一样,基于 Zinc 的编译器支持 Java 和 Scala 代码的联合编译。默认情况下,所有在 src/main/scala 的 Java 和 Scala 代码都将会进行联合编译。使用基于 Zinc 的编译器时,对于 Java 代码也会进行增量编译。
Just like Gradle's Ant based compiler, the Zinc based compiler supports joint compilation of Java and Scala code. By default, all Java and Scala code under src/main/scala will participate in joint compilation. With the Zinc based compiler, even Java code will be compiled incrementally.

增量编译需要对源代码进行依赖性分析,这个分析的结果存于由 scalaCompileOptions.incrementalOptions.analysisFile 所指定的文件中(它有一个合理的默认值)。在多项目构建中,分析文件被传给下游的 ScalaCompile 任务,以启用跨项目的增量编译。对于由 Scala 插件添加的 ScalaCompile 任务,无需对这一点进行配置。而如果是其他的 ScalaCompile 任务,需要根据类文件夹或 Jar 档案中,是哪一个的代码被传给下游的 ScalaCompile 任务的类路径,把 scalaCompileOptions.incrementalOptions.publishedCode 配置为指向它们。请注意,如果 publishedCode 设置不正确,上游代码发生变化时下游任务可能不会重新编译,从而导致编译结果不正确。
Incremental compilation requires dependency analysis of the source code. The results of this analysis are stored in the file designated by scalaCompileOptions.incrementalOptions.analysisFile (which has a sensible default). In a multi-project build, analysis files are passed on to downstream ScalaCompile tasks to enable incremental compilation across project boundaries. For ScalaCompile tasks added by the Scala plugin, no configuration is necessary to make this work. For other ScalaCompile tasks, scalaCompileOptions.incrementalOptions.publishedCode needs to be configured to point to the classes folder or Jar archive by which the code is passed on to compile class paths of downstream ScalaCompile tasks. Note that if publishedCode is not set correctly, downstream tasks may not recompile code affected by upstream changes, leading to incorrect compilation results.

由于依赖分析的开销,进行清理编译或者是在较大的代码更改之后进行编译可能耗时比基于 Ant 的编译器要更长。对于 CI 构建和发布构建,我们目前推荐使用基于 Ant 的编译器。
Due to the overhead of dependency analysis, a clean compilation or a compilation after a larger code change may take longer than with the Ant based compiler. For CI builds and release builds, we currently recommend to use the Ant based compiler.

注意,现在 Zinc 基于 Nailgun 的守护进程模式还不支持。相反,我们计划增强 Gradle 自己的编译器守护进程,以便在跨 Gradle 调用时继续保活​​,重用相同的 Scala 编译器。预计这将为 Scala 的编译带来另一个方面上的明显提速。
Note that Zinc's Nailgun based daemon mode is not supported. Instead, we plan to enhance Gradle's own compiler daemon to stay alive across Gradle invocations, reusing the same Scala compiler. This is expected to yield another significant speedup for Scala compilation.

25.11. Eclipse 集成

25.11. Eclipse Integration

当 Eclipse 插件遇到 Scala 项目时,它会添加额外的配置,以使该项目能够与 Scala IDE 直接配合使用。具体而言,该插件添加了一个 Scala 特性和依赖容器。
When the Eclipse plugin encounters a Scala project, it adds additional configuration to make the project work with Scala IDE out of the box. Specifically, the plugin adds a Scala nature and dependency container.

25.12. IntelliJ IDEA 集成

25.12. IntelliJ IDEA Integration

当 IDEA 插件遇到 Scala 项目时,它会添加额外的配置以使该项目能够与 IDEA 直接配合使用。具体来说,该插件添加了一个 Scala facet 和一个与项目类路径上的 Scala 版本相匹配 Scala 编译器库。
When the IDEA plugin encounters a Scala project, it adds additional configuration to make the project work with IDEA out of the box. Specifically, the plugin adds a Scala facet and a Scala compiler library that matches the Scala version on the project's class path.