Gradle堪称Java(JVM)世界构建技术的一个量子跃迁。本文介绍了Gradle的常用概念,并通过对Gradle项目(使用Gradle)自身构建过程的分析进一步了解Gradle。

2017-08-25 更新

剔除Jar包中包含的文件。结果可使用 vim jarname.jar 来查看。

jar {
    exclude ('application.properties')
}

2017-07-05 更新

显示项目的依赖树:

# 只显示某个子项目的某一个scope的依赖
→ ~/work/test-proj (master) $ ./gradlew :subproject:dependencies --configuration compile

2017-05-22 更新

The –refresh-dependencies option tells Gradle to ignore all cached entries for resolved modules and artifacts. A fresh resolve will be performed against all configured repositories, with dynamic versions recalculated, modules refreshed, and artifacts downloaded.

当开发团队更新了API包却没有变更 artifact version,使用 --refresh-dependencies 参数来重新下载依赖包。

→ ~/work/test-proj (master) $ ./gradlew clean -Penv=testing distz --refresh-dependencies

–end–

Gradle提供:

  • 类似Ant、灵活、通用的构建工具
  • 可切换,build-by-convention(个人理解为可通过不同的命令进行不同的构建)
  • 多项目构建(multi-project)的支持
  • 强大的依赖管理(基于Apache Ivy)
  • 支持已存在的Maven或Ivy仓库
  • 支持传递依赖关系管理,而不需要远程存储库或pom.xml和ivy.xml文件
  • Ant任务和构建是一等公民
  • Groovy构建脚本
  • 丰富的用于描述构建的域模型(Domain Specific Language)
  • Gradle wrapper允许用户在没有安装Gradle的机器上执行Gradle builds, 这对于CI非常有用

安装Gradle

Gradle依赖于Java JDK或JRE,版本>=7。Gradle本身包含了Groovy库,已安装的Groovy会被Gradle忽略。下面使用SDKMAN进行安装:

# 安装sdkman
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"

# 安装groovy,这一步不需要
$ sdk install groovy

# 安装gradle,版本3.1
$ sdk install gradle 3.1

Gradle Command-Line

任务之间有依赖关系,如下图所示:

commandLineTutorialTasks

build.gradle文件内容如下:

task comile << {
    println 'compiling source'
}

task compileTest(dependsOn: compile) << {
    println 'compiling unit tests'
}

task test(dependsOn: [compile, compileTest]) << {
    println 'running unit tests'
}

task dist(dependsOn: [compile, test]) << {
    println 'building the distribution'
}

当执行gradle dist test的时候,compile只执行一次,执行gradle test testtest也只执行一次。

# Excluding tasks, 任务test不会执行
$ gradle dist -x test

# Continue the build when a failure occurs
# 如果一个任务的依赖都没有出错,那么--continue会保证该任务一定会被执行,如果它的依赖失败了,则该任务不会执行
$ gradle dist --continue

# 任务名简写,可以使用驼峰
# same as `gradle dist`
$ gradle di

# same as `gradle comTest`
$ gradle cT

# gradle默认会查找在当前文件夹下的构建文件,可以使用-b选项来选择另一个build file,这样setting.gradle不会被使用
# -q, --quite    Log errors only
$ gradle -q -b subdir/myproject.gradle hello

# -p 选择项目目录
$ gradle -q -p subdir hello

# Forcing tasks to execute, 会让所有dist依赖的task强制执行
$ gradle --rerun-tasks dist

# Listing projects
$ gradle -q projects

# Listing tasks
$ gradle -q tasks
$ gradle -q tasks --all

# Show task usage detail
$ gradle -q help --task libs

# Listing project dependencies
$ gradle -q dependencies api:dependencies webapp:dependencies

# Listing project properties
$ gradle -q api:properties

Gradle Wrapper

