---
# System prepended metadata

title: Gradle 基礎 - 腳本
tags: [Gradle]

---

---
title: 'Gradle 基礎 - 腳本'
disqus: kyleAlien
---

Gradle 基礎 - 腳本
===

## Overview of Content

請先簡單建立一個 Gradle 項目，接下來的腳本都會操作該項目

[TOC]

## 項目、插件、任務 - 概述

1. **項目**：Gradle 可以管理 1 ~ 多個項目

    > 其中項目可能是 Jar Library, Android Application, Web Application... 等等

    :::warning
    項目不一定會有產品產出（建造東西），它可能代表某項需要完成的事項（eg. 部署）
    :::
    
2. **任務**：Gradle 中一個項目可由 1 ~ 多個任務定義，**任務在 Gradle 中就是最小單位（原子）**
    
    > 其中任務可能有清理、產生 Jar、發布任務 ... 等等

    :::success
    * **插件是任務的一種表示方式**，同常一個插件內會有 1 ~ 多個不等的任務
    :::

### 第一個腳本 - hello 

* Gradle 的腳本允許使用 `kotlin`、`groovy` 這兩種程式語言開發；接下來我們寫兩個簡單腳本（編寫專案中的 `build.gradle` 文件）


    1. 最簡單的腳本

        ```groovy=
        tasks.register("hello") {
            
            doLast {
                println "Hello script"
            }
            
        }
        ```

        使用 gradle CLI 執行 `hello` 任務

        ```shell=
        gradle -q "hello"
        ```
        > ![](https://hackmd.io/_uploads/rJPKev1Bn.png)

    2. 腳本中使用程式語言特性

        ```groovy=
        tasks.register("program") {

            doLast {
                println "Hello script".toUpperCase()
            }

        }
        ```
        使用 gradle CLI 執行 `program` 任務

        ```shell=
        gradle -q "program"
        ```
        > ![](https://hackmd.io/_uploads/SyPXGw1rn.png)

## 任務特性


### 依賴性 & 無順序性

* **任務之間可以有依賴性，而 Gradle 也會依照其依賴性，對任務順序作出調整**；範例如下

    `Banana` 任務，依賴於 `Apple` 任務
    ```groovy=
    tasks.register("Apple") {
        doLast {
            println "Apple task"
        }
    }

    tasks.register("Banana") {
        dependsOn("Apple")
        doLast {
            println "Banana task."
        }
    }
    ```
    使用 gradle CLI 執行 `Banana` 任務

    ```shell=
    gradle -q "Banana"
    ```
    > ![](https://hackmd.io/_uploads/BJi1vv1H2.png)

    :::info
    * **無順序性**：
        
        雖然是腳本，但任務也符合高級語言特性，不會有順序性（C 語言就有順序性）；修改如下也可以

        ```groovy=
        // 修改腳本順序

        tasks.register("Banana") {
            dependsOn("Apple")
            doLast {
                println "Banana task."
            }
        }

        tasks.register("Apple") {
            doLast {
                println "Apple task"
            }
        }
        ```
    :::

### 動態註冊

* 任務可以使用動態註冊（依照邏輯在運行時產生相對應任務）

    ```groovy=
    3.times { time ->

        tasks.register("hello_$time") {
            println "task $time finish."
        }

    }
    ```
    
    Gradle CLI 運行多個任務
    
    ```shell=
    gradle hello_0 hello_1 hello_2
    ```
    > ![](https://hackmd.io/_uploads/rJPQtw1Sh.png)


### 透過 Name 獲得任務

* 在腳本中有一個 `tasks` 參數，它存有全部的任務，透過 `tasks#named` 函數就可以取得之前已註冊的任務

    ```groovy=
    3.times { time ->

        tasks.register("hello_$time") {
            println "task $time finish."
        }

    }

    tasks.named("hello_1") {
        dependsOn("hello_0", "hello_2")
    }
    ```
    使用 gradle CLI 執行 `hello_1` 任務

    ```shell=
    gradle -q "hello_1"
    ```
    > ![](https://hackmd.io/_uploads/rydIoDkrh.png)


## 腳本特性


### 方法

* 撰寫 Gradle 腳本如同寫一般程式，腳本中可以有自己的方法，提供腳本內覆用
    
    > 你使用什麼語言就可以直接使用那種語言的方法格式
    > 
    > 以下使用 Groovy 語言，所以方法格式就是 Groovy 方法的格式
    ```groovy=
    tasks.register("script_function") {

        doLast {
            println getMsg()
        }

    }

    private String getMsg() {
        return "Hello function."
    }
    ```
    使用 gradle CLI 執行 `script_function` 任務

    ```shell=
    gradle -q "script_function"
    ```
    > ![](https://hackmd.io/_uploads/BJz_pv1H2.png)


### 默認任務 - defaultTasks

* 腳本本身有提供一個 `defaultTasks` 方法，該方法是 **提供所有任務的基礎依賴**（所有任務執行前都必須執行這些依賴）

    ```groovy=
    defaultTasks("clean", "init_")

    tasks.register("clean") {
        doLast {
            println "clean project."	
        }
    }

    tasks.register("init_") {
        doLast {
            println "init project."	
        }
    }
    ```
    
    運行空任務命令
    
    ```shell=
    gradle -q
    ```
    
    > ![](https://hackmd.io/_uploads/Skxz5JOkHn.png)


### 外部依賴 - Plugin

* 我們目前撰寫的腳本內所提供的方法，都是 gradle 提供給我們的，如果 **腳本需要使用到自己、第三方所提供的方法，那就必須寫外部依賴**
    
    > 這個外部依賴是針對腳本，讓腳本有其他 API 可用，**專案源碼 (Source code) 是無法使用外部依賴所提供的 API**

    :::info
    * 這種外部依賴通常就是 **以 `Plugin` 的方式出現**，提供給腳本使用
    :::
    
    
    ```groovy=
    import org.apache.commons.codec.binary.Base64

    buildscript {
        repositories {
            mavenCentral()
        }
        // 依賴外部程式，拓展腳本可使用的 API
        dependencies {
            classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
        }
    }

    tasks.register('encode') {
        doLast {
            // 原本 gradle 並沒有提供 Base64 類
            def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
            println new String(encodedString)
        }
    }
    ```
    
    Gradle CLI 執行 `encode` 任務
    
    ```shell=
    gradle -q encode
    ```
    
    > ![](https://hackmd.io/_uploads/r1oYG_ySh.png)

## API & Gradle

前面我們有提到（也有使用到），在腳本內可以使用某一些 API，而這些 API 又是 Gradle 提供；這裡我們就要來說明這些 API 與腳本的關係

### 項目的 API

* 我們知道一個專案中可以有多個項目，而 **一個項目又會配對一個腳本**，然後 **Gradle 在建構流程中就會幫我們創建一個 ++`Project` 對象++**
    
    **這個 ++`Project` 對象++ 就會提供給所有腳本使用**！

    :::success
    * [**Project API**](https://docs.gradle.org/8.1.1/dsl/org.gradle.api.Project.html) 全集請查看官方提供的 API

    * 所有腳本持有的 `Project` 對象 都相同
    :::

* 以下是幾個腳本中常見的 `Project` API

    | 參數名 | 類型 | 說明 |
    | - | - | - |
    | project | Project | Project 實例 |
    | name | String | 項目名稱 |
    | path | String | 項目絕對路徑 |
    | description | String | 項目描述 |
    | projectDir | File | 包含建構腳本的目錄 |
    | buildDir | File | 項目的 `build` 資料夾 |
    | group | Object | 未指定 |
    | version | Object | 未指定 |

    ```groovy=
    println "Project instance: " + project
    println "Project name: " + name
    println "Path: " + path
    println "Description: " + description
    println "ProjectDir: " + projectDir
    println "BuildDir: " + buildDir
    println "Group: " + group
    println "Version: " + version
    ```
    
    > ![](https://hackmd.io/_uploads/BJ99tOyr3.png)

### 腳本的 API

* 當 Gradle 在運行時，會將腳本（所有 `.gradle`、`.gradle.kts` 結尾的文件）進行分析，並 **創建出一個 ++`Script` 對象++**

    這個 **++`Script` 對象++ 就可以提供各自腳本使用**
    
    :::info
    * 不同語言腳本產生的對象也不同
        
        * `.gradle` 腳本產生 [**`Script` 對象**](https://docs.gradle.org/8.1.1/dsl/org.gradle.api.Script.html)
    
        * `.gradle.kts` 腳本產生 [**`KotlinBuildScript` 對象**](https://gradle.github.io/kotlin-dsl-docs/api/org.gradle.kotlin.dsl/-kotlin-build-script/index.html)
    :::

    // TODO: Some sample


## 腳本變量

我們可以在腳本中建立所需的變量，在 Gradle 中變量分為幾類：^1.^ **局部變量**、^2.^ **額外屬性**、^3.^ **腳本變量**


### 局部變量

* 局部變量會根據你腳本使用的語言而有不同的宣告方式

    * `Groovy` 使用 `def` 關鍵字：**只在聲明範圍內可見（也就是超出範圍後，就不在可見）**
        
        > `Kotlin` 則使用 `val` 關鍵字，同樣在聲明範圍內可見


        範例如下：
        
        ```groovy=
        def localExValue = "Local ex-value"

        tasks.register("Hello") {
            def innerValue = 10

            doLast {
                println "Local ex-value: " + localExValue
                println "Inner value: " + innerValue
            }
        }

        tasks.register("World") {
            doLast {
                println "Local ex-value: " + localExValue
                // Error ！！ 
                println "Inner value: " + innerValue
            }
        }
        ```
        > ![](https://hackmd.io/_uploads/rkd_gtySn.png)
        
        
### 額外屬性 - ext

* 額外屬性 (`ext`) 是 Project 屬性中的一個成員（類型為 [**ExtraPropertiesExtension**](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html)），透過它就可以拓展而外的屬性

    :::info
    * Gradle 中的 `Project` 對像在單項目構建中是唯一的，而在多項目構建中，根項目和每個子項目都有一個對應的 `Project` 對象
        
        > 也就是說每個項目都可以有自己的 `ext` 拓展
    
    :::

    範例如下
    
    ```groovy=
    plugins {
        id 'java-library'
    }

    // 額外屬性設置
    ext {
        springVersion = "3.1.0.RELEASE"
        emailNotification = "build@master.org"
    }

    sourceSets.configureEach { ext.purpose = null }

    sourceSets {
        main {
            purpose = "production"
        }
        test {
            purpose = "test"
        }
        plugin {
            purpose = "production"
        }
    }

    tasks.register('printProperties') {
        def springVersion = springVersion
        def emailNotification = emailNotification
        def productionSourceSets = provider {
            sourceSets.matching { it.purpose == "production" }.collect { it.name }
        }
        doLast {
            println springVersion
            println emailNotification
            productionSourceSets.get().each { println it }
        }
    }
    ```

    > ![](https://hackmd.io/_uploads/HJBACsgH2.png)

### 配置任意對象 - configure

* `configure` 也是 Project 提供的方法，透過它可以有結構性的獲得你需要的對象

    ```groovy=
    // configure_object.gradle

    class UserInfo {
        String name
        String email
    }

    tasks.register('configure') {
        def user = configure(new UserInfo()) {
            name = "Isaac Newton"
            email = "isaac@newton.me"
        }
        doLast {
            println user.name
            println user.email
        }
    }
    ```
    > ![](https://hackmd.io/_uploads/rJI5x3gr3.png)

### 腳本變量 - 作用域

* Groovy 有三種腳本變量，它聲明方式也不同
    
    * **使用 `def` 聲明的變量**：只能在該 **腳本範圍內被使用**

        ```groovy=
        def localScope2 = 'localScope2'
        ```
    
    * **使用指定類型聲明**：同上，只能在腳本內使用
    
        ```groovy=
        String localScope1 = 'localScope1'
        ```
        
    * **無任何聲明**：無任何聲明的變量，可以在 **任何腳本內獲取**

        :::warning
        * 最新版本 8.1.1 好像已經取消無聲明變數了
        :::

## Appendix & FAQ

:::info
:::

###### tags: `Gradle`
