Gradle之Project,Task

Gradle之Project,Task

gradle基本概念

百度百科:Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置。

gradle是一个构建工具,也是一个编程框架

gradle组成:

  • groovy核心语法
  • build script block
  • gradle api

gradle的优势:

  • 相比于maven,ant构建更加灵活
  • 可以服用各种插件
  • 兼容上,兼容maven等所有功能
  • 细粒度,可以自定义编译过程

gradle的生命周期

gradle的生命周期主要分为3个阶段:Initialization初始化阶段,Configuration配置阶段,Execution执行阶段

  1. Initialization:解析真个工程中的所有Project,构建所有Project赌赢的project对象
  2. Configuration:解析project中的所有的task,构建成一个有向无环图
  3. Execution:执行具体的task以及其依赖的task

setting.gradle:这个文件是在初始化阶段执行,一个gradle项目中必须由setting.gradle这个文件,因为它决定了那些项目参与构建。在gradle的构建中这是最先执行的一个文件。

如何监听gradle的生命周期呢

在setting.gradle文件中可以监听初始化阶段和配置之前的阶段

1
2
3
4
5
6
gradle.settingsEvaluated {
println('初始化阶段开始执行...')
}
gradle.beforeProject {
println('配置之前调用...')
}

在build.gradle文件中,可以监听配置之后、task的执行阶段和执行完成的阶段

1
2
3
4
5
6
7
8
9
10
11
12
this.afterEvaluate {
println('配置之后....')
}
gradle.taskGraph.beforeTask {
println "执行阶段 before task"
}
gradle.taskGraph.afterTask {
println "执行阶段 afterTask "
}
this.gradle.buildFinished {
println("执行阶段完毕,构建完毕...")
}

gradle Project

官方文档:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project

在一个gradle项目中,怎么算是一个Project呢,以一个Android项目为例,跟工程算是一个Project,其内部的子module也是一个Project。准确的说,只要该文件夹下有build.gradle文件,它就是一个Project。每一个module在编译期间都会生成一个对应的project对象,我们在build.gradle中编写的代码,其实都是在一个project对象内部编写。

获取一个项目所有的project

1
2
3
this.getAllprojects().eachWithIndex { Project entry, int index ->
println(entry.name)
}

获取Project中的目录

1
2
println(this.project.getRootDir().absolutePath)
println(this.project.getBuildDir().absolutePath)

gradle中拷贝文件,非常简单,比如把app目录下的proguard-rules.pro文件拷贝到根目录下的build文件夹下面

1
2
3
4
copy {
from(file('proguard-rules.pro'))
into(getRootProject().getBuildDir())
}

file(‘’)函数可以定位当前project中的某个文件。

Project的依赖:

