ShuSheng007

  • 首页
  • 关于
  • 联系
ShuSheng007
天行健,君子以自强不息 地势坤,君子以厚德载物
  1. 首页
  2. 自动构建
  3. Gradle
  4. 正文

秒懂Gradle之从完全懵逼到似懂非懂

06/13/2020 938点热度 0人点赞 0条评论

[版权申明]非商业目的注明出处可自由转载
出自: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官方宣称其具有如下特点:

  1. 高效 (High performance)
  2. 基于JVM ,其运行在JVM上 (JVM foundation)
  3. 遵守惯例(Conventions),其在Ant和Maven之后出现,使用了很多约定俗成的概念
  4. 易于扩展 (Extensibility)
  5. IDE支持(IDE support),例如 Android Studio, IntelliJ IDEA, Eclipse和NetBeans
  6. 可视化(Insight),通过Build scans 可以生成构建信息,易于调试和分析

俯瞰Gradle:

会当凌绝顶,一览众山小,就如《金字塔原理》描述的那样,看待一个事物应该先从全局着眼,然后再深入细节,才能做到心中有数。下面几点是我们必须要知道的:

  1. Gradle是一个通用的自动化构建工具,而不针对某个特定平台及语言
  2. 核心模式是基于Task的

如上图所示,其会构建一张Task的依赖图,整个构建过程就变成了一个task接着一个task的执行,直达完成的过程

task是Gradle的最小执行单元,由如下3部分构成:

  • 输入:值,文件等
  • 动作:执行的动作
  • 输出:值,文件等

前一个task的输出是后一个task的输入

  1. gradle分3个阶段顺序执行

    • 初始化阶段: 设置构建环境
    • 配置阶段 : 确定任务执行序列, 此阶段每次 build run 都会执行
    • 执行阶段: 按照配置执行
  2. 支持多种扩展方式

    • 自定义Task类型
    • 自定义Task动作 Task.doFirst() and Task.doLast() methods.
    • 在project 与 task 使用 Extra properties
      ...
  3. 构建脚本基于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出来

  1. 安装gradle

如果是window系统,下载zip包并解压,然后配置环境变量即可

  1. 创建一个文本文件,命名为build.gradle
  2. 在此文件中创建一个名为hello的task
    task hello{
    doLast{ println "hello world!" }
    }
  3. 执行task

导航到build.gradle 文件目录下,在命令行中执行

gradle hello
  1. 查看输出
> 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,首先就需要熟悉其语法构成元素:

语法元素

  1. Project : build script 的隐含对象

通过它来使用gradle的功能,例如下面我们熟悉的块都是project对象的方法,只不过满足一定的方法签名,可以写成block的形式,这个我们后面会细说。

buildscript{
  ...
}
configurations {
  ...
}
  1. 属性

能使用=和$ (模板符号)的都是属性

version = '1.0.1'
myCopyTask.description = 'Copies some files'

file("$buildDir/classes")
println "Destination: ${myCopyTask.destinationDir}"

上面的代码中,version和buildDir都是project的属性,description和destinationDir 是myCopyTask 这个Task的属性

  1. 方法

有()的都是方法,但是groovy 支持无()调用,所以无括号时就看后面有没有=号,没有就是方法。

ext.resourceSpec = copySpec()   // `copySpec()` comes from `Project`    
file('src/main/java')
println 'Hello, World!'

其中println 也是方法,这里的方法一般都是实例方法,就是说其是某个实例的方法。

  1. 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

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: gradle gradle 脚本 自动构建
最后更新:03/11/2023

shusheng007

never give up ,keep going!

打赏 点赞

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

版权 © 2021 shusheng007.top 享有所有版权.

Theme Kratos Made By Seaton Jiang

津ICP备17001709号