1. 程式人生 > >Building and Testing with Gradle筆記2——Gradle Tasks

Building and Testing with Gradle筆記2——Gradle Tasks

宣告一個Task

task hello

執行gradle tasks輸出當前Project中所有task

:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Help tasks
----------
components - Displays the components produced by root project 'gradle-test-2'. [incubating] dependencies - Displays all dependencies declared in root project 'gradle-test-2'. dependencyInsight - Displays the insight into a specific dependency in root project 'gradle-test-2'. help - Displays a help message. model - Displays the configuration model of root project 'gradle-test-2'
. [incubating] projects - Displays the sub-projects of root project 'gradle-test-2'. properties - Displays the properties of root project 'gradle-test-2'. tasks - Displays the tasks runnable from root project 'gradle-test-2'. Other tasks ----------- hello

Gradle每當開始執行一個Task,都會輸出一個冒號加上該task的名字,如:tasks

。因此tasks也是一個task,正如輸出所示。

新增Task要執行的操作

聲明後的Task可以作為一個可程式設計的物件,使用左移操作符給其新增要執行的操作

task hello

hello << {
    print 'hello, '
}

hello << {
    println 'world'
}

配置Task

task initializeDatabase
initializeDatabase << { println 'connect to database' }
initializeDatabase << { println 'update database schema' }
initializeDatabase { println 'configuring database connection' }

執行gradle -b scratch.gradle initializeDatabase輸出:

configuring database connection
:initializeDatabase
connect to database
update database schema

首先,-b選項可以執行gradle要執行的build檔案。
其次,沒有使用左移操作符的程式碼塊是指定task的配置部分。在Gradle的宣告週期的配置階段執行,早於task的執行階段。比配置階段更早的是初始化階段。

配置程式碼塊同樣可以追加,如下:

task initializeDatabase
initializeDatabase << { println 'connect to database' }
initializeDatabase << { println 'update database schema' }
initializeDatabase { print 'configuring ' }
initializeDatabase { println 'database connection' }

Task是物件

Task物件的預設型別是DefaultTask,Gradle中的所有Task都是派生自該型別。正如Java中的所有物件都派生自java.lang.Object

DefaultTask的方法

dependsOn(task)

// Declare that world depends on hello
// Preserves any previously defined dependencies as well
task loadTestData {
    dependsOn createSchema
}

// An alternate way to express the same dependency
task loadTestData {
    dependsOn << createSchema
}

// Do the same using single quotes (which are usually optional)
task loadTestData {
    dependsOn 'createSchema'
}

// Explicitly call the method on the task object
task loadTestData
loadTestData.dependsOn createSchema

// A shortcut for declaring dependencies
task loadTestData(dependsOn: createSchema)

如果一個Task有多個依賴,如下:

// Declare dependencies one at a time
task loadTestData {
    dependsOn << compileTestClasses
    dependsOn << createSchema
}

// Pass dependencies as a variable-length list
task world {
    dependsOn compileTestClasses, createSchema
}

// Explicitly call the method on the task object
task world
world.dependsOn compileTestClasses, createSchema

// A shortcut for dependencies only
// Note the Groovy list syntax
task world(dependsOn: [ compileTestClasses, createSchema ])

doFirst(closure)

傳遞一個closure在指定Task執行之前執行,注意和Task配置區分。

task setupDatabaseTests << {
    // This is the task's existing action
    println 'load test data'
}

setupDatabaseTests.doFirst {
    println 'create schema'
}

執行gradle setupDatabaseTests輸出,

:setupDatabaseTests
create schema
load test data

當然,這個closure可以在Task配置塊中指定:

task setupDatabaseTests << {
    println 'load test data'
}

setupDatabaseTests {
    doFirst {
        println 'create schema'
    }
}

doFirst也可以追加closure:

task setupDatabaseTests << {
    println 'load test data'
}

setupDatabaseTests.doFirst {
    println 'create database schema'
}

setupDatabaseTests.doFirst {
    println 'drop database schema'
}

輸出如下:

:setupDatabaseTests
drop database schema
create database schema
load test data

doLast(closure)

示例如下:

task setupDatabaseTests << {
    println 'create database schema'
}

setupDatabaseTests.doLast {
    println 'load test data'
}

setupDatabaseTests.doLast {
    println 'update version table'
}

本質上,<<操作符等同於doLast方法。

onlyIf(closure)

在Task執行以前,進行判斷,是否要執行該Task。

task createSchema << {
    println 'create database schema'
}

task loadTestData(dependsOn: createSchema) << {
    println 'load test data'
}

loadTestData.onlyIf {
    System.properties['load.data'] == 'true'
}

直接執行build loadTestData輸出:

:createSchema
create database schema
:loadTestData SKIPPED

指定一個系統屬性並執行gradle -Dload.data=true loadTestData,輸出:

:createSchema
create database schema
:loadTestData
load test data

DefaultTask的屬性

didWork

判斷一個Task是否執行成功

apply plugin: 'java'

task emailMe(dependsOn: compileJava) << {
    if(tasks.compileJava.didWork) {
        println 'SEND EMAIL ANNOUNCING SUCCESS'
    }
}

enabled

是否啟用Task

task templates << {
    println 'process email templates'
}

task sendEmails(dependsOn: templates) << {
    println 'send emails'
}

sendEmails.enabled = false

