--- title: 'Gradle 基礎 - Gradle、GradleWrapper' disqus: kyleAlien --- Gradle 基礎 - Gradle、GradleWrapper === ## OverView of Content [TOC] ## Gradle 概述 **Gradle 是一款==建構系統工具==**,它的 **DSL 基於 ++Groovy 實現++**,可以控制 DSL 來達到建構系統的目的 > Gradle 建構的大部分功能都是通過插件的方式來實現,當然若不符合需求可以自定義所需的插件 ### GPL & DSL 語言 1. `GPL` 的全名是 **General-purpose language** (通用用途語言),它是對語言的描述,代表了該類型語言是跨應用領域 > 又稱為第三等級語言:C/C++、Java 2. `DSL` 的全名是 **Domain-specific language** (區域特定語言),它描述的是針對特定應用領域使用的語言 (像是在編譯程式) > 又稱為第四等級語言:Gradle、make ### 個家專案管理 - 特點 * 一般來講,我們撰寫的 Source code 需要透過不同的工具編譯、聚集、打包、測試,這些工作最初都是由人工手動執行,如果是大項目很可能會造成花費過多時間在這裡 (出錯可能性也變大) * 藉此引入自動化工具 (Gradle 就是其中一個) 有以下好處 1. 項目自動化可以盡量避免手動操作所導致的錯誤 2. 項目自動化可以有效、有順序的執行編譯、測試、打工等等工作,因為這些工作都有固定的步驟 3. 自動化工具 **必定與操作平台無關**,不依賴指定 IDE、平台 * 在 Gradle 出現之前有三個基於 Java 開發的建構工具:^1.^ `Ant`、^2.^ `Gant`、^3.^ `Maven` | 建構工具 | 格式 | 特點 | 缺點 | | -------- | -------- | -------- | - | | Ant | XML (`build.xml`) | 主要由 Project、Target、Task 組成 | 須配合 Ivy 管理、無法獲取運行時訊息、項目大腳本難理解 (自由度高) | | Gant | Groovy (`build.gant`) | 基於 Ant | | | Maven | XML (`pom.xml`) | 繼承 Ant 項目建構功能 | 具有依賴管理、項目管理,提供下載的 Repository | :::success * **Ant** vs. **Maven** ? 1. Ant 必須每個項目都重新建構 (包括沒有改動到的檔案),而 Maven 每個階段的建構都是 **使用插件實現** 2. Ant 必須配合 Iny 作依賴項目管理,而 Maven 自身就可以依賴項目管理 3. Maven 有需多默認參數 (包括建構 src 的路徑等等),而 Ant 沒有預設值,必須全部寫在 XML 中 ::: :::warning * **Maven** 缺點 ? 1. Maven 提供默認的結構、生命週期,而部分項目可能無法適配 2. Maven 的擴展較為繁瑣 > Gradle 則改善了 Maven 的缺點 ::: ### Gralde - 5 個特點 * Gradle 是眾多建構專案工具中的其中一個,我們可以先了解幾個它的特點,這可以幫助我們學習;Gradle 特點如下 1. **Gradle 是通用的建構工具** 我初次認識到 Gradle 是由於 Android 專案,但 Gradle 其實並沒有限制於建構某項專案;Gradle 幾乎可以建構所有專案 > Gradle 透過添加協議、插件來建構(也可以讓我們拓展,建構自己的插件) 2. **Gradle 核心基於任務** Gralde 將每個建構(Build)的基本單位稱為任務,**這些任務是有向任務(DAG)**;當任務運行時,Gradle 就會依照有向依賴來執行不同任務(如下圖) > ![](https://i.imgur.com/az5NibT.png) 任務的組成包括如下(**以下皆是可選選項,並非固定要實現**) * `Action`:任務的確切行為,抱能包括複製、編譯、檢查... 等等 * `Input`:該任務可操作的文件、目錄... 等等 * `Output`:該任務最終的輸出,可能是生成文件、目錄... 等等 3. **Gradle 建構階段**: 這可以認為是任務的生命週期,其階段是 ^1.^ 初始化、^1.^ 配置、^1.^ 執行任務 :::success * Gradle 建構腳本是由聲明式(抽象)配置,而不是命令式(具體)邏輯 聲明式配置指的是在 Gradle 腳本中聲明你希望使用的庫、插件、依賴、任務...等等,而不是像命令式邏輯一樣寫明每個步驟的具體實現方式 > 聲明式配置的好處是可以讓你的腳本更加簡潔和易讀,並且容易進行重複使用和重構 命令式的概念就如同 Bash Shell 要寫出具體的每個命令步驟 > 項目變大時,其邏輯就會複雜,難以維護 ::: 4. **可拓展的建構** 除了 Gradle 為我們提供的基本任務之外,還可以透過自訂任務的方式拓展,其包括了: * 自定義任務類型 * 自定義任務操作 * Project & Task 的拓展屬性 * 客製化 conventions (契約、約定) > 可以簡化建構的步驟 * 客製化 module(模型) 5. **腳本操作 API** 可簡單地將 Gralde 看作為建構一個可執行的文件(將任務之間做依賴、串接,而不必關注任務的具體細節) 我們在建構的腳本會相應對照到 Gralde API :::info * 由於 Gralde 運行在 JVM 環境之下,所以可以使用大部分的 Java API > Kotlin 建構的腳本,則可以使用 Kotlin 的 API ::: ## Gradle 環境配置 以下描述的環境配置在 Windows、Ubuntu 兩個環境底下,配置好這環境才可以使用 Gradle,**以下主要描述 Ubuntu 配置的環境變量,配置 ==Java==、==Gradle== 環境** ### Gradle - Window 環境配置 * Window 只需安裝過後將安裝目錄加入到系統環境變量 > Window 環境變數,`控制台` -> `系統及安全性` -> `系統` -> `進階系統設定` -> `進階` -> `環境變數`;加入 Gradle、Java 的子目錄 bin > > ![](https://i.imgur.com/UBa5ICZ.png) ### Gradle - Ubuntu 環境 * 由於 Groovy 也是運行在 JVM 虛擬機之上,所以必須安裝 `JDK` or `JRE` 兩個環境,在 Ubuntu 下是使用 `open jdk` 1. Ubuntu 下載 open jdk、[**gradle**](https://gradle.org/releases/) * Gradle 下載 > ![](https://i.imgur.com/Q6lo4GK.png) 解壓縮 Gradle,會得到以下這些文件 | 資料夾名稱 | 功能、介紹 | | -------- | -------- | | bin | bat 腳本 | | docs | API、DSL、指南文件等等 | | init.d | gradle 的初始化腳本目錄 | | lib | 相關的庫 | | src | source code 原文件 | > ![](https://hackmd.io/_uploads/B1qyyUmN3.png) * Open jdk 下載 ```shell= # open jdk sudo apt install -y openjdk-11-jdk ``` :::success * [**SDK Main**](https://sdkman.io/) SDK Main 是一個集成很多工具的管理器,可以透過它來下載 OpenJDK、Gradle ::: 3. 配置 bin 目錄到 [**環境變量**](https://hackmd.io/MtOap5UaR7KcJpdTisc75g#%E7%92%B0%E5%A2%83%E8%AE%8A%E9%87%8F) 中 > 以下是 Gradle 的範例 ```shell= # 編譯使用者的 .bashrc 文件 vim ~/.bashrc # 使用者的 Gradle 文件目錄 GRADLE_HOME=/home/alien/gradle # PATH 串接,上方 gradle 目錄/bin PATH=${PATH}:${GRADLE_HOME}/bin # 使其生效 Export GRADLE_HOME PATH ``` 4. 確認是否設置環境變數成功 ```shell= # check java java -version # check gradle gradle -v ``` ## Gradle wrapper 顧名思義 Wrapper 就是多了一層對 Gradle 的包裝,它的好處有 * **便於團隊開發統一 Gradle 建構的版本**,也就是說開發同個專案的人會使用相同版本的 Gradle 建構 * 為不同環境(Linux, Mac, Windows)又或是不同 IDE 的使用者提供對應版本的 Gradle,而且 **版本的修改完後,Wrapper 會自動幫你下載** > 概念圖如下 > > ![](https://hackmd.io/_uploads/HkTfjaY4h.png) ### Gradle wrapper 配置 1. 使用指令 `gradle wrapper`,**在項目的根目錄下使用** :::warning * 如果你尚未初始化 gradle 請先執行 `gradle init` 的命令,初始化過後的資料夾內容應該如下 > 這裡我建立了 Java Application 項目 > > ![](https://hackmd.io/_uploads/SymmPat4n.png) ::: ```groovy= // 指令 gradle wrapper ``` 2. 先介紹 Gralde init & Gradle wrapper 生成的資料 & 檔案;以下介紹幾個較重要的檔案 | 檔案 | 功能 | | -------- | -------- | | `gradlew` | Linux 可執行腳本 | | `gradlew.bat` | Window 可執行腳本 | | `gradlew-wrapper.jar` | **Gradle wrapper 發行版,實現的 jar 包** | | `gradlew-wrapper.properties` | **配置文件,用來==配置使用哪個版本的 gradle==** | > ![](https://hackmd.io/_uploads/SyXZO6t4n.png) ### Wrapper 指令設定 Gradle 版本 * 我們可以透過 CLI Gradle wrapper 指令去指定不同版本的 Gradle,指定完後它就會幫我們下載指定版本的 Gradle > 這些指令最終都會影響到 `gradle-wrapper.properties` 內容 | Options | 說明 | | -------- | -------- | | `--gradle-version` | 指定 Gradle 版本(可用指定標籤) | | `--gradle-distribution-url` | **指定下載 Gradle 發行版本的 url 地址** | | `--gradle-distribution-sha256-sum` | 下載有驗證的 gradle 版本 | | `--distribution-type` | Gradle 版本類型,可選 `bin` or `all`,預設為 `bin` | | `--network-timeout` | 指令等待時長,預設為 10000 | 1. 範例:透過 gradle wrapper `--gradle-version` 將專案中的 Gradle 設定為 2.4 版 ```shell= # 設定 2.4 版本 gradle wrapper --gradle-version 2.4 ``` > ![](https://hackmd.io/_uploads/SJwZlRtN3.png) 2. 範例:透過 gradle wrapper `--gradle-version` 將專案中的 Gradle 設定為最新版(透過 `latest` 標籤) > 目前標籤有 `latest`、`release-candidate`、`nightly`、`release-nighty` ```shell= # 設定最新版本 gradle wrapper --gradle-version latest --distribution-type all ``` > ![](https://hackmd.io/_uploads/BJLqUAFE2.png) 3. 範例:gradle wrapper `--gradle-distribution-url` 將專案中的 Gradle 設定為 5.6.4 版 ```shell= # 設定下載 URL,切換成 5.6.4 gradle wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-5.6.4-all.zip ``` > ![](https://hackmd.io/_uploads/S1RwlRFVn.png) :::success * Gradle 其他版本 [**網址來源**](https://services.gradle.org/distributions/) :::warning * 若是指令等待時間過久 or 卡住,可能要檢查是否可以對此 url 進行訪問 > 有可能 URL 不合法、或者該版本根本被下架了 ::: ::: ### gradle-wrapper.properties 內容 * 看到上面使用 gradle wrapper 指令下載後,`gradle-wrapper.properties` 參數會跟著修改,接下來看看 properties 內部參數說明 > ![](https://hackmd.io/_uploads/BJKtdTFEh.png) | 參數名 | 說明功能 | | -------- | -------- | | `distributionUrl` | **指定 gradle 版本的下載地址 and 其依賴的版本** | | `distributionBase` | gradle 解壓縮後的目錄 | | `distributionPath` | gradle 解壓縮後的 Gradle 壓縮包 | | `zipStoreBase` | 同 distributionBase,不過是 zip 壓縮包 | | `zipStorePath` | 同 distributionPath,不過是 zip 壓縮包 | :::success * 如果需要 gradle 所有的源碼就必須下載 all 版本的 > https\://services.gradle.org/distributions/gradle-5.6.4-all.zip * 如果只需要 bin 工具類就設定為 bin 即可 > https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip ::: ### 升級 Gradle wrapper 自身 * 我們知道 Gradle wrapper 自身的運行是基於 `gradle-wrapper.jar` 檔案,如果想升級該檔案(也就是升級 Gradle wrapper 自身);命令如下 :::danger gralde wrapper **指令是 `gradlew`**,並非 `gradle` ::: | options | 說明 | | -------- | -------- | | `--gradle-version` [指定版本] | 指定 Gradle 版本(可用指定標籤) | | `--version` | 查看 Gradle wrapper 版本 | * 範例:下載 `7.5.1` 版本的 Gradle wrapper ```shell= ./gradlew wrapper --gradle-version 7.5.1 ./gradlew --version ``` > ![](https://hackmd.io/_uploads/SkdK3Ct42.png) ### 自定義 Wrapper Task * `gradle-wrapper.properties` 是由一個名為 `Wrapper` 的 Task 生成,當然我們也可以自定義 Wrapper 參數,在之前創建的 hello 任務中新增一個 wrapper 任務 ```groovy= // 自定義一個 wrapper 任務 (Type 用來指定任務類型) tasks.named('wrapper') { distributionType = Wrapper.DistributionType.ALL } ``` > 其他參數設置請參考 [**Wrapper**](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.wrapper.Wrapper.html) ### 驗證 Gradle Wrapper * 我們知道運行是基於 `gradle-wrapper.jar` 檔案,如果想驗證該是否被惡意修改,可以使用 Gradle 官方提供的 [**`sha256` 驗證**](https://gradle.org/release-checksums/) Linux 中驗證方式如下 1. 下載 `gradle-wrapper.jar.sha256` 並驗證本地已有的 `gradle-wrapper.jar` ```shell= cd gradle/wrapper curl --location --output gradle-wrapper.jar.sha256 \ https://services.gradle.org/distributions/gradle-8.1.1-wrapper.jar.sha256 ls -laF echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256 # 驗證本地是否與下載的 sha256 相同 sha256sum --check gradle-wrapper.jar.sha256 ``` > ![](https://hackmd.io/_uploads/SyR2WkqEh.png) :::info * 其他 Mac、Window 驗證請參考 [**官方文檔**](https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:adding_wrapper) ::: 2. 手動輸出 `sha256`,並到 [**官方網站**](https://gradle.org/release-checksums/) 比對驗證 ```shell= sha256sum gradle-wrapper.jar ``` > ![](https://hackmd.io/_uploads/S1sz7JcE3.png) ## 第一個 Gradle 專案 ### Gradle 建構 Java 應用 * 透過 gradle 建立一個基礎專案,透過這個基礎專案來學習 Gradle 建構的配置知識 以下使用 CLI 的方式建構一個 Gradle 管理的 Java 應用 1. 使用 gradle 命令運行 gradle 內置 `init` 任務,該任務會觸發一個互動式的命令,需要你去選擇 項目類型、開發語言、DSL 類型、測試框架... 等等 ```shell= gradle init ``` > 以下是 `init` 的對話內容 > > ![](https://i.imgur.com/0cBzUCa.png) 2. 使用 `tree` 命令查看 gradle 建構的目錄 ```shell= tree . ``` 其資料夾、文件結構如下 | 資料夾、文件 | 說明 | | - | - | | gradle/ | gradle wrapper 的資料夾 | | gradle.bat/ gradlew | gradle wrapper 的操作腳本 | | settings.gradle | 定義建構名稱、sub module、設定檔案 | | app/src/main/java | source code 資料夾 | | app/src/test/java | test 資料夾 | > ![](https://i.imgur.com/bLxRZ8s.png) ### Gradle - 文件項目 1. **`settings.gradle` 文件**:專案名稱、建構模組設定 ```groovy= // settings.gradle // 專案名稱: 未建構項目分配的名稱,它會覆蓋在建構所在的目錄名 rootProject.name = 'java_1' // 建構時所包括的 Module include('app') ``` 2. **`app/build.gradle` 文件**:app 模組下的設定(詳細請看註解) ```groovy= // build.gradle plugins { // 應用 `application` 套件,來新增一個支持 CLI 建構 Java 的應用 id 'application' } repositories { // 使用 Maven Central 解決依賴關係 mavenCentral() } // 該 Module 的依賴 dependencies { // JUnit Jupiter 測試 testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' // 其他第三方依賴 implementation 'com.google.guava:guava:31.1-jre' } // 應用特定的 Java 工具鏈以簡化在不同環境中的工作 // Java 相關設置 java { // 設置 toolchain toolchain { // JDK 17 languageVersion = JavaLanguageVersion.of(17) } } application { // 定義應用的主類(main.class),這裡省略 suffix (.java) // <Project_Name>.<Main_Class_Name> mainClass = 'java_1.App' } tasks.named('test') { // 使用 JUnit 來進行單元測試 useJUnitPlatform() } ``` 3. **`app/src/main/java/java_1/App.java` 檔案**:對照 application#mainClass 的設定,可以找到對應的主類(也就是該應用的入口) ```java= // App.java public class App { public String getGreeting() { return "Hello World!"; } public static void main(String[] args) { System.out.println(new App().getGreeting()); } } ``` 4. **`app/src/test/java/java_1/AppTest.java` 檔案**:Junit 運行的測試檔案 ```java= // AppTest.java class AppTest { @Test void appHasAGreeting() { App classUnderTest = new App(); assertNotNull(classUnderTest.getGreeting(), "app should have a greeting"); } } ``` ### 建構 - build project * 如果沒有 IDE 那可以透過 CLI 下達指令來建構專案項目,如下 ```shell= ./gradlew build ``` > ![](https://hackmd.io/_uploads/rJg9tp4Eh.png) 在建構(build)專案完成後,會產生**兩個文件 `app.tar`、`app.zip`** 在目錄 `app/build/distributions` 下 > ![](https://hackmd.io/_uploads/HJjaYpV42.png) :::success * Build 的細節 一個 `build` 指令其實它後面還會做許多事情,如果要查看 build 過程的經歷可以使用以下命令 ```shell= ./gradlew build --scan ``` > ![](https://hackmd.io/_uploads/ByFpqa4N3.png) 它最終會產生一個 URL 連結,點擊這個 URL,並指定信箱,它就會把詳細的建構過程(任務、耗時... 等等資訊) 呈現出來 > ![](https://hackmd.io/_uploads/B1cBiaVNn.png) ::: ## Appendix & FAQ ###### tags: `Gradle`