在一个Android项目的根目录中的build.gradle中,我们都会看到下面的一段配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
buildscript {
repositories {
google()
jcenter()
maven{
url 'http://localhost:8081/xxxx'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'

}
}

buildscript 就是依赖配置的核心部分,它接受一个闭包,内部可以设置很多参数,最重要的就是前面的两个repositories和dependencies。

  • repositories :配置工程的仓库地址,按照顺序从不同的仓库中查找依赖。这里也可以添加本地的maven仓库地址
  • dependencies :配置工程的插件依赖地址(gradle程序所依赖的第三方库)。比如com.android.tools.build:gradle:3.5.1就是谷歌开发的Android相关的插件还有平时开发经常用到的tinker相关插件,butterknife相关插件都在这里。

在Project中执行外部指令,比如下面的一个复制的任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
task('copytask'){
//保证是在执行阶段
doLast{
def fromPath = this.buildDir.path + '\\outputs\\apk'

def toPath = 'D:\\data'
//外部命令脚本
def command = "xcopy ${fromPath} /s ${toPath}"
println(command)
//执行代码块
exec {
try {
//执行 dir命令, /c 表示 cmd窗口执行完 dir 命令后立即关掉,
// 至于 cmd 后可带哪些参数,可在终端下 cmd /? 查看
commandLine 'cmd', '/c', command
println('copytask is success')
}catch(GradleException e){
println('copytask is failed')
}
}
}
}

上面的代码:定义一个资源路径、一个目标路径和一个复制的指令。这里使用的是windows中的复制目录的指令xcopy,在mac和linux中需要使用对应系统的指令。然后使用commandLine来执行指令。

gradle Task

官方文档 :https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task

task是构建过程中的基本单元,每一个task都属于一个project,每一个task都有一个自己的名字和一个唯一的路径,该路径在所有project和所有的task中都是唯一的。

Task的创建方式:

第一种直接创建:

1
2
3
task('helloTask'){
println('hello task')
}

第二种通过TaskContainer容器创建

1
2
3
this.tasks.create('helloTask2'){
println('hello task2')
}

这两种方式创建的task没有区别,TaskContainer可以更好的管理task,它里面有创建task和查找task等方法。

Task的配置方式

同样,Task的配置也有两种方式:比如下面的两种配置分组和描述信息。

1
2
3
4
5
6
7
8
9
10
//第一种
task helloTask(group:'hello',description:'hello task des'){
println('hello task')
}
//第二种
this.tasks.create(name:'helloTask2'){
setGroup('hello')
setDescription('hello create task')
println('hello task2')
}

不同的task设置相同的分组,方便查找,比如把我们自己自定义的task都设置到一个同样的分组中,跟系统的区分开。

自定义Task的执行时机

当我们在studio的命令行执行前面的代码的时候,比如执行gradlew helloTask,我们只执行第一个task,执行结果会看到helloTask和helloTask2都会输出。这是因为前面的写法都是在gradle生命周期的配置阶段执行。

那如何让一个自定义的task在gradle的执行阶段执行,可以使用doFirst和doLast这两个闭包函数。因为系统中会有很多默认的task,比如Android中build和clean等。doFirst就是在系统task执行之前执行,doLast就是在系统的task执行之后执行。

1
2
3
4
5
6
7
8
9
10
11
12
task helloTask(group:'hello',description:'hello task des'){
//配置阶段执行
println('hello task')
//系统task执行之前
doFirst{
println('hello task do First')
}
//系统task执行之后
doLast{
println('hello task do Last')
}
}

通过doFirst和doLast,我们可以做一些事情,比如统计build执行阶段的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def buildStartTime
def buildEndTime
this.afterEvaluate { Project project ->
//afterEvaluate配置阶段完成后执行
def preTask = project.tasks.getByName('preBuild')
preTask.doFirst{
buildStartTime = System.currentTimeMillis()
}
def endTask = project.tasks.getByName('build')
endTask.doLast{
buildEndTime = System.currentTimeMillis()
println("build 执行时间:${buildEndTime - buildStartTime}")
}
}

//输出
> Task :app:build
build 执行时间:28018

Task的依赖

一个task可能会依赖一个或者多个别的task,那么在执行的时候,它所依赖的task会先执行

静态添加依赖,当我们明确知道需要依赖那几个task的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
task taskA{
doLast{
println('i am taskA')
}
}
task taskC{
doLast{
println('i am taskC')
}
}
task taskB(dependsOn:[taskA,taskC]){
doLast{
println('i am taskB')
}
}

taskB依赖了taskA和taskC,运行可以看到,taskA和taskC在taskB之前执行。

动态添加依赖,比如下面的taskB依赖所有name以lib开头的task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
task lib1 {
doLast{
println('i am lib1')
}
}
task lib2 {
doLast{
println('i am lib2')
}
}
task lib3 {
doLast{
println('i am lib3')
}
}

task taskB {
dependsOn project.tasks.findAll {
task ->
return task.name.startsWith('lib')
}
doLast{
println('i am taskB')
}
}

控制task的执行顺序可以通过前面的依赖的方式,也可以通过mustRunAfter这个关键字来指定某个task的执行必须在谁的后面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
task taskC{
doLast{
println('i am taskC')
}
}
task taskB {
mustRunAfter taskC
doLast{
println('i am taskB')
}
}
task taskA{
mustRunAfter taskB
doLast{
println('i am taskA')
}
}

前面我们知道,通过this.afterEvaluate {}这个闭包我们可以在配置完成之后来执行我们自己的一些操作,那如何将我们自己的task插入到系统构建的中间部位执行呢?

办法就是:通过mustRunAfter关键字,让我们的task必须执行在一个系统的task之后,然后通过dependsOn来让另一个一个系统的task依赖我们自己的task。这样就把我们自己的task插入到了这两个系统的task之间了。

Task的输入输出

从proguard-rules.pro中读取数据,写入到destination.txt文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
task combineFileContent {
def source = file('proguard-rules.pro')
def destination = file('destination.txt')

//inputs 属性应该被赋值为 一个目录、一个或多个文件、或是一个任意属性
inputs.file source // 将sources声明为该Task的inputs
//outputs 应该被赋值为一个或多个目录或者一个或多个文件
outputs.file destination // 将destination声明为outputs

doLast {
source.withReader {
reader ->
def lines = reader.readLines()
destination.withWriter { writer ->
lines.each { line ->
writer.append(line+'\r\n')
}
}
}
}
}
# 架构

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×