讀懂 Gradle 的 DSL

如今 Android 開發免不了要和 Gradle 打交道,全部的 Android 開發確定都知道這麼在 build.gradle 中添加依賴,或者添加配置批量打包,可是真正理解這些腳本的人恐怕不多。其實 Gradle 的 build.gradle 能夠說是一個代碼文件,熟悉 Java 的人理解起來很簡單的,之因此不肯意去涉及,主要感受沒有必要去研究。要能看懂 build.gradle,除了要了解 Groovy 的語法,還要了解 Gradle 的構建流程,要研究仍是要花一些時間的,因此這篇文章可讓一個 Java 程序員在一個小時內看懂 Gradle 的腳本。java

Gradle 簡單介紹

Gradle 構建由 Project 和 Task 組成,Project 保存項目的屬性,例如 name,版本號,代碼文件位置。Task 也是 Project 的一部分,可是它是可執行的任務,咱們最常使用的 build 就是一個 Task,Task 能夠依賴於另一個 Task,一個 Task 在執行的時候,它依賴的 Task 會先執行。這樣,當咱們 build 的時候,這個 Task 可能依賴不少的 Task,好比代碼檢查、註解處理,這樣一層層的依賴,最終經過 build Task 所有執行。android

Gradle 和 Groovy

Gradle 和 Groovy 這兩個名字很容易讓人產生混淆,這裏先解釋一下,Groovy 是一門編程語言,和 Java 同樣。Gradle 和一個自動化構建工具,其餘知名的構建工具還有 Maven 和 Ant。什麼自動化構建工具?用 Android 來舉例,打包一個 Apk 文件要作不少工做,代碼預處理,lint代碼檢查、處理資源、編譯 Java 文件等等,使用自動化構建工具,一個命令就能夠生成 Apk 了。程序員

Gradle 的 DSL 目前支持兩種語言的格式,Groovy 和 Kotlin,Kotlin 格式的 DSL 是在 5.0 引入的,相比 Groovy,Kotlin 使用的人數更多,也更好理解,在這兒主要介紹 Groovy 格式的 DSL。apache

介紹一下什麼是 DSL,DSL 是 Domain Specific Language 的縮寫,既領域專用語言。Gradle 的 DSL 專門用於配置項目的構建,不能作其餘工做,而像 Java 、C/C++ 這些就屬於通用語言,能夠作任何工做。編程

咱們還要理解什麼是腳本文件。在寫代碼 Java 代碼時,程序是從 main() 函數開始執行的,只有在 main() 中調用的代碼纔會執行。可是腳本文件不同,只要在文件內寫的代碼都會執行,Groovy 是支持腳本文件的,咱們配置好 Groovy 的開發環境,新建一個文件 test.groovy,輸入如下內容:api

String hello = "Hello World!"
println(hello)

println("The End")

而後運行:閉包

groovy test.groovy

輸出結果爲:app

Hello World!
The End

雖然沒有 main 函數,可是裏面的代碼都執行了。很明顯,build.gradle 就是一個 Groovy 的腳本文件,裏面就是 Groovy 代碼,裏面添加的全部代碼都會運行,咱們能夠試驗如下,隨便打開一個 Gradle 格式的項目,在 build.gradle 最下面添加一些 Java 代碼:maven

String hello = "Hello World!"
System.out.println(hello)

而後執行:編程語言

./gradlew -q # -q 是不輸出額外的信息

咱們會看到輸出了 Hellow World,說明咱們添加的代碼被執行了,那麼爲何能夠在 build.gradle 裏面寫 Java 代碼呢?這是由於 Groovy 是支持 Java 的語法的,在 Groovy 文件寫 Java 代碼是徹底沒有問題的。

build.gradle 的執行方式

如今總結一下,build.gradle 就是一個 Groovy 格式腳本文件,裏面是 Groovy 或者 Java 代碼,構建的時候會順序執行,可是打開 build.gradle,可能仍是一頭霧水,一個個字符和大括號組成的東西究竟是什麼鬼?咱們來看一下最長使用的 dependencies

dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

implementation 也能夠這樣寫:

implementation('com.google.guava:guava:26.0-jre')

implementation 其實就是一個函數,在 Groovy 中,函數調用可使用空格加參數的形式調用,例如

void foo(String params1, int param2) {
    println("param1 = $params1, param2 = $param2")
}

foo "aaa", 2

implementation 'com.google.guava:guava:26.0-jre' 就是調用了 implementation 函數添加了一個依賴。以此類推,dependencies 也是一個函數,在 IDEA 中,咱們能夠直接 Ctrl 加鼠標左鍵點擊進去看它的聲明:

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    // ...
    void dependencies(Closure configureClosure);
    // ...
}

咱們看到 dependenciesProject 一個方法,爲何能夠在 build.gradle 調用 Project 的方法呢,官方文檔裏面有相關的介紹。一個 Gradle 項目通常有一個 settings.gradle 文件和一個 build.gradle 文件,settings.gradle 用來配置目錄結構,子工程就是在 settings.gradle 裏面配置,Projectbuild.gradle 是一一對應的關係,Gradle 的構建流程以下:

一、生成一個 Settings 對象,執行 settings.gradle 對這個對象進行配置
二、使用 Settings 對象生成工程結構,建立 Project 對象
三、對全部 Project 執行對應的 build.gradle 進行配置

build.gradle 就是對 Project 的操做,例如,在 build.gradle 中輸入如下代碼

