Tạo trình thực thi liên tục

Trình thực thi liên tục có thể giúp quá trình xây dựng của bạn diễn ra nhanh hơn. Nếu bạn có các hành động lặp lại trong quá trình xây dựng có chi phí khởi động cao hoặc sẽ được hưởng lợi từ việc lưu vào bộ nhớ đệm trên nhiều hành động, thì bạn có thể triển khai trình thực thi liên tục của riêng mình để thực hiện các hành động này.

Máy chủ Bazel giao tiếp với trình thực thi bằng stdin/stdout. Máy chủ này hỗ trợ việc sử dụng bộ đệm giao thức hoặc chuỗi JSON.

Quá trình triển khai trình thực thi có 2 phần:

Tạo trình thực thi

Trình thực thi liên tục phải đáp ứng một số yêu cầu:

  • Trình thực thi này đọc WorkRequests từ stdin.
  • Trình thực thi này ghi WorkResponses (và chỉ WorkResponses) vào stdout.
  • Trình thực thi này chấp nhận cờ --persistent_worker. Trình bao bọc phải nhận dạng cờ dòng lệnh --persistent_worker và chỉ tự duy trì nếu cờ đó được truyền, nếu không, trình bao bọc phải thực hiện quá trình biên dịch một lần và thoát.

Nếu chương trình của bạn đáp ứng các yêu cầu này, thì chương trình đó có thể được dùng làm trình thực thi liên tục!

Yêu cầu công việc

WorkRequest chứa danh sách các đối số cho trình thực thi, danh sách các cặp đường dẫn-tiêu hoá đại diện cho các đầu vào mà trình thực thi có thể truy cập (không bắt buộc, nhưng bạn có thể dùng thông tin này để lưu vào bộ nhớ đệm) và mã yêu cầu (là 0 đối với trình thực thi đơn).

LƯU Ý: Mặc dù thông số bộ đệm giao thức sử dụng "snake case" (request_id), nhưng giao thức JSON sử dụng "camel case" (requestId). Tài liệu này sử dụng camel case trong các ví dụ JSON, nhưng snake case khi nói về trường bất kể giao thức.

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

Bạn có thể dùng trường verbosity không bắt buộc để yêu cầu đầu ra gỡ lỗi bổ sung từ trình thực thi. Trình thực thi hoàn toàn có quyền quyết định nội dung và cách xuất. Giá trị càng cao thì đầu ra càng chi tiết. Việc truyền cờ --worker_verbose đến Bazel sẽ đặt trường verbosity thành 10, nhưng bạn có thể dùng các giá trị nhỏ hơn hoặc lớn hơn theo cách thủ công cho các lượng đầu ra khác nhau.

Trường sandbox_dir không bắt buộc chỉ được dùng bởi các trình thực thi hỗ trợ hộp cát đa hợp.

Phản hồi công việc

WorkResponse chứa mã yêu cầu, mã thoát bằng 0 hoặc khác 0 và chuỗi đầu ra mô tả mọi lỗi gặp phải trong quá trình xử lý hoặc thực thi yêu cầu. Trường output chứa nội dung mô tả ngắn; nhật ký hoàn chỉnh có thể được ghi vào stderr của trình thực thi. Vì trình thực thi chỉ có thể ghi WorkResponses vào stdout, nên trình thực thi thường chuyển hướng stdout của mọi công cụ mà trình thực thi này sử dụng sang stderr.

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

Theo tiêu chuẩn của protobuf, tất cả các trường đều không bắt buộc. Tuy nhiên, Bazel yêu cầu WorkRequestWorkResponse tương ứng phải có cùng mã yêu cầu, vì vậy, bạn phải chỉ định mã yêu cầu nếu mã này khác 0. Đây là WorkResponse hợp lệ.

{
  "requestId" : 12,
}

