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

Báo cáo vấn đề Xem nguồn Hằng đêm · 7.3 · 7.2 · 7.1 · 7 · 6,5

Worker ổn định có thể giúp bản dựng của bạn nhanh hơn. Nếu bạn có các hành động lặp lại trong bản dựng gây ra chi phí khởi động cao hoặc hưởng lợi từ việc lưu nhiều hành động vào bộ nhớ đệm, bạn nên triển khai phương thức lưu trữ cố định worker 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. Nó hỗ trợ sử dụng vùng đệm giao thức hoặc chuỗi JSON.

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

Tạo dựng nhân viên

Một worker ổn định đáp ứng được một số yêu cầu:

  • Có nội dung WorkRequests từ stdin.
  • Viết WorkResponses (và chỉ WorkResponse) đối với 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ờ hiệu dòng lệnh --persistent_worker và chỉ tự tạo sự cố định nếu cờ đó đã được truyền, nếu không 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 những yêu cầu này, bạn có thể sử dụng chương trình này làm !

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

WorkRequest chứa danh sách các đối số cho worker, danh sách các đối số các cặp chuỗi đại diện đại diện cho đầu vào mà worker có thể truy cập (đây không phải là đượ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 đối với trình thực thi singleplex.

LƯU Ý: Mặc dù thông số kỹ thuật vùng đệm giao thức sử dụng "ví dụ về trường hợp rắn" (request_id), giao thức JSON sử dụng "kiểu lạc đà" (requestId). Tài liệu này sử dụng quy ước viết hoa kiểu lạc đà trong các ví dụ về JSON, nhưng viết hoa con 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ể dùng trường verbosity (không bắt buộc) để yêu cầu thêm dữ liệu đầu ra gỡ lỗi khỏi trình thực thi. Điều này hoàn toàn tuỳ thuộc vào việc nhân viên xử lý nội dung gì và cách xuất dữ liệu ra sao. Cao hơn các giá trị biểu thị kết quả chi tiết hơn. Chuyển cờ --worker_verbose đến Bazel đặt trường verbosity thành 10, nhưng bạn có thể sử dụng 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.

Chỉ những worker hỗ trợ trường sandbox_dir (không bắt buộc) mới được sử dụng hộp cát Multiplex.

Trả lời cho bài tập

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 worker. Vì worker chỉ có thể viết WorkResponses đến stdout, trình chạy này thường chuyển hướng stdout bất kỳ công cụ nào mà nó dùng để 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 yêu cầu WorkRequestWorkResponse tương ứng để có cùng yêu cầu mã, vì vậy, bạn phải chỉ định mã yêu cầu nếu khác 0. Đây là một hồ sơ thanh toán hợp lệ WorkResponse.

{
  "requestId" : 12,
}

request_id là 0 biểu thị "singleplex" yêu cầu, được dùng khi yêu cầu này không thể 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 cụ thể nhận được các yêu cầu chỉ có request_id 0 hoặc chỉ request_id lớn hơn 0. Các yêu cầu singleplex được gửi nối tiếp, trong đó Ví dụ: nếu máy chủ không gửi một 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ỷ, xem bên dưới).

Lưu ý

  • Mỗi vùng đệm giao thức bắt đầu bằng độ 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.
  • Yêu cầu JSON duy trì cấu trúc giống như protobuf, nhưng sử dụng cấu trúc tiêu chuẩn JSON và sử dụng quy ước viết hoa kiểu lạc đà (camel case) 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à tiến dưới dạng protobuf, trình thực thi JSON phải chấp nhận các trường không xác định trong những thông báo này, và sử dụng giá trị mặc định của protobuf cho các giá trị còn 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 cách sử dụng định dạng JSON của protobuf

Huỷ

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 nối với quá trình thực thi động, trong đó quá trình thực thi thường có thể bị gián đoạn bởi quá trình thực thi từ xa nhanh hơn. Để cho phép huỷ, thêm supports-worker-cancellation: 1 vào execution-requirements (xem bên dưới) và đặt giá trị Cờ --experimental_worker_cancellation.

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

Mỗi tin nhắn WorkRequest không huỷ phải được trả lời chính xác một lần, bất kể là chứ không phải yêu cầu đó đã bị huỷ. Sau khi máy chủ gửi yêu cầu huỷ, worker có thể phản hồi bằng WorkResponse với tập hợp request_idwas_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 outputexit_code sẽ bị bỏ qua.

Sau khi phản hồi được gửi cho WorkRequest, worker không được chạm vào trong thư mục đang hoạt động của nó. Máy chủ 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 phải tạo một quy tắc tạo ra các hành động cần được thực hiện bởi . Việc lập ra một quy tắc Starlark sử dụng một worker cũng giống như tạo bất kỳ quy tắc nào khác.

Ngoài ra, quy tắc này cần phải chứa tệp 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à nó tạo ra.

Tham chiếu đến worker

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

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

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

Nếu tệp nhị phân worker bạn tạo nằm trong gói có tên "work", gói này nằm ở trên cùng cấp bản dựng, đâ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 cần tạo worker để chạy trên nền tảng thực thi thay vì trên nền tảng mục tiêu (ví dụ: worker được sử dụng làm công cụ trong quá trình tạo bản dựng).

Yêu cầu đối với tác vụ công việc

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

  • Trường "Arguments" (đối số). Thao tác này sẽ 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 worker khi khởi động. Phần tử cuối cùng trong các "đối số" danh sách là một đối số flag-file (@ có trước). Đã đọc trình thực thi các đối số từ tệp cờ được chỉ định trên cơ sở mỗi WorkRequest. Thông tin 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 "đối số" và "yêu cầu thực thi" là trường bắt buộc cho tất cả hành động gửi đến trình thực thi. Ngoài ra, các thao tác cần được thực thi bằng Trình thực thi JSON cần bao gồm "requires-worker-protocol" : "json" trong phần tử 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 worker (trình thực thi proto), vì chúng là mặc định.

    Bạn cũng có thể thiết lập worker-key-mnemonic trong các yêu cầu thực thi. Chiến dịch 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 của 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 đó nên được lưu vào thư mục của worker. Thao tác này sẽ bật chức năng hộp cát.

Giả sử định nghĩa quy tắc có từ "worker" được mô tả ở trên, ngoài việc thành "srcs" thuộc tính biểu thị đầu vào, "đầu ra" phân bổ biểu thị kết quả đầu ra và một "args" thuộc tính đại diện cho worker đối số khởi động, 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 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 ngoài một ví dụ về trình thực thi JSON mà chúng tôi sử dụng trong thử nghiệm tích hợp.

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

Để xem ví dụ về quy tắc sử dụng worker, hãy xem kiểm thử tích hợp nhân viên.

Các cộng tác viên bên ngoài đã triển khai worker bằng nhiều ngôn ngữ; lấy một xem Triển khai Polyglot cho trình thực thi cố định Bazel. Bạn có thể xem nhiều ví dụ khác trên GitHub!