Gradle是一个开源的、通用型自动构建系统。Gradle可用于构建Java项目、C++项目、前端开发项目,移动应用项目等不同种类、不同编程语言的项目。Gradle并未采用XML语言;它使用了Apache Groovy或者Kotlin语言来描述和定义项目构建的过程,给予了开发人员极大的灵活性。当运行Gradle时,Gradle会在内部构建一个有向无环图(Directed Acyclic Graph)来确定项目构建的步骤和先后顺序。Gradle同时支持Apache Maven和Apache Ivy的仓库(Repository),并支持代码库的依赖管理。Gradle会自动分析构建过程;每次运行时,Gradle仅运行那些需要再次运行的任务,从而提高运行效率。因为Gradle的众多特性,越来越多的厂商和项目采用Gradle自动构建系统。
Gradle和Apache Maven都是通用型构建系统。与Apache Maven相比,Gradle有哪些优势呢?
在接下来的内容中,我们将着重介绍如何使用Gradle工具构建Java工程。下面的内容将使用Groovy语言。在阅读时,读者无需事先掌握任何Groovy语言的知识,也能完整的理解全部内容。
Gradle可以同时管理一个或者多个工程(Projects)。每个工程是由一个或者多个任务(Task)组成的。在一个任务中,开发人员可以完成一项工作,例如:编译源代码、运行测试、打包、或者生成文档等。
使用Groovy语言定义一个任务是一件容易的事情。如下面的例子所示,该例定义了一个名为HelloLittleWaterdrop的任务。在运行时,该任务会打印字符串"Hello Little Waterdrop"。从语法结构来看,一个任务的定义由关键字task开始,随后紧跟着任务名称(HelloLittleWaterdrop)。任务的主体部分包含在一对花括号之间。
一个任务(Task)可以包含一个或者多个动作(Action)。当执行一个任务时,Gradle会依次执行该任务包含的所有动作(Actions)。在这个例子中,doLast是一个预定义的名称;它不仅定义了一个动作(该动作的内容在随后的一对花括号中定义),而且,该动作被添加在任务的动作队列(Action List)尾部。doFirst是另一个预定义的名称,使用方法与doLast类似。只不过,doFirst会将动作添加至任务的动作列表头部。
// 文件 build.gradle
task HelloLittleWaterdrop {
doLast {
println 'Hello Little Waterdrop'
}
}
我们可以使用如下命令执行一个任务。
> gradle -q HelloLittleWaterdrop
Hello Little Waterdrop
doLast和doFirst可以多次使用。Gradle会按照它们出现的顺序依次将动作添加至队尾或者队头。然后再按照任务队列中的顺序依次执行所有任务。
// 文件 build.gradle
task HelloLittleWaterdrop {
doFirst {
println 'Step 2: Print Hello Little Waterdrop'
}
doFirst {
println 'Step 1: Prepare to print Hello Little Waterdrop'
}
doLast {
println 'Step 3: Hello Little Waterdrop had been printed'
}
doLast {
println 'Step 4: Congraduations'
}
}
执行结果为:
> gradle -q HelloLittleWaterdrop
Step 1: Prepare to print Hello Little Waterdrop
Step 2: Print Hello Little Waterdrop
Step 3: Hello Little Waterdrop had been printed
Step 4: Congraduations
一个项目(Project)是对一个项目构建过程的描述。每一个项目都有一个build.gradle文件。在构建一个项目时,Gradle会根据该项目的build.gradle文件的内容依次运行任务,完成构建过程。因为每一个项目都有着一个对应的build.gradle文件,因此,在构建一个多项目工程的过程中,Gradle会读取和运行多个build.gradle文件。
settings.gradle是Gradle构建过程中的配置文件,它描述着构建过程中的一些Gradle的行为。settings.gradle是可选的;当settings.gradle不存在时,Gradle会采用默认的行为方式生成一个settings.gradle。在每一次构建过程中,Gradle只会读取一个settings.gradle文件(无论是单工程构建或者多工程构建)。我们在第四节介绍多工程构建时,会再次讨论settings.gradle。
Gradle的项目构建过程大致可分为以下四个步骤:
一个插件(Plugins)是一组任务(Tasks)的集合。使用插件的目的是为了提供更加丰富的构建方法,扩展Gradle的基本功能。在工程中使用插件能帮助开发人员更加快速、方便的搭建项目的构建过程,使用统一的配置方式,遵循业界流行的构建方法。
一般来讲,插件包含的任务是紧密相关的。例如:Gradle的Java插件(The Java Plugin)包含了Java语言的编译、测试和打包等任务。使用Java插件,开发人员能够快速的构建、测试和部署Java项目。
最后,我们再来讨论一些任务(Task)的重要属性。开发人员可以为任务(Task)添加描述信息。例如:下面的示例为HelloLittleWaterdrop任务添加了描述信息"This is an example of Task definition"。
// 文件 build.gradle
task HelloLittleWaterdrop {
description 'This is an example of Task definition'
doLast {
println 'Hello Little Waterdrop'
}
}
执行结果为:
> gradle -q HelloLittleWaterdrop
Hello Little Waterdrop
在定义任务内容的同时,还可以同时指定任务的依赖关系。例如:下面的示例中定义了两个任务HelloLittleWaterdrop和ByeLittleWaterdrop。任务ByeLittleWaterdrop需要在任务HelloLittleWaterdrop执行完毕后再执行。当在任务中使用dependsOn属性后,开发人员可以直接使用任务名称来指定所依赖的任务。当本任务(Task)依赖于另一个项目(Project)中的任务(Task)时,则可用":ProjectName:TaskName"的方式来指定任务。我们称这种任务指定的方式为任务定位(Task Locating)。在完成任务依赖的介绍之后,我们会介绍任务定位的几种方式。
// 文件 build.gradle
task HelloLittleWaterdrop {
doLast {
println 'Hello Little Waterdrop'
}
}
task ByeLittleWaterdrop {
dependsOn 'HelloLittleWaterdrop'
doLast {
println 'Bye Little Waterdrop'
}
}
执行结果为:
> gradle -q ByeLittleWaterdrop
Hello Little Waterdrop
Bye Little Waterdrop
任务定位方法一:在开发build.gradle时,开发人员常常需要定位任务(Task)。在相同的build.gradle文件中,可以使用任务的名字定位该任务,或者使用项目的名称来指定哪个项目中的任务。
// 文件 build.gradle
task HelloLittleWaterdrop {
doLast {
println 'Hello Little Waterdrop'
}
}
println HelloLittleWaterdrop.name
println project.HelloLittleWaterdrop.name
执行结果为:
> gradle -q HelloLittleWaterdrop
HelloLittleWaterdrop
HelloLittleWaterdrop
Hello Little Waterdrop
任务定位方法二:在Gradle运行时,Gradle会创建一个任务集合(Task Collection) tasks。所以,在build.gradle文件中可以通过tasks变量来定位任务。
// 文件 build.gradle
task HelloLittleWaterdrop {
doLast {
println 'Hello Little Waterdrop'
}
}
println tasks.HelloLittleWaterdrop.name
println tasks.named('HelloLittleWaterdrop').get().name
执行结果为:
> gradle -q HelloLittleWaterdrop
HelloLittleWaterdrop
HelloLittleWaterdrop
Hello Little Waterdrop
第三种任务定位的方法是使用任务的路径(Task's Path)。我们在指定任务依赖(Task Dependency)时使用过。在下面的例子中,getByPath()函数接收一个任务路径,并返回该任务对象。任务路径的结构和文件系统(File System)的文件路径(File Path)非常相似;任务路径以冒号(:)分隔项目(Project)和任务(Task)。以冒号开始的任务路径为绝对任务路径(Absolute Task Path);其他的路径为相对任务路径(Relative Task Path)。
// 文件 build.gradle
task HelloLittleWaterdrop {
doLast {
println 'Hello Little Waterdrop'
}
}
println tasks.getByPath('HelloLittleWaterdrop').name
println tasks.getByPath(':HelloLittleWaterdrop').name
println tasks.getByPath('project:HelloLittleWaterdrop').name
println tasks.getByPath(':project:HelloLittleWaterdrop').name
Gradle的任务(Task)还可以传递参数。在一些场景下,开发人员可能需要向任务传递参数。那么,在这种情况下,开发人员可以定义一个新的任务类,并通过其构造函数来接收参数,并创建任务实例。例如:在下面的例子中定义了一个新的任务类PrintMessageTask,用于打印输出消息。该消息的内容由构造函数的参数确定。在该例中,PrintMessageTask继承自Gradle提供的DefaultTask类。其中,@Inject(@javax.inject.Inject)标注了新任务类的构造函数;@TaskAction(@org.gradle.api.tasks.TaskAction)标注了新任务类中的动作(Action)。
class PrintMessageTask extends DefaultTask {
final String message
@Inject
PrintMessageTask(String message) {
this.message = message
}
@TaskAction
def print() {
println this.message
}
}
tasks.create('HelloLittleWaterdrop', PrintMessageTask, 'Hello Little Waterdrop')
在了解了Gradle的基本概念之后,接下来,我们将会创建并运行一个Java工程,并使用Gradle来完成这个工程的构建过程。在本章中,我们将以创建Java应用程序(Java Application)为例,若对其他编程语言或者其他类型的工程感兴趣的读者可参考Gradle init文档。
Gradle init命令(更准确的说是Gradle init plugin)主要用来创建一个新的工程。当开发人员在一个空的工程目录下执行gradle init之后,Gradle会引导开发人员完成一个新工程的创建,如下所示。在创建过程中,Gradle会询问:1. 创建的工程类型(回答:应用程序Application);2. 编程语言(回答:Java);3. 使用Groovy或者Kotlin脚本描述工程构建过程(回答:Groovy);4. 选择测试框架(回答:JUnit 4);5. 工程名称(回答:FirstJavaProject);6. 源代码的包名(回答:com.littlewaterdrop)。
> mkdir FirstJavaProject
> cd FirstJavaProject/
> gradle init
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Swift
Enter selection (default: Java) [1..5] 3
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2] 1
Select test framework:
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4] 1
Project name (default: project): FirstJavaProject
Source package (default: FirstJavaProject): com.littlewaterdrop
> Task :init
Get more help with your project: https://docs.gradle.org/6.6.1/userguide/tutorial_java_projects.html
BUILD SUCCESSFUL in 1m 8s
2 actionable tasks: 2 executed
在新创建的工程目录下,Gradle init生成了一些初始文件;它们可分为以下几类。
src目录下有main和test两个目录。main目录存放的是工程的源代码文件;而test目录下存放的是工程的测试源代码文件。这些文件用于测试src/main下的源代码的功能。App.java和AppTest.java放在com.littlewaterdrop的包目录下,这正对应着在运行gradle init时输入的包名称com.littlewaterdrop。
在main和test目录下分别有resources目录,可以存放工程配置文件(例如:日志配置文件,或者工程的配置文件)。
FirstJavaProject
|- build.gradle
|- settings.gradle
|- gradlew
|- gradlew.bat
|- gradle
| |- wrapper
| | |- gradle-wrapper.jar
| | |- gradle-wrapper.properties
|- src
| |- main
| | |- java
| | | |- com
| | | | |- littlewaterdrop
| | | | | |- App.java
| | |- resources
| |- test
| | |- java
| | | |- com
| | | | |- littlewaterdrop
| | | | | |- AppTest.java
| | |- resources
在初始创建的版本中,gradle.build文件包含了一些最基本的工程构建信息。
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java project to get you started.
* For more details take a look at the Java Quickstart chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.6.1/userguide/tutorial_java_projects.html
*/
plugins {
// Apply the java plugin to add support for Java
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
// mavenCentral() // 当开发人员选择使用Maven Central库时
// google() // 当开发人员选择使用Google Android库时
// maven { // 当开发人员选择使用内部Maven库时
// url "https://maven.littlewaterdrop.com/internal/release"
// }
}
dependencies {
// This dependency is used by the application.
implementation 'com.google.guava:guava:29.0-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.13'
}
application {
// Define the main class for the application.
mainClassName = 'com.littlewaterdrop.App'
}
在工程初始化后,App.java和AppTest.java的内容如下:
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package com.littlewaterdrop;
public class App {
public String getGreeting() {
return "Hello world.";
}
public static void main(String[] args) {
System.out.println(new App().getGreeting());
}
}
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package com.littlewaterdrop;
import org.junit.Test;
import static org.junit.Assert.*;
public class AppTest {
@Test public void testAppHasAGreeting() {
App classUnderTest = new App();
assertNotNull("app should have a greeting", classUnderTest.getGreeting());
}
}
当我们准备构建该工程时,我们可以使用如下命令查询该工程的任务(Task)列表。注意,此时我们执行的是工程根目录下的gradlew命令,而不是gradle构建工具的gradle命令。因为列表很长,我们仅展示了前面的几行信息。
> ./gradlew task --all
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------
Application tasks
-----------------
run - Runs this project as a JVM application
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
...
构建该工程可运行./gradlew build。
> ./gradlew build
BUILD SUCCESSFUL in 2s
7 actionable tasks: 7 executed
构建完毕后,工程的输出文件在build目录下,如下所示。在build目录下,会出现"classes"、"distributions"、"generated"、"libs"、"reports"、"scripts"、"test-results"和"tmp"目录。其中,classes目录包含的是java源代码编译出的.class文件;distributions目录下存放的是.jar文件,用于发布该工程。
FirstJavaProject
|- build
| |- classes
| |- distributions
| |- generated
| |- libs
| |- reports
| |- scripts
| |- test-results
| |- tmp
|- gradle
| ...
|- src
| ...
点击此处下载Gradle Java工程模板。
> ./gradlew clean build
> ./gradlew compileJava
> ./gradlew test
> ./gradlew jar
> ./gradlew javadoc
同时管理多个工程/模块是Gradle的一个重要特色。在一个大型工程的开发过程中,这个大型工程可能会被分解为多个子工程/子模块;各子工程/子模块由各自的开发团队维护。日常开发时,团队自己管理自己的模块,而在产品测试或者发布时,所有的子工程/子模块需要全部打包在一起发布。在这种场景下,Gradle是一款非常合适的工程构建工具。
因为多工程管理和应用的场景各不相同,各个模块之间的依赖关系较为复杂。在本文中,我们仅使用一个简单的例子来展示Gradle的基本使用方法。学习更高级的用法,可参考Gradle的官方文档多模块工程构建。
我们假设小水滴正在开发一个大型跨平台(Cross-Platform)工程HelloLittleWaterdrop。它由两个子工程构成。LittleWaterdropPlatform为平台代码库,它向下提供各个平台的兼容与适配;向上提供统一的平台服务。LittleWaterdropApp为一款应用程序,它实现了该工程的业务逻辑(Business Logic)。两个子工程均由Java语言实现。LittleWaeterdropApp依赖于LittlewaterdropPlatform。更准确的说,LittlewaterdropPlatform工程输出一个jar文件,而LittleWaeterdropApp工程依赖于这个jar文件。
首先我们来看一下工程的目录结构。在HelloLittleWaterdrop目录下包含两个目录,分别包含LittleWaterdropPlatform和LittleWaterdropApp工程的文件。在HelloLittleWaterdrop目录下还包含了build.gradle和settings.gradle两个文件,分别用于描述如何构建HelloLittleWaterdrop工程,以及两个子工程/子模块之间的关系。
在主工程HelloLittleWaterdrop目录下的build.gradle文件描述的是主工程的构建过程。两个子工程还分别包含各自的build.gradle的文件,用于分别描述子工程的构建过程。为了简化工程构建的配置,各个子工程中相同的内容可以放置在主工程的build.gradle文件中。在我们接下来讲解的例子中,子工程的build.gradle文件是空的。所有的配置都在主工程的build.gradle文件中。因此,在实际应用中,空白的子工程build.gradle文件是可以删除的。
HelloLittleWaterdrop
|- LittleWaterdropPlatform
| |- build.gradle
| |- src
| | |- main
| | | |- java
| | | | |- com
| | | | | |- littlewaterdrop
| | | | | | |- Platform.java
|- LittleWaterdropApp
| |- build.gradle
| |- src
| | |- main
| | | |- java
| | | | |- com
| | | | | |- littlewaterdrop
| | | | | | |- App.java
|- build.gradle
|- settings.gradle
主工程的settings.gradle文件非常简单,只有两行。它指定了主工程的名称,和包含的子工程的名称。在运行时,gradle会依次构建各个子工程。
rootProject.name = 'HelloLittleWaterdrop'
include ':LittleWaterdropPlatform', ':LittleWaterdropApp'
在主工程的build.gradle文件中,主要分为三个部分。第一个部分是allprojects属性的定义。在allprojects属性中,可以为所有的子工程添加属性。例如,下面的配置为主工程和两个子工程添加了一个任务hello。当运行主任务的hello时,子工程的任务也会执行。
allprojects {
task hello {
doLast { task ->
println "I'm $task.project.name"
}
}
}
执行结果如下。从执行的结果可以看出,三个工程都执行了任务hello。
> gradle -q hello
I'm HelloLittleWaterdrop
I'm LittleWaterdropApp
I'm LittleWaterdropPlatform
然后,第二个部分是定义子工程的相同属性。在运行时,gradle会将这些属性自动添加到子工程的build.gradle对象中。从下面的配置可以看出,两个子工程都应用了插件java,输出的jar文件的目标运行环境为java 11版本环境。它们都使用Maven库搜索第三方依赖库。它们都依赖于guava库和junit库。
subprojects {
apply plugin: 'java'
sourceCompatibility = 11
targetCompatibility = 11
repositories {
mavenCentral()
}
dependencies {
// This dependency is used by the application.
implementation 'com.google.guava:guava:29.0-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.13'
}
}
最后,我们配置LittleWaterdropApp工程独有的属性。因为,LittleWaterdropApp是一个应用程序,它不仅输出jar文件,而且还定义了主类(Main Class)。并且,它依赖于子工程LittleWaterdropPlatform输出的jar文件。因此,针对这些不同之处,下面的配置额外的应用了插件application,定义了依赖和主类的类名。
project(':LittleWaterdropApp') {
apply plugin: 'application'
dependencies {
implementation project(':LittleWaterdropPlatform')
}
application {
// Define the main class for the application.
mainClassName = 'com.littlewaterdrop.App'
}
}
最后,在主工程的根目录下运行gradle build来构建主工程以及两个子工程。主工程的输出在build目录中。
> gradle build
BUILD SUCCESSFUL in 3s
11 actionable tasks: 11 executed
点击此处下载Gradle Java多工程的模板。
本章介绍了通用型工程构建工具Gradle。与Apache Maven相比,Gradle具备更多的优点。例如:Gradle的性能更好,支持更多的编程语言和工程类型;Gradle给予开发人员更多的便利和灵活性。从我们给出的例子也可以看出,通过简单的配置,就能支持一些经典应用场景的构建。
注册用户登陆后可留言