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

Báo cáo vấn đề Xem nguồn Nightly/3}

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 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 không gian làm việc cơ sở của bạn hoặc thư mục con bất kỳ cùng loại 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 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 bài 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ử được chỉ định.
  • version: In thông tin phiên bản của Bazel.

Nhận trợ giúp

  • bazel help command: In thông tin trợ giúp và các tuỳ chọn cho command.
  • bazel helpstartup_options: Các lựa chọn cho máy ảo JVM lưu trữ Bazel.
  • bazel helptarget-syntax: Giải thích cú pháp để chỉ định mục tiêu.
  • bazel help info-keys: Cho thấy 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 thành phần phổ biến nhất là bazel buildbazel 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 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, theo sau là mục tiêu bạn muốn tạo.

bazel build //foo

Sau khi 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 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 tạo bản dựng. Cuối cùng, Bazel thực thi 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 bản dựng, Bazel in thông báo về tiến trình. 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 quá trình này bắt đầu và số đã hoàn thành trên tổng số hành động tạo bản dựng. Khi bản dựng bắt đầu, tổng số thao tác thường tăng lên khi Bazel khám phá ra toàn bộ biểu đồ hành động, nhưng số lượng sẽ ổn định trong vòng vài giây.

Khi kết thúc quá trình tạo bản dựng, Bazel sẽ in các mục tiêu được yêu cầu, cho dù các mục tiêu đó đã được tạo thành công hay chưa và nếu có thì 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à null build. 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 tạo bản 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 nó, thì Bazel sẽ thực thi lại một số thao tác tạo bản dựng hoặc hoàn tấ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ững mẫu này được gọi chung 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 dùng để chỉ định nhiều 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 lại chỉ định nhiều mục tiêu. Mẫu mục tiêu là nội dung tổng quát của cú pháp nhãn cho nhóm mục tiêu bằng cách 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 chính xác 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ỉ //foo/bar:wiz mục tiêu duy nhất.
//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 mọi gói trong thư mục foo.
//foo/...:all Tất cả mục tiêu quy tắc trong mọi gói trong 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ả mục tiêu trong các gói trong không gian làm việc. Điều này không bao gồm các mục tiêu từ kho lưu trữ bên ngoài.
//:all Tất cả mục tiêu trong gói cấp cao nhất, nếu có tệp "BUILD" tại 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 làm việc hiện tại. Những 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 thì là //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, các đường liên kết tượng trưng của thư mục sẽ đi theo các mẫu đích đệ quy, ngoại trừ những đường liên kết 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á 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 trên gói, cho biết tất cả các gói theo đệ quy bên dưới thư mục foo (cho tất 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 gói đã khớp, bao gồm cả các tệp thường không được tạo bằng bất kỳ quy tắc nào, chẳng hạn như các 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 khó hiểu 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 một danh sách các mẫu mục tiêu làm đối số và tất cả đều tôn trọng toán tử phủ định tiền tố -. Bạn có thể dùng giá trị này để trừ một tập hợp mục tiêu từ 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à 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 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à "xây dựng tất cả cá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". (Đối số -- là bắt buộc để 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 lưu ý là mặc dù 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à các phần phụ thuộc của mục tiêu không bị trừ. Ví dụ: nếu có một //foo:all-apis mục tiêu mà trong số các mục tiêu khác phụ thuộc vào //foo/bar:api, thì đối tượng sau sẽ được tạo trong quá trình tạo mục tiêu.

Các mục tiêu có tags = ["manual"] không được đưa vào các 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 buildbazel test; bạn nên chỉ định các mục tiêu kiểm thử đó 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ử chúng. 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 (sẽ không đáp ứng 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, việc này có thể không được mong muốn vì 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 một chuyến bay mà bạn sẽ ngoại tuyến). 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 về kho lưu trữ NDK và SDK Android 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 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 tạo bản dựng, bạn cần chạy bazel fetch:

  • Trước khi bạn tạo ứng dụng lần đầu.
  • 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 WORKSPACE 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ụ: 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 mã:

bazel fetch //...

