[版权申明]非商业目的注明出处可自由转载
出自:shusheng007
概述
最近公司给Pluralsight 上开了账号,然后评测了一下自己Android相关的知识,发现gradle方面的知识比较欠缺,仔细想想也确实是,自己从来没有仔细研究过这个构建工具。刚好这两天有点时间,查阅了一下官方文档,争取从概念上理解这个构建工具,做到可以写build script,可以写gradle plugin 。
相信有很多做了几年Android开发的同学对于构建工具gradle仍然是一知半解的,甚至都不能完全理解那些build script,更别说修改了,每次要实现一个功能时都是Google一下,然后copy到自己的项目中,如果OK了就很开心,如果不OK就再搜一个,完全不能理解其中含义。这是最要命的,学习什么东西都应该从概念和原理上理解它,特别是当这个东西你已经接触很久了,更应该去理解其背后的设计。那样不仅记得牢,还会举一反三,进而在其基础上创新。
概述
本文确切的说是一篇读书笔记,阅读完本文后你会在脑中对gradle有个大概的理解,特别是使用过gradle的同学会有一种豁然开朗的感觉,你会突然发现你熟悉的那些build script 原来是这么个意思啊。。。这种醍醐灌顶的感觉是最好的。俗话说,授人以鱼不如授人以渔,在行文中我也会试图告诉你在遇到问题的解决思路。
本文包括的内容
- gradle 概述
- gradle 脚本基础
- gradle 脚本实例
Gradle 概述
Gradle 是一个开源的自动构建工具,其几乎可以构建任何类型的软件,而不仅限于Java项目。
特点
Gradle官方宣称其具有如下特点:
- 高效 (High performance)
- 基于JVM ,其运行在JVM上 (JVM foundation)
- 遵守惯例(Conventions),其在Ant和Maven之后出现,使用了很多约定俗成的概念
- 易于扩展 (Extensibility)
- IDE支持(IDE support),例如 Android Studio, IntelliJ IDEA, Eclipse和NetBeans
- 可视化(Insight),通过Build scans 可以生成构建信息,易于调试和分析
俯瞰Gradle:
会当凌绝顶,一览众山小,就如《金字塔原理》描述的那样,看待一个事物应该先从全局着眼,然后再深入细节,才能做到心中有数。下面几点是我们必须要知道的:
- Gradle是一个通用的自动化构建工具,而不针对某个特定平台及语言
- 核心模式是基于Task的
如上图所示,其会构建一张Task的依赖图,整个构建过程就变成了一个task接着一个task的执行,直达完成的过程
task是Gradle的最小执行单元,由如下3部分构成:
- 输入:值,文件等
- 动作:执行的动作
- 输出:值,文件等
前一个task的输出是后一个task的输入
-
gradle分3个阶段顺序执行
- 初始化阶段: 设置构建环境
- 配置阶段 : 确定任务执行序列, 此阶段每次 build run 都会执行
- 执行阶段: 按照配置执行
-
支持多种扩展方式
- 自定义Task类型
- 自定义Task动作 Task.doFirst() and Task.doLast() methods.
- 在project 与 task 使用 Extra properties
...
-
构建脚本基于API调用,而非基于配置
你可以将构建脚本当代码阅读,而不是配置文件。它只会告诉你如何一步一步的将软件构建出来,而不会告诉你每一步是如何做的。这也是与我们日常工作最密切的部分。
阅读完以上的5点,你应该对gradle有一个模糊的概念了,接下来才是本文的重点,也是与日常开发息息相关的:gradle script
Gradle 脚本
Gradle script 是用来指导Gradle 如何构建项目的,一般由Groovy DSL 编写,现在也支持Kotlin DSL 编写。
以前我天真的以为我不会写gradle 脚本是因为我不懂Groovy语言,这下好了,gradle支持了Kotlin我应该会写了。后来发现完全不是那回事,人家使用的是领域特定语言,英文为domain-specific language (DSL) ,与我们平时使用的 general-purpose language (GPL) 完全不是一回事,关键是它还有自己的语法和API,所以使用Kotlin和Groovy基本一毛一样。
让我们来感受一下使用两种语言定义一个gradle的task
Groovy:
task hello{
doLast{ println "hello world!" }
}
Kotlin
task ("hello"){
doLast{ println "hello world!" }
}
上面两种写法几乎是相同的,所以别再骗自己了,让我们去懂它吧。
脚本基础对象
理解 gradle 最关键的是要理解 project 和task,这两个概念是构成gradle script的基石。
- project
这个project是指gradle的project,并非我们项目。具体代表什么和我们要构建的软件类型相关,例如我们建立了一个多module的Android 应用,各个module就各是一个gradle project, 他们一起构成了一个gradle build。project比较抽象,需要慢慢理解。
- task
task 代表构建过程中的不可分割的执行任务。
例如编译一下classes,创建一个JAR,产生Javadoc 等等,都可以是一个task。每个project又包含一个或者多个task。
依照IT世界的惯例,先写一个hello world出来
- 安装gradle
如果是window系统,下载zip包并解压,然后配置环境变量即可
- 创建一个文本文件,命名为build.gradle
- 在此文件中创建一个名为hello的task
task hello{ doLast{ println "hello world!" } }
- 执行task
导航到build.gradle 文件目录下,在命令行中执行
gradle hello
- 查看输出
> Task :hello
hello world!
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
是不是很简单啊?
上面的脚本创建了一个叫hello的task,然后输出一句hello world
。
其中task是gradle的Api, 其是Project 接口的一个方法,签名如下
//Creates a {@link Task} with the given name and adds it to this project.
Task task(String name) throws InvalidUserDataException;
注意,那个project是gradle的一个接口,这个可厉害了,我们都是通过它来使用gradle的功能的。签名如下:
//This interface is the main API you use to interact with Gradle from your build file. From a <code>Project</code>,
//you have programmatic access to all of Gradle's features.
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
...
}
doLast 是Task接口里面的一个方法,在一个task体执行完其他任务后执行。
doLast 源码:
@Override
public Task doLast(final Closure action) {
hasCustomActions = true;
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
taskMutator.mutate("Task.doLast(Closure)", new Runnable() {
@Override
public void run() {
getTaskActions().add(convertClosureToAction(action, "doLast {} action"));
}
});
return this;
}
看到这些源码是不是感觉很亲切啊,为什么到了gradle script中就看不懂了呢?嗯,那是因为gradle 脚本语法与传统的groovy,kotlin 语法不一样,其DSL,有自己的语法。
接下来,我们就简单的介绍一下gradle script,达到可以满足日常开发需求,不至于一遇到gradle 脚本就两眼一抹黑,修改全靠搜,不行重复搜。
脚本基础语法
要想读懂gradle script,首先就需要熟悉其语法构成元素:
语法元素
- Project : build script 的隐含对象
通过它来使用gradle的功能,例如下面我们熟悉的块都是project对象的方法,只不过满足一定的方法签名,可以写成block的形式,这个我们后面会细说。
buildscript{
...
}
configurations {
...
}
- 属性
能使用=
和$
(模板符号)的都是属性
version = '1.0.1'
myCopyTask.description = 'Copies some files'
file("$buildDir/classes")
println "Destination: ${myCopyTask.destinationDir}"
上面的代码中,version和buildDir都是project的属性,description和destinationDir 是myCopyTask 这个Task的属性
- 方法
有()
的都是方法,但是groovy 支持无()
调用,所以无括号时就看后面有没有=
号,没有就是方法。
ext.resourceSpec = copySpec() // `copySpec()` comes from `Project`
file('src/main/java')
println 'Hello, World!'
其中println 也是方法,这里的方法一般都是实例方法,就是说其是某个实例的方法。
- Block
一种特殊的方法,最有用也最具有迷惑性,其表现如下
<obj>.<name> {
...
}
<obj>.<name>(<arg>, <arg>) {
...
}
只有符合如下签名的方法才可以使用block语法
- 至少有一个方法入参
- 方法的最后一个入参类型必须是
groovy.lang.Closure
或者org.gradle.api.Action
.
例如下面这个输入project的copy方法
copy {
into "$buildDir/tmp"
from 'custom-resources'
}
它为什么可以写成block的形式呢,让我们看一下它的签名。它有两个版本,一个的入参是groovy.lang.Closure
类型,一个对应的版本入参是org.gradle.api.Action
。Closure 版本是为了向前兼容的,新加的API都是使用Action版本。
//Closure 版本
/ * Copies the specified files. The given closure is used to configure a {@link CopySpec}, which is then used to copy the files*/
WorkResult copy(Closure closure);
//Action版本
WorkResult copy(Action<? super CopySpec> action);
可以看到,其签名是符合block语法要求的,我们可以发现 into 和 from也是两个方法,但是这两个方法是属于哪个类型的呢?是project吗?
通过查询确认不是。其实这两个方法是属于 CopySpec 类型的。
对于如何得知 block里面的方法属于哪个类型,就需要分情况了。对于 Closure 版本需要看注释,或者文档。对于Action版本看泛型类型就行,gradle新增的API 都使用 Action的方式,即使是老的API也添加了对应的Action版本。
Task 依赖
task hello {
doLast {
println 'Hello world!'
}
}
task intro {
dependsOn hello
doLast {
println "I'm Gradle"
}
}
通过dependsOn,intro task 依赖了hello,当我们执行into Task时,hello task也就被执行了。
Extra task properties
这个用的很多,Extra 就相当于一个Map,我们可以把值按key-value的形式放在里面,用的时候通过key来取。
task myTask {
ext.myProperty = "myValue"
}
task printTaskProperties {
doLast {
println myTask.myProperty
}
}
上面代码在myTask中给其ext 放入了 myProperty- myValue ,然后在其他task中就可以通过key 获取到那个value了。 groovy代码表现的不明显,让我们看下kotlin的。
tasks.register("myTask") {
extra["myProperty"] = "myValue"
}
tasks.register("printTaskProperties") {
doLast {
println(tasks["myTask"].extra["myProperty"])
}
}
是不是清晰多了,先找到myTask 然后从其extr中使用myProperty 做为可以获取其value。
使用方法和Ant
task loadfile2 {
doLast {
fileList('./antLoadfileResources').each { File file ->
ant.loadfile(srcFile: file, property: file.name)
println " *** $file.name ***"
println "${ant.properties[file.name]}"
}
}
}
File[] fileList(String dir) {
file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
}
和其他脚本语言一样,我们可以把常用的功能分装成method,然后在Task中调用,方法的定义就非常接近Java了。
上面的方法fileList()作用是将某个目录下的文件过滤出来并按名称排序。在loadfile2 task 中使用ant task 读取文件内容并展示。
有的同学又懵了,日了个狗,Ant又是什么东西? 为什么说基础扎实,经验丰富的程序员学啥都快呢?因为一个新的东西不会凭空出现,往往与既有的体系有千丝万缕的联系,人类之所以可以不断进步,就是因为我们的知识可以一代一代的传承。 简单来说,Ant 与gradle是竞品,Ant是gradle的前辈,gradle和maven是站在Ant的肩膀上发展起来的。所以gradle也会吸收Ant很多优秀的东西,包括思想和实现。
在build script 中添加外部依赖
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
这个在android开发中已经见的很多了,不再赘述。
总结
行文到此就要结束了,如果你认真阅读了以上内容,相信你对gradle script会有新的认识,特别是block语法,当你阅读gradle 脚本的时候心中再也不慌了,虽然你可能不知道具体的含义,但是你清楚的知道他们背后是什么,如何去查询,这就是本文的目的。
想要更进一步,当然是去看官方网站 官网
如果有什么问题可以在留言区留言讨论。由于本人一向思维缜密,bug极少... Hi,施主你别喷我啊!施主,你别拍。。。施主。。。 我r, 干s你。。。fighting
文章评论