Gradle DSL
Posted Jeanboy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle DSL相关的知识,希望对你有一定的参考价值。
Gradle DSL
Gradle 是一个编译打包工具,但实际上它也是一个编程框架。Gradle 有自己的 API 文档,对应链接如下:
Gradle User Manual - 官方介绍文档
DSL Reference - API 文档
由此可以看出,编写 Gradle 脚本时,我们实际上就是调用 Gradle 的 API 编程。
基本组件
Project
Gradle 中,每一个待编译的工程都是一个 Project。
例如:用 androidStudio 构建项目时,每一个 module 都是 Gradle 定义的 Project。
Task
每一个 Project 在构建的时候都包含一系列的 Task。
比如,一个 Android APK 的编译可能包含:Java 源码编译 Task、资源编译 Task、JNI 编译 Task、lint 检查 Task、打包生成 APK 的 Task、签名 Task 等。
对于 Gradle 的编译打包流程而言,Task 就是最小的执行单元,其中将调用具体的函数来完成实际的工作。
Plugin
一个 Project 到底包含多少个 Task,在很大程度上依赖于编译脚本指定的插件。插件定义了一系列基本的 Task,并指定了这些 Task 的执行顺序。
整体来看,Gradle 作为一个框架,负责定义通用的流程和规则;根据不同的需求,实际的编译工作则通过具体的插件来完成。
例如:编译 Java 项目时使用 Java 插件、编译 Groovy 项目时使用 Groovy 插件、编译 Android App 有 Android App 插件,编译 Android Library 有 Android Library 插件。
举个例子来说,我们在 Android APK 对应的 build.gradle 中经常可以看到如下代码:
groovy apply plugin:'com.android.application'
这就是使用编译 APK 的插件。
同样,在编译 Android Library 时可以看到如下代码,用于指定使用编译 Library 的插件:
groovy apply plugin:'com.android.library'
到现在为止,我们知道每一个 Library 和每一个 App 都是单独的 Project。根据 Gradle 的要求,每一个 Project 在其根目录下都需要有一个 build.gradle
。 build.gradle
文件就是该 Project 的编译脚本,类似于 Makefile。
构建过程
新建一个 Android 项目,目录结构如下:
ProjectName
|-app
|-build
|-lib
|-src
|-build.gradle //后面讨论
|-library-test
|-build.gradle //后面讨论
|-gradle
|-wrapper
|-build.gradle //*
|-settings.gradle //*
通过目录结构可以看出来,每一个 Project 中都有一个 build.gradle
文件,里面的内容后面再介绍。
在上面项目中有 app
和 library-test
两个 Project,如果编译某个 Project 则需要 cd 到某个 Project 目录中。比如, cd xxx/app
然后执行 gradle xxx
xxx 是 task 的名字。
这很麻烦啊,有 10 个独立 Project,就得重复执行 10 次这样的命令。更有甚者,所谓的独立 Project 其实有依赖关系的。那么,我想在项目目录下,直接执行 gradle xxx
是否能够把所有的 Project 都编译出来呢?
答案是可以的。 在Gradle中,这叫Multi-Projects Build。需要在根目录下放一个 build.gradle
和一个 settings.gradle
。
可以看到 project/build.gradle
文件中的内容类似如下:
buildscript {
//为当前项目配置仓库
repositories {
//jcenter 是一个函数,表示编译过程中依赖的库,
//所需的插件可以在 jcenter 仓库中下载
jcenter()
}
//定义编译脚本依赖的库
dependencies {
//表示我们编译的时候,依赖 Android 开发的 gradle 插件
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
//为所有的子项目配置
allprojects {
repositories {
jcenter()
}
}
// 项目 clean task
task clean(type: Delete) {
delete rootProject.buildDir
}
这个 build.gradle
主要作用是配置其他子 Project 。比如,为子 Project 添加一些属性。这个 build.gradle
有没有都无所属。
project/settings.gradle
则主要定义了根目录下具体有多少个 Gradle Project ,其内容类似于:
include ':app', ':library-test'
这个文件很重要,名字必须是 settings.gradle
。它里边用来告诉 Gradle,这个 Multi-Projects 包含多少个子 Project。
生命周期
当我们执行 Gradle 的时候,Gradle 首先是按顺序解析各个 Gradle 文件。这里边就有所所谓的生命周期的问题,即先解析谁,后解析谁。
Build Lifecycle - 官方文档
Gradle 构建系统有自己的生命周期,初始化、配置和运行三个阶段。
初始化阶段
读取项目根目录中
setting.gradle
中的 include 信息,决定有哪几个工程加入构建,并为每个项目创建一个 Project 对象实例,比如下面有两个工程:所以 Gradle 将会为它们两个分别创建一个 Project 对象实例。
include ':app', ':library-test'
配置阶段
执行所有 Project 中的
build.gradle
脚本,配置 Project 对象,一个对象由多个任务组成,此阶段也会去创建、配置 task 及相关信息。运行阶段
根据 Gradle 命令传递过来的 task 名称,执行相关依赖任务。task 的执行阶段。首先执行
doFirst{}
闭包中的内容,最后执行doLast{}
闭包中的内容。
Gradle 基于 Groovy,Groovy 又基于 Java。所以,Gradle 执行的时候和 Groovy 一样,会把脚本转换成 Java 对象。Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应,在 Gradle 执行的时候,会将脚本转换成对应的对端:
Gradle 对象
当我们执行
gradle xxx
或者什么的时候,gradle 会从默认的配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的配置脚本。Project 对象
每一个
build.gradle
会转换成一个 Project 对象。Settings 对象
显然,每一个
settings.gradle
都会转换成一个 Settings 对象。
对于其他 gradle 文件,除非定义了 class,否则会转换成一个实现了 Script 接口的对象。
Task
Task 是 Gradle 中的一种数据类型,它代表了一些要执行或者要干的工作。不同的插件可以添加不同的 Task。每一个 Task 都需要和一个 Project 关联。
Task 的 API 文档位于:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html。
任务创建
task 中有一个 action list,task 运行时会顺序执行 action list 中的 action,doLast 或者 doFirst 后面跟的闭包就是一个 action,doLast 是把 action 插入到 list 的最后面,而 doFirst 是把 action 插入到 list 的最前面。
task hello {
doLast {//doLast 可用 << 代替,不推荐此写法
println "hello"//在 gradle 的运行阶段打印出来
}
}
task hello {
println "hello"//在 gradle 的配置阶段打印出来
}
任务依赖
当我们在 Android 工程中执行
./gradlew build
的时候,会有很多任务运行,因为 build 任务依赖了很多任务,要先执行依赖任务才能运行当前任务。任务依赖主要使用
dependsOn
方法,如下所示:另外,你也可以在 Task 的配置区中来声明它的依赖:
mustRunAfter:
例如下面的场景,A 依赖 B,A 又同时依赖 C。但执行的结果可能是 B -> C -> A,我们想 C 在 B 之前执行,可以使用 mustRunAfter。
finalizedBy:在 Task 执行完之后要执行的 Task。
task A << {println 'Hello from A'}
task B << {println 'Hello from B'}
task C << {println 'Hello from C'}
A.dependsOn B
A.dependsOn C
B.mustRunAfter C //B 必须在 C 之后执行
task A << {println 'Hello from A'}
task B {
dependsOn A
doLast {
println 'Hello from B'
}
}
task A << {println 'Hello from A'}
task B << {println 'Hello from B'}
task C << {println 'Hello from C'}
B.dependsOn A //执行 B 之前会先执行 A
C.dependsOn B //执行 C 之前会先执行 B
增量构建
你在执行 Gradle 命令的时候,是不是经常看到有些任务后面跟着 [UP-TO-DATE],这是怎么回事?
在 Gradle 中,每一个 Task 都有 inputs 和 outputs,如果在执行一个 Task时,如果它的输入和输出与前一次执行时没有发生变化,那么 Gradle 便会认为该 Task 是最新的,因此 Gradle 将不予执行,这就是增量构建的概念。
一个 Task 的 inputs 和 outputs 可以是一个或多个文件,可以是文件夹,还可以是 project 的某个 property,甚至可以是某个闭包所定义的条件。自定义 Task 默认每次执行,但通过指定 inputs 和 outputs,可以达到增量构建的效果。
依赖传递
Gradle 默认支持传递性依赖,比如当前工程依赖包A,包 A 依赖包 B,那么当前工程会自动依赖包 B。同时,Gradle 支持排除和关闭依赖性传递。
之前引入远程 AAR,一般会这样写:
上面的写法会关闭依赖性传递,所以有时候可能就会出问题,为什么呢?
本来以为 @aar 是指定下载的格式,但其实不然,远程仓库文件下载格式应该是由 pom 文件中 packaging 属性决定的,@ 符号的真正作用是 Artifact only notation,也就是只下载文件本身,不下载依赖,相当于变相的关闭了依赖传递。
compile 'com.somepackage:LIBRARY_NAME:1.0.0@aar'
常用命令
$ gradle tasks // 查看根目录包含的 task
$ gradle tasks -all // 查看根目录包含的所有 task
$ gradle app:tasks //查看具体 Project 中的 task
$ gradle projects //查看项目下所有的子 Project
$ gradle build //构建项目
Android Studio 的 Terminal 中:
$ ./gradlew tasks // 查看根目录包含的 task
$ ./gradlew tasks -all // 查看根目录包含的所有 task
$ ./gradlew app:tasks //查看具体 Project 中的 task
$ ./gradlew projects //查看项目下所有的子 Project
$ ./gradlew build //构建项目
环境变量
Mac 中使用 Gradle 命令会出现 bash gradle commandnotfound
原因是没有配置环境变量。
找到 Gradle 所在的路径
在 Finder 应用程序中 -> Android Studio 右键 -> 显示包内容,打开之后按照 Contents -> gradle -> gradle-xxx -> bin -> gradle。
找到 gradle 文件后,右键 -> 显示简介,复制路径,类似:
/Applications/Android\ Studio.app/Contents/gradle/gradle-4.4
设置环境变量
$ cd ~ //返回 Home 目录
$ touch .baseprofile //创建 baseprofile 文件
$ open -e .baseprofile //使用文本编辑器打开 baseprofile 文件
输入以下内容:
jsexportGRADLE_HOME=/Applications/Android\Studio.app/Contents/gradle/gradle-4.4exportPATH=${PATH}:${GRADLE_HOME}/bin
$ source .bash_profile //使修改生效
$ gradle -v //显示 gradle 版本
```js WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/Applications/Android%20Studio.app/Contents/gradle/gradle-4.4/lib/groovy-all-2.4.12.jar) to method java.lang.Object.finalize() WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.reflection.CachedClass WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release
Gradle 4.4
Build time: 2017-12-06 09:05:06 UTC Revision: cf7821a6f79f8e2a598df21780e3ff7ce8db2b82
Groovy: 2.4.12 Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017 JVM: 9.0.1 (Oracle Corporation 9.0.1+11) OS: Mac OS X 10.13.3 x86_64 ```
如果显示如下问题,需要修改权限:
js-bash:/Applications/AndroidStudio.app/Contents/gradle/gradle-4.4/bin/gradle:Permissiondenied
修改权限
//进入 gradle 中 bin 目录
$ cd /Applications/Android\ Studio.app/Contents/gradle/gradle-4.4/bin
$ ls -l //查看权限
js total24-rw-r--r--1jeanboy admin528641617:49gradle-rw-r--r--1jeanboy admin225041617:49gradle.bat
$ chmod +x gradle //增加权限 $ chmod +x gradle.bat //增加权限
js total24-rwxr-xr-x1jeanboy admin528641617:49gradle-rwxr-xr-x1jeanboy admin225041617:49gradle.bat
推荐阅读
1.
2.
3.
4.
5.
6.
欢迎 长按下图 -> 识别图中二维码
以上是关于Gradle DSL的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 kotlinscript DSL (build.gradle.kts) 通过 url 添加 maven 存储库
在 buildSrc 中定义的 Gradle Kotlin DSL 未解析的引用
使用 Gradle Kotlin DSL 发布 Kotlin MPP 元数据