Bạn không cần 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 lọ 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ó cùng một tệp trong nhiều không gian làm việc, hoặc nếu định nghĩa về kho lưu trữ bên ngoài đã thay đổi nhưng vẫn cần tải cùng một tệp xuống. Để thực hiện việc này, bazel sẽ lưu vào bộ nhớ đệm tất cả các tệp đã tải xuống trong 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ả các không gian làm việc và phiên bản đã cài đặt của bazel. Một mục nhập sẽ được lấy từ bộ nhớ đệm nếu Bazel biết chắc rằng bản sao đó 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à một 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 cuối một tệp trong thư mục bộ nhớ đệm, chẳng hạn như để dọn dẹp bộ nhớ đệm theo cách thủ công. Bộ nhớ đệm này 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.

Thư mục tệp phân phối

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

Bằng cách 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 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 WORKSPACE.

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 mang lại hiệu quả, ngay cả khi số lượng tệp trong một thư mục như vậy tăng 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 được tách nhóm và chỉ tìm nạp khi xây dựng các dự án Android.

Tuy nhiên, những 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 WORKSPACE. Để giải quyết vấn đề đó, bạn có thể chuẩn bị một thư mục phân phối 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 gián đoạn theo phương thức ngoại tuyến.

Để 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 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ạ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 tarball chứa các phần phụ thuộc ngầm ẩn trong thời gian chạy 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 được vào môi trường đệm của bạn. Lưu ý cờ --strip-components, vì --distdir có thể khá khó khăn với mức độ lồng của 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 không gian, hãy chuyển cờ --distdir trỏ đến thư mục. Để thuận tiện, bạn có thể thêm mục 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à 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 2 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 xây dựng, giá trị của các thuộc tính và tập hợp hoàn chỉnh 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 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 lựa 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 bất kỳ bản dựng nhất định nào, có thể có nhiều cấu hình. Hãy cân nhắc biên dịch chéo, trong đó bạn tạo 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ó 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 tạo nhiều công cụ dùng trong quá trình tạo bản dựng (ví dụ: các công cụ được tạo từ nguồn, sau đó được sử dụng lại chẳng hạn như quy tắc gen) 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 tôi có thể xác định hai cấu hình: cấu hình thực thi (được 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, nhưng chúng ta thường nói "cấu hình mục tiêu" hơn dù từ đó đã có nhiều nghĩa), dùng để tạo tệp nhị phân mà sau cùng 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ụ, ví dụ như một số thư viện cơ sở. Các thư viện như vậy phải được tạo nhiều lần cho cấu hình thực thi và cấu hình mục tiêu. Bazel đảm bảo rằng tất cả các biến thể đều được tạo và giữ riêng các tệp phát sinh để tránh can thiệp. Thông thường, 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 các thông báo tiến trình cho biết một mục tiêu nhất định đang được tạo nhiều lần, thì đây rất có thể là lời giải thích.

Cấu hình exec thực thi được lấy từ cấu hình mục tiêu như sau:

  • Nền tảng thực thi cho mục tiêu yêu cầu trở thành nền tảng mục tiêu cho cấu hình thực thi.
  • 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ị của --host_cpu cho --cpu (mặc định: k8).
  • Sử dụng cùng 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 và nếu bạn 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 máy chủ lưu trữ.
  • 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 các 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át 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ể có 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.

Chỉnh 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 các bản dựng 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 sai lầm 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 đơn điệu. Mặc dù đây là trường hợp điển hình, nhưng chúng ta rất dễ vi phạm giả định này; việc đồng bộ hoá với bản sửa đổi tệp sớm hơn sẽ khiến thời gian sửa đổi tệp giảm xuống; Các hệ thống dựa trên Make sẽ không được 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 thay đổi nào đối với các lệnh. Nếu bạn thay đổi các tuỳ chọn được truyền cho 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 phải loại bỏ các đầu ra 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 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á rằng tệp đầu ra bị cắt bớt là hợp lệ (vì tệp này mới hơn đầu vào) và sẽ không được tạo lại. Tương tự, nếu quy trình Tạo bị dừng, tình huống tương tự cũng 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 gồm 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 thấy rằng tập hợp các tệp đầu vào (và dấu thời gian của chúng) cho bước tạo bản dựng đó và lệnh biên dịch cho bước tạo đó khớp chính xác với một trong cơ sở dữ liệu, và tập hợp các tệp đầu ra (và dấu thời gian của chúng) cho mục 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ẽ 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ủ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, giảm bớt thời gian chờ tạo bản dựng lại nhờ sử dụng make clean, dù cần thiết hay mua trước.)

