从零开始学习Gradle之一---初识Gradle
前提: 安装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文件,然后输入如下内容:
task hello { doLast{ println "hello world" } } |
这个构建的脚本很简单,就是输出hello world。为了运行这个构建,我们应该在当前目录下执行
"gradle hello",即gradle TaskName。
doLast意思是定义一个行为(映射Gradle中的Action类),放在当前task的最后,类似的,还有doFirst,
表示将定义的行为放在当前task最前面,例如
task hello { doLast{ println "Hello world" } doFirst{ println "I am xxx" } } |
执行gradle hello, 将输出
"I am xxx"
"Hello world"
另外,你也可以使用如下更简洁的方式来定义task:
task hello << { println "hello world" } |
这里也许大家可能会觉得很奇怪,为什么可以用"<<"来定义Task的执行内容呢,还是让我们看看Gradle的代码是怎么实现的吧:
1 public abstract class AbstractTask implements TaskInternal, DynamicObjectAware { 2 private List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>(); 3 4 public Task doFirst(Action<? super Task> action) { 5 if (action == null) { 6 throw new InvalidUserDataException("Action must not be null!"); 7 } 8 actions.add(0, wrap(action)); 9 return this; 10 } 11 12 public Task doLast(Action<? super Task> action) { 13 if (action == null) { 14 throw new InvalidUserDataException("Action must not be null!"); 15 } 16 actions.add(wrap(action)); 17 return this; 18 } |
从上面的代码可以看出,Task类里有个Action的集合actions,当使用doFirst或者doLast时,实际上是将定义的执行部分实例化成Action的对象,然后添加到actions集合里。
明白了这一点,接下来让我们看看为什么可以使用<<定义Task--------Groovy作为强大的支持DSL的动态语言,早已经重载了
<< 操作符,使得我们可以方便的使用<<向集合添加元素。
说道这,相信真相已经大白了:原来就是使用Groovy的特性,往集合里添加Action而已。对,这就是Gradle的语法,利用Groovy的DSL特性,帮助我们更容易的定义我们的构建脚本。
不过也许大家会觉得,这个例子实在是没有什么代表性,只是一个简单的 hello
world,说明不了什么问题。好吧,别着急,下次我们会继续研究Gradle的其他部分,不过先记住:作为一个构建工具,Gradle真的很强大哦!
从零开始学习Gradle之二---如何使用Task
1. Project和Task
对于build.gradle配置文件,当运行Gradle <Task> 时,Gradle会为我们创建一个Project的对象,来映射build.gradle中的内容。其中呢,对于不属于任何Task范畴的代码,Gradle会创建一个Script类的对象,来执行这些代码;对于Task的定义,Gradle会创建Task对象,并将它会作为project的属性存在(实际上是通过getTaskName完成的)。ok,看一个简单的例子:
新建文件basic/build.gradle,然后加入如下部分代码:
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,输出的结果将是:
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中,应该这样使用这个变量:
task printSysProps << { println System.properties['system'] } |
2)Project Properties
Project Properties是Gradle专门为Project定义的属性。它的最大优点在于当运行gradle的时候,我们可以使用-P来设置它的值。比如,
gradle xxx -PmyProjectProp=xxxxx
而在build.gradle中,可以这样使用这个变量:
task printProps << { if (project.hasProperty('commandLineProjectProp')) { println commandLineProjectProp } } |
同时,当我们执行gradle properties查看属性列表时,这个变量的名称以及值会显示在结果中。
3)Ext(ra) Properties
另外,我们还可以为Project或者Task定义Ext属性,也称动态属性,我们必须使用关键字ext(对应ExtraPropertiesExtension的实例)去定义动态属性。从这点可以看出,Gradle已经为我们设计了很多不同的类,去做不同的事情,我们只需要遵循Convention,使用他们即可。如果忘记写ext关键字,gradle运行时则会提示:
"Dynamic properties are deprecated...."。这是因为以前版本的gradle定义动态属性时,不需要加ext关键字的。
对于Project和Task而言,动态属性定义的方式完全一样,只是作用域不一样。
当定义完成后,我们就可以使用project.prop 或者 task.prop来访问这些动态属性了。下面让我们看一个例子:
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"的关键字,如下所示:
task hello << { println 'Hello world!' } task intro(dependsOn: hello) << { println "I'm Gradle" } |
执行 "gradle intro",结果将是:
由此可见,当执行gradle intro时,intro依赖的task hello会先被执行。除此之外,dependensOn也支持定义多个task的依赖,使用[]括起来即可。例如
task A(dependensOn:['B','C','D']) <<{ xxx } |
除了使用dependensOn跟字符串来定义依赖,我们也可以使用taskX.dependensOn taskY这种形式:
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskX.dependsOn taskY |
或者,也可以使用闭包:
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,加入如下代码:
task hello << { println 'hello world' } hello.onlyIf { !project.hasProperty('skipHello') } |
当我们直接执行"gradle hello"时,没有任何结果,当我们执行"gradle
hello -PskipHello=xxxx"时,会输出"hello world"。
4. 总结
OK.今天对gradle的总结到此为止。总体而言,用DSL的代码而不是xml来定义构建的过程,还是很fancy的。
从零开始学习Gradle之三---多项目构建
随着信息化的快速发展,IT项目变得越来越复杂,通常都是由多个子系统共同协作完成。对于这种多系统、多项目的情况,很多构建工具都已经提供了不错的支持,像maven、ant。Gradle除了借鉴了ant或者maven的继承的方式定义子项目,也提供了一种更为方便的集中配置的方式,大大减少了构建带来的复杂度。除此之外,Gradle还提供了清晰的Project树模型来映射多项目的组织结构。下面,让我们了解一下如何使用Gradle构建多项目。
1. 多项目定义及结构
在Gradle中,使用文件settings.gradle定义当前项目的子项目,格式如下所示:
include 'sub-project1', 'sub-project2', 'sub-project3', |
它表示在当前的项目下建立三个子项目,分别为'sub-project1', 'sub-project2',
'sub-project3'。默认情况下,每个子项目的名称对应着当前操作系统目录下的一个子目录。
当Gradle运行时,会根据settings.gradle的配置情况,构建一个单根节点的项目树。其中的每个子节点代表一个项目(Project),每个项目都有一个唯一的路径表示它在当前树中的位置,路径的定义方式类似:
Root:<Level1-子节点>:<Level2-子节点>:<Level3-子节点> |
也可以简写成“:<Level1-子节点>:<Level2-子节点>:<Level3-子节点>”。借助这种路径的定义方式,我们可以在build.gradle去访问不同的子项目。另外,对于单项目,实际上是一种特殊的、只存在根节点,没有子节点的项目树。
例如,我们有个产品A,包括以下几个组件core,web,mobile。分别代表"核心逻辑"、"网站"、“手机客户端”。
因为每个组件是独立的部分,这个时候最好我们能定义多个子项目,让每个子项目分别管理自己的构建。于是我们可以这样定义A/settings.gradle
include 'core', 'web', 'mobile' |
按照之前描述的,core组件对应A/core目录,web组件对应A/web目录,mobile组件对应A/mobile目录。接下来,我们就可以在每个组件内部,定义build.gradle负责管理当前组件的构建。
Gradle提供了一个内建的task 'gradle projects',可以 帮助我们查看当前项目所包含的子项目,下面让我们看看gradle
projects的输出结果:
$ gradle projects :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'A' +--- Project ':core' +--- Project ':mobile' \--- Project ':web |
结果一目了然,首先是Root级别的项目A,然后是A下面的子项目'core', 'mobile', 'mobile'
最终的文件以及目录结构如下所示:
A --settings.gradle --build.gradle --core --build.gradle --web --build.gradle --mobile --build.gradle |
如果你不喜欢这种默认的结构,也可以按照如下方式定义子项目的名称和物理目录结构:
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中使用的是类似 “ :<SubProject>”的方式访问对应的子项目,所以目录结构的改变,对我们Gradle的构建脚本并不会产生影响。
接下来,考虑一个更复杂的情况,随着产品的发展,mobile这个组件慢慢的划分成了Android和IOS两个部分,这时我们只需要在目录A/mobile下定义新的settings.gradle,并加入如下部分:
现在,mobile组件下将存在两个新的子项目 "android"和"ios"
于是,这时候'gradle projects'的目录结构就变成
A --settings.gradle --core --build.gradle --web --build.gradle --mobile --settings.gradle --ios --build.gradle --android --build.gradle |
2. 多项目的集中配置
对于大多数构建工具,对于子项目的配置,都是基于继承的方式。Gradle除了提供继承的方式来设置子项目,还提供了另外一种集中的配置方式,方便我们统一管理子项目的信息。下面看一个例子,打开A/build.gradle,输入如下部分:
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 <Task>时,gradle会遍历当前项目以及其所有的子项目,依次执行所有的同名Task,注意:子项目的遍历顺序并不是按照setting.gradle中的定义顺序,而是按照子项目的首字母排列顺序。
基于刚才的例子,如果我们在根目录下,执行gradle hello,那么所有子项目的“hello” Task都会被执行。如果我们在mobile目录下执行gradle
hello,那么mobile、android以及IOS的“hello” Task都会被执行。关于该例子的运行结果,这里就不贴出来了。大家如果有兴趣的话可以试试。
4. 总结
这篇文章主要描述了使用Gradle管理多项目的知识。相比Ant或者Maven,Gradle提供了更灵活的配置方式。更重要的是,Gradle还提供了很多内建的Task帮助我们查看或者管理项目。这次就先聊到这里,下次我们来看看Gradle的生命周期。 |