(译) Organizing Gradle Projects
翻译自《Organizing Gradle Projects》,介绍了Gradle的若干最佳实践。
每个软件工程的源代码和构建逻辑应该以有意义的方式来组织。本文介绍了若干最佳实践来实现可读性好、易维护的项目结构。同时也介绍了若干常见问题以及解决方法。
单独存放不同语言的源码
Gradle的编程语言插件建立了发现和编译源码的约定。比如,使用了Java插件的工程会自动编译src/main/java
目录下的代码。其他语言的插件也遵循类似的模式。目录路径的最后一部分通常表明了对应的语言。
某些编译器支持编译同一个源码目录中的不同语言写的代码。Groovy编译器可以处理混放在src/main/groovy
目录下的Java和Groovy源码。Gradle建议按不同的语言来存放代码,以达到更好的构建性能。此外,the user and build can make stronger assumptions。
The following source tree contains Java and Kotlin source files. Java source files live in src/main/java, whereas Kotlin source files live in src/main/kotlin.
1 | . |
不同类型的测试代码分开存放
Have a look at the sample sample that demonstrates how a separate integration tests configuration can be added to a Java-based project.
一个项目中有不同类型的测试代码,这个很常见。比如单元测试,集成测试,功能测试或冒烟测试。每种类型的测试代码应当放在专门的源码目录中(可选的)。分开存放对可维护性有好处,也能让你更关注特定类型的测试。
这个sample展示了如何在一个Java项目中添加不同的测试配置。
尽可能使用标准约定
Gradle核心插件遵守软件工程范例convention over configuration。插件逻辑在特定上下文中为用户提供有意义的缺省值以及约定。以Java plugin插件为例:
src/main/java
作为缺省的源码位置- 编译后的产物放在
build
目录
严格遵守缺省约定的话,加入项目的新开发者能马上知道如何开始工作。当然,约定也支持重新配置,只不过构建脚本的用户和作者更难维护构建逻辑和构建输出。应当尽可能尝试严格遵守缺省约定,除非你需要适配遗留项目结构。参考各相关插件的手册来学习其缺省约定。
定义settings文件
每次构建时,Gradle会尝试找到settings.gradle
(Groovy DSL)或者settings.gradle.kts
(Kotlin DSL)文件。基于这个目的,运行时会沿着目录树结构往上一直搜索到根目录。一旦找到settings文件后立即停止查找。
一定要在根目录中添加一个settings.gradle
以避免性能问题。这个建议对单工程构建和多工程构建都有效。该文件可以为空,也可以定义工程名。
一个典型的带settings文件的Gradle工程结构如下:
1 | . |
使用 buildSrc 抽象逻辑
复杂的构建逻辑通常适合封装成自定义任务或者二进制插件。自定义任务和二进制插件不应放到项目的构建脚本中。如果这些逻辑不需要在多个独立的项目中共享,那么可以使用buildSrc
。
buildSrc
目录视为一个included build。一旦 Gradle 发现这个目录,它会自动编译和测试其中的代码并将其添加到构建脚本的classpath。对于多项目构建(multi-project builds),只能有一个buildSrc
目录,这个目录位于项目根目录。应优先使用buildSrc
而不是插件(script plugins),因为前者代码更容易维护、重构和测试。
buildSrc
使用跟Java和Groovy项目相同的代码结构(source code conventions)。它可以直接访问Gradle API。buildSrc
目录下的build.gradle
脚本中可以添加其他依赖。
Example 1. Custom buildSrc build script
1 | repositories { |
一个包含buildSrc
的工程,其项目结构如下。buildSrc
下的代码使用跟应用代码类似的包。如果有额外的配置需要,buildSrc
目录可以放一个可选的构建脚本(比如,使用插件或声明依赖)。
1 | . |
注意buildSrc
中的变更会引起整个项目变成out-of-date状态。因此,当进行小的增量变更时,–no-rebuild command-line option可加快编译速度。记住buildSrc
修改完成后要定期进行全量构建。
在 gradle.properties 文件中定义属性
Gradle中可以在构建脚本中定义属性,也可以在gradle.properties
定义属性,或者在命令行参数中定义属性。
命令行参数中定义属性在ad-hoc场景下很常见。比如,你想传特定的属性值来控制某次构建的运行时行为。构建脚本中的属性很容易带来维护性问题。gradle.properties
用于将属性跟构建脚本分离。它适用于保存控制构建环境的属性。
典型的工程中将gradle.properties
文件放在根目录。另外,如果你想将其应用于所有构建任务的话,也可将该文件放在GRADLE_USER_HOME
目录。
1 | . |
避免覆盖任务输出
Task 应当定义输入和输出以利用 incremental build functionality 提升性能。当声明 task 的输出时,应当确认输出目录是独有的。
混合或覆盖不同task的输出,会导致 up-to-date 检查过程复杂化,从而拖慢构建过程。另一方面,文件系统的变化可能让Gradle的构建缓存(build cache)难以识别和缓存应当缓存的task。
发布自定义的Gradle
企业常常想通过定义通用约定或规则来为所有的项目做标准化构建。你可以借助初始化脚本来实现这个功能。Initialization scripts可以非常容易地为同一台机器上的各个项目应同一构建逻辑。比如,使用一个私有的repo以及其凭证。
这种方式有期缺点。首先,你必须跟公司的所有开发人员沟通标准化设置过程。另外,统一升级初始化脚本逻辑也是个挑战。
发布自定义Gradle是个可行的解决方案。自定义的Gradle包含标准的Gradle发布版本,以及一个或多个自定义 initialization script。初始化脚本跟发布版本打包在一起,并且可应用到每次的构建上。开发者只需要将他们的 wraper 文件指向自定义Gradle的url。
创建自定义Gradle发布版本的典型步骤如下:
- 实现下载和重新打包Gradle发布版本的逻辑
- 定义一个或多个初始化脚本
- 将初始化脚本跟Gradle发布包打包到一起
- 将Gradle发布包上传到HTTP服务器
- 将所有项目的wrapper文件指向自定义Gradle发布版本的url