Xây dựng tính nhất quán và các bản dựng gia tăng

Về mặt chính thức, chúng tôi xác định trạng thái của bản dựng là nhất quán khi tất cả các tệp đầu ra dự kiến tồn tại và nội dung của các tệp này đều 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 được cho là không nhất quán và không nhất quán cho đến khi tiếp theo bạn chạy công cụ bản dựng để hoàn tất thành công. Chúng tôi mô tả tình huống này là không nhất quán không ổn định vì đây chỉ là tạm thời. 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ó một loại sự không nhất quán khác gây nguy hại, đó là sự 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 được tính nhất quán: bản dựng đã bị "mắc kẹt" và kết quả đầu ra vẫn không chính xác. Trạng thái không nhất quán và ổn định là lý do chính khiến người dùng sử dụng tuỳ chọn Tạo (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 đã gặp sự cố theo cách này (và sau đó khôi phục từ công cụ đó) có thể tốn thời gian và rất khó chịu.

Về mặt lý thuyết, cách đơn giản nhất để có được một bản dựng nhất quán là loại bỏ tất cả kết quả của bản dựng trước đó và bắt đầu lại: làm cho mọi bản dựng đều trở thành một bản dựng sạch. Phương pháp này rõ ràng là tốn quá nhiều thời gian và khó khả thi (ngoại trừ các kỹ sư phát hành). Do đó, để trở nên 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 làm kém hiệu quả trong việc tránh các trạng thái không nhất quán ổn định trong quá trình tạo bản dựng gia tăng. Ngược lại, Bazel đưa ra bảo đảm sau: sau khi gọi thành công công cụ xây dựng mà bạn không thực hiện chỉnh sửa nào, bản dựng sẽ có 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ố dòng 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ìm lỗi trong quá trình phân tích phần phụ thuộc gia tăng. Tuy nhiên, 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 và ổn định phát sinh từ việc sử dụng công cụ xây dựng một cách bình thường hoặc "hợp lý".

Nếu bạn phát hiện thấy một trạng thái ổn định không nhất quán với Bazel, vui lòng báo cáo lỗi.

Thực thi theo mô hình hộp cát

Bazel sử dụng hộp cát để đảm bảo các thao tác chạy một cách khép 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, hộp cát hoạt động trên Linux 3.12 trở lên và có bật tuỳ chọn CONFIG_USER_NS cũng như trên macOS 10.11 trở lên.

Bazel sẽ in một 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 cho bạn về việc các bản dựng không được đảm bảo sẽ mang tính khép kín và có thể ảnh hưởng đến hệ thống máy chủ 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 bị huỷ kích hoạt 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 giá trị 0, thì bạn có thể kích hoạt không gian tên của 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ể thực thi các quy tắc do quá trình thiết lập hệ thống. Triệu chứng thường là lỗi đư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 genrules bằng --strategy=Genrule=standalone và cho các quy tắc khác có --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à đề cập đến bản phân phối Linux 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 xảy ra theo 3 giai đoạn riêng biệt. Với tư cách là người dùng, việc hiểu rõ sự khác biệt giữa các giai đoạn 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

Cách đầ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à việc đóng bắc cầu các phần phụ thuộc đều đượ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 được 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à các lỗi đánh giá.

Giai đoạn phân tích

Giai đoạn thứ hai là phân tích, bao gồm việc phân tích và xác thực ngữ nghĩa của từng quy tắc tạo bản dựng, tạo 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 tạo bản dựng.

Giống như tải, bản phân tích cũng mất vài giây khi được 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 giữa các bản dựng và chỉ phân tích lại nội dung cần thiết. Điều này có thể giúp các bản dựng gia tăng trở nên cực kỳ 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ả cá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 vì Bazel tránh được I/O tệp không cần thiết ở giai đoạn này và chỉ đọc các tệp BUILD để xác định công việc cần hoàn thành. Điều này là do 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 ở 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 kết quả của từng bước trong bản dựng nhất quán với dữ liệu đầ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ờ cho 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.