gradle入门

构建系统介绍

之前只会用 make,第一次接触 gradle 时,明显感受和 make 的套路很不同。若是说 make 是通用构建系统,那 gradle 构建脚本第一眼看上去甚至感受跟构建毫无关联。html

本文章解决的疑问: 对比 make/ant, 为何 gradle 被称为构建系统以及和其余构建系统的类似之处。java

make

make 是一个很是古老且经久不衰的构建系统。shell

make 的关键: shell/文件。apache

make 的规则

make 是基于 shell 的构建系统。api

make 的规则很是简单,一个简单的用法就是把长命令精简为短命令. 下面是一个简单的例子。bash

mycli:
    echo "this is a very very long command!"
    echo "this is another very very long command!"

将上面的内容保存到当前目录的 makefile 文件,而后测试如下命令。jvm

$ make mycli

    echo "this is a very very long command!"
    this is a very very long command!
    echo "this is another very very long command!"
    this is another very very long command!

这种用法下, make 至关于给一组命令提供了快捷方式,当执行 make <shortcut> 的时候,
就会执行对应的命令,并且会把执行的命令打印出来。
不过通常状况下,这样回显命令不太好看,能够加上 -s 取消回显。
(在每条命令前面加上 @ , 也能够防止命令回显)maven

$ make -s mycli

    this is a very very long command!
    this is another very very long command!

这个例子中 mycli 能够看做是make 的 目标.ide

make 目标的依赖

声明目标时,能够冒号后面声明依赖的子目标。
语法以下:函数

<target>: <subtarget1> <subtarget2>
    command1
    command2
    ...

<subtarget1>: ...
    ...

上面声明的 target 依赖了 subtarget1/subtarget2

make 目标的状态

make 真正的做用是做为构建系统,而 make 的目标有另一层含义,
目标通常来讲并非一个简单的字符串,而是一个文件名。

下面看一个真实的例子:

# 过滤出 test.txt 里面全部非空行, 生成 content.txt
content.txt: test.txt
    grep -E ".+" test.txt > content.txt

测试:

$ make content.txt
    grep -E ".+" test.txt > content.txt

$ make content.txt
    make: “content.txt”已经是最新。

能够看到,第一次生成 content.txt 后,第二次执行 make 会跳过命令,
它的原理是比较目标和依赖的时间戳,只有发现依赖(test.txt)更新的状况下
才会执行下面的 grep 命令生成(content.txt)目标。
即 make 经过文件系统时间戳来表示构建目标的状态。

这另外一个例子,组合了上面全部特性。

backup.tar.gz: content.txt
    tar -zcvf backup.tar.gz userlist.txt

content.txt: test.txt
    grep -E '.*' text.txt > content.txt

ant

apache ant 是一个相似 make 的构建系统,主要优点在于使用 java 编写,跨平台。

下载地址

对比

ant 与 make 对比

_ make ant
目标 文件 字符串
脚本 makefile xml
指令 shell java class

样例

一个简单的 ant 脚本(build.xml):

<project name="MyProject">
    <target name="MyTarget" depends="SubTarget">
        <echo message="This is target message." level="error"/>
    </target>
    <target name="SubTarget">
        <echo message="This is sub-target message1." />
        <echo message="This is sub-target message2." />
    </target>
</project>

运行效果:

$ ant MyTarget
Buildfile: D:\sphinx\record\docs\gradle\build.xml

SubTarget:
     [echo] This is sub-target message1.
     [echo] This is sub-target message2.

MyTarget:
     [echo] This is target message.

BUILD SUCCESSFUL
Total time: 0 seconds

这个组织结构和 makefile 很像。

原理

xml 中分为三层: project、target、task。
其中 target 即对应 makefile 中的目标,重点是 task。

makefile 中 task 通常对应一条 shell 命令,而 ant 的 task 通常对应执行了一个 java class。

例如 <echo message="This is target message." level="error"/>, 能够翻译成代码:
new Echo("This is target message", "error").execute()

echo 伪代码以下:

public class Echo extends Task {
    Echo(String message, String level) {
        this.message = message;
        this.level = level;
    }
    void execute() {
        System.out.println("[%s] %s", this.level, this.message);
    }
}

ant 中内置了不少这种经常使用 task 。并且用户也能够本身实现自定义 Task。

构建系统的基本要素

从 make 和 apache ant 中能够大体感知到, 构建系统两个重要功能就是:

  1. 状态管理(增量构建)
  2. 依赖管理(DAG 图)

gradle

gradle 是一个更偏向 Java 定制化的构建工具。

使用的话,最好使用新版的 gradle,若是要跟 jetbrains、kotlinDsl 结合使用,最好用 gradle-all 版本。

