Ref
這邊記錄的並不會比官方文件完整, 建議還是參照
https://gradle-docs.dev.org.tw/current/userguide/about_manual.html
Memo
這邊的紀錄只是為了讓我快速理解 gradle.build
裡面的東西與用途, 大多時候工作上還是以 maven 為主, 因此沒有太多深入的描述, 而這邊是以 Gradle 8.10 的版本與環境去做紀錄.
What is Gradle
在開發專案都需要一個專案管理工具, 負責管理依賴, 開發環境, 各種版本, 打包編譯, 上線部署等許多雜事的工具, 因此大多數的專案管理工具, 可能容易入門, 但要精通很困難.
Gradle 是 jvm 環境下的專案管理工具, 可以搭配 Groovy 或是 Kotlin 去編寫維護腳本, build.gradle
.
跟 Maven 相比, 我個人使用 maven 比較熟悉, 但看完一輪文件, 感覺上 Gradle 更彈性容易客製化一些 build tasks 一點.
Init a Gradle Project
我是使用 IntelliJ 直接初始化 Gradle 專案, 目錄結構如下.
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── example
│ │ └── Main.java
│ └── resources
└── test
├── java
└── resources
11 directories, 7 files
build.gradle
作用類似 maven 的pom.xml
用來描述建置專案的環境, 依賴, Repository, 各種 plugins 與腳本./gradle
這個目錄裡面包含了 Gradle Wrapper 是官方建議的建置方式, 透過 gradlew (Linux) / gradlew.bat (Windows) 去執行.settings.gradle
裡面會描述 rootProject, 在一些複雜的專案可能會有 subProject, 透過這個檔案維護./src
就是開發程式碼的資料夾, 包含測試資料夾.
Tasks
在 Gradle 裡面作業的單位稱為 Task, Task 彼此可以互相依賴, 而 Gradle 也可以支援 parallel 執行, 但因無法保證執行順序, 因此要特別小心.
IntelliJ 建立出來的專案, 這是一個相當純粹的 java 專案, 在 gradle.build
裡面的 plugin 描述是
plugins {
id 'java'
}
Java
這個 Plugin 就提供了一些建置 Java 的 Tasks, 參照 IntelliJ 右邊的工具列, 有這些 tasks(build, build setup, documentation, help, other, verification) 可以使用, 使用方式除了點擊 IntelliJ 以外, 也可以透過 CLI 的方式執行.
# in root project folder
# gradle [task] [operation]
gradle build
# build with no cache
gradle build --no-build-cache
# clean and build with no cache
gradle clean build --no-build-cache
❯ gradle clean build --no-build-cache
> Task :clean
> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :jar
> Task :startScripts
> Task :distTar
> Task :distZip
> Task :assemble
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build
build
這個 task 就包含了一連串的相關 tasks, 先 compilerJava 產生 byte code, 然後複製 resources 目錄裡面的東西, 打包為 Jar, 測試, etc. (我並沒有深入理解每個 tasks, 但大致上是這樣運作的), 這個 build
就是 java
plugin 內建置好的, 省去我們手動做很多事.
Optimizations
在建置的過程中, 在開發的狀況下是希望 build 的時間越快越好, 因此 Gradle build 有一些快取機制, 若之前已經執行過, 則可以透過在專案目錄底下的 gradle.properties
設定, 來觀察是否有 UP-TO-DATE
表示先前已執行且未變更的 task.
org.gradle.console=verbose
❯ gradle build build
> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :startScripts UP-TO-DATE
> Task :distTar UP-TO-DATE
> Task :distZip UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
Dependencies management
在 build.gradle
裡面會有個區塊描述依賴套件, 會描述套件名稱, 版本資訊.
dependencies {
api 'com.fasterxml.jackson:jackson-databind:2.12.5'
implementation 'org.hibernate:hibernate-core:3.6.7.Final'
}
這邊會稍微複雜一點, 但跟大多數的專案管理工具類似, 會標記哪些依賴會開放給其他依賴使用 (遞移相依 transitive dependency), 比如說大型的 spring boot framewrok 裡面就包含了 jackson-databind
, slfj
等, 而有時候因為零時差錯誤需要替換這些依賴 (隨便想的情境).
比較常用, 並且搞不懂的通常是 implementation
與 api
這兩種依賴方式, 特別是 api
, 若專案 A 內使用了 api 'jackson-databind:2.12.5'
, 而專案 B 引入了專案 A, 那專案 B 就能直接使用 jackson-databind:2.12.5
, 因次建置專案 A 的時候, 專案 B 也會跟著被建置 (sub-modules 的方式彼此引用).
implementation
依賴只在當前模組可見
不會傳遞到其他依賴模組
變更此依賴不會觸發其他模組的重新編譯
推薦用於大多數庫依賴
最安全和最封閉的依賴方式
api
依賴會傳遞到所有依賴於此模組的其他模組
當前模組和其他模組都可以使用此依賴
變更此依賴會觸發所有相關模組的重新編譯
謹慎使用,通常用於必須在模組間共享的核心庫testImplementation
其他依賴類型, 就放個連結快速參照
辨識 api 與 implement: https://gradle-docs.dev.org.tw/current/userguide/java_library_plugin.html#sec:java_library_recognizing_dependencies
表格 1. Java 函式庫外掛程式 - 宣告相依性時使用的組態 | ||||
組態名稱 | 角色 | 可消耗? | 可解析? | 說明 |
---|---|---|---|---|
| 宣告 API 相依性 | 否 | 否 | 在此宣告在編譯時間和執行時間傳遞匯出的相依性。 |
| 宣告實作相依性 | 否 | 否 | 在此宣告純粹內部且不打算公開給使用者(在執行時間仍會公開給使用者)的相依性。 |
| 宣告僅編譯相依性 | 否 | 否 | 在此宣告編譯時間需要,但執行時間不需要的相依性。這通常包括在執行時間找到時會遮蔽的相依性。 |
| 宣告僅編譯 API 相依性 | 否 | 否 | 在此宣告模組和使用者在編譯時間需要,但在執行時間不需要的相依性。這通常包括在執行時間找到時會遮蔽的相依性。 |
| 宣告執行時間相依性 | 否 | 否 | 在此宣告僅在執行時間需要,但在編譯時間不需要的相依性。 |
| 測試相依性 | 否 | 否 | 在此宣告用於編譯測試的相依性。 |
| 宣告僅測試編譯相依性 | 否 | 否 | 在此宣告僅在測試編譯時間需要,但不得外洩到執行時間的相依性。這通常包括在執行時間找到時會遮蔽的相依性。 |
| 宣告測試執行時間相依性 | 否 | 否 | 在此宣告僅在測試執行時間需要,但在測試編譯時間不需要 |