Hướng dẫn này trình bày những 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à BUILD tệp.
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
- Hình dung 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
- Tham chiếu các mục tiê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ị cho hướng dẫn này, trước tiên hãy Cài đặt Bazel nếu bạn chưa cài đặt.
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 đều đượ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 "Duyệt 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ác cửa sổ hộp thoại.
Tải 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/examplesDự á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
└── MODULE.bazel
Tạo bằng Bazel
Thiết lập không gian làm việc
Trước khi có thể tạo một dự án, bạn cần thiết lập không gian làm việc cho dự án đó. Không gian làm việc là một thư mục chứa các tệp nguồn của dự án và đầu ra bản dựng của Bazel. Không gian làm việc cũng chứa các tệp mà Bazel nhận dạng là đặc biệt:
Tệp
MODULE.bazelxác định thư mục và nội dung của thư mục đó là không gian làm việc của Bazel và nằm ở gốc của cấu trúc thư mục của dự án,Một hoặc nhiều tệp
BUILDcho Bazel biết cách tạo 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ệpBUILDlà một gói. Bạn sẽ tìm hiểu về các gói sau trong hướng dẫn này.)
Để chỉ định một thư mục là không gian làm việc của Bazel, hãy tạo một tệp trống có tên là
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 nằm 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 BUILD
Tệp BUILD chứa nhiều loại hướng dẫn khác nhau cho Bazel.
Loại quan trọng nhất là quy tắc bản dựng, cho Bazel biết cách tạo các
đầu ra mong muốn, chẳng hạn như tệp nhị phân thực thi hoặc thư viện. Mỗi thực thể
của quy tắc bản dựng trong tệp BUILD được gọi là mục tiêu và trỏ đến một
tập hợp cụ thể gồm các tệp nguồn và phần phụ thuộc. 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 tôi, mục tiêu ProjectRunner tạo thực thể cho quy tắc
java_binary tích hợp của Bazel. Quy tắc này cho Bazel biết cách
tạo tệp .jar và 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 mục tiêu đó.
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. (Có thể bạn đã 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 các tệp nguồn đến Bazel
thay vì liệt kê từng tệp một.)
Tạo dự án
Để tạo dự án mẫu, hãy chuyển đến thư mục java-tutorial và chạy:
bazel build //:ProjectRunnerTrong 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à chính 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 ra đầ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! Bazel đặt đầ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 thư mục này để nắm được cấu trúc đầu ra của Bazel.
Bây giờ, hãy kiểm tra tệp nhị phân mới tạo:
bazel-bin/ProjectRunnerXem xét biểu đồ phần phụ thuộc
Bazel yêu cầu các phần phụ thuộc bản dựng phải được khai báo rõ ràng trong các 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, điều này cho phép tạo bản dựng gia tăng chính xác.
Để hình dung 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 này ở thư mục gốc của không gian làm việc:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graphLệ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 các phần phụ thuộc ngầm ẩn và máy chủ) và định dạng
đầu ra dưới dạng biểu đồ.
Sau đó, hãy 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, tạo dự án và kiểm tra các phần phụ thuộc của dự án, bạn có thể thêm một số độ phức tạp.
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 có thể muốn chia các dự án lớn hơn thành nhiều mục tiêu và gói để cho phép tạo bản dựng gia tăng nhanh chóng (tức là chỉ tạo lại những gì đã thay đổi) và để tăng tốc quá trình tạo 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 nội dung 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, Bazel sẽ tạo thư viện greeter trước, sau đó là tệp nhị phân
ProjectRunner. Thuộc tính deps trong java_binary cho Bazel biết rằng
thư viện greeter là bắt buộc để 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 //:ProjectRunnerBazel tạo ra đầ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 tra tệp nhị phân mới tạo:
bazel-bin/ProjectRunnerNếu bây giờ bạn sửa đổi ProjectRunner.java và tạo lại dự án, Bazel sẽ 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 rằng ProjectRunner phụ thuộc vào
cùng một dữ liệu đầu vào như trước đây, nhưng cấu trúc của bản dựng lại khác:
Bây giờ, bạn đã tạo dự án với 2 mục tiêu. Mục tiêu ProjectRunner tạo
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 thư mục
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 cùng với một số tệp nguồn. Do đó, đối với Bazel, không gian làm việc hiện
chứa 2 gói, //src/main/java/com/example/cmdline và // (vì
có một tệp BUILD ở gốc của 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, để bản dựng thành công, bạn phải cho phép mục tiêu runner trong //src/main/java/com/example/cmdline/BUILD hiển thị các mục tiêu trong
//BUILD bằ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 chi tiết về việc triển khai
bị 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__"],
)
Bây giờ, bạn có thể tạo gói mới bằng cách chạy lệnh sau ở gốc của không gian làm việc:
bazel build //src/main/java/com/example/cmdline:runnerBazel tạo ra đầ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 tra tệp nhị phân mới tạo:
./bazel-bin/src/main/java/com/example/cmdline/runnerBây giờ, bạn đã sửa đổi dự án để tạo thành 2 gói, mỗi gói chứa một mục tiêu và hiểu các phần phụ thuộc giữa chúng.
Sử dụng nhãn để tham chiếu 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
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ụ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 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 đủ.
Khi tham chiếu các mục tiêu ở 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 BUILD
tệp, bạn thậm chí có thể bỏ qua giá trị nhận dạng thư mục 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 phải
chỉ định đường dẫn gói, vì thư mục gốc của không gian làm việc là chính một gói (//) và
2 nhãn mục tiêu của bạn chỉ đơ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ụ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. Đ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 ra 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.jarNội dung là:
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,
Greeting.class. Tập lệnh runner mà Bazel tạo sẽ thêm greeter.jar
vào đường dẫn lớp. Vì vậy, nếu bạn để nguyên như thế này, tập lệnh sẽ chạy cục bộ, nhưng nó
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ó thể triển khai, khép kín. Để tạo tệp nhị phân 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.jarBazel tạo ra đầ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. Bạn có thể chạy tệp này độc lập bên ngoài
môi trường phát triển vì tệp này chứa các phần phụ thuộc thời gian chạy bắt buộc.
Hãy xem nội dung của JAR độc lập này bằng cùng một lệnh như trước:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jarNộ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 để biết 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 sử dụng 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 C++ để bắt đầu tạo dự án C++ bằng Bazel.
Hướng dẫn về ứng dụng Android và ứng dụng iOS) để bắt đầu với tạo ứng dụng di động cho Android và iOS bằng Bazel.
Chúc bạn tạo thành công!