Xây dựng chương trình bằng Bazel

Báo cáo sự cố Xem nguồn

Trang này trình bày cách tạo chương trình bằng Bazel, cú pháp lệnh tạo 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 cơ sở của bạn hoặc bất kỳ thư mục con nào trong thư mục đó rồi nhập bazel. Hãy xem phần bản dựng nếu bạn cần tạo không gian làm việc mới.

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

Các lệnh hiện có

  • 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 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à dừng máy chủ (không bắt buộc).
  • cquery: Thực thi truy vấn biểu đồ phần phụ thuộc sau 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 trợ giúp cho các lệnh hoặc chỉ mục.
  • info: Hiển thị thông tin thời gian chạy liên quan đến máy chủ bazel.
  • fetch: Tìm nạp tất cả phần phụ thuộc bên ngoài của mục tiêu.
  • mobile-install: Cài đặt ứng dụng trên thiết bị di động.
  • query: Thực thi một 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ử được chỉ định.
  • version: In thông tin phiên bản của Bazel.

Nhận trợ giúp

  • bazel help command: Trợ giúp in và các tuỳ chọn cho command.
  • bazel helpstartup_options: Các lựa chọn để Máy ảo Java lưu trữ Bazel.
  • bazel helptarget-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 thông tin sử dụng.

Công cụ bazel thực hiện nhiều hàm, được gọi là lệnh. Các hàm được dùng phổ biến nhất là bazel buildbazel test. Bạn có thể duyệt xem 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

Để có thể bắt đầu tạo bản dựng, bạn cần có 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 để xây dựng ứng dụng. Bazel cho phép bạn tạo bản dựng từ một ổ đĩa hoàn toàn chỉ có thể đọc.

Để tạo một chương trình bằng Bazel, hãy nhập bazel build rồi nhập mục tiêu mà bạn muốn tạo.

bazel build //foo

Sau khi phát 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 đó có các phần phụ thuộc đã khai báo, 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 (transitive dependency) là các tệp được liệt kê trong tệp BUILD của các 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 phân tích các phần phụ thuộc đó để đảm bảo tính chính xác và tạo các hành động xây 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 in các thông báo tiến trình. Các thông báo tiến trình bao gồm bước tạo 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 nó bắt đầu và số đã hoàn thành trên tổng số hành động xây dựng. Khi quá trình tạo bản dựng bắt đầu, tổng số 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 kết thúc bản dựng, Bazel sẽ in các 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ó thì có thể tìm thấy 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; hãy xem --show_result để biết thêm thông tin chi tiết.

Nếu bạn nhập lại cùng một 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à một null build. Vì không có gì thay đổi nên không có gói nào cần tải lại cũng như không có bước xây dựng nào để thực thi. Nếu có thay đổi nào đó trong "foo" hoặc các phần phụ thuộc của nó, Bazel sẽ thực thi lại một số hành động tạo bản dựng hoặc hoàn thành bản dựng tăng dần.

Tạo nhiều mục tiêu

Bazel hỗ trợ một số cách để chỉ định mục tiêu cần xây dựng. Nói chung, đâ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 các mục tiêu riêng lẻ, chẳng hạn như để khai báo các phần phụ thuộc trong tệp BUILD, thì mẫu mục tiêu của Bazel sẽ chỉ định nhiều mục tiêu. Mẫu mục tiêu là tổng quát 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ệ, giúp xác định tập hợp chính xác một mục tiêu.

Tất cả các mẫu mục tiêu bắt đầu bằng // đều được giải quyết liên quan đến 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ả các mục tiêu quy tắc trong gói foo/bar.
//foo/... Mọi quy tắc đều nhắm mục tiêu trong mọi gói bên dưới thư mục foo.
//foo/...:all Mọi quy tắc đều nhắm mục tiêu trong mọi 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 mọi gói trong thư mục foo.
//... Tất cả đều nhắm mục tiêu quy tắc trong các gói thuộc kho lưu trữ chính. Không bao gồm các mục tiêu từ các kho lưu trữ bên ngoài.
//:all Tất cả các mục tiêu quy tắc trong gói cấp cao nhất, nếu có tệp "BUILD" ở 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 làm việc hiện tại. Các ví dụ này giả định một 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:
  • //foo/bar/wiz:wiz nếu foo/bar/wiz là một gói
  • //foo/bar:wiz nếu foo/bar là một gói
  • Nếu không, //foo:bar/wiz
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, hệ thống sẽ tuân theo các đường liên kết tượng trưng của thư mục cho các mẫu mục tiêu đệ quy, trừ những mẫu trỏ đến cơ sở đầu ra, chẳng hạn như các đường liên kết tượng trưng tiện lợi được tạo trong thư mục gốc của không gian làm việc.