Gradle建议所有的Gradle项目设置Wrapper,它可以省去Gradle的安装并且保证使用正确的工具版本。每个Wrapper都与一个特定的Gradle版本绑定,第一次执行./gradlew <task>时,Wrapper会下载对应版本的Gradle(保存在~/.gradle/wrapper/dists/目录下)并使用它来进行构建。所以,使用./gradlew替换上述命令的gradle,效果是一样的。

Adding the Wrapper to a Project

Wrapper应该使用Version Control来管理,这就意味着任何人都可以clone你的repo,进行构建却不用安装Gradle。

# 在项目根目录运行
$ gradle wrapper --gradle-version 3.1
:wrapper

BUILD SUCCESSFUL

Total time: 4.507 secs

该命令会生成以下文件:

  • .gradle
  • gradlew (Unix Shell script)
  • gradlew.bat (Windows batch file)
  • gradle/wrapper/gradle-wrapper.jar (Wrapper JAR)
  • gradle/wrapper/gradle-wrapper.properties (Wrapper properties)

.gradle是一个临时文件夹,不同的构建环境会导致其下的内容会频繁变化,因此需要将它排除在代码管理工具中,即在.gitignore中添加.gradle。构建过程中生成的.class文件及Jar包等都会存放到各个项目的build文件夹下,所以也需要将它写入.gitignore。

依赖管理

在Ant中,只能通过说明依赖jar包的绝对路径或相对路径,而Gradle只需要定义依赖包的名称,而由其它层来确定依赖包的具体位置。相同的特性Ant可以通过Apache Ivy来实现,但是Gradle做的更好。依赖本身也会有依赖的包,这叫做传递依赖transitive dependencies,这对Gradle来说不成问题。Gradle还可以将构建的包上传到远程的Maven或Ivy仓库,这叫做Publishing

build.gradle文件

apply plugin: 'java'
apply plugin: 'maven'

// maven仓库
repositories {
    // Use a Maven central repoitory
    mavenCentral()

    // Use a remote maven repoitory
    maven {
        url 'https://repo.gradle.org/gradle/libs-releases'
    }
}

// External dependencies
dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    deployerJars "org.apache.maven.wagon:wagon-ssh:2.2"
}

// [Interacting with Maven repositories][maven-repo]
UploadArchives {
    repositories {
        mavenDeployer {
            configuration = configurations.deployerJars
            repository(url: "scp://repos.mycompany.com/releases") {
                authentications(userName: "me", password: "myPassword")
            }
        }
    }
}

Gradle的依赖被分组到不同的configurations。每个configuration都是简单的依赖名字集合,用于定义项目的外部依赖。

Java Plugin定义了一组标准的configurations,这些configuration代表了Java plugin使用的classpaths。除了上述的compiletestCompile,常见的还有runtimetestRuntime

multi-project build

一个multi-project build的结构通常都包含如下部分:

  • 根文件夹中的setting.gradle
  • 根文件夹中的build.gradle
  • 各子项目文件夹包含自己的*.gradle(有些multi-project builds会忽略子项目的构建脚本)

可以使用子项目的文件夹名命名gradle构建脚本,或者更简单的方式是将所有的子项目的构建文件统一命名为build.gradle

Writing Gradle build scripts

从这一节开始,是Gradle的重点

Gradle中的所有东西都基于两个基本的概念:projectstasks,每一个Gradle项目由一个或多个project构成。

定义task

task hello {
    doLast {
        println 'Hello world!'
    }
}

# 使用shortcut定义
task hello << {
    println 'Hello world!'
}

# enhanced tasks
tasks.create(name: 'hello')

# print task name
println hello.name
println project.hello.name
println tasks.hello.name
println tasks['hello'].name

Some useful definition

// 定义默认任务
defaultTasks 'clean', 'run'

task clean << {
    println 'Default Cleaning!'
}

// gradle -q distribution
task distribution << {
    println "We build the zip with version=$version"
}

// gradle -q release
task release(dependsOn: 'distribution') << {
    println 'We release now'
}

