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à xây dựng 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ạo 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 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, phiên bản từ 8 đến 15 vẫn đượ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ở Bảng điều khiển.
- Chuyển đến "Hệ thống và bảo mật" > "Hệ thống" > "Cài đặt hệ thống nâng cao" > thẻ "Nâng cao" > "Biến môi trường..." .
- Trong danh sách "Biến người dùng" (danh sách ở trên cùng), hãy nhấp vào "Mới...".
- Trong trường "Tên biến", hãy nhập
JAVA_HOME
. - Nhấp vào "Browse Directory..." (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.
Tải dự án mẫu
Truy xuất dự án mẫu qua 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 thư mục examples/java-tutorial
và có cấu trúc như sau:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
Xây dựng bằ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 trữ các tệp nguồn của dự án và đầu ra của bản dựng của Bazel. Thư viện này cũng chứa các tệp mà Bazel công nhận là đặc biệt:
Tệp
WORKSPACE
xác định thư mục và nội dung trong đó là không gian làm việc Bazel và nằm ở gốc trong 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 xây dựng các phần khác nhau của dự án. (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 là WORKSPACE
trong thư mục đó.
Khi Bazel xây dựng dự án, tất cả dữ liệu đầu vào và phần phụ thuộc phải đặt trong cùng một không gian làm việc. Các tệp nằm trong các không gian làm việc khác nhau sẽ độc lập với nhau trừ phi được liên kết. Điều này nằm ngoài phạm vi của hướng dẫn này.
Tìm hiểu về tệp XÂY DỰNG
Tệp BUILD
chứa nhiều loại hướng dẫn về Bazel.
Loại quan trọng nhất là quy tắc xây dựng, cho Bazel biết cách tạo các đầu ra mong muốn, chẳng hạn như các tệp nhị phân hoặc thư viện có thể thực thi. Mỗi thực thể của quy tắc bản dựng trong tệp BUILD
được gọi là một mục tiêu và trỏ đến một nhóm tệp nguồn cũng như phần phụ thuộc cụ thể. Một mục tiêu cũng có thể trỏ đến các mục tiêu khác.
Hãy xem tệp java-tutorial/BUILD
:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
Trong ví dụ của chúng ta, mục tiêu ProjectRunner
tạo thực thể cho quy tắc java_binary
tích hợp sẵn của Bazel. Quy tắc này yêu cầu Bazel tạo tệp .jar
và tập lệnh shell 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.
Mặc dù thuộc tính name
là bắt buộc, nhưng nhiều thuộc tính là 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ể nhận thấy rằng ví dụ của chúng tôi sử dụng glob để truyền một tập hợp tệp nguồn cho Bazel thay vì liệt kê từng tệp một.)
Xây dựng dự án
Để tạo dự án mẫu, hãy chuyển đến thư mục java-tutorial
rồi chạy:
bazel build //:ProjectRunner
Trong nhãn mục tiêu, phần //
là vị trí của tệp BUILD
so với thư mục gốc của không gian làm việc (trong trường hợp này là thư mục gốc) và ProjectRunner
là tên mục tiêu trong tệp BUILD
. (Bạn sẽ 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 đầu ra 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 mục tiêu Bazel đầu tiên của mình! Bazel đặt các đầu ra bản dựng trong thư mục bazel-bin
ở gốc của không gian làm việc. Duyệt qua nội dung của trình duyệt để 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 được tạo:
bazel-bin/ProjectRunner
Xem biểu đồ phần phụ thuộc
Bazel yêu cầu bạn phải khai báo rõ ràng các phần phụ thuộc của 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, giúp tạo các bản dựng gia tăng chính xác.
Để trực quan hoá các phần phụ thuộc của dự án mẫu, bạn có thể tạo bản trình bày văn bản của biểu đồ phần phụ thuộc bằng cách chạy lệnh sau tại thư mục gốc của không gian làm việc:
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 //:ProjectRunner
mục tiêu (ngoại trừ các phần phụ thuộc lưu trữ và ngầm ẩn) rồi định dạng kết quả 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 2 tệp nguồn mà 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 tạo dự án và kiểm tra các phần phụ thuộc của dự án, sau đó bạn có thể thêm một số phần phụ thuộc.
Tinh chỉnh bản dựng Bazel
Mặc dù một mục tiêu là đủ cho các dự án nhỏ, nhưng bạn nên chia các dự án lớn hơn thành nhiều mục tiêu và gói để cho phép xây dựng các bản dựng có mức độ gia tăng nhanh (tức là chỉ tạo lại những nội dung đã thay đổi) và tăng tốc độ bản dựng bằng cách tạo nhiều phần của 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 2 mục tiêu. Thay thế nội dung của tệp java-tutorial/BUILD
bằng:
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 đó tạo tệp nhị phân ProjectRunner
. Thuộc tính deps
trong java_binary
cho Bazel biết rằng cần phải 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 đầu ra 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 được tạo:
bazel-bin/ProjectRunner
Nếu giờ bạn đang sửa đổi ProjectRunner.java
và xây dựng lại dự án, thì Bazel chỉ 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 ProjectRunner
phụ thuộc vào cùng dữ liệu đầu vào như trước đây, nhưng cấu trúc của bản dựng thì khác:
Giờ thì bạn đã xây dựng được dự án với 2 mục tiêu. Mục tiêu ProjectRunner
tạo hai tệp nguồn và phụ thuộc vào một mục tiêu khác (:greeter
) tạo nên 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 xem thư mục src/main/java/com/example/cmdline
, bạn có thể thấy thư mục này cũng chứa 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 chứa hai gói, //src/main/java/com/example/cmdline
và //
(vì 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 đó có 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, để việc tạo bản dựng thành công, bạn phải cung cấp mục tiêu runner
trong chế độ hiển thị //src/main/java/com/example/cmdline/BUILD
một cách rõ ràng cho các mục tiêu trong //BUILD
bằng cách sử dụng thuộc tính visibility
. Điều này là do theo mặc định, các mục tiêu 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 chế độ hiển thị mục tiêu để ngăn chặn các vấn đề như thư viện chứa thông tin triển khai rò rỉ vào các 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__"],
)
Giờ đây, 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 đầu ra 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 được tạo:
./bazel-bin/src/main/java/com/example/cmdline/runner
Bây giờ, bạn đã sửa đổi dự án để xây dựng thành hai gói, mỗi gói chứa một mục tiêu và hiểu được các phần phụ thuộc giữa các gói đó.
Sử dụng nhãn để tham chiếu đến các mục tiêu
Trong các tệp BUILD
và ở dòng lệnh, Bazel sử dụng nhãn mục tiêu để tham chiếu đến các mục tiêu – ví dụ: //:ProjectRunner
hoặc //src/main/java/com/example/cmdline:runner
. Cú pháp của chúng như sau:
//path/to/package:target-name
Nếu mục tiêu là một mục tiêu quy tắc, thì path/to/package
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 mục tiêu trong tệp BUILD
(thuộc tính name
). Nếu mục tiêu là mục tiêu tệp, 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 mục tiêu, 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ỉ cần sử dụng //:target-name
. Khi tham chiếu các mục tiêu trong cùng một tệp BUILD
, bạn thậm chí có thể bỏ qua giá trị nhận dạng gốc của không gian làm việc //
và chỉ cần 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 phải chỉ định đường dẫn gói, vì gốc của không gian làm việc chính là một gói (//
) và hai nhãn mục tiêu của bạn chỉ 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 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 tạo tệp nhị phân với tất cả các phần phụ thuộc thời gian chạy của mục tiêu đó. Việc 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 của mình.
Như bạn đã nhớ, quy tắc bản dựng java_binary sẽ tạo ra .jar
và tập lệnh shell bao bọc. Hãy xem nội dung của runner.jar
bằng lệnh này:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
Nội dung bao gồm:
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 là Greeting.class
. Tập lệnh runner
mà Bazel tạo sẽ thêm greeter.jar
vào classpath. Vì vậy, nếu bạn để tập lệnh này như vậy, tập lệnh sẽ chạy cục bộ, nhưng sẽ không chạy độc lập trên một máy khác. May mắn là quy tắc java_binary
cho phép bạn tạo một tệp nhị phân độc lập và có thể triển khai. Để tạo đối tượng này, hãy thêm _deploy.jar
vào tên mục tiêu:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel tạo đầu ra 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
mà bạn có thể chạy độc lập với môi trường phát triển của mình vì nó chứa các phần phụ thuộc bắt buộc của thời gian chạy. Hãy xem nội dung của tệp JAR độc lập này bằng cách sử dụng cùng một lệnh như trước đây:
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 chi tiết, hãy xem:
rules_jvm_external cho các quy tắc để 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à kho lưu trữ 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 dự án C++ bằng Bazel.
Hướng dẫn về ứng dụng Android và hướng dẫn ứng dụng iOS) để bắt đầu tạo ứng dụng dành cho thiết bị di động cho Android và iOS bằng Bazel.
Chúc bạn xây dựng thành công!