Ngoài ra, Bazel không đi theo các đườ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 có chứa một 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 trên gói, cho biết tất cả các gói được đệ quy trong thư mục foo (dành cho tất cả gốc của đường dẫn gói). :all là ký tự đại diện trên mục tiêu, khớp với tất cả quy tắc trong một gói. Hai ký tự này có thể được kết hợp, như trong foo/...:all và khi sử dụng cả hai ký tự đại diện, thì từ này có thể được viết tắt là foo/....

Ngoài ra, :* (hoặc :all-targets) là một ký tự đại diện khớp với mọi mục tiêu trong các gói đã 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ư các tệp _deploy.jar liên kết với các 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 không mong muốn xây dựng 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 yêu cầu của 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 //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ả đều tuân theo toán tử phủ định tiền tố -. Bạn có thể dùng hàm này để trừ tập hợp mục tiêu khỏi tập hợp được chỉ định bởi các đối số trước. Xin lưu ý rằng điều này có nghĩa là đơn đặt hàng rất quan trọng. Ví dụ:

bazel build foo/... bar/...

có nghĩa là "tạo tất cả mục tiêu bên dưới foo tất cả mục tiêu bên dưới bar", trong khi

bazel build -- foo/... -foo/bar/...

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 cần có đối số -- để ngăn các đối số tiếp theo bắt đầu bằng - bị hiểu là các tuỳ chọn bổ sung.)

Điều quan trọng cần chỉ ra 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 đó sẽ 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ục tiêu //foo:all-apis mà trong số những mục tiêu khác phụ thuộc vào //foo/bar:api, thì mục tiêu sau sẽ được xây dựng như một phần của quá trình xây dựng mục tiêu trước.