request_id bằng 0 cho biết yêu cầu "đơn", được dùng khi yêu cầu này không thể được xử lý song song với các yêu cầu khác. Máy chủ đảm bảo rằng một trình thực thi nhất định chỉ nhận được các yêu cầu có request_id bằng 0 hoặc chỉ nhận được các yêu cầu có request_id lớn hơn 0. Các yêu cầu đơn được gửi theo tuần tự, ví dụ: nếu máy chủ không gửi yêu cầu khác cho đến khi nhận được phản hồi (ngoại trừ yêu cầu huỷ, hãy xem bên dưới).

Lưu ý

  • Mỗi bộ đệm giao thức đều có độ dài ở định dạng varint (xem MessageLite.writeDelimitedTo().
  • Yêu cầu và phản hồi JSON không có chỉ báo kích thước.
  • Yêu cầu JSON duy trì cùng cấu trúc như protobuf, nhưng sử dụng JSON tiêu chuẩn và sử dụng camel case cho tất cả tên trường.
  • Để duy trì các thuộc tính tương thích ngược và tương thích chuyển tiếp giống như protobuf, trình thực thi JSON phải chấp nhận các trường không xác định trong các thông báo này và sử dụng giá trị mặc định của protobuf cho các giá trị bị thiếu.
  • Bazel lưu trữ các yêu cầu dưới dạng protobuf và chuyển đổi các yêu cầu đó sang JSON bằng định dạng JSON của protobuf

Hủy

Trình thực thi có thể tuỳ ý cho phép huỷ yêu cầu công việc trước khi hoàn tất. Điều này đặc biệt hữu ích khi kết nối với quá trình thực thi động, trong đó quá trình thực thi cục bộ có thể thường xuyên bị gián đoạn bởi quá trình thực thi từ xa nhanh hơn. Để cho phép huỷ, hãy thêm supports-worker-cancellation: 1 vào trường execution-requirements (xem bên dưới) và đặt cờ --experimental_worker_cancellation.

Yêu cầu huỷWorkRequest có trường cancel được đặt (và tương tự, phản hồi huỷWorkResponse có trường was_cancelled được đặt). Trường duy nhất khác phải có trong yêu cầu huỷ hoặc phản hồi huỷ là request_id, cho biết yêu cầu cần huỷ. Trường request_id sẽ là 0 đối với trình thực thi đơn hoặc request_id khác 0 của WorkRequest đã gửi trước đó đối với trình thực thi đa hợp. Máy chủ có thể gửi yêu cầu huỷ cho các yêu cầu mà trình thực thi đã phản hồi. Trong trường hợp đó, yêu cầu huỷ phải bị bỏ qua.

Mỗi thông báo WorkRequest không huỷ phải được trả lời chính xác một lần, bất kể thông báo đó có bị huỷ hay không. Sau khi máy chủ gửi yêu cầu huỷ, trình thực thi có thể phản hồi bằng WorkResponserequest_id được đặt và trường was_cancelled được đặt thành true. Việc gửi WorkResponse thông thường cũng được chấp nhận, nhưng các trường outputexit_code sẽ bị bỏ qua.

Sau khi gửi phản hồi cho WorkRequest, trình thực thi không được chạm vào các tệp trong thư mục làm việc của mình. Máy chủ có thể dọn dẹp các tệp, bao gồm cả tệp tạm thời.

Tạo quy tắc sử dụng trình thực thi

Bạn cũng cần tạo quy tắc tạo các hành động mà trình thực thi sẽ thực hiện. Việc tạo quy tắc Starlark sử dụng trình thực thi cũng giống như việc tạo bất kỳ quy tắc nào khác.

Ngoài ra, quy tắc này cần chứa tham chiếu đến chính trình thực thi và có một số yêu cầu đối với các hành động mà quy tắc này tạo ra.

Tham chiếu đến trình thực thi

Quy tắc sử dụng trình thực thi cần chứa một trường tham chiếu đến chính trình thực thi. Vì vậy, bạn cần tạo một thực thể của quy tắc \*\_binary để xác định trình thực thi. Nếu trình thực thi của bạn có tên là MyWorker.Java, thì đây có thể là quy tắc được liên kết:

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

Thao tác này sẽ tạo nhãn "worker", tham chiếu đến tệp nhị phân của trình thực thi. Sau đó, bạn sẽ xác định quy tắc sử dụng trình thực thi. Quy tắc này phải xác định một thuộc tính tham chiếu đến tệp nhị phân của trình thực thi.

Nếu tệp nhị phân của trình thực thi mà bạn đã tạo nằm trong gói có tên là "work" (ở cấp cao nhất của bản dựng), thì đây có thể là định nghĩa thuộc tính:

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

cfg = "exec" cho biết rằng trình thực thi phải được tạo để chạy trên nền tảng thực thi của bạn thay vì trên nền tảng mục tiêu (tức là trình thực thi được dùng làm công cụ trong quá trình xây dựng).

Yêu cầu về hành động công việc

Quy tắc sử dụng trình thực thi sẽ tạo các hành động để trình thực thi thực hiện. Các hành động này có một số yêu cầu.

  • Trường "arguments". Trường này lấy danh sách các chuỗi, tất cả trừ chuỗi cuối cùng là các đối số được truyền đến trình thực thi khi khởi động. Phần tử cuối cùng trong danh sách "arguments" là đối số flag-file (có tiền tố @). Trình thực thi đọc các đối số từ tệp cờ đã chỉ định dựa trên mỗi WorkRequest. Quy tắc của bạn có thể ghi các đối số không khởi động cho trình thực thi vào tệp cờ này.

  • Trường "execution-requirements", lấy từ điển chứa "supports-workers" : "1", "supports-multiplex-workers" : "1", hoặc cả hai.

    Các trường "arguments" và "execution-requirements" là bắt buộc đối với tất cả các hành động được gửi đến trình thực thi. Ngoài ra, các hành động mà trình thực thi JSON sẽ thực thi cần bao gồm "requires-worker-protocol" : "json" trong trường yêu cầu thực thi. "requires-worker-protocol" : "proto" cũng là một yêu cầu thực thi hợp lệ, mặc dù không bắt buộc đối với trình thực thi proto, vì đây là giá trị mặc định.

    Bạn cũng có thể đặt worker-key-mnemonic trong các yêu cầu thực thi. Điều này có thể hữu ích nếu bạn đang dùng lại tệp thực thi cho nhiều loại hành động và muốn phân biệt các hành động theo trình thực thi này.

  • Các tệp tạm thời được tạo trong quá trình thực hiện hành động phải được lưu vào thư mục của trình thực thi. Điều này cho phép hộp cát.

Giả sử định nghĩa quy tắc có thuộc tính "worker" được mô tả ở trên, ngoài thuộc tính "srcs" đại diện cho các đầu vào, thuộc tính "output" đại diện cho các đầu ra và thuộc tính "args" đại diện cho các đối số khởi động trình thực thi, thì lệnh gọi đến ctx.actions.run có thể là:

ctx.actions.run(
  inputs=ctx.files.srcs,
  outputs=[ctx.outputs.output],
  executable=ctx.executable.worker,
  mnemonic="someMnemonic",
  execution_requirements={
    "supports-workers" : "1",
    "requires-worker-protocol" : "json"},
  arguments=ctx.attr.args + ["@flagfile"]
 )

Để xem ví dụ khác, hãy xem phần Triển khai trình thực thi liên tục.

Ví dụ

Cơ sở mã Bazel sử dụng trình thực thi trình biên dịch Java, ngoài ví dụ về trình thực thi JSON được dùng trong các bài kiểm thử tích hợp của chúng tôi.

Bạn có thể dùng giàn giáo của họ để biến mọi công cụ dựa trên Java thành trình thực thi bằng cách truyền lệnh gọi lại chính xác.

Để xem ví dụ về quy tắc sử dụng trình thực thi, hãy xem bài kiểm thử tích hợp trình thực thi của Bazel's .

Người đóng góp bên ngoài đã triển khai trình thực thi bằng nhiều ngôn ngữ; hãy xem phần Triển khai đa ngôn ngữ của trình thực thi liên tục Bazel. Bạn có thể tìm thấy nhiều ví dụ khác trên GitHub!