println "project name is ${this.name}"

輸出結果爲: project name is java_demojava_demo 就是咱們的 project name,咱們能夠認爲對 this 的操做就是對 project 的操做。

Groovy 也是有語法糖的,類的屬性能夠直接使用名字,例如 Project 的有兩個函數:

Object getVersion();
void setVersion(Object version);

那麼這就說明 Project 有一個 version 屬性,在 build.gradle 中咱們能夠這樣來使用:

version = "1.0" // 賦值,調用 setVersion()
println version // 讀取,調用 getVersion()

Project 中沒有 getter 方法的屬性是不能賦值的,例如 name,咱們能夠輸出 name 的值,可是 name = "demo" 是錯誤的。

因此,在 build.gradle 中的代碼就是修改 Project,方式就是修改屬性或者調用相關的方法,plugins 方法是添加插件,repositories 方法是添加代碼倉庫,

Groovy 閉包

閉包能夠認爲是能夠執行的代碼塊,Groovy 中閉包的聲明和執行方式以下:

Closure closure = { String item ->
    println(item)
}

closure("Hello") // 執行

和 Lambda 表達式很像,可是 Groovy 的閉包能夠先聲明,而後設置代理來執行,例如咱們聲明一個閉包:

Closure closure = {
    sayHello()
}

這個閉包裏面執行了 sayHello() 函數,可是咱們沒有在任何地方聲明這個函數,在 Java 中,這是個編譯錯誤,可是 Groovy 是容許的,完整的執行的例子以下:

Closure closure = {
    sayHello()
}
class Foo {
    void sayHello() {
        println("Hello!!!")
    }
}
def foo = new Foo()

closure.delegate = foo
closure()

輸出結果爲:

Hello!!!

咱們爲閉包設置了一個代理 delegate,只要這個代理有 sayHello() 方法,代碼就能執行,這就是爲何咱們查看 Project 的源碼,裏面不少函數參數類型都是 Closure,例如:

void repositories(Closure configureClosure);
void dependencies(Closure configureClosure);

repositoriesbuild.gradle 中是這樣調用的:

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

咱們經過 IDE 進入 jcenter() 的聲明,進入的是:

public interface RepositoryHandler extends ArtifactRepositoryContainer {
    // ...
}

因爲沒看過源碼,我也只能猜,我猜 repositories 這個閉包的 delegate 是一個 RepositoryHandler,經過執行 RepositoryHandler 的方法,爲工程添加 Repository

Plugin

來看咱們使用最多的 dependencies

dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'

    implementation('com.google.guava:guava:26.0-jre')

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

在 Java 和 Android 項目中 implementation 是必定會用到的,可是一個 Gradle Basic 項目是沒有 implementation 的,實際上,在 dependencies 是不能直接添加任何依賴的。

這裏咱們有說一下 Gradle 怎麼解決依賴。

Gradle 空白項目沒有編譯 Java 項目的能力,可是它能從倉庫下載依賴的庫而且配置到 Project 中。在咱們編譯 Java 項目的時候,一個配置是不夠的,至少要有個測試版,正式版,兩個版本依賴的庫多是不同的,兩個版本部分代碼也是不同的,那麼咱們怎麼區分呢?在 Gradle 中,是經過 configurations,也就是配置,每一個配置能夠單獨的添加依賴,在編譯的時候,也就是執行某個 Task 的時候,經過讀取配置中的依賴來添加 classpath,例如:

repositories {
    mavenCentral()
}

configurations {
    test
    release
}

dependencies {
    test 'org.apache.commons:commons-lang3:3.0'
    release 'org.slf4j:slf4j-log4j12:1.7.2'
}



task buildTest {
    doLast {
        println configurations.test.name
        println configurations.test.asPath
    }
}

執行 ./gradlew buildTest -q,輸出結果爲:

test
/Users/xxx/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.0/8873bd0bb5cb9ee37f1b04578eb7e26fcdd44cb0/commons-lang3-3.0.jar

若是在 buildTest 這個 Task 中進行編譯工做的話,咱們就能夠直接讀取 configurations.test 的路徑設置爲 classpath

implementation 就是經過添加了一個 implementation 配置來實現的。這個配置是經過:

plugins {
    // Apply the java plugin to add support for Java
    id 'java'

    // Apply the application plugin to add support for building an application
    id 'application'
}

添加的,咱們經過 plugins 能夠給 Project 添加屬性,Tasks,配置,例如咱們寫一個最簡單的插件:

package com.demo

import org.gradle.api.Plugin
import org.gradle.api.Project

class DemoPlugin implements Plugin<Project> {
    void apply(Project project) {

        project.task("hello") {
            doLast {
                println "Hello World"
            }
        }

        project.configurations {
            demoCompile
        }
    }
}

這個插件爲 Project 添加了一個 Task,添加了一個配置,咱們將這個文件 DemoPlugin.groovy 放在項目根目錄下的 buildSrc/src/main/groovy/demo/ 下,就能夠在 build.gradle 中直接使用了:

apply plugin: com.demo.DemoPlugin

buildscript

對於 buildscript,例如:

buildscript {
    repositories {
        mavenCentral()
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.0'
    }
}

它的做用是爲構建腳本提供依賴,例如咱們在項目中使用了 Android 的 Plugin,這個 Plugin 的要從哪找下載?這就須要在 buildscript 中指定。

相關文章
相關標籤/搜索