Trang này trình bày cách tạo một chương trình bằng Bazel, cú pháp lệnh bản dựng và cú pháp mẫu mục tiêu.
Bắt đầu nhanh
Để chạy Bazel, hãy chuyển đến thư mục workspace (không gian làm việc) cơ sở hoặc bất kỳ thư mục con nào của thư mục đó rồi nhập bazel
. Xem phần tạo nếu bạn cần tạo một không gian làm việc mới.
bazel help
[Bazel release bazel version]
Usage: bazel command options ...
Các câu lệnh có thể dùng
analyze-profile
: Phân tích dữ liệu hồ sơ bản dựng.aquery
: Thực thi truy vấn trên biểu đồ hành động sau khi phân tích.build
: Tạo các mục tiêu đã chỉ định.canonicalize-flags
: Chuẩn hoá cờ Bazel.clean
: Xoá các tệp đầu ra và tuỳ ý dừng máy chủ.cquery
: Thực thi truy vấn biểu đồ phần phụ thuộc sau khi phân tích.dump
: Kết xuất trạng thái nội bộ của quy trình máy chủ Bazel.help
: In phần trợ giúp cho các lệnh hoặc chỉ mục.info
: Hiện thông tin thời gian chạy về máy chủ bazel.fetch
: Tìm nạp tất cả phần phụ thuộc bên ngoài của một mục tiêu.mobile-install
: Cài đặt ứng dụng trên thiết bị di động.query
: Thực thi truy vấn biểu đồ phần phụ thuộc.run
: Chạy mục tiêu đã chỉ định.shutdown
: Dừng máy chủ Bazel.test
: Tạo và chạy các mục tiêu kiểm thử đã chỉ định.version
: In thông tin phiên bản cho Bazel.
Nhận trợ giúp
bazel help command
: In thông tin trợ giúp và các tuỳ chọn chocommand
.bazel help
startup_options
: Các tuỳ chọn cho JVM lưu trữ Bazel.bazel help
target-syntax
: Giải thích cú pháp để chỉ định mục tiêu.bazel help info-keys
: Hiển thị danh sách các khoá mà lệnh info sử dụng.
Công cụ bazel
thực hiện nhiều hàm, được gọi là lệnh. Các loại phổ biến nhất là bazel build
và bazel test
. Bạn có thể duyệt qua các thông báo trợ giúp trực tuyến bằng bazel help
.
Xây dựng một mục tiêu
Trước khi có thể bắt đầu một bản dựng, bạn cần một không gian làm việc. Không gian làm việc là một cây thư mục chứa tất cả các tệp nguồn cần thiết để tạo ứng dụng. Bazel cho phép bạn tạo bản dựng từ một phương tiện chỉ có thể đọc.
Để tạo một chương trình bằng Bazel, hãy nhập bazel build
, theo sau là mục tiêu mà bạn muốn tạo.
bazel build //foo
Sau khi đưa ra lệnh tạo //foo
, bạn sẽ thấy kết quả tương tự như sau:
INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions
Trước tiên, Bazel tải tất cả các gói trong biểu đồ phần phụ thuộc của mục tiêu. Trong đó bao gồm các phần phụ thuộc đã khai báo, các tệp được liệt kê trực tiếp trong tệp BUILD
của mục tiêu và phần phụ thuộc bắc cầu, các tệp được liệt kê trong các tệp BUILD
của phần phụ thuộc của mục tiêu. Sau khi xác định tất cả các phần phụ thuộc, Bazel sẽ phân tích để đảm bảo tính chính xác và tạo các hành động tạo bản dựng. Cuối cùng, Bazel thực thi các trình biên dịch và các công cụ khác của bản dựng.
Trong giai đoạn thực thi của bản dựng, Bazel sẽ in thông báo tiến trình. Thông báo tiến trình bao gồm bước bản dựng hiện tại (chẳng hạn như trình biên dịch hoặc trình liên kết) khi bắt đầu và số lượng đã hoàn thành trên tổng số hành động bản dựng. Khi quá trình tạo bản dựng bắt đầu, số lượng hành động thường tăng lên khi Bazel khám phá toàn bộ biểu đồ hành động, nhưng con số này sẽ ổn định trong vòng vài giây.
Khi quá trình xây dựng kết thúc, Bazel sẽ in những mục tiêu đã được yêu cầu, liệu các mục tiêu đó có được tạo thành công hay không và nếu có, bạn có thể tìm thấy các tệp đầu ra ở đâu. Các tập lệnh chạy bản dựng có thể phân tích cú pháp kết quả này một cách đáng tin cậy; xem --show_result
để biết thêm chi tiết.
Nếu bạn nhập lại chính lệnh này, bản dựng sẽ hoàn tất nhanh hơn nhiều.
bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action
Đây là bản dựng rỗng. Vì không có gì thay đổi nên không có gói nào để tải lại và không có bước xây dựng nào để thực thi. Nếu có gì thay đổi trong "foo" hoặc các phần phụ thuộc của lớp này, Bazel sẽ thực thi lại một số thao tác bản dựng hoặc hoàn tất một bản dựng gia tăng.
Xây dựng nhiều mục tiêu
Bazel cho phép một số cách chỉ định mục tiêu cần xây dựng. Nhìn chung, các mẫu này được gọi là mẫu mục tiêu. Cú pháp này được dùng trong các lệnh như build
, test
hoặc query
.
Trong khi nhãn được dùng để chỉ định từng mục tiêu, chẳng hạn như để khai báo phần phụ thuộc trong tệp BUILD
, thì mẫu mục tiêu của Bazel chỉ định nhiều mục tiêu. Mẫu mục tiêu là một khái quát của cú pháp nhãn cho nhóm mục tiêu, sử dụng ký tự đại diện. Trong trường hợp đơn giản nhất, mọi nhãn hợp lệ cũng là một mẫu mục tiêu hợp lệ, xác định một tập hợp gồm đúng một mục tiêu.
Tất cả mẫu mục tiêu bắt đầu bằng //
đều được phân giải tương ứng với không gian làm việc hiện tại.
//foo/bar:wiz |
Chỉ một mục tiêu //foo/bar:wiz . |
//foo/bar |
Tương đương với //foo/bar:bar . |
//foo/bar:all |
Tất cả mục tiêu quy tắc trong gói foo/bar . |
//foo/... |
Tất cả mục tiêu quy tắc trong tất cả gói bên dưới thư mục foo . |
//foo/...:all |
Tất cả mục tiêu quy tắc trong tất cả gói bên dưới thư mục foo . |
//foo/...:* |
Tất cả mục tiêu (quy tắc và tệp) trong mọi gói trong thư mục foo . |
//foo/...:all-targets |
Tất cả mục tiêu (quy tắc và tệp) trong tất cả các gói bên dưới thư mục foo . |
//... |
Tất cả mục tiêu quy tắc trong các gói trong kho lưu trữ chính. Không bao gồm các mục tiêu từ kho lưu trữ bên ngoài. |
//:all |
Mọi quy tắc đều nhắm mục tiêu trong gói cấp cao nhất, nếu có tệp "BUILD" ("XÂY DỰNG") ở thư mục gốc của không gian làm việc. |
Các mẫu mục tiêu không bắt đầu bằng //
sẽ được phân giải tương ứng với thư mục đang hoạt động. Các ví dụ này giả định thư mục đang hoạt động là foo
:
:foo |
Tương đương với //foo:foo . |
bar:wiz |
Tương đương với //foo/bar:wiz . |
bar/wiz |
Tương đương với:
|
bar:all |
Tương đương với //foo/bar:all . |
:all |
Tương đương với //foo:all . |
...:all |
Tương đương với //foo/...:all . |
... |
Tương đương với //foo/...:all . |
bar/...:all |
Tương đương với //foo/bar/...:all . |
Theo mặc định, các đường liên kết tượng trưng của thư mục sẽ được tuân theo cho các mẫu mục tiêu đệ quy, ngoại trừ các đường liên kết tượng trưng trỏ đến trong cơ sở đầu ra, chẳng hạn như các đường liên kết tượng trưng thuận tiện được tạo trong thư mục gốc của không gian làm việc.
Ngoài ra, Bazel không tuân theo đường liên kết tượng trưng khi đánh giá các mẫu mục tiêu đệ quy trong bất kỳ thư mục nào chứa tệp có tên như sau: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN
foo/...
là ký tự đại diện cho gói, cho biết tất cả các gói theo cách đệ quy bên dưới thư mục foo
(đối với tất cả thư mục gốc của đường dẫn gói). :all
là một ký tự đại diện trên các mục tiêu, khớp với tất cả quy tắc trong một gói. Bạn có thể kết hợp hai ký tự này, như trong foo/...:all
. Khi sử dụng cả hai ký tự đại diện, tên này có thể được viết tắt thành foo/...
.
Ngoài ra, :*
(hoặc :all-targets
) là ký tự đại diện khớp với mọi mục tiêu trong các gói được so khớp, bao gồm cả các tệp thường không được tạo theo quy tắc nào, chẳng hạn như tệp _deploy.jar
liên kết với quy tắc java_binary
.
Điều này ngụ ý rằng :*
biểu thị một tập mẹ của :all
; mặc dù có thể gây nhầm lẫn, nhưng cú pháp này cho phép sử dụng ký tự đại diện :all
quen thuộc cho các bản dựng thông thường, trong đó người dùng không muốn tạo các mục tiêu như _deploy.jar
.
Ngoài ra, Bazel cho phép sử dụng dấu gạch chéo thay vì dấu hai chấm theo cú pháp nhãn; điều này thường thuận tiện khi sử dụng tính năng mở rộng tên tệp Bash.
Ví dụ: foo/bar/wiz
tương đương với //foo/bar:wiz
(nếu có gói foo/bar
) hoặc tương đương với //foo:bar/wiz
(nếu có gói foo
).
Nhiều lệnh Bazel chấp nhận danh sách các mẫu mục tiêu làm đối số và tất cả các lệnh này đều tuân theo toán tử phủ định tiền tố -
. Bạn có thể dùng hàm này để trừ một tập hợp mục tiêu khỏi tập hợp do các đối số trước chỉ định. Xin lưu ý rằng điều này có nghĩa là thứ tự cũng rất quan trọng. Ví dụ:
bazel build foo/... bar/...
nghĩa là "xây dựng tất cả các mục tiêu có giá trị thấp hơn foo
và tất cả các mục tiêu có giá trị dưới bar
", trong khi
bazel build -- foo/... -foo/bar/...
có nghĩa là "tạo tất cả mục tiêu bên dưới foo
ngoại trừ các mục tiêu bên dưới foo/bar
". (Bạn bắt buộc phải sử dụng đối số --
để ngăn các đối số tiếp theo bắt đầu bằng -
được diễn giải là các tuỳ chọn bổ sung.)
Tuy nhiên, điều quan trọng cần lưu ý là việc trừ các mục tiêu theo cách này sẽ không đảm bảo rằng các mục tiêu đó không được tạo, vì chúng có thể là phần phụ thuộc của các mục tiêu không bị trừ. Ví dụ: nếu có một mục tiêu //foo:all-apis
trong số các mục tiêu khác phụ thuộc vào //foo/bar:api
, thì mục tiêu sau sẽ được tạo trong quá trình tạo mục tiêu trước.
Các mục tiêu có tags = ["manual"]
không được đưa vào mẫu mục tiêu ký tự đại diện (...
, :*
, :all
, v.v.) khi được chỉ định trong các lệnh như bazel build
và bazel test
(nhưng chúng được đưa vào mẫu mục tiêu ký tự đại diện âm, tức là chúng sẽ được trừ đi). Bạn nên chỉ định các mục tiêu kiểm thử như vậy bằng các mẫu mục tiêu rõ ràng trên dòng lệnh nếu bạn muốn Bazel tạo/kiểm thử các mục tiêu đó. Ngược lại, bazel query
không tự động thực hiện bất kỳ hoạt động lọc nào như vậy (điều này sẽ làm mất mục đích của bazel query
).
Đang tìm nạp các phần phụ thuộc bên ngoài
Theo mặc định, Bazel sẽ tải xuống và liên kết tượng trưng với các phần phụ thuộc bên ngoài trong quá trình tạo bản dựng. Tuy nhiên, điều này có thể không mong muốn, vì bạn muốn biết thời điểm thêm các phần phụ thuộc bên ngoài mới hoặc vì bạn muốn "tải trước" các phần phụ thuộc (ví dụ: trước chuyến bay mà bạn sẽ không có mạng). Nếu muốn ngăn việc thêm các phần phụ thuộc mới trong quá trình tạo bản dựng, bạn có thể chỉ định cờ --fetch=false
. Xin lưu ý rằng cờ này chỉ áp dụng cho các quy tắc kho lưu trữ không trỏ đến một thư mục trong hệ thống tệp cục bộ. Ví dụ: các thay đổi đối với local_repository
, new_local_repository
và các quy tắc kho lưu trữ SDK Android và NDK sẽ luôn có hiệu lực bất kể giá trị --fetch
là bao nhiêu.
Nếu bạn không cho phép tìm nạp trong quá trình tạo bản dựng và Bazel tìm thấy các phần phụ thuộc bên ngoài mới, thì bản dựng của bạn sẽ không thành công.
Bạn có thể tìm nạp các phần phụ thuộc theo cách thủ công bằng cách chạy bazel fetch
. Nếu không cho phép tìm nạp trong quá trình xây dựng, bạn cần chạy bazel fetch
:
- Trước khi bạn tạo bản dựng lần đầu tiên.
- Sau khi bạn thêm một phần phụ thuộc bên ngoài mới.
Sau khi chạy, bạn không cần chạy lại cho đến khi tệp MODULE.bazel thay đổi.
fetch
lấy danh sách mục tiêu để tìm nạp các phần phụ thuộc. Ví dụ: lệnh này sẽ tìm nạp các phần phụ thuộc cần thiết để tạo //foo:bar
và //bar:baz
:
bazel fetch //foo:bar //bar:baz
Để tìm nạp tất cả phần phụ thuộc bên ngoài cho một không gian làm việc, hãy chạy mã:
bazel fetch //...
Với Bazel 7 trở lên, nếu đã bật Bzlmod, bạn cũng có thể tìm nạp tất cả các phần phụ thuộc bên ngoài bằng cách chạy
bazel fetch
Bạn không cần chạy tính năng tìm nạp bazel nếu có tất cả các công cụ bạn đang sử dụng (từ các tệp jar thư viện đến chính JDK) trong thư mục gốc của không gian làm việc.
Tuy nhiên, nếu bạn đang sử dụng bất kỳ nội dung nào bên ngoài thư mục không gian làm việc, thì Bazel sẽ tự động chạy bazel fetch
trước khi chạy bazel build
.
Bộ nhớ đệm của kho lưu trữ
Bazel cố gắng tránh tìm nạp cùng một tệp nhiều lần, ngay cả khi cần cùng một tệp trong nhiều không gian làm việc hoặc nếu định nghĩa của kho lưu trữ bên ngoài thay đổi nhưng vẫn cần cùng một tệp để tải xuống. Để làm như vậy, bazel sẽ lưu tất cả các tệp đã tải xuống vào bộ nhớ đệm kho lưu trữ. Theo mặc định, bộ nhớ đệm này nằm tại ~/.cache/bazel/_bazel_$USER/cache/repos/v1/
. Bạn có thể thay đổi vị trí bằng tuỳ chọn --repository_cache
. Bộ nhớ đệm được chia sẻ giữa tất cả không gian làm việc và các phiên bản bazel đã cài đặt.
Một mục nhập được lấy từ bộ nhớ đệm nếu Bazel biết chắc chắn rằng mục đó có bản sao của tệp chính xác, tức là nếu yêu cầu tải xuống có tổng SHA256 của tệp được chỉ định và tệp có hàm băm đó nằm trong bộ nhớ đệm. Vì vậy, việc chỉ định một hàm băm cho từng tệp bên ngoài không chỉ là một ý tưởng hay về khía cạnh bảo mật, mà còn giúp tránh được các tệp tải xuống không cần thiết.
Đối với mỗi lượt truy cập vào bộ nhớ đệm, thời gian sửa đổi của tệp trong bộ nhớ đệm sẽ được cập nhật. Bằng cách này, bạn có thể dễ dàng xác định lần sử dụng gần đây nhất của một tệp trong thư mục bộ nhớ đệm, chẳng hạn như để dọn sạch bộ nhớ đệm theo cách thủ công. Bộ nhớ đệm không bao giờ được tự động dọn dẹp vì bộ nhớ đệm này có thể chứa bản sao của một tệp không còn ở trên.
[Không dùng nữa] Thư mục tệp phân phối
Không dùng nữa: Nên sử dụng bộ nhớ đệm kho lưu trữ để tạo bản dựng ngoại tuyến.
Thư mục phân phối là một cơ chế khác của Bazel để tránh tải xuống không cần thiết. Bazel tìm kiếm các thư mục phân phối trước bộ nhớ đệm của kho lưu trữ. Điểm khác biệt chính là thư mục phân phối yêu cầu bạn phải chuẩn bị theo cách thủ công.
Khi sử dụng tuỳ chọn --distdir=/path/to-directory
, bạn có thể chỉ định các thư mục chỉ có thể đọc bổ sung để tìm tệp thay vì tìm nạp các tệp đó. Tệp được lấy từ một thư mục như vậy nếu tên tệp
bằng với tên cơ sở của URL và ngoài ra, hàm băm của tệp
bằng với tên được chỉ định trong yêu cầu tải xuống. Cách này chỉ hiệu quả nếu hàm băm tệp được chỉ định trong phần khai báo quy tắc kho lưu trữ.
Mặc dù không cần điều kiện về tên tệp để đảm bảo tính chính xác, nhưng việc này sẽ giảm số lượng tệp đề xuất xuống còn một tệp cho mỗi thư mục được chỉ định. Bằng cách này, việc chỉ định thư mục tệp phân phối vẫn hiệu quả, ngay cả khi số lượng tệp trong thư mục đó tăng lên lớn.
Chạy Bazel trong một môi trường thoáng đãng
Để duy trì kích thước nhị phân của Bazel nhỏ, các phần phụ thuộc ngầm ẩn của Bazel sẽ được tìm nạp qua mạng trong lần đầu chạy. Những phần phụ thuộc ngầm ẩn này chứa các chuỗi công cụ và quy tắc có thể không cần thiết cho mọi người. Ví dụ: các công cụ Android chỉ được huỷ gói và tìm nạp khi tạo dự án Android.
Tuy nhiên, các phần phụ thuộc ngầm ẩn này có thể gây ra sự cố khi chạy Bazel trong môi trường tách biệt, ngay cả khi bạn đã cung cấp tất cả các phần phụ thuộc bên ngoài. Để giải quyết vấn đề đó, bạn có thể chuẩn bị bộ nhớ đệm kho lưu trữ (với Bazel 7 trở lên) hoặc thư mục phân phối (với Bazel trước phiên bản 7) chứa các phần phụ thuộc này trên một máy có quyền truy cập mạng, sau đó chuyển các phần phụ thuộc đó sang môi trường tách biệt với mạng bằng phương pháp ngoại tuyến.
Bộ nhớ đệm lưu trữ (với Bazel 7 trở lên)
Để chuẩn bị bộ nhớ đệm kho lưu trữ, hãy sử dụng cờ --repository_cache
. Bạn sẽ cần thực hiện việc này một lần cho mỗi phiên bản nhị phân Bazel mới, vì các phần phụ thuộc ngầm ẩn có thể khác nhau tuỳ theo mỗi bản phát hành.
Để tìm nạp các phần phụ thuộc đó bên ngoài môi trường khoảng không quảng cáo, trước tiên, hãy tạo một không gian làm việc trống:
mkdir empty_workspace && cd empty_workspace
touch MODULE.bazel
Để tìm nạp các phần phụ thuộc Bzlmod tích hợp, hãy chạy
bazel fetch --repository_cache="path/to/repository/cache"
Nếu bạn vẫn dựa vào tệp WORKSPACE cũ, để tìm nạp các phần phụ thuộc WORKSPACE tích hợp, hãy chạy
bazel sync --repository_cache="path/to/repository/cache"
Cuối cùng, khi bạn sử dụng Bazel trong môi trường tách biệt, hãy truyền cùng một cờ --repository_cache
. Để thuận tiện, bạn có thể thêm thuộc tính này dưới dạng mục nhập .bazelrc
:
common --repository_cache="path/to/repository/cache"
Ngoài ra, bạn cũng có thể cần phải nhân bản
BCR cục bộ và sử dụng
cờ --registry
để trỏ bản sao cục bộ của bạn nhằm ngăn Bazel truy cập vào
BCR thông qua Internet. Hãy thêm dòng sau vào .bazelrc
:
common --registry="path/to/local/bcr/registry"
Thư mục phân phối (với Bazel phiên bản trước 7)
Để chuẩn bị thư mục phân phối, hãy sử dụng cờ --distdir
. Bạn sẽ cần thực hiện việc này một lần cho mỗi phiên bản tệp nhị phân Bazel mới, vì các phần phụ thuộc ngầm ẩn có thể khác nhau đối với mỗi bản phát hành.
Để tạo các phần phụ thuộc này bên ngoài môi trường khoảng không quảng cáo, trước tiên, hãy kiểm tra cây nguồn Bazel ở phiên bản phù hợp:
git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"
Sau đó, hãy tạo tệp tar chứa các phần phụ thuộc thời gian chạy ngầm ẩn cho phiên bản Bazel cụ thể đó:
bazel build @additional_distfiles//:archives.tar
Xuất tệp tarball này vào một thư mục có thể sao chép vào môi trường tách biệt. Lưu ý cờ --strip-components
, vì --distdir
có thể khá khó chịu với cấp độ lồng thư mục:
tar xvf bazel-bin/external/additional_distfiles/archives.tar \
-C "$NEW_DIRECTORY" --strip-components=3
Cuối cùng, khi bạn sử dụng Bazel trong môi trường tách biệt, hãy truyền cờ --distdir
trỏ đến thư mục. Để thuận tiện, bạn có thể thêm thuộc tính này dưới dạng mục nhập .bazelrc
:
build --distdir=path/to/directory
Cấu hình bản dựng và biên dịch chéo
Tất cả dữ liệu đầu vào chỉ định hành vi và kết quả của một bản dựng nhất định có thể được chia thành hai danh mục riêng biệt. Loại đầu tiên là thông tin nội tại được lưu trữ trong các tệp BUILD
của dự án: quy tắc bản dựng, giá trị của các thuộc tính và tập hợp đầy đủ các phần phụ thuộc bắc cầu.
Loại thứ hai là dữ liệu bên ngoài hoặc môi trường, do người dùng hoặc công cụ xây dựng cung cấp: lựa chọn cấu trúc mục tiêu, tuỳ chọn biên dịch và liên kết, cũng như các tuỳ chọn cấu hình chuỗi công cụ khác. Chúng tôi gọi một bộ dữ liệu môi trường hoàn chỉnh là cấu hình.
Trong một bản dựng bất kỳ, có thể có nhiều cấu hình. Hãy cân nhắc việc biên dịch chéo, trong đó bạn tạo một tệp thực thi //foo:bin
cho kiến trúc 64 bit, nhưng máy trạm của bạn là máy 32 bit. Rõ ràng là bản dựng sẽ yêu cầu tạo //foo:bin
bằng một chuỗi công cụ có thể tạo các tệp thực thi 64 bit, nhưng hệ thống xây dựng cũng phải tạo nhiều công cụ được sử dụng trong chính bản dựng đó, chẳng hạn như các công cụ được tạo từ nguồn, sau đó được sử dụng trong một genrule, và các công cụ này phải được tạo để chạy trên máy trạm của bạn. Do đó, chúng ta có thể xác định hai cấu hình: cấu hình thực thi dùng để tạo các công cụ chạy trong quá trình xây dựng và cấu hình mục tiêu (hoặc cấu hình yêu cầu, nhưng chúng ta thường nói "cấu hình mục tiêu" mặc dù từ đó đã có nhiều nghĩa), dùng để tạo tệp nhị phân mà bạn đã yêu cầu.
Thông thường, có nhiều thư viện là điều kiện tiên quyết của cả mục tiêu bản dựng được yêu cầu (//foo:bin
) và một hoặc nhiều công cụ thực thi, ví dụ: một số thư viện cơ sở. Bạn phải tạo các thư viện đó hai lần, một lần cho cấu hình thực thi và một lần cho cấu hình mục tiêu. Bazel đảm bảo rằng cả hai biến thể đều được tạo và các tệp phái sinh được giữ riêng biệt để tránh can thiệp; thường thì các mục tiêu như vậy có thể được tạo đồng thời vì chúng độc lập với nhau. Nếu bạn thấy thông báo tiến trình cho biết một mục tiêu nhất định đang được tạo hai lần, thì đây có thể là lý do.
Cấu hình thực thi được lấy từ cấu hình mục tiêu như sau:
- Sử dụng cùng một phiên bản Crosstool (
--crosstool_top
) như đã chỉ định trong cấu hình yêu cầu, trừ phi--host_crosstool_top
được chỉ định. - Sử dụng giá trị
--host_cpu
cho--cpu
(mặc định:k8
). - Sử dụng cùng một giá trị của các tuỳ chọn này như được chỉ định trong cấu hình yêu cầu:
--compiler
,--use_ijars
và nếu sử dụng--host_crosstool_top
, thì giá trị của--host_cpu
sẽ được dùng để tra cứudefault_toolchain
trong Crosstool (bỏ qua--compiler
) cho cấu hình thực thi. - Sử dụng giá trị của
--host_javabase
cho--javabase
- Sử dụng giá trị của
--host_java_toolchain
cho--java_toolchain
- Sử dụng bản dựng được tối ưu hoá cho mã C++ (
-c opt
). - Không tạo thông tin gỡ lỗi (
--copt=-g0
). - Xoá thông tin gỡ lỗi khỏi các tệp thực thi và thư viện dùng chung (
--strip=always
). - Đặt tất cả tệp phái sinh ở một vị trí đặc biệt, khác với vị trí mà mọi cấu hình yêu cầu có thể sử dụng.
- Loại bỏ việc đóng dấu tệp nhị phân bằng dữ liệu bản dựng (xem các tuỳ chọn
--embed_*
). - Tất cả các giá trị khác vẫn giữ nguyên giá trị mặc định.
Có nhiều lý do khiến bạn nên chọn một cấu hình thực thi khác với cấu hình yêu cầu. Quan trọng nhất:
Trước tiên, bằng cách sử dụng các tệp nhị phân đã loại bỏ và tối ưu hoá, bạn sẽ giảm thời gian liên kết và thực thi các công cụ, dung lượng ổ đĩa mà các công cụ chiếm dụng và thời gian I/O mạng trong các bản dựng được phân phối.
Thứ hai, bằng cách tách riêng cấu hình thực thi và yêu cầu trong tất cả các bản dựng, bạn sẽ tránh được việc tạo lại rất tốn kém do các thay đổi nhỏ đối với cấu hình yêu cầu (chẳng hạn như thay đổi tuỳ chọn trình liên kết), như mô tả trước đó.
Sửa bản dựng lại tăng dần
Một trong những mục tiêu chính của dự án Bazel là đảm bảo việc tạo lại gia tăng chính xác. Các công cụ xây dựng trước đây, đặc biệt là các công cụ dựa trên Make, đưa ra một số giả định không hợp lý trong quá trình triển khai các bản dựng gia tăng.
Thứ nhất, dấu thời gian của các tệp tăng lên một cách đơn điệu. Mặc dù đây là trường hợp điển hình, nhưng bạn rất dễ mắc phải giả định này; việc đồng bộ hoá với bản sửa đổi trước đó của một tệp sẽ làm giảm thời gian sửa đổi của tệp đó; các hệ thống dựa trên Make sẽ không tạo lại.
Nói chung, mặc dù phát hiện được các thay đổi đối với tệp, nhưng Make không phát hiện được các thay đổi đối với lệnh. Nếu bạn thay đổi các tuỳ chọn được truyền đến trình biên dịch trong một bước bản dựng nhất định, thì Make sẽ không chạy lại trình biên dịch và bạn cần phải loại bỏ kết quả không hợp lệ của bản dựng trước đó theo cách thủ công bằng make clean
.
Ngoài ra, Make không phải là một quy trình mạnh trong trường hợp chấm dứt một trong các quy trình con không thành công sau khi quy trình phụ đó bắt đầu ghi vào tệp đầu ra. Mặc dù quá trình thực thi hiện tại của Make sẽ không thành công, nhưng lệnh gọi tiếp theo của Make sẽ giả định một cách mù quáng rằng tệp đầu ra bị cắt ngắn là hợp lệ (vì tệp này mới hơn các tệp đầu vào) và sẽ không được tạo lại. Tương tự, nếu quá trình Tạo bị huỷ, thì một tình huống tương tự có thể xảy ra.
Bazel tránh những giả định này và các giả định khác. Bazel duy trì một cơ sở dữ liệu về tất cả công việc đã thực hiện trước đó và sẽ chỉ bỏ qua một bước xây dựng nếu phát hiện thấy tập hợp tệp đầu vào (và dấu thời gian của các tệp đó) cho bước xây dựng đó và lệnh biên dịch cho bước xây dựng đó khớp chính xác với một bước trong cơ sở dữ liệu và tập hợp tệp đầu ra (và dấu thời gian của các tệp đó) cho mục nhập cơ sở dữ liệu khớp chính xác với dấu thời gian của các tệp trên ổ đĩa. Mọi thay đổi đối với tệp đầu vào hoặc tệp đầu ra hoặc đối với chính lệnh sẽ khiến bước tạo bản dựng được thực thi lại.
Lợi ích cho người dùng của các bản dựng tăng dần là: ít lãng phí thời gian hơn do không nhầm lẫn. (Ngoài ra, bạn sẽ mất ít thời gian hơn để chờ tạo lại do sử dụng make
clean
, cho dù cần thiết hay phòng ngừa.)
Tăng tính nhất quán và tạo bản dựng tăng dần
Về mặt chính thức, chúng ta xác định trạng thái của một bản dựng là nhất quán khi tất cả các tệp đầu ra dự kiến đều tồn tại và nội dung của các tệp đó là chính xác, như được chỉ định theo các bước hoặc quy tắc cần thiết để tạo các tệp đó. Khi bạn chỉnh sửa một tệp nguồn, trạng thái của bản dựng được cho là không nhất quán và vẫn không nhất quán cho đến khi bạn chạy công cụ bản dựng thành công vào lần tiếp theo. Chúng tôi mô tả tình huống này là không ổn định và không nhất quán, vì tình huống này chỉ là tạm thời và tính nhất quán sẽ được khôi phục bằng cách chạy công cụ bản dựng.
Còn một loại không nhất quán khác cũng rất nguy hiểm: không nhất quán ổn định. Nếu bản dựng đạt đến trạng thái không nhất quán ổn định, thì việc gọi công cụ bản dựng thành công nhiều lần sẽ không khôi phục tính nhất quán: bản dựng đã "bị treo" và kết quả vẫn không chính xác. Các trạng thái không nhất quán ổn định là lý do chính khiến người dùng Make (và các công cụ xây dựng khác) nhập make clean
.
Việc phát hiện công cụ xây dựng không hoạt động theo cách này (rồi khôi phục từ công cụ đó) có thể tốn thời gian và rất khó chịu.
Về mặt khái niệm, cách đơn giản nhất để đạt được một bản dựng nhất quán là loại bỏ tất cả các kết quả bản dựng trước đó và bắt đầu lại: tạo mọi bản dựng là một bản dựng sạch. Rõ ràng là phương pháp này quá tốn thời gian để có thể áp dụng (có thể ngoại trừ các kỹ sư phát hành). Do đó, để hữu ích, công cụ xây dựng phải có thể thực hiện các bản dựng gia tăng mà không làm giảm tính nhất quán.
Rất khó để phân tích chính xác phần phụ thuộc gia tăng và như mô tả ở trên, nhiều công cụ xây dựng khác không hiệu quả trong việc tránh các trạng thái không nhất quán ổn định trong các bản dựng gia tăng. Ngược lại, Bazel đưa ra cam kết sau: sau khi gọi thành công công cụ bản dựng mà bạn không chỉnh sửa gì, bản dựng sẽ ở trạng thái nhất quán. (Nếu bạn chỉnh sửa các tệp nguồn trong một bản dựng, Bazel sẽ không đảm bảo về tính nhất quán của kết quả của bản dựng hiện tại. Tuy nhiên, điều này đảm bảo rằng kết quả của bản dựng tiếp theo sẽ khôi phục tính nhất quán.)
Giống như mọi đảm bảo, cũng có một số chữ in nhỏ: có một số cách đã biết để chuyển sang trạng thái không nhất quán ổn định với Bazel. Chúng tôi không đảm bảo sẽ điều tra những vấn đề như vậy phát sinh từ việc cố tình tìm lỗi trong phân tích phần phụ thuộc gia tăng, nhưng chúng tôi sẽ điều tra và cố gắng hết sức để khắc phục tất cả các trạng thái không nhất quán ổn định phát sinh từ việc sử dụng công cụ bản dựng theo cách thông thường hoặc "hợp lý".
Nếu bạn phát hiện thấy trạng thái không nhất quán ổn định với Bazel, vui lòng báo cáo lỗi.
Thực thi trong hộp cát
Bazel sử dụng hộp cát để đảm bảo rằng các hành động chạy kín và chính xác. Bazel chạy spawns (nói ngắn gọn là các thao tác) trong những hộp cát chỉ chứa tập hợp tệp tối thiểu mà công cụ yêu cầu để thực hiện công việc của mình. Hiện tại, tính năng hộp cát hoạt động trên Linux 3.12 trở lên khi bật tuỳ chọn CONFIG_USER_NS
, cũng như trên macOS 10.11 trở lên.
Bazel sẽ in cảnh báo nếu hệ thống của bạn không hỗ trợ hộp cát để cảnh báo bạn rằng các bản dựng không được đảm bảo là kín và có thể ảnh hưởng đến hệ thống lưu trữ theo những cách không xác định. Để tắt cảnh báo này, bạn có thể truyền cờ --ignore_unsupported_sandboxing
đến Bazel.
Trên một số nền tảng như nút cụm Google Kubernetes Engine hoặc Debian, theo mặc định, không gian tên của người dùng sẽ bị vô hiệu hoá do các vấn đề về bảo mật. Bạn có thể kiểm tra điều này bằng cách xem tệp /proc/sys/kernel/unprivileged_userns_clone
: nếu tệp này tồn tại và chứa số 0, thì bạn có thể kích hoạt không gian tên người dùng bằng sudo sysctl kernel.unprivileged_userns_clone=1
.
Trong một số trường hợp, hộp cát Bazel không thực thi được các quy tắc do chế độ thiết lập hệ thống. Triệu chứng thường là một lỗi xuất ra thông báo tương tự như namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory
.
Trong trường hợp đó, hãy thử huỷ kích hoạt hộp cát cho genrules bằng --strategy=Genrule=standalone
và cho các quy tắc khác bằng --spawn_strategy=standalone
. Ngoài ra, vui lòng báo cáo lỗi trên trình theo dõi lỗi của chúng tôi và đề cập đến bản phân phối Linux mà bạn đang sử dụng để chúng tôi có thể điều tra và cung cấp bản sửa lỗi trong bản phát hành tiếp theo.
Các giai đoạn của bản dựng
Trong Bazel, một bản dựng diễn ra trong ba giai đoạn riêng biệt; với tư cách là người dùng, việc hiểu được sự khác biệt giữa các giai đoạn này sẽ cung cấp thông tin chi tiết về các tuỳ chọn kiểm soát bản dựng (xem bên dưới).
Giai đoạn tải
Giai đoạn đầu tiên là tải, trong đó tất cả các tệp BUILD cần thiết cho các mục tiêu ban đầu và phần phụ thuộc đóng kín bắc cầu của các mục tiêu đó sẽ được tải, phân tích cú pháp, đánh giá và lưu vào bộ nhớ đệm.
Đối với bản dựng đầu tiên sau khi máy chủ Bazel khởi động, giai đoạn tải thường mất vài giây trong khi số lượng tệp BUILD được tải từ hệ thống tệp. Trong các bản dựng tiếp theo, đặc biệt là nếu không có tệp BUILD nào thay đổi, quá trình tải sẽ diễn ra rất nhanh.
Các lỗi được báo cáo trong giai đoạn này bao gồm: không tìm thấy gói, không tìm thấy mục tiêu, lỗi từ vựng và ngữ pháp trong tệp BUILD và lỗi đánh giá.
Giai đoạn phân tích
Giai đoạn thứ hai, phân tích, bao gồm việc phân tích ngữ nghĩa và xác thực từng quy tắc bản dựng, xây dựng biểu đồ phần phụ thuộc bản dựng và xác định chính xác công việc cần làm trong từng bước của bản dựng.
Giống như việc tải, quá trình phân tích cũng mất vài giây khi tính toán toàn bộ. Tuy nhiên, Bazel lưu biểu đồ phần phụ thuộc vào bộ nhớ đệm từ bản dựng này sang bản dựng tiếp theo và chỉ phân tích lại những gì cần thiết, nhờ đó có thể tạo các bản dựng gia tăng cực nhanh trong trường hợp các gói không thay đổi kể từ bản dựng trước.
Các lỗi được báo cáo ở giai đoạn này bao gồm: các phần phụ thuộc không phù hợp, dữ liệu đầu vào không hợp lệ cho một quy tắc và tất cả thông báo lỗi theo quy tắc.
Các giai đoạn tải và phân tích diễn ra nhanh chóng vì Bazel tránh các hoạt động I/O tệp không cần thiết ở giai đoạn này, chỉ đọc các tệp BUILD để xác định công việc cần làm. Đây là thiết kế và giúp Bazel trở thành nền tảng tốt cho các công cụ phân tích, chẳng hạn như lệnh truy vấn của Bazel, được triển khai trên giai đoạn tải.
Giai đoạn thực thi
Giai đoạn thứ ba và cũng là giai đoạn cuối cùng của quá trình tạo bản dựng là thực thi. Giai đoạn này đảm bảo rằng đầu ra của mỗi bước trong bản dựng nhất quán với đầu vào, chạy lại các công cụ biên dịch/liên kết/v.v. nếu cần. Bước này là nơi bản dựng dành phần lớn thời gian, từ vài giây đến hơn một giờ đối với một bản dựng lớn. Các lỗi được báo cáo trong giai đoạn này bao gồm: thiếu tệp nguồn, lỗi trong công cụ được thực thi bởi một số hành động trong bản dựng hoặc lỗi của công cụ trong việc tạo tập hợp đầu ra dự kiến.