可以作为官方指南(以下简称"指南")Creating Multi-project Builds的一个翻译版本,与指南不同和参考的地方会做声明。
首先说下这里的“工程”,有两个含义: 根工程(root project)和子工程(subproject)。根工程用于全局的管理,子工程作为根工程的一个模块(module). 为了简明, 不引起混淆情况下,文中"项目"指根工程, "模块"指子工程。
Gradle中统一使用Project 来实例化对应的构建脚本(build.gradle). 可以简单的将build.gradle所在的目录称为一个project, 如果这个project又是用于管理其他project的就称为root project, 而其余的都是subproject. 模块化是gradle构建的一个重要功能, "模块"也是集成开发环境中提供的称呼, 比如 IntelliJ IDEA里面就是"New Module". 实在理不清就直接用英文root/sub project来代替,不需要翻译了, 撸起袖子敲上一个demo可能就明白了.
多模块的构建有助于项目的模块化, 可以使你专注于大项目中的某个区域,而Gradle负责项目的依赖。这个多模块demo同时作为gradle命令的一个练习.
环境
列举我的开发环境, 以本机环境自行微调,不细表. jdk1.8, gradle-4.6-all, Linux系统. 另外markdown的原因无法提供指南中groovy和kotlin两个版本, 这里用groovy(应该是目前流行的方式), 所以kotlin环境也没必要列出
创建项目
首先为新项目创建一个文件夹,并将Gradle Wrapper添加到项目中(对比指南)。
$ mkdir creating-multi-project-builds
$ cd creating-multi-project-builds/
$ gradle wrapper --gradle-version 4.6 --distribution-type all #指定已有版本,免得下载
$ ./gradlew init #初始化项目
执行后项目结构:
creating-multi-project-builds
├── build.gradle #当前项目的配置脚本, 顶级构建脚本
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar #Gradle Wrapper执行的jar
│ └── gradle-wrapper.properties #Gradle Wrapper配置属性
├── gradlew #Unix系统 Gradle Wrapper执行脚本
├── gradlew.bat #Windows系统 Gradle Wrapper执行脚本
└── settings.gradle #Gradle构建设置
可以删除 build.gradle和settings.gradle中的注释.
配置
build.gradle
allprojects {
repositories {
jcenter()
}
}
allprojects
块用于添加根工程和所有子工程的配置, 类似的subprojects
块用于添加所有子工程(模块)的配置. 可以在根工程中任意的使用这两个块. 现在通过subproject
来配置所有模块的版本, 在顶级build.gradle中加入:
subprojects {
version = '1.0'
}
添加一个Groovy库模块
$ mkdir greeting-library
$ cd greeting-library
$ ../gradlew init --type groovy-library
保留build.gradle和src目录, 其余文件(夹)全部删除, 添加include 'greeting-library'
到项目顶级settings.gradle中, 删掉不必要的注释. 这样不需要手动创建约定的源码目录.
修改 greeting-library/build.gradle
plugins {
id 'groovy'
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.13'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4',{
exclude module:'groovy-all'
}
}
repositories {
jcenter()
}
settings.gradle
include 'greeting-library'
最后, 在 greeting-library
中创建包目录 greeter
$ mkdir -p src/main/groovy/greeter
$ mkdir -p src/test/groovy/greeter
添加一个 GreetingFormatter
类到src/main/groovy/greeter
.
greeting-library/src/main/groovy/greeter/GreetingFormatter.groovy
package greeter
import groovy.transform.CompileStatic
@CompileStatic
class GreetingFormatter {
static String greeting(final String name) {
"Hello, ${name.capitalize()}"
}
}
添加Spock 框架测试类GreetingFormatterSpec
到src/test/groovy/greeter
.
创建greeting-library/src/test/groovy/greeter/GreetingFormatterSpec.groovy
package greeter
import spock.lang.Specification
class GreetingFormatterSpec extends Specification {
def 'Creating a greeting'() {
expect: 'The greeting to be correctly capitalized'
GreetingFormatter.greeting('gradlephant') == 'Hello, Gradlephant'
}
}
从根工程目录中执行 ./gradlew build
. 单个模块并不能真正构建多模块下面添加一个将使用这个库的模块.
添加Java应用模块
$ mkdir greeter
$ cd greeter
$ ../gradlew init --type java-application
$ rm -r gradle* sett* src/*/*/*.java #删除多余的文件(夹)
$ mkdir -p src/main/java/greeter src/test/java/greeter
添加include 'greeter'
到顶级settings.gradle中(另起一行)
创建java文件greeter/src/main/java/greeter/Greeter.java
package greeter;
public class Greeter {
public static void main(String[] args) {
final String output = GreetingFormatter.greeting(args[0]);
System.out.println(output);
}
}
目标是java应用,需要告诉Gradle那个类作为入口点. 修改当前模块build.gradle,将一个拥有main
方法的java类名设置给mainClassName
:
mainClassName = 'greeter.Greeter'
项目根目录执行 ./gradlew build
:
/xxx/creating-multi-project-builds/greeter/src/main/java/greeter/Greeter.java:5: 错误: 找不到符号
final String output = GreetingFormatter.greeting(args[0]);
^
符号: 变量 GreetingFormatter
位置: 类 Greeter
1 个错误
FAILURE: Build failed with an exception.
构建失败这是因为greeter
模块不知道去哪里找greeting-library
。创建模块不会自动在其他模块中可用——这样会使得项目变得脆弱(任何改动都会波及整个项目)。Gradle具有特定语法将一个模块链接到另一个模块的依赖项。编辑greeter/build.gradle添加:
dependencies {
compile project(':greeting-library')
}
执行 ./gradlew build --console verbose
将会看到以下输出:
$ ./gradlew build --console verbose
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes
> Task :greeting-library:jar
> Task :greeter:compileJava
> Task :greeter:processResources NO-SOURCE
> Task :greeter:classes
> Task :greeter:jar
> Task :greeter:startScripts
> Task :greeter:distTar
> Task :greeter:distZip
> Task :greeter:assemble
> Task :greeter:compileTestJava NO-SOURCE
> Task :greeter:processTestResources NO-SOURCE
> Task :greeter:testClasses UP-TO-DATE
> Task :greeter:test NO-SOURCE
> Task :greeter:check UP-TO-DATE
> Task :greeter:build
> Task :greeting-library:assemble
> Task :greeting-library:compileTestJava NO-SOURCE
> Task :greeting-library:compileTestGroovy
> Task :greeting-library:processTestResources NO-SOURCE
> Task :greeting-library:testClasses
> Task :greeting-library:test
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:check
> Task :greeting-library:build
我用的Gradle版本执行
./gradlew build
输出没有指南的详细,-h
查看帮助中的--console
如下描述--console Specifies which type of console output to generate. Values are 'plain', 'auto' (default), 'rich' or 'verbose'.
因此采用
--console verbose
版本输出来展示详细的构建执行时序
请注意每个模块如何在输出中作为前缀,以便您知道正在执行哪个模块的任务。另外Gradle在移动到另一个模块之前不会处理所有任务。
添加测试以确保应用程序本身的代码有效。由于Spock 框架也是测试Java代码的流行方法,因此首先将Groovy插件添加到greeter
模块的构建脚本中来创建测试。这需要groovy
插件, 并且已经包含了java
插件,因此您可以将java
替换成groovy
:
greeter/build.gradle
plugins {
id 'groovy'
}
dependencies {
//将junit替换成下面
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
创建测试类greeter/src/test/groovy/greeter/GreeterSpec.groovy
package greeter
import spock.lang.Specification
class GreeterSpec extends Specification {
def 'Calling the entry point'() {
setup: 'Re-route standard out'
def buf = new ByteArrayOutputStream(1024)
System.out = new PrintStream(buf)
when: 'The entrypoint is executed'
Greeter.main('gradlephant')
then: 'The correct greeting is output'
buf.toString() == "Hello, Gradlephant\n".denormalize()
}
}
切换到项目根目录下执行greeter
的test
任务:./gradlew :greeter:test
添加文档
这部分直接参看指南Add documentation, 不想看也可以跳过
重构公共的构建脚本
此时您可能已经注意到在模块greeting-library
和greeter
构建脚本中都有通用脚本代码。Gradle的一个关键特性是能够在根项目中放置这样的公共构建脚本代码。编辑根目录下的build.gradle增加:
configure(subprojects.findAll { it.name == 'greeter' || it.name == 'greeting-library' }) {
apply plugin: 'groovy'
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
}
删除greeting-library下已在顶级build.gradle中的所有配置:
plugins {
id 'groovy'
}
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4',{
exclude module:'groovy-all'
}
}
repositories {
jcenter()
}
此时greeting-library/build.gradle只剩下
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.13'
}
对于greeter类似的删除重复代码, 在根目录重新运行./gradlew clean build
以确保它仍然有效。此外, 项目还可以在IntelliJ IDEA等支持gradle构建的IDE中打开来进行开发。
总结
按照上面的步骤进行操作, 您已经掌握了
- 通过组合多个模块来创建模块化的项目。
- 让一个模块消费另一个模块的产品。
- 轻松使用多语言项目(Java + Groovy + ...)。
- 在所有模块中运行名字相似的任务。
- 在特定模块中运行任务而无需切换到该模块目录下。
- 将通用的模块设置重构到根项目中。
- 从根项目中有选择地配置模块。