Gradle

在 Grade 中,我们常见的几个关键术语有 Project、Plugin 以及 Task。和 Maven 一样,Gradle 只是提供了构建项目的一个框架,真正起作用的是 Plugin。Gradle 在默认情况下为我们提供了许多常用的 Plugin,其中包括有构建 Java 项目的 Plugin,还有 War,Ear 等。与 Maven 不同的是,Gradle 不提供内建的项目生命周期管理,只是 Java Plugin 向 Project 中添加了许多 Task,这些 Task 依次执行,为我们营造了一种如同 Maven 般项目构建周期。换言之,Project 为 Task 提供了执行上下文,所有的 Plugin 要么向 Project 中添加用于配置的 Property,要么向 Project 中添加不同的 Task。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译 Java 源代码,拷贝文件,打包 Jar 文件,甚至可以是执行一个系统命令或者调用 Ant。另外,一个 Task 可以读取和设置 Project 的 Property 以完成特定的操作。

Gradle 快速开始

  • 首先,先 download 最新版本的 gradle,网址如下:download。然后将下载下来的 zip 包放在你要安装的路径上,我安装在 /usr/local/bin;Copy to clipboardErrorCopied 然后打开电脑上的.bash_profile 文件,输入以下命令:
    GRADLE_HOME=/usr/local/bin/gradle-1.8;
    export GRADLE_HOME
    export PATH=$PATH:$GRADLE_HOME/binCopy to clipboardErrorCopied
    
  • 然后再在 console 上输入以下命令:
    $ source ~/.bash_profileCopy to clipboardErrorCopied
    
  • 这样就安装成功啦,可以通过以下命令来查看是否安装成功。
    gradle -versionCopy to clipboardErrorCopied
    
  • 如果提示没有 gradle 命令,则有可能是:
    • GRADLE_HOME 路径可能不对;
    • 没有执行 source ~/.bash_profile

初始化项目

init 初始化

Gradle 有一个内置的任务,叫做 init,可以在一个空的文件夹中初始化一个新的 Gradle 项目。init 任务使用(也是内置的)wrapper 任务来创建一个 Gradle 包装脚本,即 gradlew。

$ mkdir demo
$ cd demo

在新项目目录下,在终端使用以下命令运行 init 任务:gradle init。当提示时,选择2:应用程序项目类型和3:Java作为实现语言。接下来你可以选择用于编写构建脚本的 DSL - 1: Groovy2: Kotlin。对于其他问题,按回车键,使用默认值。

$ gradle init
Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3
Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1
Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4]
Project name (default: demo):
Source package (default: demo):
BUILD SUCCESSFUL
2 actionable tasks: 2 executed

init 任务生成的新项目的结构如下:

├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── app
    ├── build.gradle
    └── src
        ├── main
        │   └── java
        │       └── demo
        │           └── App.java
        └── test
            └── java
                └── demo
                    └── AppTest.java

