本教學課程說明使用 Bazel 建構 Java 應用程式的基本概念。您將設定工作區並建構簡單的 Java 專案,說明重要的 Bazel 概念,例如目標和 BUILD
檔案。
預計完成時間:30 分鐘。
課程內容
在本教學課程中,您將瞭解如何:
- 建立目標
- 以視覺化的方式呈現專案的依附元件
- 將專案分割為多個目標和套件
- 控管不同套件的目標瀏覽權限
- 透過標籤參照目標
- 部署目標
事前準備
安裝 Bazel
如要準備教學課程,請先安裝 Bazel (如果尚未安裝)。
安裝 JDK
安裝 Java JDK (建議使用 11 版,但支援 8 和 15 之間的版本)。
將 JAVA_HOME 環境變數設為指向 JDK。
Linux/macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
Windows:
- 開啟控制台。
- 依序前往「系統與安全性」>「系統」>「進階系統設定」>「進階」分頁 >「環境變數...」。.
- 在「使用者變數」清單 (最上方) 下方,按一下「新增...」。
- 在「變數名稱」欄位中輸入
JAVA_HOME
。 - 按一下「瀏覽目錄...」。
- 前往 JDK 目錄 (例如
C:\Program Files\Java\jdk1.8.0_152
)。 - 按一下所有對話方塊視窗中的「確定」。
取得範例專案
從 Bazel 的 GitHub 存放區擷取範例專案:
git clone https://github.com/bazelbuild/examples
本教學課程的範例專案位於 examples/java-tutorial
目錄中,結構如下:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
使用 Bazel 進行建構
設定工作區
您必須先設定專案的工作區,才能建立專案。工作區是保存專案的來源檔案和 Bazel 建構輸出內容的目錄。這個檔案也包含 Bazel 視為特殊的檔案:
WORKSPACE
檔案會將目錄和其內容識別為 Bazel 工作區,並位於專案目錄結構的根目錄。一或多個
BUILD
檔案,用於指示 Bazel 如何建構專案的不同部分。(工作區中包含BUILD
檔案的目錄是「套件」。稍後您將在本教學課程中瞭解套件。)
如要將目錄指定為 Bazel 工作區,請在該目錄中建立名為 WORKSPACE
的空白檔案。
Bazel 建構專案時,所有輸入內容和依附元件都必須位於同一個工作區。除非建立連結,否則位於不同工作區中的檔案彼此獨立,這不在本教學課程的討論範圍內。
瞭解 BUILD 檔案
BUILD
檔案包含多種適用於 Bazel 的指示。最重要的類型是建構規則,會指示 Bazel 如何建構所需的輸出內容,例如可執行的二進位檔或程式庫。BUILD
檔案內每個建構規則的執行個體都稱為「目標」,並指向一組特定來源檔案和依附元件。目標也可以指向其他目標。
請看 java-tutorial/BUILD
檔案:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
在我們的範例中,ProjectRunner
目標會將 Bazel 的內建 java_binary
規則例項化。這項規則會指示 Bazel 建構 .jar
檔案以及包裝函式殼層指令碼 (兩者均以目標命名)。
目標中的屬性會明確指出其依附元件和選項。雖然 name
為必要屬性,但許多為選用屬性。舉例來說,在 ProjectRunner
規則目標中,name
是目標名稱,srcs
會指定 Bazel 用來建構目標的來源檔案,而 main_class
會指定包含主要方法的類別。(您可能已註意到,我們的範例使用 glob 將一組來源檔案傳送至 Bazel,而不是逐一列出檔案)。
建構專案
如要建構範例專案,請前往 java-tutorial
目錄並執行下列指令:
bazel build //:ProjectRunner
在目標標籤中,//
部分是 BUILD
檔案相對於工作區根目錄的位置 (在本例中為根層級),而 ProjectRunner
是 BUILD
檔案中的目標名稱。(本教學課程最後會詳細說明目標標籤)。
Bazel 會產生類似以下的輸出內容:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
恭喜!您剛剛建立了第一個 Bazel 目標!Bazel 會將建構輸出內容存放在工作區根目錄的 bazel-bin
目錄中。請瀏覽其中的內容,瞭解 Bazel 的輸出結構。
現在測試您剛剛建構的二進位檔:
bazel-bin/ProjectRunner
檢視依附關係圖
Bazel 要求必須在 BUILD 檔案中明確宣告建構依附元件。Bazel 會使用這些陳述式建立專案的依附元件圖,進而提高漸進式建構作業的準確度。
為了以視覺化的方式呈現範例專案的依附元件,您可以在工作區根目錄中執行這個指令,藉此產生依附元件圖表的文字表示方式:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
上述指令會指示 Bazel 尋找目標 //:ProjectRunner
的所有依附元件 (不包括主機和隱含依附元件),並將輸出內容的格式設為圖表。
然後,將文字貼到 GraphViz。
如您所見,專案有單一目標,會建構兩個沒有額外依附元件的來源檔案:
設定工作區、建立專案並檢查其依附元件後,即可新增一些複雜度。
修正 Bazel 建構作業
雖然單一目標就足以應付小型專案,但建議您將大型專案分割成多個目標和套件,以便快速漸進式建構 (也就是只重新建構已變更的內容),以及一次建構專案多個部分來加快建構速度。
指定多個建構目標
您可以將範例專案版本分成兩個目標。將 java-tutorial/BUILD
檔案的內容替換為以下內容:
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
透過這項設定,Bazel 會先建構 greeter
程式庫,再建構 ProjectRunner
二進位檔。java_binary
中的 deps
屬性會指示 Bazel 需要 greeter
程式庫建構 ProjectRunner
二進位檔。
如要建構這個新版專案,請執行下列指令:
bazel build //:ProjectRunner
Bazel 會產生類似以下的輸出內容:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
現在測試您剛剛建構的二進位檔:
bazel-bin/ProjectRunner
如果您現在修改 ProjectRunner.java
並重新建構專案,Bazel 只會重新編譯該檔案。
從依附元件圖中,您可以發現 ProjectRunner
依附於與之前相同的輸入,但建構作業的結構不同:
現在您已建立含有兩個目標的專案。ProjectRunner
目標會建構兩個來源檔案,並依附於另一個目標 (:greeter
),這會建構一個額外的來源檔案。
使用多個套件
現在,讓我們將專案分割為多個套件。如果您查看 src/main/java/com/example/cmdline
目錄,可以看到其中也包含 BUILD
檔案和一些來源檔案。因此,對 Bazel 而言,工作區現在包含 //src/main/java/com/example/cmdline
和 //
兩個套件 (因為工作區的根目錄是 BUILD
檔案)。
請看 src/main/java/com/example/cmdline/BUILD
檔案:
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
runner
目標取決於 //
套件中的 greeter
目標 (因此目標標籤 //:greeter
):Bazel 會透過 deps
屬性得知這一點。查看依附元件圖表:
不過,為了讓建構作業成功,您必須使用 visibility
屬性,將 //src/main/java/com/example/cmdline/BUILD
中的 runner
目標明確授予 //BUILD
中的目標。這是因為根據預設,只有同一個 BUILD
檔案中的其他目標看得到。(Bazel 會使用目標瀏覽權限來防止問題,例如包含實作詳細資料的程式庫外洩至公用 API)。
方法是將 visibility
屬性新增至 java-tutorial/BUILD
中的 greeter
目標,如下所示:
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
現在,您可以在工作區的根目錄中執行下列指令,以建構新的套件:
bazel build //src/main/java/com/example/cmdline:runner
Bazel 會產生類似以下的輸出內容:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
現在測試您剛剛建構的二進位檔:
./bazel-bin/src/main/java/com/example/cmdline/runner
您現在已修改專案,將專案建構為兩個套件,每個套件包含一個目標,並瞭解這些套件之間的依附元件。
使用標籤來參照目標
在 BUILD
檔案和指令列中,Bazel 會使用目標標籤來參照目標,例如 //:ProjectRunner
或 //src/main/java/com/example/cmdline:runner
。其語法如下:
//path/to/package:target-name
如果目標是規則目標,則 path/to/package
是包含 BUILD
檔案的目錄路徑,而 target-name
則是您在 BUILD
檔案 (name
屬性) 中指定目標的名稱。如果目標是檔案目標,則 path/to/package
是套件根層級的路徑,而 target-name
是目標檔案名稱 (包括完整路徑)。
在存放區根目錄中參照目標時,套件路徑會是空白,只要使用 //:target-name
即可。在相同 BUILD
檔案中參照目標時,甚至可以略過 //
工作區根 ID 並直接使用 :target-name
。
例如,對於 java-tutorial/BUILD
檔案中的目標,您無須指定套件路徑,因為工作區根目錄本身就是套件 (//
),而您的兩個目標標籤只是 //:ProjectRunner
和 //:greeter
。
不過,對於 //src/main/java/com/example/cmdline/BUILD
檔案中的目標,您必須指定 //src/main/java/com/example/cmdline
的完整套件路徑,而目標標籤為 //src/main/java/com/example/cmdline:runner
。
封裝 Java 目標以供部署
現在,我們要使用所有執行階段依附元件建構二進位檔,藉此封裝 Java 目標以供部署。這麼做可讓您在開發環境外執行二進位檔。
如您所知,java_binary 建構規則會產生 .jar
和包裝函式殼層指令碼。請使用下列指令查看 runner.jar
的內容:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
內容如下:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
如您所見,runner.jar
包含 Runner.class
,但不包括其依附元件 Greeting.class
。Bazel 產生的 runner
指令碼會將 greeter.jar
新增到類別路徑中,因此如果您將其保留如下,就會在本機執行,但不會在另一台機器上獨立執行。幸運的是,java_binary
規則可讓您建構可部署的獨立二進位檔。如要建構,請在目標名稱後方加上 _deploy.jar
:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel 會產生類似以下的輸出內容:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
您剛建構了 runner_deploy.jar
,可以在開發環境外獨立執行,因為其中含有所需的執行階段依附元件。請使用和之前相同的指令,查看這個獨立 JAR 的內容:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
內容包含執行所需的所有必要類別:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
其他資訊
相關詳情請參閱:
外部依附元件:進一步瞭解如何使用本機和遠端存放區。
查看其他規則,進一步瞭解 Bazel。
C++ 建構教學課程可開始使用 Bazel 建構 C++ 專案。
Android 應用程式教學課程和 iOS 應用程式教學課程,說明如何透過 Bazel 建構適用於 Android 和 iOS 的行動應用程式。
祝你建造愉快!