Worker 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 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 giữa các hành động, thì bạn có thể muốn triển khai worker 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 worker 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 worker có 2 phần:
Tạo worker
Worker liên tục phải tuân thủ một số yêu cầu:
- Worker này đọc
WorkRequests
từ
stdin. - Worker này ghi
WorkResponses
(và chỉ
WorkResponses) vàostdout. - Worker này chấp nhận cờ
--persistent_worker. Trình bao bọc phải nhận ra cờ dòng lệnh--persistent_workervà chỉ tự làm cho mình trở nên liên tục 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 tuân thủ các yêu cầu này, thì chương trình đó có thể được dùng làm worker liên tục !
Yêu cầu công việc
Một WorkRequest chứa một danh sách các đối số cho worker, một danh sách các
cặp đường dẫn-tiêu hoá đại diện cho các đầu vào mà worker có thể truy cập (không
bắt buộc, nhưng bạn có thể sử dụng thông tin này để lưu vào bộ nhớ đệm) và một mã yêu cầu, là 0
đối với các worker đơn hợp.
LƯU Ý: Mặc dù thông số kỹ thuật về 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ể sử dụng trường verbosity không bắt buộc để yêu cầu thêm thông tin đầu ra gỡ lỗi
từ worker. Worker hoàn toàn có quyền quyết định nội dung và cách xuất. Giá trị càng cao
thì thông tin đầ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ể sử 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 thông tin đầu ra khác nhau.
Trường sandbox_dir không bắt buộc chỉ được dùng bởi các worker hỗ trợ
hộp cát đa hợp.
Phản hồi công việc
Một WorkResponse chứa một mã yêu cầu, một mã thoát bằng 0 hoặc khác 0 và một
thông báo đầ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. Worker phải nắm bắt stdout và stderr của mọi công cụ mà worker
gọi và báo cáo thông qua WorkResponse. Việc ghi vào stdout của
quy trình worker là không an toàn vì sẽ gây trở ngại cho giao thức worker.
Việc ghi vào stderr của quy trình worker là an toàn, nhưng kết quả được
thu thập trong một tệp nhật ký trên mỗi worker thay vì được gán cho các hành động riêng lẻ.
{
"exitCode" : 1,
"output" : "Action failed with the following message:\nCould not find input
file \"/path/to/my/file/1\"",
"requestId" : 12
}
Theo quy tắc chung đối với protobuf, tất cả các trường đều không bắt buộc. Tuy nhiên, Bazel yêu cầu
WorkRequest và WorkResponse 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à một
WorkResponse hợp lệ.
{
"requestId" : 12,
}
request_id bằng 0 cho biết yêu cầu "đơn hợp", đượ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 worker nhất định nhận được các yêu cầu chỉ có request_id bằng 0 hoặc chỉ có
request_id lớn hơn 0. Các yêu cầu đơn hợp đượ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 đặt trước bằng độ dài của bộ đệm đó ở định dạng
varint(xemMessageLite.writeDelimitedTo(). - Yêu cầu và phản hồi JSON không được đặt trước bằng chỉ báo kích thước.
- Yêu cầu JSON tuân thủ cùng một 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 , worker 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 chúng thành JSON bằng định dạng JSON của protobuf
Hủy
Worker có thể tuỳ ý cho phép huỷ các 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 hợp 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ỷ là WorkRequest có trường cancel được đặt (và
tương tự, phản hồi huỷ là 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 các worker đơn hợp hoặc request_id khác 0 của
đã gửi trước đó cho các worker đa hợp.WorkRequest Máy chủ có thể gửi yêu cầu huỷ
cho các yêu cầu mà worker đã 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 không huỷ WorkRequest phải được trả lời chính xác một lần, cho dù
thông báo đó có bị huỷ hay không. Sau khi máy chủ gửi yêu cầu huỷ, worker có thể
phản hồi bằng WorkResponse với request_id được đặt và was_cancelled
trường đượ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
output và exit_code sẽ bị bỏ qua.
Sau khi gửi phản hồi cho WorkRequest, worker 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ể tự do 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 worker
Bạn cũng cần tạo một quy tắc tạo ra các hành động mà worker sẽ thực hiện. Việc tạo quy tắc Starlark sử dụng worker 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 cần chứa một tham chiếu đến chính worker và có một số yêu cầu đối với các hành động mà quy tắc tạo ra.
Tham chiếu đến worker
Quy tắc sử dụng worker cần chứa một trường tham chiếu đến chính worker
, vì vậy, bạn cần tạo một thực thể của quy tắc \*\_binary để xác định
worker. Nếu worker của bạn được gọi 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 worker. Sau đó, bạn sẽ xác định một quy tắc sử dụng worker. Quy tắc này phải xác định một thuộc tính mà tham chiếu đến tệp nhị phân worker.
Nếu tệp nhị phân worker mà bạn đã tạo nằm trong một 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 worker phải được tạo để chạy trên nền tảng thực thi
thay vì trên nền tảng mục tiêu (tức là worker đượ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 worker sẽ tạo các hành động để worker thực hiện. Các hành động này có một vài yêu cầu.
Trường "arguments". Trường này lấy một 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 worker khi khởi động. Phần tử cuối cùng trong danh sách "arguments" là đối số
flag-file(được đặt trước bằng @). Worker đọc các đối số từ tệp cờ đã chỉ định trên cơ sở mỗi WorkRequest. Quy tắc của bạn có thể ghi các đối số không khởi động cho worker vào tệp cờ này.Trường "execution-requirements", lấy một 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 worker. Ngoài ra, các hành động mà worker JSON phải 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 các worker proto, vì đây là giá trị mặc định.Bạn cũng có thể đặt
worker-key-mnemonictrong các yêu cầu thực thi. Điều này có thể hữu ích nếu bạn đang sử 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 worker 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 worker. Điều này cho phép tạo 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 worker,
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 một ví dụ khác, hãy xem phần Triển khai worker liên tục.
Ví dụ
Cơ sở mã Bazel sử dụng các worker trình biên dịch Java, ngoài một worker JSON mẫu được dùng trong các bài kiểm tra tích hợp của chúng tôi.
Bạn có thể sử dụng giàn giáo của họ để biến bất kỳ công cụ dựa trên Java nào thành worker 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 worker, hãy xem bài kiểm tra tích hợp worker của Bazel.
Những người đóng góp bên ngoài đã triển khai worker bằng nhiều ngôn ngữ; hãy xem phần Triển khai đa ngôn ngữ của worker liên tục Bazel. Bạn có thể tìm thấy nhiều ví dụ khác trên GitHub!