Gradle构建工具的学习与使用

时间:2021-02-21 08:53:16

http://my.eoe.cn/william_sim/archive/10691.html

 

Gradle 是以 Groovy 语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自动化构建工具。
官网:http://www.gradle.org/

前提: 安装Gradle。安装过程非常简单:
1. 下载Gradle
2. 将GRADLE_HOME/bin/gradle加入$PATH。

  1. 基本概念(Project 和 Task)

    Gradle中有两个基本的概念:project和task。每个Gradle的构建由一个project构成,它代表着需要被构建的组件或者构建的整个项目。每个project由一个或者多个task组成。task代表着Gradle构建过程中可执行的最小单元。例如当构建一个组件时,可能需要先编译、打包、然后再生成文档或者发布等,这其中的每个步骤都可以定义成一个task。

  2. 构建第一个Task
    和Ant运行时读取build.xml类似,Gradle运行时默认会读取build.gradle这个文件, 当然你也可以使用参数"-b"来指定其他的xxx.gradle

下面,让我们新建一个build.gradle文件,然后输入如下内容:

1
2
3
4
5
 task hello {
doLast{
println "hello world"
}
}

这个构建的脚本很简单,就是输出hello world。为了运行这个构建,我们应该在当前目录下执行 "gradle hello",即gradle TaskName。
doLast意思是定义一个行为(映射Gradle中的Action类),放在当前task的最后,类似的,还有doFirst, 表示将定义的行为放在当前task最前面,例如

1
2
3
4
5
6
7
8
task hello {
doLast{
println "Hello world"
}
doFirst{
println "I am xxx"
}
}

执行gradle hello, 将输出
"I am xxx"
"Hello world"

另外,你也可以使用如下更简洁的方式来定义task:

1
2
3
task hello <<  {
println "hello world"
}

这里也许大家可能会觉得很奇怪,为什么可以用"<<"来定义Task的执行内容呢,还是让我们看看Gradle的代码是怎么实现的吧:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
private List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>();

public Task doFirst(Action<? super Task> action) {
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
actions.add(0, wrap(action));
return this;
}

public Task doLast(Action<? super Task> action) {
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
actions.add(wrap(action));
return this;
}

从上面的代码可以看出,Task类里有个Action的集合actions,当使用doFirst或者doLast时,实际上是将定义的执行部分实例化成Action的对象,然后添加到actions集合里。
明白了这一点,接下来让我们看看为什么可以使用<<定义Task--------Groovy作为强大的支持DSL的动态语言,早已经重载了 << 操作符,使得我们可以方便的使用<<向集合添加元素。
说道这,相信真相已经大白了:原来就是使用Groovy的特性,往集合里添加Action而已。对,这就是Gradle的语法,利用Groovy的DSL特性,帮助我们更容易的定义我们的构建脚本。
不过也许大家会觉得,这个例子实在是没有什么代表性,只是一个简单的 hello world,说明不了什么问题。好吧,别着急,下次我们会继续研究Gradle的其他部分,不过先记住:作为一个构建工具,Gradle真的很强大哦!

如何使用Task

1. Project和Task

对于build.gradle配置文件,当运行Gradle 时,Gradle会为我们创建一个Project的对象,来映射build.gradle中的内容。其中呢,对于不属于任何Task范畴的代码,Gradle会创建一个Script类的对象,来执行这些代码;对于Task的定义,Gradle会创建Task对象,并将它会作为project的属性存在(实际上是通过getTaskName完成的)。ok,看一个简单的例子:

新建文件basic/build.gradle,然后加入如下部分代码:

1
2
3
4
5
println "the project name is $name"
task hello << {
println "the current task name is $name"
println "hello world"
}

当运行这个例子时,首先Gradle会创建一个Porject的对象,然后将它和build.gradle的内容映射起来。在这个例子中,project包括两部分:
1)可执行脚本定义

