Gradle的Scala插件扩展了Java插件,增加了对Scala工程的支持。可以处理纯Scala代码,Scala和Java代码,甚至纯java代码(并不推荐这种使用方式)。此插件支持联合编译,允许开发者自由的混编Scala和Java,并支持两者的依赖管理。例如:一个Scala类可以扩展一个Java类,而这个Java类本身也扩展自另一个Scala类。这使得开发者在工作中可以自由选择更合适的语言,也可以任意重写其他语言的类。
1. 使用
使用Scala插件需要在build脚本中增加下面这行:
apply plugin: 'scala'
2. 任务
Scala插件会给工程添加下面的任务
名称 | 依赖 | 类型 | 描述 |
---|---|---|---|
compileScala | compileJava | ScalaCompile | 编译Scala生产源码 |
compileTestScala | compileTestJava | ScalaCompile | 编译Scala测试源码 |
compileSourceSetScala | compileSourceSetJava | ScalaCompile | 编译指定Scala源码 |
scaladoc | - | ScalaDoc | 为Scala生产源码生成API文档 |
同时Scala插件将向Java插件的任务中添加如下依赖:
名称 | 依赖 |
---|---|
classes | compileScala |
testClasses | compileTestScala |
sourceSetClasses | compileSourceSetScala |
3. 项目布局
Scala插件的假定项目布局如下。其中所有Scala代码目录都可以包含Scala和Java代码。但Java代码目录只能含有Java源码。这些目录并不必须存在,也不需要包含任何内容;Scala插件会自动编译它自己能找到的东西。
目录 | 含义 |
---|---|
src/main/java | Java 生产代码 |
src/main/resources | 生产资源文件 |
src/test/java | Java 测试代码 |
src/test/resources | 测试资源文件 |
src/test/scala | Scala测试资源文件。同时允许包含Java资源 |
src/sourceSet/java | 指定source set专用的Java资源文件 |
src/sourceSet/resources | 指定source set专用的资源文件 |
src/sourceSet/Scala | 指定source set专用的Scala资源文件,允许包含Java资源 |
3.1 修改项目布局
和Java插件一样,Scala插件也允许你自定义布局:
sourceSets {
main {
scala {
srcDirs = ['src/scala']
}
}
test {
scala {
srcDirs = ['test/scala']
}
}
}
4. 依赖管理
Scala工程需要声明scala-library依赖项。它不仅用于编译期和运行期的class path,还用于获取Scala编译器和ScalaDoc工具。
如果Scala用于生产代码,那么scala-library依赖必须被加入到compile配置:
repositories {
mavenCentral()
}
dependencies {
compile 'org.scala-lang:scala-library:2.11.8'
testCompile 'org.scalatest:scalatest_2.11:3.0.0'
testCompile 'junit:junit:4.12'
}
如果Scala仅用于测试代码,那么它需要被加入到testCompile配置:
dependencies {
testCompile "org.scala-lang:scala-library:2.11.1"
}
5. scalaClasspath的自动配置
ScalaCompile和ScalaDoc任务会从两种途径来消费Scala代码:从它们的classpath及它们的scalaClasspath。前者用于定位由源代码引用的类,通常包含scala-library以及其他库。后者用于加载和执行Scala编译器和Scala工具,应该只包含Scalascala-compiler库及其依赖项。
除非明确地配置scalaClasspath,否则Scala插件将尝试从任务的classpath中推断它。具体如下:
- 如果在classpath中找到了scala-library,而且工程至少含有一个仓库声明,那么scala-compiler仓库会被加入到scalaClasspath
- 否则,执行任务时会失败,并提示无法推断scalaClasspath
6. 设置Zinc编译器
Scala插件使用 "zinc" 配置项来配置Zinc编译器及其依赖。gradle提供了Zinc的默认版本,如果需要指定特定版本,需要在配置中写明版本号,如 "com.typesafe.zinc:zinc:0.3.6"。gradle支持0.3.0及以上版本。但是由于Zinc编译器回退的原因,0.3.2到0.3.5.2之间的版本无法使用。
dependencies {
zinc 'com.typesafe.zinc:zinc:0.3.9'
}
声明scala-library时需要格外小心它与Zinc编译器之间的兼容关系。一般来说gradle会为你添加一个兼容的scala-library,但是一个更大的依赖解析规则可能会强制导入一个不兼容的版本。
比如,使用 configurations.all 强制指定一个 scala-library 版本的同时也会修改Zinc compiler版本:
configurations.all {
resolutionStrategy.force "org.scala-lang:scala-library:2.11.7"
}
当需要排查错误时,你可以通过运行 dependencyInsight 来检查Zinc编译器实际版本。
7. 约定属性
Scala插件不会给工程添加任何新的约定属性。(Java插件会添加好几个)
8. Source set 属性
Scala插件会向Source set添加下列属性。你可以在build脚本中使用这些属性,就像使用source set对象自身的属性一样。
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
scala | SourceDirectorySet (read-only) | Not null | 本source set中Scala的代码文件。 包含Scala代码目录中的所有.scala和.java文件,过滤了所有其他文件类型 |
scala.srcDirs | Set<File> | [projectDir/src/name/scala] | 本source set中包含Scala代码文件的目录。也包括Java代码 |
allScala | FileTree (read-only) | Not null | 本source set中所有Scala代码文件。 只包含在Scala代码目录中找到的 .scala文件 |
这些约定的属性由类型为ScalaSourceSet的约定对象提供。
同时Scala插件也会修改一些已有的source set属性:
名称 | 修改 |
---|---|
allJava | 新加入了所有Scala代码目录中找到的.java文件 |
allSource | 新加入了所有Scala代码目录中的文件 |
9. 外部编译
Scala在一个外部的进程中编译。
外部进程的内存设置为JVM的默认值。可以通过修改 scalaCompileOptions.forkOptions 来根据需要调整内存:
tasks.withType(ScalaCompile) {
configure(scalaCompileOptions.forkOptions) {
memoryMaximumSize = '1g'
jvmArgs = ['-XX:MaxPermSize=512m']
}
}
10. 增量编译
增量编译指仅重新编译那些上次编译后改动的相关类,可以显著缩短编译时间。当项目开发阶段需要频繁的编译一些小的改动时,这项技术十分有用。
Scala插件默认集成Zinc(一个独立的sbt增量Scala编译器)来实现增量编译。(Zinc好像迁移到sbt下了, 老Zinc只维护,无新功能)。可以将force设置为true来禁用增量编译。
tasks.withType(ScalaCompile) {
scalaCompileOptions.with {
force = true
}
}
注意:至少有一个代码文件改变后,全量编译才会起作用。如果没有任何代码改变,compileScala任务将视为UP-TO-DATE。
Zinc编译器支持Java和Scala的混编。默认情况下,src/main/scala 目录下的Java和Scala代码将被联合编译。Java的部分也是增量编译的。
增量编译依赖于对代码的相关性分析。这些分析结果将存放在由scalaCompileOptions.incrementalOptions.analysisFile 指定的文件中(有默认值)。在多项目的构建中,分析文件被传递给下游的ScalaCompile任务以支持跨项目的增量编译。对于Scala插件的ScalaCompile任务,此工作无需任何配置。但是对于其他由你自己添加的ScalaCompile任务,需要配置scalaCompileOptions.incrementalOptions.publishedCode 来指向此任务生成的class目录或者Jar文件,然后下游的ScalaCompile任务会通过此配置来编译增量。当 publishedCode 设置不正确时,上游修改代码时下游任务可能不会重新编译,从而引发编译错误。
注意:Zinc基于Nailgun的守护进程模式目前还不支持。因此我们计划增强gradle自身的编译器守护进程特性,使其跨Gradle的调用Scala编译器时保持存活。这将为Scala的编译速度带来显著提升。
11. Java6和Java7的编译及测试
Scala编译器会忽略Gradle的 targetCompatibility 及 sourceCompatibility 设置。在Scala2.11中,Scala编译器编译Java 6兼容的字节码。在Scala2.12中,Scala编译器编译Java 8兼容的字节码。如果项目中同时存在Java代码,可以按照Java插件相同的步骤来保证Java编译器的正确使用。
# in $HOME/.gradle/gradle.properties
java6Home=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
sourceCompatibility = 1.6
assert hasProperty('java6Home') : "Set the property 'java6Home' in your your gradle.properties pointing to a Java 6 installation"
def javaExecutablesPath = new File(java6Home, 'bin')
def javaExecutables = [:].withDefault { execName ->
def executable = new File(javaExecutablesPath, execName)
assert executable.exists() : "There is no ${execName} executable in ${javaExecutablesPath}"
executable
}
tasks.withType(AbstractCompile) {
options.with {
fork = true
forkOptions.javaHome = file(java6Home)
}
}
tasks.withType(Test) {
executable = javaExecutables.java
}
tasks.withType(JavaExec) {
executable = javaExecutables.java
}
tasks.withType(Javadoc) {
executable = javaExecutables.javadoc
}
12. Eclipse集成
Eclipse插件会为Scala工程添加额外的配置,以让工程与scala IDE一起正常工作。确切地说,插件会增加Scala特性及依赖容器。
13. IntelliJ IDEA集成
IDEA插件会为Scala工程添加额外的配置以让工程与IDEA一起正常工作。确切的说,插件添加了Scala SDK(IntelliJ IDEA 14+)及符合项目路径Scala版本的Scala编译器库。Scala插件向后兼容IntelliJ IDEA的早期版本,可以通过在IdeaModel上配置targetVersion添加Scala特性,替代默认的Scala SDK。
idea {
targetVersion = "13"
}