Mục tiêu có tags = ["manual"] không có trong mẫu mục tiêu theo ký tự đại diện (..., :*, :all, v.v.) khi được chỉ định trong các lệnh như bazel buildbazel test (nhưng có trong mẫu mục tiêu ký tự đại diện âm, nghĩa là chúng sẽ bị trừ). 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 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ỳ quá trình lọc nào như vậy (điều này sẽ đánh bại 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 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ể là ngoài mong muốn, do bạn muốn biết khi nào các phần phụ thuộc bên ngoài mới được thêm vào hoặc vì bạn muốn "tìm nạp trước" các phần phụ thuộc (chẳng hạn như trước chuyến bay mà bạn sẽ không có kết nối mạng). Nếu muốn ngăn không cho thêm các phần phụ thuộc mới trong quá trình xây 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 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 lưu trữ của SDK Android và NDK sẽ luôn có hiệu lực bất kể giá trị --fetch là gì .

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 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 tạo bản 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 phần phụ thuộc bên ngoài mới.

Sau khi chạy, bạn sẽ không cần chạy lại cho đến khi tệp WORKSPACE thay đổi.

fetch lấy một danh sách mục tiêu để tìm nạp các phần phụ thuộc. Ví dụ: thao tác này sẽ tìm nạp các phần phụ thuộc cần thiết để tạo //foo:bar//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:

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ả phần phụ thuộc bên ngoài bằng cách chạy

bazel fetch

Bạn không cần phải chạy tìm nạp bazel nếu có tất cả các công cụ đ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 về một 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. Để thực hiện việc này, bazel lưu tất cả tệp đã tải xuống vào bộ nhớ đệm của kho lưu trữ, theo mặc định, 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 đã cài đặt của bazel. Một mục đượ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 đúng tệp, 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à một tệp có hàm băm đó đang ở trong bộ nhớ đệm. Vì vậy, việc chỉ định hàm băm cho mỗi tệp bên ngoài không chỉ là ý tưởng tốt về mặt bảo mật, mà còn giúp tránh các tệp tải xuống không cần thiết.

Sau mỗi lần nhấn vào bộ nhớ đệm, thời gian sửa đổi tệp trong bộ nhớ đệm sẽ được cập nhật. Bằng cách này, lần sử dụng cuối cùng của một tệp trong thư mục bộ nhớ đệm có thể dễ dàng xác định, chẳng hạn như để dọn dẹp 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 một bản sao của tệp không còn truy cập được ở trên.

[Không dùng nữa] Thư mục tệp phân phối

Không dùng nữa: Ưu tiên sử dụng bộ nhớ đệm kho lưu trữ để có bản dựng ngoại tuyến.

Thư mục phân phối là một cơ chế Bazel khác để 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 quy trình chuẩn bị thủ công.

Khi sử dụng tuỳ chọn --distdir=/path/to-directory, bạn có thể chỉ định thêm các thư mục chỉ có thể đọc để 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 hàm băm được chỉ định trong yêu cầu tải xuống. Thao tác này chỉ hoạt động nếu hàm băm của tệp được chỉ định trong phần khai báo WORKSPACE.

Mặc dù không cần thiết phải đảm bảo tính chính xác của điều kiện trên tên tệp, nhưng điều kiện này sẽ giảm số lượng tệp ứng viên 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 các thư mục tệp phân phối vẫn duy trì hiệu quả, ngay cả khi số lượng tệp trong thư mục đó tăng lớn.

Chạy Bazel trong môi trường thoáng mát

Để giảm kích thước nhị phân của Bazel, 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 chạy đầu tiên. Các 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 đối với mọi người. Ví dụ: các công cụ Android chỉ được tách nhóm và tìm nạp khi tạo các dự án Android.

Tuy nhiên, các phần phụ thuộc ngầm này có thể gây ra sự cố khi chạy Bazel trong một môi trường có giới hạn, ngay cả khi bạn đã cung cấp tất cả các phần phụ thuộc bên ngoài của mình. Để giải quyết vấn đề đó, bạn có thể chuẩn bị một 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 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 chúng sang môi trường không dây bằng phương thức ngoại tuyến.

Bộ nhớ đệm kho lưu trữ (với Bazel 7 trở lên)

Để chuẩn bị bộ nhớ đệm của kho lưu trữ, hãy sử dụng cờ --repository_cache. Bạn sẽ phải 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 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 có khoảng trống, 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
touch WORKSPACE

Để 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ũ, hãy chạy để tìm nạp các phần phụ thuộc WORKSPACE tích hợp sẵn, 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 có khoảng cách giữa các khoảng trống, hãy truyền cùng một cờ --repository_cache. Để thuận tiện, bạn có thể thêm mã này dưới dạng một mục nhập .bazelrc:

common --repository_cache="path/to/repository/cache"

Ngoài ra, bạn cũng cần sao chép BCR cục bộ và sử dụng cờ --registry để trỏ đến bản sao cục bộ nhằm ngăn Bazel truy cập BCR qua Internet. Thêm dòng sau vào .bazelrc của bạn:

common --registry="path/to/local/bcr/registry"
Danh bạ phân phối (dành cho Bazel trước 7)

Để chuẩn bị thư mục phân phối, hãy sử dụng cờ --distdir. Bạn sẽ phải 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 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 có khoảng trống, 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 tarball chứa các phần phụ thuộc thời gian chạy ngầm cho phiên bản Bazel cụ thể đó:

bazel build @additional_distfiles//:archives.tar

Xuất tệp tar này sang một thư mục có thể sao chép vào môi trường có khoảng trống. Hãy lưu ý cờ --strip-components, vì --distdir có thể khá khó khăn với mức độ lồng ghép 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 có khoảng trống, hãy truyền cờ --distdir trỏ đến thư mục đó. Để thuận tiện, bạn có thể thêm mã này dưới dạng một mục nhập .bazelrc:

build --distdir=path/to/directory

Cấu hình bản dựng và tính năng biên dịch chéo

Có thể chia 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 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 tạo 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 của quy tắc đó. Loại thứ hai là dữ liệu bên ngoài hoặc dữ liệu 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 tập dữ liệu môi trường hoàn chỉnh là một 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 phương pháp 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, bản dựng này sẽ yêu cầu tạo //foo:bin bằng một chuỗi công cụ có khả năng tạo các tệp thực thi 64 bit, nhưng hệ thống xây dựng cũng phải xây dựng nhiều công cụ dùng trong chính quá trình tạo bản dựng, ví dụ như các công cụ được tạo từ nguồn, sau đó được dùng để tạo quy tắc và những công cụ này phải được xây dựng để chạy trên máy trạm của bạn. Do đó, chúng ta có thể xác định 2 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 tạo bản dựng) và cấu hình mục tiêu (hoặc cấu hình yêu cầu – được dùng để gọi "cấu hình mục tiêu) thường xuyên hơn mặc dù từ đó đã có nhiều nghĩa), được dùng để tạo tệp nhị phân mà bạn yêu cầu sau cùng.

Thông thường, có nhiều thư viện là điều kiện tiên quyết đối với 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, chẳng hạn như một số thư viện cơ sở. Các thư viện như vậy phải được xây dựng 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 sẽ đảm bảo việc tạo cả hai biến thể và giữ riêng các tệp dẫn xuấ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ì rất có thể đây là nội dung giải thích.

Cấu hình executor đượ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ư được chỉ định trong cấu hình yêu cầu, trừ phi --host_crosstool_top được chỉ định.
  • Sử dụng giá trị của --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ư đã chỉ định trong cấu hình yêu cầu: --compiler, --use_ijars. Nếu sử dụng --host_crosstool_top, thì giá trị của --host_cpu sẽ được dùng để tra cứu default_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).
  • Tạo thông tin không gỡ lỗi (--copt=-g0).
  • Xoá thông tin gỡ lỗi của các tệp thực thi và thư viện dùng chung (--strip=always).
  • Đặt tất cả các tệp phát sinh vào một vị trí đặc biệt, khác với vị trí mà bất kỳ cấu hình yêu cầu khả thi nào sử dụng.
  • Loại bỏ việc đóng dấu các 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 tại sao bạn nên chọn một cấu hình thực thi riêng biệt trong 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à được tối ưu hoá, bạn sẽ giảm thời gian dành cho việc 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 cũng như thời gian I/O của mạng trong các bản dựng được phân phối.