gradle.taskGraph.whenReady { taskGraph ->
    if (taskGraph.hasTask(release)) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

task taskX << {
    println 'taskX'
}

task taskY << {
    println 'taskY'
}

taskX.dependsOn taskY

taskX.dependsOn {
    tasks.findAll {task -> task.name.startsWith('libs')}
}

task lib1 << {
    println 'lib1'
}

task lib2 << {
    println 'lib2'
}

// Ordering tasks
taskY.mustRunAfter taskX
taskY.shouldRunAfter taskX

// Multi-project
allprojects {
    task hello << { task -> println "I'm $task.project.name"}
}

subprojects {
    hello << {print "- I denpend on water"}
}

project(':bluewhale').hello << { task ->
    println "I'am subproject $task.project.name"
}

Up-to-data checks (增量构建)

增量构建避免了对同一个task重复执行,一旦你的源码被编译过了,那么在没有编写改变影响输出的代码前,不应该重新编译。

定义incremental task action

build.gradle

class IncrementalReverseTask extends DefaultTask {
    @InputDirectory
    def File inputDir

    @OutputDirectory
    def File outputDir

    @Input
    def inputProperty

    @TaskAction
    void execute(IncrementalTaskInputs inputs) {
        println inputs.incremental ? "CHANGE inputs considered out of data"
                                   : "All inputs considered out of data"
        if (!inputs.incremental)
            project.delete(outputDir.listFiles())

        inputs.outOfDate { change ->
            println "out of data: ${change.file.name}"
            def targetFile = new File(outputDir, change.file.name)
            targetFile.text = change.file.test.reverse()
        }

        inputs.removed { change ->
            println "removed: ${change.file.name}"
            def targetFile = new File(outputDir, change.file.name)
            targetFile.delete()
        }
    }
}

// An incremental task in action
task incrementalReverse(type: IncrementalReverseTask) {
    inputDir = file('inputs')
    outputDir = file("$buildDir/outputs")
    inputProperty = project.properties['taskInputProperty'] ?: "original"
}

Java Plugin

这个插件自动向项目中添加一些任务来对Java源码编译和单元测试,并将它打包成JAR文件。常用的命令有:

# build and the tasks
$ gradle build
:compileJava
:processResources
:classes
:jar
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

$ gradle clean

$ gradle assemble

$ gradle check

将一个Maven项目迁移到Gradle

在项目根目录下执行如下命令,亲测很强大,稍改一下就可以build了。

$gradle init

该命令会逐一解析存在的POM文件并生成Gradle构建文件以及包含muilt-project各项目信息的settings.gradle文件。

需要注意的是assemblies并不会自动转换,需要手动编写。这需要借助到Application Plugin

如下是我写的gradle构建文件,仅供参考。提醒一点,拷贝文件时的filter对@someParam@有效,而不是${someParam}

build.gradle

Maven Publish

apply plugin: 'maven-publish'
apply plugin: 'application'

def deployUsername = hasProperty("MAVEN_USERNAME") ? MAVEN_USERNAME : "your_repo_username"
def deployPassword = hasProperty("MAVEN_PASSWORD") ? MAVEN_PASSWORD : "your_repo_password"

publishing {
    // 上传位置

    repositories {
        maven {
            url "$artifactRepoBase/${project.version.endsWith('-SNAPSHOT')? 'snapshots':'releases'}"
            credentials {
                username deployUsername
                password deployPassword
            }
        }
    }

    // 发布
    publications {
        // 上传Jar包
        //mavenJava(MavenPublication) {
        //    from components.java
        //}

        pubZip(MavenPublication) {
            groupId 'com.startimestv.server.upms'
            artifactId "upms-${env}"
            version '3.11-SNAPSHOT'

            artifact distZip
        }
    }

}

上传命令:./gradlew publish

Another Gradle project

https://github.com/zhjwpku/ijkplayer/tree/master/android/ijkplayer

gradle的构建我不想写了,自己看吧…

– Updated 2016-11-21 –
Build C/C++

Gradle and C/C++ Workshop Part 1
Gradle and C/C++ Workshop Part 2