按照之前提到的,可执行脚本的定义将直接被创建成对应的Script类的对象
在这个例子中,Script对应的部分就是第一行println的部分,然后执行的结果就是打印出 "the project name is basic"。
默认的,Project的名字是当前build.gradle所在目录的名字,在这个例子中,build.gradle放在basic目录下,因此,project的name也就是basic.

2)Task定义

在这个例子中,Gradle将创建一个Task的实例,将其和定义的task内容关联起来。另外,按照之前所说的,当Gradle运行时,我们可以使用访问project属性的方式去访问它。

例如,这个例子中,我们可以使用project.hello来访问这个task。因为这个task hello已经成为project的一个属性,那么当我们使用gradle properties(properties是gradle自带的一个Task,它能列出当前project级别的所有属性,可以使用gradle tasks查看更多内建的Task)来获取project级别的属性列表时,也将会得到'hello'。

另外,有一点要注意的是,在这个例子中,task中使用的$name,并不是Project的name,它是当前Task的name,因为它被使用在Task的scope里。

执行Gradle hello,输出的结果将是:

1
2
3
current project name is test
the current task name is hello
hello world

2. 定义属性

在Gradle中,我们可以定义以下三种属性并使用它们:

1)System Properties

System Properties 实际是指的JVM的system properties。我们知道,在运行java程序时,可以使用-D来设置Java的系统变量,在Gradle中,你也可以做同样的事情。比如

gradle xxx -DmySystemProp=xxxx

同时,在build.gradle中,应该这样使用这个变量:

1
2
3
task printSysProps << {
println System.properties['system']
}

2)Project Properties

Project Properties是Gradle专门为Project定义的属性。它的最大优点在于当运行gradle的时候,我们可以使用-P来设置它的值。比如,

gradle xxx -PmyProjectProp=xxxxx

而在build.gradle中,可以这样使用这个变量:

1
2
3
4
5
task printProps << {
if (project.hasProperty('commandLineProjectProp')) {
println commandLineProjectProp
}
}

同时,当我们执行gradle properties查看属性列表时,这个变量的名称以及值会显示在结果中。
3)Ext(ra) Properties

1
另外,我们还可以为Project或者Task定义Ext属性,也称动态属性,我们必须使用关键字ext(对应ExtraPropertiesExtension的实例)去定义动态属性。从这点可以看出,Gradle已经为我们设计了很多不同的类,去做不同的事情,我们只需要遵循Convention,使用他们即可。如果忘记写ext关键字,gradle运行时则会提示:

"Dynamic properties are deprecated...."。这是因为以前版本的gradle定义动态属性时,不需要加ext关键字的。

对于Project和Task而言,动态属性定义的方式完全一样,只是作用域不一样。
当定义完成后,我们就可以使用project.prop 或者 task.prop来访问这些动态属性了。下面让我们看一个例子:

 1
2
3
4
5
6
7
8
9
10
ext.projectProperties="ext projectProperties-value"
task printExtProps << {
ext.taskProperties="ext.task.properties-value"
if (project.hasProperty('projectProperties')){
println "ext.projectProperties values is " + projectProperties
}
if (printExtProps.hasProperty('taskProperties')){
println "task has defined ext.taskProperties value is " + taskProperties
}
}

注意:,对于ext定义的动态属性,并不能通过外部的方式修改它的值,只能在build.gradle中去设置或者修改它的值。

同时,如果是为project定义的ext动态属性,也会显示在gradle properties的结果中。

3. Task依赖

当构建一个复杂的项目时,不同task之间存在依赖是必然的。比如说,如果想运行'部署'的task,必然要先运行 编译、打包、检测服务器等task,只有当这被些被依赖的task执行完成后,才会部署。对于这种行为之间的依赖,Ant、Maven都提供了声明式的定义,非常简单。同样,使用Gradle定义task之间的依赖也是件很容易的事。

例如,定义如下两个Task,并且在"intro"里加上"dependendsOn"的关键字,如下所示:

