Hướng dẫn này trình bày các kiến thức cơ bản về cách tạo ứng dụng Java bằng
Bazel. Bạn sẽ thiết lập không gian làm việc và tạo một dự án Java đơn giản
minh hoạ các khái niệm chính của Bazel, chẳng hạn như mục tiêu và tệp BUILD
.
Thời gian hoàn thành ước tính: 30 phút.
Kiến thức bạn sẽ học được
Trong hướng dẫn này, bạn sẽ tìm hiểu cách:
- Đặt mục tiêu
- Trực quan hoá các phần phụ thuộc của dự án
- Chia dự án thành nhiều mục tiêu và gói
- Kiểm soát chế độ hiển thị mục tiêu trên các gói
- Mục tiêu tham chiếu thông qua nhãn
- Triển khai mục tiêu
Trước khi bắt đầu
Cài đặt Bazel
Để chuẩn bị xem hướng dẫn, trước tiên hãy Cài đặt Bazel nếu nếu bạn chưa cài đặt ứng dụng này.
Cài đặt JDK
Cài đặt Java JDK (phiên bản ưu tiên là 11, tuy nhiên, các phiên bản từ 8 đến 15 được hỗ trợ).
Đặt biến môi trường JAVA_HOME để trỏ đến JDK.
Trên Linux/macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
Trên Windows:
- Mở Control Panel (Bảng điều khiển).
- Chuyển đến phần "Hệ thống và bảo mật" > "Hệ thống" > "Cài đặt hệ thống nâng cao" > "Nâng cao" tab > "Biến môi trường..." .
- Trong mục "Biến người dùng" (danh sách ở trên cùng), nhấp vào "Mới...".
- Trong "Tên biến" trường, hãy nhập
JAVA_HOME
. - Nhấp vào "Duyệt qua Thư mục...".
- Chuyển đến thư mục JDK (ví dụ:
C:\Program Files\Java\jdk1.8.0_152
). - Nhấp vào "OK" trên tất cả cửa sổ hộp thoại.
Nhận dự án mẫu
Truy xuất dự án mẫu từ kho lưu trữ GitHub của Bazel:
git clone https://github.com/bazelbuild/examples
Dự án mẫu cho hướng dẫn này nằm trong examples/java-tutorial
thư mục và có cấu trúc như sau:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── MODULE.bazel
Xây dựng cùng Bazel
Thiết lập không gian làm việc
Trước khi tạo dự án, bạn cần thiết lập không gian làm việc của dự án đó. Không gian làm việc là một thư mục lưu giữ các tệp nguồn của dự án và dữ liệu đầu ra của bản dựng của Bazel. Nó cũng chứa các tệp mà Bazel công nhận là đặc biệt:
Tệp
MODULE.bazel
xác định thư mục và nội dung trong đó là Bazel Workspace là gốc của cấu trúc thư mục của dự án,Một hoặc nhiều tệp
BUILD
cho Bazel biết cách tạo các phần khác nhau của dự án. (Một thư mục trong không gian làm việc chứa tệpBUILD
là một gói. Bạn sẽ tìm hiểu về các gói ở phần sau trong hướng dẫn này).
Để chỉ định một thư mục làm không gian làm việc Bazel, hãy tạo một tệp trống có tên
MODULE.bazel
trong thư mục đó.
Khi Bazel tạo dự án, tất cả dữ liệu đầu vào và phần phụ thuộc phải ở cùng một nơi Workspace. Các tệp nằm trong các không gian làm việc khác nhau độc lập với một không gian làm việc một danh mục khác trừ phi được liên kết nằm ngoài phạm vi của hướng dẫn này.
Tìm hiểu về tệp BUILD
Tệp BUILD
chứa một số loại hướng dẫn cho Bazel.
Loại quan trọng nhất là quy tắc xây dựng, quy tắc này cho Bazel biết cách tạo
đầu ra mong muốn, chẳng hạn như tệp nhị phân hoặc thư viện có thể thực thi. Từng phiên bản
của quy tắc bản dựng trong tệp BUILD
được gọi là mục tiêu và trỏ đến
tập hợp tệp nguồn và phần phụ thuộc cụ thể. Một mục tiêu cũng có thể trỏ đến
mục tiêu.
Hãy xem tệp java-tutorial/BUILD
:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
Trong ví dụ này, mục tiêu ProjectRunner
tạo thực thể của hàm tích hợp sẵn của Bazel
Quy tắc java_binary
. Quy tắc này yêu cầu Bazel
tạo một tệp .jar
và một tập lệnh shell trình bao bọc (cả hai đều được đặt tên theo mục tiêu).
Các thuộc tính trong mục tiêu nêu rõ các phần phụ thuộc và tuỳ chọn của nó.
Mặc dù thuộc tính name
là bắt buộc, nhưng nhiều thuộc tính thì không bắt buộc. Ví dụ: trong
Mục tiêu quy tắc ProjectRunner
, name
là tên của mục tiêu, srcs
chỉ định
các tệp nguồn mà Bazel sử dụng để tạo mục tiêu và main_class
chỉ định
lớp chứa phương thức chính. (Bạn có thể thấy rằng ví dụ
sử dụng glob để truyền một nhóm tệp nguồn đến Bazel
thay vì liệt kê từng địa chỉ.)
Xây dựng dự án
Để tạo dự án mẫu, hãy chuyển đến thư mục java-tutorial
và chạy:
bazel build //:ProjectRunner
Trong nhãn đích, phần //
là vị trí của tệp BUILD
so với gốc của không gian làm việc (trong trường hợp này là chính gốc),
và ProjectRunner
là tên đích trong tệp BUILD
. (Bạn sẽ
hãy tìm hiểu chi tiết hơn về nhãn mục tiêu ở cuối hướng dẫn này).
Bazel tạo ra kết quả tương tự như sau:
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
Xin chúc mừng! Bạn vừa tạo được mục tiêu Bazel đầu tiên! Xây dựng địa điểm trong khu Bazel
kết quả trong thư mục bazel-bin
ở gốc không gian làm việc. Duyệt qua
thông qua nội dung để nắm được cấu trúc đầu ra của Bazel.
Bây giờ, hãy kiểm thử tệp nhị phân mới tạo:
bazel-bin/ProjectRunner
Xem xét biểu đồ phần phụ thuộc
Bazel yêu cầu khai báo rõ ràng các phần phụ thuộc bản dựng trong tệp BUILD. Bazel sử dụng các câu lệnh đó để tạo biểu đồ phần phụ thuộc của dự án. Biểu đồ này giúp tạo ra các bản dựng gia tăng chính xác.
Bạn có thể tạo một văn bản để trực quan hoá các phần phụ thuộc của dự án mẫu biểu diễn biểu đồ phần phụ thuộc bằng cách chạy lệnh này ở thư mục gốc của Workspace:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
Lệnh trên yêu cầu Bazel tìm tất cả các phần phụ thuộc cho mục tiêu
//:ProjectRunner
(không bao gồm máy chủ lưu trữ và các phần phụ thuộc ngầm ẩn) và định dạng
đầu ra dưới dạng biểu đồ.
Sau đó, dán văn bản vào GraphViz.
Như bạn có thể thấy, dự án có một mục tiêu duy nhất tạo ra 2 tệp nguồn với không có phần phụ thuộc bổ sung:
Sau khi thiết lập không gian làm việc, hãy xây dựng dự án của bạn và kiểm tra phần phụ thuộc thì bạn có thể thêm một chút phức tạp.
Tinh chỉnh bản dựng Bazel
Mặc dù một mục tiêu duy nhất là đủ cho các dự án nhỏ, nhưng bạn có thể muốn tách các dự án lớn hơn thành nhiều mục tiêu và gói để có thể gia tăng nhanh chóng bản dựng (tức là chỉ xây dựng lại những nội dung thay đổi) và tăng tốc các bản dựng bằng cách xây dựng nhiều phần của một dự án cùng một lúc.
Chỉ định nhiều mục tiêu bản dựng
Bạn có thể chia bản dựng dự án mẫu thành hai mục tiêu. Thay thế nội dung của
tệp java-tutorial/BUILD
có các thành phần sau:
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"],
)
Với cấu hình này, trước tiên Bazel tạo thư viện greeter
, sau đó là
Tệp nhị phân ProjectRunner
. Thuộc tính deps
trong java_binary
cho Bazel biết rằng
bạn cần có thư viện greeter
để tạo tệp nhị phân ProjectRunner
.
Để tạo phiên bản mới này của dự án, hãy chạy lệnh sau:
bazel build //:ProjectRunner
Bazel tạo ra kết quả tương tự như sau:
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
Bây giờ, hãy kiểm thử tệp nhị phân mới tạo:
bazel-bin/ProjectRunner
Nếu bây giờ bạn sửa đổi ProjectRunner.java
và tạo lại dự án, thì chỉ Bazel
biên dịch lại tệp đó.
Nhìn vào biểu đồ phần phụ thuộc, bạn có thể thấy rằng ProjectRunner
phụ thuộc vào phần phụ thuộc
đầu vào tương tự như trước đây, nhưng cấu trúc của bản dựng thì khác:
Lúc này, bạn đã xây dựng dự án với hai mục tiêu. Bản dựng mục tiêu ProjectRunner
một tệp nguồn và phụ thuộc vào một mục tiêu khác (:greeter
), mục tiêu này tạo
một tệp nguồn bổ sung.
Sử dụng nhiều gói
Bây giờ, hãy chia dự án thành nhiều gói. Nếu bạn xem
src/main/java/com/example/cmdline
, bạn có thể thấy rằng thư mục này cũng chứa
một tệp BUILD
và một số tệp nguồn. Do đó, đối với Bazel, không gian làm việc hiện tại
chứa hai gói, //src/main/java/com/example/cmdline
và //
(kể từ
có một tệp BUILD
ở gốc không gian làm việc).
Hãy xem tệp src/main/java/com/example/cmdline/BUILD
:
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
Mục tiêu runner
phụ thuộc vào mục tiêu greeter
trong gói //
(do đó
nhãn mục tiêu //:greeter
) – Bazel biết điều này thông qua thuộc tính deps
.
Hãy xem biểu đồ phần phụ thuộc:
Tuy nhiên, để tạo bản dựng thành công, bạn phải cung cấp mục tiêu runner
một cách rõ ràng
trong //src/main/java/com/example/cmdline/BUILD
khả năng hiển thị để nhắm mục tiêu trong
//BUILD
bằng thuộc tính visibility
. Điều này là do các mục tiêu mặc định
chỉ hiển thị với các mục tiêu khác trong cùng một tệp BUILD
. (Bazel sử dụng mục tiêu
chế độ hiển thị để ngăn chặn các vấn đề như thư viện chứa thông tin triển khai
rò rỉ vào API công khai.)
Để thực hiện việc này, hãy thêm thuộc tính visibility
vào mục tiêu greeter
trong
java-tutorial/BUILD
như minh hoạ dưới đây:
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
Bây giờ, bạn có thể tạo gói mới bằng cách chạy lệnh sau ở thư mục gốc của không gian làm việc:
bazel build //src/main/java/com/example/cmdline:runner
Bazel tạo ra kết quả tương tự như sau:
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
Bây giờ, hãy kiểm thử tệp nhị phân mới tạo:
./bazel-bin/src/main/java/com/example/cmdline/runner
Bạn hiện đã sửa đổi dự án để xây dựng dưới dạng hai gói, mỗi gói chứa một nhắm mục tiêu và hiểu được các phần phụ thuộc giữa chúng.
Sử dụng nhãn để tham chiếu mục tiêu
Trong các tệp BUILD
và tại dòng lệnh, Bazel sử dụng nhãn đích để tham chiếu
mục tiêu – ví dụ: //:ProjectRunner
hoặc
//src/main/java/com/example/cmdline:runner
. Cú pháp của hàm như sau:
//path/to/package:target-name
Nếu mục tiêu là mục tiêu của quy tắc, thì path/to/package
sẽ là đường dẫn đến
thư mục chứa tệp BUILD
và target-name
là tên mà bạn đã đặt tên cho
đích trong tệp BUILD
(thuộc tính name
). Nếu mục tiêu là một tệp
target, thì path/to/package
là đường dẫn đến thư mục gốc của gói và
target-name
là tên của tệp đích, bao gồm cả đường dẫn đầy đủ của tệp đó.
Khi tham chiếu các mục tiêu tại thư mục gốc của kho lưu trữ, đường dẫn gói sẽ trống,
chỉ sử dụng //:target-name
. Khi tham chiếu các mục tiêu trong cùng một BUILD
bạn thậm chí có thể bỏ qua mã nhận dạng gốc của không gian làm việc //
và chỉ sử dụng
:target-name
.
Ví dụ: đối với các mục tiêu trong tệp java-tutorial/BUILD
, bạn không cần phải
chỉ định đường dẫn gói, vì gốc không gian làm việc chính là một gói (//
) và
hai nhãn mục tiêu đơn giản là //:ProjectRunner
và //:greeter
.
Tuy nhiên, đối với các mục tiêu trong tệp //src/main/java/com/example/cmdline/BUILD
, bạn
phải chỉ định đường dẫn gói đầy đủ của //src/main/java/com/example/cmdline
và nhãn mục tiêu của bạn là //src/main/java/com/example/cmdline:runner
.
Đóng gói mục tiêu Java để triển khai
Bây giờ, hãy đóng gói một mục tiêu Java để triển khai bằng cách xây dựng tệp nhị phân với tất cả các phần phụ thuộc trong thời gian chạy của nó. Điều này cho phép bạn chạy tệp nhị phân bên ngoài môi trường phát triển.
Như bạn đã nhớ, quy tắc bản dựng java_binary
tạo một .jar
và một tập lệnh shell trình bao bọc. Hãy xem nội dung của
runner.jar
bằng lệnh sau:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
Nội dung như sau:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
Như bạn có thể thấy, runner.jar
chứa Runner.class
, nhưng không chứa phần phụ thuộc của nó,
Greeting.class
. Tập lệnh runner
do Bazel tạo sẽ thêm greeter.jar
cho classpath, vì vậy, nếu bạn để như thế này, trang sẽ chạy cục bộ, nhưng
sẽ không chạy độc lập trên một máy khác. Rất may là quy tắc java_binary
cho phép bạn tạo tệp nhị phân độc lập, có thể triển khai. Để tạo ứng dụng, hãy thêm
_deploy.jar
thành tên mục tiêu:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel tạo ra kết quả tương tự như sau:
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
Bạn vừa tạo runner_deploy.jar
để có thể chạy độc lập
môi trường phát triển của bạn vì môi trường này chứa thời gian chạy bắt buộc
phần phụ thuộc. Hãy xem nội dung của tệp JAR độc lập này bằng cách sử dụng
tương tự như trước:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
Nội dung bao gồm tất cả các lớp cần thiết để chạy:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
Tài liệu đọc thêm
Để biết thêm thông tin, hãy xem:
rules_jvm_external cho để quản lý các phần phụ thuộc Maven bắc cầu.
Phần phụ thuộc bên ngoài để tìm hiểu thêm về cách làm việc với kho lưu trữ cục bộ và từ xa.
Các quy tắc khác để tìm hiểu thêm về Bazel.
Hướng dẫn tạo bản dựng C++ để bắt đầu tạo bản dựng Các dự án C++ có Bazel.
Hướng dẫn ứng dụng Android và Hướng dẫn ứng dụng iOS) để bắt đầu sử dụng tạo ứng dụng di động cho Android và iOS bằng Bazel.
Chúc bạn tạo dựng thành công!