Thứ hai, bằng cách phân tách tệp thực thi và các cấu hình yêu cầu trong tất cả bản dựng, bạn tránh việc xây dựng lại rất tốn kém dẫn đến những thay đổi nhỏ đối với cấu hình yêu cầu (chẳng hạn như thay đổi các tuỳ chọn trình liên kết), như đã mô tả trước đó.

Chỉnh sửa bản dựng lại gia tăng

Một trong những mục tiêu chính của dự án Bazel là đảm bảo tạo lại các bản dựng gia tăng chính xác. Các công cụ xây dựng trước đây, đặc biệt là những công cụ dựa trên Make, đưa ra một số giả định không hợp lý khi triển khai các bản dựng tăng dần.

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 rất dễ mắc lỗi giả định này; việc đồng bộ hoá với một bản sửa đổi trước đó của 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.

Nhìn chung, mặc dù tính năng Make phát hiện các thay đổi đối với tệp, nhưng tính năng này 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 chuyển đến trình biên dịch trong một bước tạo bản dựng nhất định, Make sẽ không chạy lại trình biên dịch và bạn cần loại bỏ các kết quả không hợp lệ của bản dựng trước theo cách thủ công bằng cách sử dụng make clean.

Ngoài ra, Make không mạnh mẽ trước việc chấm dứt không thành công một trong các quy trình con 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 lệnh Make sẽ không thành công, lệnh gọi tiếp theo của hàm Make sẽ giả định rằng tệp đầu ra bị cắt bớt là hợp lệ (vì tệp này mới hơn dữ liệu đầu vào) và sẽ không được tạo lại. Tương tự, nếu quy trình Tạo bị tắt, trường hợp tương tự có thể xảy ra.

Bazel tránh những giả định này và nhiều giả định khác. Bazel duy trì cơ sở dữ liệu của tất cả công việc đã thực hiện trước đó và sẽ chỉ bỏ qua bước bản dựng nếu thấy rằng tập hợp tệp đầu vào (và dấu thời gian của các tệp đó) so với bước tạo bản dựng đó, và lệnh biên dịch cho bước bản dựng đó khớp chính xác với bước trong cơ sở dữ liệu, đồng thời tập hợp các tệp đầu ra (và dấu thời gian của chúng) 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, tệp đầu ra hoặc chính lệnh, đều sẽ dẫn đến việc thực thi lại bước tạo bản dựng.

Lợi ích cho người dùng các bản dựng tăng dần chính xác là: ít thời gian bị lãng phí do sự nhầm lẫn. (Ngoài ra, giảm thời gian chờ tạo lại do việc sử dụng make clean, cho dù cần thiết hay được chuẩn bị trước.)

Tạo dựng tính nhất quán và các bản dựng gia tăng

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 trong 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 tệp nguồn, trạng thái của bản dựng sẽ không nhất quán và không nhất quán cho đến khi bạn chạy công cụ bản dựng đến khi hoàn tất thành công. Chúng tôi mô tả trường hợp này là sự không nhất quán không ổn định, vì đâ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ụ xây dựng.