1
2
3
4
5
6
task hello << {
println 'Hello world!'
}
task intro(dependsOn: hello) << {
println "I'm Gradle"
}

执行 "gradle intro",结果将是:

1
2
Hello World
I'm Gradle

由此可见,当执行gradle intro时,intro依赖的task hello会先被执行。除此之外,dependensOn也支持定义多个task的依赖,使用[]括起来即可。例如

1
task A(dependensOn:['B','C','D']) <<{ xxx }

除了使用dependensOn跟字符串来定义依赖,我们也可以使用taskX.dependensOn taskY这种形式:

1
2
3
4
5
6
7
task taskX << {
println 'taskX'
}
task taskY << {
println 'taskY'
}
taskX.dependsOn taskY

或者,也可以使用闭包:

1
2
3
4
5
6
7
8
9
task taskX << {
println 'taskX'
}
taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
}
task lib1 << {
println 'lib1'
}

除了之前讲的task的部分,Gradle还为我们提供了很多可用的API,更多的细节大家可以参考下Gradle的文档。这里我列出一个常用的方法onlyIf。在Gradle里,我们可以使用“OnlyIf()”来决定当前task是否需要被执行,例如:新建build.gradle,加入如下代码:

1
2
3
4
task hello << {
println 'hello world'
}
hello.onlyIf { !project.hasProperty('skipHello') }

当我们直接执行"gradle hello"时,没有任何结果,当我们执行"gradle hello -PskipHello=xxxx"时,会输出"hello world"。
4. 总结

总体而言,用DSL的代码而不是xml来定义构建的过程,还是很fancy的。

多项目构建

对于多系统、多项目的情况,很多构建工具都已经提供了不错的支持,像maven、ant。Gradle除了借鉴了ant或者maven的继承的方式定义子项目,也提供了一种更为方便的集中配置的方式,大大减少了构建带来的复杂度。除此之外,Gradle还提供了清晰的Project树模型来映射多项目的组织结构。下面,让我们了解一下如何使用Gradle构建多项目。

1. 多项目定义及结构

在Gradle中,使用文件settings.gradle定义当前项目的子项目,格式如下所示:

1
include 'sub-project1', 'sub-project2', 'sub-project3',

它表示在当前的项目下建立三个子项目,分别为'sub-project1', 'sub-project2', 'sub-project3'。默认情况下,每个子项目的名称对应着当前操作系统目录下的一个子目录。

当Gradle运行时,会根据settings.gradle的配置情况,构建一个单根节点的项目树。其中的每个子节点代表一个项目(Project),每个项目都有一个唯一的路径表示它在当前树中的位置,路径的定义方式类似:

1
Root:<Level1-子节点>:<Level2-子节点>:<Level3-子节点>

也可以简写成“:::”。借助这种路径的定义方式,我们可以在build.gradle去访问不同的子项目。另外,对于单项目,实际上是一种特殊的、只存在根节点,没有子节点的项目树。

例如,我们有个产品A,包括以下几个组件core,web,mobile。分别代表"核心逻辑"、"网站"、“手机客户端”。 因为每个组件是独立的部分,这个时候最好我们能定义多个子项目,让每个子项目分别管理自己的构建。于是我们可以这样定义A/settings.gradle

1
include 'core', 'web', 'mobile'

按照之前描述的,core组件对应A/core目录,web组件对应A/web目录,mobile组件对应A/mobile目录。接下来,我们就可以在每个组件内部,定义build.gradle负责管理当前组件的构建。

Gradle提供了一个内建的task 'gradle projects',可以 帮助我们查看当前项目所包含的子项目,下面让我们看看gradle projects的输出结果:

1
2
3
4
5
6
7
8
9
$ gradle projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'A'
+--- Project ':core'
+--- Project ':mobile'
--- Project ':web

结果一目了然,首先是Root级别的项目A,然后是A下面的子项目'core', 'mobile', 'mobile'

