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

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

Trình chạy liên tục có thể giúp bản dựng của bạn nhanh hơn. Nếu các hành động lặp lại trong bản dựng có chi phí khởi động cao hoặc sẽ hưởng lợi từ việc lưu vào bộ nhớ đệm trên nhiều hành động, thì bạn nên 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 worker bằng stdin/stdout, hỗ trợ việc sử dụng vùng đệm giao thức hoặc chuỗi JSON.

Quá trình triển khai trình thực thi gồm hai phần:

Làm cho worker

Một nhân viên kiên trì duy trì một số yêu cầu:

  • Tác vụ này đọc WorkRequests từ stdin của nó.
  • Công cụ này ghi WorkResponses (và chỉ WorkResponse) vào stdout.
  • Phương thức 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_worker và chỉ tự duy trì nếu cờ đó được chuyển, nếu không thì phải thực hiện 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ì bạn có thể sử dụng chương trình làm một 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 thông báo đường dẫn đại diện cho các đầu vào mà trình thực thi có thể truy cập (điều này không được thực thi 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 cho trình thực thi singleplex.

LƯU Ý: Mặc dù thông số kỹ thuật của vùng đệm giao thức sử dụng "trường hợp con rắn" (request_id), nhưng giao thức JSON sử dụng "quy tắc viết hoa Camel" (requestId). Tài liệu này sử dụng quy tắc viết hoa Camel trong các ví dụ về JSON, nhưng trường hợp này là trường hợp rắn 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 kết quả gỡ lỗi từ trình thực thi. Điều này hoàn toàn tuỳ thuộc vào nhân viên về việc gì và cách đầu ra như thế nào. Giá trị càng cao thì kết quả đầu ra càng chi tiết. Việc truyền cờ --worker_verbose cho Bazel sẽ thiết lập 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 số lượng đầu ra khác nhau.

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

Câu trả lời về bài tập

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 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; bạn có thể ghi nhật ký hoàn chỉnh 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 thường thì trình thực thi này sẽ chuyển hướng stdout của bất kỳ công cụ nào mà nó sử dụng đến 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 là không bắt buộc. Tuy nhiên, Bazel đòi hỏi WorkRequestWorkResponse tương ứng phải có cùng một mã yêu cầu. Vì vậy, bạn phải chỉ định mã yêu cầu nếu mã khác 0. Đây là WorkResponse hợp lệ.

{
  "requestId" : 12,
}

request_id có giá trị 0 cho biết yêu cầu "singleplex", được dùng khi không thể xử lý song song yêu cầu này 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 sẽ nhận được các yêu cầu chỉ có request_id 0 hoặc chỉ có request_id lớn hơn 0. Yêu cầu singleplex được gửi theo nối tiếp, 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ừ các yêu cầu huỷ, hãy xem bên dưới).

Ghi chú

  • Mỗi vùng đệm giao thức đều đứng sau độ dài ở định dạng varint (xem MessageLite.writeDelimitedTo().
  • Yêu cầu và phản hồi JSON không được đặt sau chỉ báo kích thước.
  • Các yêu cầu JSON duy trì cấu trúc tương tự như protobuf, nhưng sử dụng JSON tiêu chuẩn và sử dụng kiểu viết hoa Camel cho tất cả các tên trường.
  • Để duy trì cùng các thuộc tính tương thích ngược và xuôi 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 chúng thành JSON bằng định dạng JSON của protobuf

Huỷ

Nhân viên 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 liên quan đến 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 do 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à một WorkRequest với trường cancel được đặt (và tương tự như phản hồi huỷ là một WorkResponse với nhóm trường was_cancelled). 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 nào cần huỷ. Trường request_id sẽ là 0 đối với các worker (trình thực thi) singleplex hoặc trường request_id không phải là 0 của WorkRequest đã gửi trước đó đối với multiplex worker. 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 này, yêu cầu huỷ phải được bỏ qua.

Mỗi thông báo WorkRequest không bị huỷ phải được trả lời chính xác một lần, cho dù có bị huỷ hay không. Sau khi máy chủ gửi yêu cầu huỷ, worker này có thể phản hồi bằng WorkResponse với request_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 đang hoạt động. Máy chủ có thể miễn phí dọn dẹp các tệp, bao gồm cả cá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 các thao tác để trình thực thi 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 cần chứa tệp 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 thao tác mà quy tắc tạo ra.

Tham chiếu đến worker

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ó tên là MyWorker.Java, thì đây có thể là quy tắc liên kết:

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

Thao tác này sẽ tạo nhãn "worker", chỉ tệp nhị phân của worker. Sau đó, bạn sẽ xác định một quy tắc sử dụng trình thực thi này. 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 chạy.

Nếu tệp nhị phân của trình chạy bạn đã tạo nằm trong một gói có tên "work" (công việc) ở 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 trình thực thi này 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 đối với công việc

Quy tắc sử dụng worker này sẽ tạo các thao tác để worker thực hiện. Những thao tác này có một số yêu cầu.

  • Trường "đối số". Thao tác này sẽ lấy một danh sách các chuỗi, tất cả trừ chuỗi cuối cùng là đố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 "argument" (đối số) là một đối số flag-file (@-preceded). Worker sẽ đọc các đối số từ tệp cờ đượ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 từ điển có chứa "supports-workers" : "1", "supports-multiplex-workers" : "1" hoặc cả hai.

    Các trường "đối số" và "yêu cầu thực thi" là bắt buộc đối với tất cả các thao tác được gửi đến trình thực thi. Ngoài ra, các hành động mà worker cần thực thi cần đưa "requires-worker-protocol" : "json" vào 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ù đây là yêu cầu không bắt buộc đối với các trình thực thi proto, vì đây là yêu cầu mặc định.

    Bạn cũng có thể đặt worker-key-mnemonic trong phần 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 bằng worker này.

  • Các tệp tạm thời được tạo trong quá trình thực hiện thao tác phải được lưu vào thư mục của trình thực thi. Thao tác này sẽ bật hộp cát.

Giả sử định nghĩa quy tắc với thuộc tính "worker" được mô tả ở trên, ngoài thuộc tính "srcs" đại diện cho dữ liệu đầu vào, thuộc tính "đầu ra" đại diện cho dữ liệu đầu ra và thuộc tính "args" đại diện cho đối số khởi động của worker, 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"]
 )

Để biết một 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 biên dịch Java bên cạnh trình chạy JSON mẫu được dùng trong kiểm thử tích hợp của chúng tôi.

Bạn có thể sử dụng scaffolding (giàn giáo) để biến bất kỳ công cụ dựa trên Java nào thành worker bằng cách truyền vào đúng lệnh gọi lại.

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

Cộng tác viên bên ngoài đã triển khai trình thực thi bằng nhiều ngôn ngữ. Hãy xem cách triển khai trình thực thi Polyglo của trình thực thi liên tục Bazel. Bạn có thể xem nhiều ví dụ khác trên GitHub!