下面的例子所有使用 kotlin-dsl 举例,构建脚本为 build.gradle.kts

注意: 使用 jetbrains idea 能够对 gradle 构建脚本有更好的提示。

简介

gradle 使用 groovy/kotlin 语言做为构建语言,能够理解为是一个构建脚本。

Ant 中存在 project、target、task 的概念,gradle 也拥有 project/task 的概念。对好比下

概念 ant gradle
项目 project project
构建目标 target task
构建指令 task groovy/kotlin代码

须要注意的是 gradle 的 task 对应了 ant 的 target。
脚本中能够经过相似与引用全局变量的方式引用 tasks。

用法

// build.gradle.kts
tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}
tasks.register("intro") {
    dependsOn("hello")
    doLast {
        println("I'm Gradle")
    }
}

运行:

$ gradle intro

    Hello world!
    I'm Gradle
  • task.register(name: String): 定义指定名称的 task, 大括号体包含构建这个 task 的代码。
  • doLast: task 构建完成后的执行函数。
  • dependsOn: 本 task 依赖的其余 task。

也能够编写任意复杂的脚本。

repeat(4) {
    tasks.register("intro${it}") {
        doLast {
            println("I'm ${it}")
        }
    }
}

project

对于 ant 来讲,整个 build.xml 的顶层元素就是 project; gradle 也相似。

对整个 build.gradle.kts 来讲, 整个文件都处在 project 的做用域中,
能够经过 this 访问 project 自己及其成员、方法, 不过通常都省略 this,
好比用 repositories {...}, 而不是 this.repositories {...}

project

如上面的例子,整个构建流程相似于下面的伪代码:

interface Task {
        fun doLast();
        fun dependsOn();
        ...
    }
    class TaskContainer extends Set<Task> {
        fun register(name: String): Task
    }
    class Project {
        val tasks: TaskContainer

        fun build() {
            // Start: 将 build.gradle.kts 的内容在这里展开
+           tasks.register("hello") {
+               doLast {
+                   println("Hello world!")
+               }
+           }
+           tasks.register("intro") {
+               dependsOn("hello")
+               doLast {
+                   println("I'm Gradle")
+               }
+           }
            // End: 
        }
    }

    new Project().build()

Project 具体声明见: org.gradle.api.Project

除了 tasks 外,几乎构建脚本中全部调用的方法都绑定于 Project,
这些方法的做用是设置 Project 属性。
这相似于 Ant, Ant 也有不少针对 Project 的属性设置。

好比 defaultTasks 设置默认构建目标, repositories 设置项目 maven 源, dependencies 设置项目依赖的第三方包, group 设置项目包名。

tasks

tasks 是 Project 的成员。能够经过 tasks 访问,其底层是一个容器(Set<Task>),
不过提供了不少自定义方法作其余管理操做。

tasks 具体声明见: org.gradle.api.tasks.TaskContainer

基本操做

// 建立 task: 默认类型为 DefaultTask
tasks.register("myTask") {
    // 能够在里面作一些操做
    doLast {...}
}
// 也能够在后期引用,而后进行其余设置
tasks.named("myTask") {
    dependsOn(...)
}

// 建立指定类型的 Task
tasks.registor<Copy>("copyTask") {
    from(file("srcDir"))
    to(...)
}

建立 task 能够指定类型,如上面的 Copy ,
此时能够调用 Copy 的专有方法,如 from()

第三方库

对于 kotlin-dsl, 构建脚本中可使用任何 kotlin 代码,也可使用kotlin 标准库。

可是构建脚本默认没法使用第三方库,若是须要第三方库,则要在 buildScripts 中引入.

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2")
    }
}

tasks.register("encode") {
    doLast {
        val encodedString = Base64().encode("hello world\n".toByteArray())
        println(String(encodedString))
    }
}

buildScripts.dependencies 表示构建脚本自己的第三方依赖,而不是用户项目的依赖。

plugins

gradle 脚本中默认做用域就是 Project, 因此构建脚本中能够随意调用 Project 的方法。但 Project 中的方法是有限的,可能知足不了业务需求。

gradle 插件能够扩充 Project 的方法,或者增长其余功能、task 等。

plugin 的使用:

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.4.10"
}

plugins 和 dependencies 的声明方法差很少,

因为 maven 下载速度可能很慢,能够新建 settings.gradle.kts 设置 plugins 源。

pluginManagement {
    repositories {
        maven(url="https://maven.aliyun.com/repository/public/")
        maven(url="https://maven.aliyun.com/repository/gradle-plugin")
    }
}

下一步

task入门

完整文档: gradle 安装目录的 docs。