其中,settings.gradle(.kts) 文件有两行有趣的内容。

  • rootProject.name为构建指定了一个名称,它覆盖了以构建所在的目录命名的默认行为。建议设置一个固定的名字,因为如果项目是共享的--例如作为 Git 仓库的根目录,文件夹可能会改变。
  • include("app")定义构建由一个名为app的子项目组成,包含实际的代码和构建逻辑。更多的子项目可以通过额外的`include(..)'语句添加。

我们的构建包含一个名为 app 的子项目,代表我们正在构建的 Java 应用程序。它被配置在 app/build.gradle(.kts)文件中。

plugins {
    id 'application'
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
    implementation 'com.google.guava:guava:30.1-jre'
}
application {
    mainClass = 'demo.App'
}
tasks.named('test') {
    useJUnitPlatform()
}

运行与打包

src/main/java/demo/App.java 内容如下:

package demo;
public class App {
    public String getGreeting() {
        return "Hello World!";
    }
    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}
// 对应的测试文件如下
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AppTest {
    @Test void appHasAGreeting() {
        App classUnderTest = new App();
        assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");
    }
}

多亏了 application 插件,你可以直接从命令行运行应用程序。运行任务告诉 Gradle 执行分配给 mainClass 属性的类中的主方法。

c194a9eg<!-- begin-inline-katex ./gradlew run
> Task :app:run
Hello world!
BUILD SUCCESSFUL
2 actionable tasks: 2 executed

第一次运行包装脚本 gradlew 时,可能会有延迟,因为该版本的 gradle 被下载并存储在本地的~/.gradle/wrapper/dists 文件夹中。该 application 插件还为你捆绑了应用程序及其所有的依赖性。归档文件还将包含一个脚本,可以用一个命令启动应用程序。

end-inline-katex--> ./gradlew build
BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 executed

如果你如上所示运行一个完整的构建,Gradle 将为你生成两种格式的存档。app/build/distributions/app.tarapp/build/distributions/app.zip。了解你的构建在幕后做什么的最好方法是发布一个构建扫描。要做到这一点,只需用--scan标记运行 Gradle。

$ ./gradlew build --scan
BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 executed
Publishing a build scan to scans.gradle.com requires accepting the Gradle Terms of Service defined at https://gradle.com/terms-of-service.
Do you accept these terms? [yes, no] yes
Gradle Terms of Service accepted.
Publishing build scan...
https://gradle.com/s/5u4w3gxeurtd2

私有仓库

Gradle 构建的项目,发布到仓库中,也非常容易:

apply plugin: 'maven'
uploadArchives {
    repositories {
        ivy {
            credentials {
                username "username"
                password "pw"
            }
            url "http://repo.mycompany.com"
        }
    }
}

Gradle Java 实践

Gradle 使用了一种约定俗成的方法来构建基于 JVM 的项目,它借用了 Apache Maven 的一些约定。特别是,它对源文件和资源使用相同的默认目录结构,并与 Maven 兼容的资源库一起工作。Java 中所谓的 Plugin 就是一个定义了一系列 Properties 与 Tasks 的集合。如果希望使用 Java plugin,只需要在 build.gradle 中加入这句话:

apply plugin: 'java'Copy to clipboardErrorCopiedCopy to clipboardErrorCopied

Gradle 和 Maven 一样,采用了约定优于配置的方式对 Java 项目布局,并且布局方式是和 Maven 一样的,此外,Gradle 还可以方便的自定义布局。在 Gradle 中,一般把这些目录叫做 source set:

gradle source set

这里要注意,每个 plugin 的 source set 可能都不一样。同样的,Java plugin 还定义好了一堆 task,让我们可以直接使用,比如:clean、test、build 等等。这些 task 都是围绕着 Java plugin 的构建生命周期的:

javaPluginTask

图中每一块都是一个 task,箭头表示 task 执行顺序/依赖,比如执行 task jar,那么必须先执行 task compileJava 和 task processResources。另外可以看到,Gradle 的 Java plugin 构建生命周期比较复杂,但是也表明了更加灵活,而且,在项目中,一般只使用其中常用的几个:clean test check build 等等。

Gradle 构建过程中,所有的依赖都表现为配置,比如说系统运行时的依赖是 runtime,Gradle 里有一个依赖配置叫 runtime,那么系统运行时会加载这个依赖配置以及它的相关依赖。这里说的有点绕,可以简单理解依赖和 maven 类似,只不过 Gradle 用 configuration 实现,所以更灵活,有更多选择。下图是依赖配置关系图以及和 task 调用的关系图:

javaPluginConfigurations

可以看到,基本和 Maven 是一样的。其实 Gradle 里面这些依赖(scope)都是通过 configuration 来实现的。

下一节:要读懂 Gradle,我们首先需要了解 Groovy 语言中的两个概念,一个 Groovy 中的 Bean 概念,一个是 Groovy 闭包的 delegate 机制。