Ngoài ra còn có một kiểu không nhất quán khác có thể gây hại, đó là sự không nhất quán về tính ổ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ụ tạo bản dựng thành công nhiều lần sẽ không khôi phục được tính nhất quán: bản dựng bị "lỗi" và kết quả đầu ra không chính xá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) loại make clean. Việc phát hiện ra rằng công cụ xây dựng đã bị lỗi theo cách này (và sau đó khôi phục từ đó) có thể tốn thời gian và rất khó chịu.

Về lý thuyết, cách đơn giản nhất để đạt được một bản dựng nhất quán là loại bỏ mọi kết quả của bản dựng trước đó và bắt đầu lại: biến mọi bản dựng thành một bản dựng sạch. Rõ ràng là phương pháp này tốn quá nhiều thời gian để thực tế (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ó khả năng thực hiện các bản dựng tăng dần mà không ảnh hưởng đến tính nhất quán.

Việc phân tích phần phụ thuộc gia tăng chính xác rất khó. Như đã mô tả ở trên, nhiều công cụ xây dựng khác không thể tránh được các trạng thái không nhất quán ổn định trong các bản dựng tăng dần. Ngược lại, Bazel đưa ra sự đảm bảo sau đây: sau khi gọi thành công công cụ xây 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 quá trình tạo bản dựng, Bazel 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.)

Tất cả những điều đảm bảo sẽ có một số điểm 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 điều tra các vấn đề như vậy phát sinh từ việc cố ý tìm lỗi trong quá trình phân tích phần phụ thuộc gia tăng, nhưng chúng tôi sẽ điều tra và nỗ lực 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 bình 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 và ổn định với Bazel, vui lòng báo cáo lỗi.

Thực thi trong môi trường hộp cát

Bazel sử dụng hộp cát để đảm bảo rằng các thao tác sẽ chạy khép kín và chính xác. Bazel chạy spawns (nói đơn giản là các hành động) trong các hộp cát chỉ chứa bộ tệp tối thiểu mà công cụ này cần đến để thực hiện công việc. Hiện tại, hộp cát hoạt động trên Linux 3.12 trở lên và 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 hoạt động khép 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ư các nút cụm Google Kubernetes Engine hoặc Debian, không gian tên của người dùng sẽ bị vô hiệu hoá theo mặc định do các vấn đề liên quan đến 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 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 quá trình thiết lập hệ thống. Dấu hiệu này thường là một lỗi khi đưa ra một 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 các quy tắc tạo 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 công cụ theo dõi lỗi và cho biết bạn đang dùng bản phân phối Linux nào để chúng tôi có thể điều tra và đưa ra bản sửa lỗi trong bản phát hành tiếp theo.

Các giai đoạn của một bản dựng

Trong Bazel, một bản dựng xảy ra trong 3 giai đoạn riêng biệt. Với tư cách người dùng, việc hiểu rõ sự khác biệt giữa hai giai đoạn này sẽ giúp bạn hiểu rõ hơn về các tuỳ chọn kiểm soát bản dựng (xem bên dưới).

Giai đoạn tải

Cách thứ nhất 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à quá trình đóng bắc cầu của các phần phụ thuộc đượ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 khởi động máy chủ Bazel, giai đoạn tải thường mất vài giây vì số lượng tệp BUILD được tải qua hệ thống tệp. Trong các bản dựng tiếp theo, đặc biệt là khi không có tệp BUILD nào thay đổi, thì 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, liên quan đến 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 của bản dựng và xác định chính xác công việc cần thực hiện trong mỗi bước của bản dựng.

Giống như khi 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 nội dung có trong biểu đồ. Điều này có thể giúp các bản dựng tăng dần trở nên 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: 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ụ thể.

Các giai đoạn tải và phân tích diễn ra nhanh chóng vì Bazel tránh được 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 thực hiện. Điều này là theo thiết kế và khiến Bazel trở thành một nền tảng tốt cho các công cụ phân tích, chẳng hạn như lệnh query của Bazel, được triển khai ở đầu giai đoạn tải.

Giai đoạn thực thi

Giai đoạn thứ ba và 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 từng bước trong bản dựng nhất quán với đầu vào của bước đó, chạy lại các công cụ biên dịch/liên kết/v.v. khi 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 một công cụ được thực thi bởi một số hành động xây dựng hoặc lỗi của một công cụ để tạo tập hợp đầu ra dự kiến.