Gradle简介

原文链接:https://zhuanlan.zhihu.com/p/148172832

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的输入

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

  • 初始化阶段: 设置构建环境
  • 配置阶段 : 确定任务执行序列, 此阶段每次 build run 都会执行
  • 执行阶段: 按照配置执行

4. 支持多种扩展方式

  • 自定义Task类型
  • 自定义Task动作 Task.doFirst() and Task.doLast() methods.
  • 在project 与 task 使用 Extra properties …

5. 构建脚本基于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包并解压,然后配置环境变量即可
  2. 创建一个文本文件,命名为build.gradle 在此文件中创建一个名为hello的task
 task hello{       
     doLast{ println "hello world!" }  
 }

3. 执行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,首先就需要熟悉其语法构成元素:

语法元素

1、Project :

build script 的隐含对象


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

2、 属性

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

version = '1.0.1' myCopyTask.description = 'Copies some files'
 file("$buildDir/classes") println "Destination: ${myCopyTask.destinationDir}" 

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

3、 方法

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

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

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

4、 Block

一种特殊的方法,最有用也最具有迷惑性,其表现如下

	<obj>.<name> {
	     ...
	}
	
	<obj>.<name>(<arg>, <arg>) {
	     ...
	}

只有符合如下签名的方法才可以使用block语法

  1. 至少有一个方法入参
  2. 方法的最后一个入参类型必须是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,施主你别喷我啊!施主,你别用板砖拍。。。施主。。。 我日, 干死你丫的 fighting中。。。

小涛

我知道你知道我知道

相关推荐

配置新账户

一、新增xray vless服务账户 修改xray配置文件: vi /etc/xray/config.json 增加一个账户,包含下面的email,level,id,f …