執行gradle -b enabled.gradle sendEmails,輸出:

:templates
process email templates
:sendEmails SKIPPED

path

task echoMyPath << {
    println "THIS TASK'S PATH IS ${path}"
}

執行gradle -b path.gradle echoMyPath,輸出:

THIS TASK'S PATH IS :echoMyPath

這表示該Task位於頂層Project,如果是子專案subProject的Task,那麼path應該是:subProject:echoMyPath

logger

Gradle內部的logger引用,看程式碼:

task logLevel << {
    def levels = ['DEBUG',
                'INFO',
                'LIFECYCLE',
                'QUIET',
                'WARN',
                'ERROR']

    levels.each { level ->
        logging.level = level
        def logMessage = "SETTING LogLevel=${level}"
        logger.error logMessage
        logger.error '-' * logMessage.size()
        logger.debug 'DEBUG ENABLED'
        logger.info 'INFO ENABLED'
        logger.lifecycle 'LIFECYCLE ENABLED'
        logger.warn 'WARN ENABLED'
        logger.quiet 'QUIET ENABLED'
        logger.error 'ERROR ENABLED'
        println 'THIS IS println OUTPUT'
        logger.error ' '
    }
}

總而言之,logger的級別優先順序,從低到高。

logging

description

設定一個Task的描述資訊:

task helloWorld(description: 'Says hello to the world') << {
    println 'hello, world'
}

或者

task helloWorld << {
    println 'hello, world'
}

helloWorld {
    description = 'Says hello to the world'
}

又或者:

task helloWorld << {
    println 'hello, world'
}

helloWorld.description = 'Says hello to the world'

temporaryDir

以File物件的形式返回屬於當前build檔案的一個臨時目錄

動態屬性

每個Task除了上述固有屬性外還可以任意新增屬性,本質上就是新增鍵值對。

task copyFiles {
    // Find files from wherever, copy them
    // (then hardcode a list of files for illustration)
    fileManifest = [ 'data.csv', 'config.json' ]
}

task createArtifact(dependsOn: copyFiles) << {
    println "FILES IN MANIFEST: ${copyFiles.fileManifest}"
}

執行gradle -b dynamic.gradle createArtifact,輸出:

FILES IN MANIFEST: [data.csv, config.json]

Task型別

除了DefaultTask,還有一些用於複製,打包,執行程式的Task型別。宣告一個Task型別就好像在面向物件程式設計中擴充套件一個類。

Copy

task copyFiles(type: Copy) {
    from 'resources'
    into 'target'
    include '**/*.xml', '**/*.txt', '**/*.properties'
}

from,into,和include方法都是繼承自Copy。

Jar

這是java外掛中引入的一個Task,Jar用於將原始碼打包成一個jar檔案。我們同樣可以擴充套件這個Task。

apply plugin: 'java'

task customJar(type: Jar) {
    manifest {
        attributes firstKey: 'firstValue', secondKey: 'secondValue'
    }
    archiveName = 'hello.jar'
    destinationDir = file("${buildDir}/jars")
    from sourceSets.main.classes
}

jar包的清單檔案屬性可以輕易的使用Groovy的map字面量建立。file方法用於從字串構造一個java.io.File物件。from方法是Jar從Copy繼承而來。

JavaExec

該Task用於執行一個帶有main方法的Java類

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    runtime 'commons-codec:commons-codec:1.5'
}

task encode(type: JavaExec, dependsOn: classes) {
    main = 'org.gradle.example.commandline.MetaphoneEncoder'
    args = "The rain in Spain falls mainly in the plain".split().toList()
    classpath sourceSets.main.classesDir
    classpath configurations.runtime
}

自定義Task型別

在build檔案中建立自定義Task型別

task createDatabase(type: MySqlTask) {
    sql = 'CREATE DATABASE IF NOT EXISTS example'
}

task createUser(type: MySqlTask, dependsOn: createDatabase) {
    sql = "GRANT ALL PRIVILEGES ON example.* TO [email protected] IDENTIFIED BY 'passw0rd'"
}

task createTable(type: MySqlTask, dependsOn: createUser) {
    username = 'exampleuser'
    password = 'passw0rd'
    database = 'example'
    sql = 'CREATE TABLE IF NOT EXISTS users(id BIGINT PRIMARY KEY, username VARCHAR(100))'
}

class MySqlTask extends DefaultTask {
    def hostname = 'localhost'
    def port = 3306
    def sql
    def database
    def username = 'root'
    def password = 'password'

    @TaskAction
    def runQuery() {
        def cmd
        if(database) {
            cmd = "mysql -u ${username} -p ${password} -h ${hostname} -P ${port} ${database} -e "
        } else {
            cmd = "mysql -u ${username} -p ${password} -h ${hostname} -P ${port} -e "
        }
        project.exec {
            commandLine = cmd.split().toList() + sql
        }
    }
}

上面定義的每個Task都用於執行一條sql語句。

在原始碼樹中建立自定義Task型別

如果自定的Task較為複雜,應該將它們儲存為單獨的Groovy檔案。上面的例子還可以寫成這樣,將build檔案中的class移到一個單獨的檔案中MySqlTask.groovy,專案的結構如下:

|---build.gradle
\---buildSrc
    \---src
        \---main
            \---groovy
                \---org
                    \---gradle
                        \---example
                            \---task
                                \---MySqlTask.groovy