最终的文件以及目录结构如下所示:

1
2
3
4
5
6
7
8
9
A
--settings.gradle
--build.gradle
--core
--build.gradle
--web
--build.gradle
--mobile
--build.gradle

如果你不喜欢这种默认的结构,也可以按照如下方式定义子项目的名称和物理目录结构:

1
2
3
4
5
6
7
8
include(':core)
project(':core').projectDir = new File(settingsDir, 'core-xxx')

include(':web)
project(':web').projectDir = new File(settingsDir, 'web-xxx')

include(':mobile)
project(':mobile').projectDir = new File(settingsDir, 'mobile-xxx')

在这个例子中,子项目core实际上对应的物理目录为A/core-xxx,web实际上对应的是A/web-xxx,mobile也类似。

虽然我们更改了子项目的物理目录结构,不过由于我们在build.gradle中使用的是类似 “ :”的方式访问对应的子项目,所以目录结构的改变,对我们Gradle的构建脚本并不会产生影响。

接下来,考虑一个更复杂的情况,随着产品的发展,mobile这个组件慢慢的划分成了Android和IOS两个部分,这时我们只需要在目录A/mobile下定义新的settings.gradle,并加入如下部分:
include 'android', 'ios'
现在,mobile组件下将存在两个新的子项目 "android"和"ios"

于是,这时候'gradle projects'的目录结构就变成

 1
2
3
4
5
6
7
8
9
10
11
12
A
--settings.gradle
--core
--build.gradle
--web
--build.gradle
--mobile
--settings.gradle
--ios
--build.gradle
--android
--build.gradle

2. 多项目的集中配置

对于大多数构建工具,对于子项目的配置,都是基于继承的方式。Gradle除了提供继承的方式来设置子项目,还提供了另外一种集中的配置方式,方便我们统一管理子项目的信息。下面看一个例子,打开A/build.gradle,输入如下部分:

1
2
3
4
5
6
7
8
9
allprojects {
task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
hello << {println "- I am the sub project of A"}
}
project(':core').hello << {
println "- I'm the core component and provide service for other parts."
}

对于上面所示的代码,已经很表意了:

allprojects{xxx} 这段代码表示,对于所有的project,Gradle都将定义一个名称是hello的Task { println "I'm $task.project.name"} 。

subprojects{xxxx}的这段代码表示,对于所有的子project,将在名称为hello的Task上追加Action {println "- I am the sub project of A"}

注意:关于Task和Action的关系,请看我之前写的本系列的第一部分。

project(':core')的这段代码表示,对于名称为core的project,将在名称为hello的Task上追加Action { println "- I'm the core component and provide service for other parts." }

3. 多项目的Task执行

之前我们已经了解了多项目的结构以及如何通过路径去访问子项目。现在让我们看看如何使用Gradle来执行多项目。

在Gradle中,当在当前项目上执行gradle 时,gradle会遍历当前项目以及其所有的子项目,依次执行所有的同名Task,注意:子项目的遍历顺序并不是按照setting.gradle中的定义顺序,而是按照子项目的首字母排列顺序。

基于刚才的例子,如果我们在根目录下,执行gradle hello,那么所有子项目的“hello” Task都会被执行。如果我们在mobile目录下执行gradle hello,那么mobile、android以及IOS的“hello” Task都会被执行。关于该例子的运行结果,这里就不贴出来了。大家如果有兴趣的话可以试试。

4. 总结

这篇文章主要描述了使用Gradle管理多项目的知识。相比Ant或者Maven,Gradle提供了更灵活的配置方式。更重要的是,Gradle还提供了很多内建的Task帮助我们查看或者管理项目。这次就先聊到这里,下次我们来看看Gradle的生命周期。

from eoe.cn

声明:eoe文章著作权属于作者,受法律保护,转载时请务必以超链接形式附带如下信息

原文作者: william_sim

原文地址: http://my.eoe.cn/william_sim/archive/10691.html