Phần phụ thuộc

Mục tiêu A phụ thuộc vào mục tiêu B nếu A cần B tại thời điểm tạo bản dựng hoặc thực thi. Mối quan hệ phụ thuộc tạo ra Biểu đồ tuần hoàn có hướng (DAG) trên các mục tiêu và được gọi là biểu đồ phần phụ thuộc.

Các phần phụ thuộc trực tiếp của mục tiêu là các mục tiêu khác có thể truy cập được bằng đường dẫn có độ dài 1 trong biểu đồ phần phụ thuộc. Các phần phụ thuộc bắc cầu của mục tiêu là các mục tiêu mà mục tiêu phụ thuộc thông qua một đường dẫn có độ dài bất kỳ qua biểu đồ.

Trên thực tế, trong các bản dựng, có hai biểu đồ phần phụ thuộc là biểu đồ phần phụ thuộc thực tế và biểu đồ phần phụ thuộc đã khai báo. Hầu như mọi lúc, 2 biểu đồ giống nhau đến mức không cần phải phân biệt nhưng sẽ rất hữu ích cho việc thảo luận bên dưới.

Phần phụ thuộc thực tế và phần phụ thuộc đã khai báo

Mục tiêu X thực sự phụ thuộc vào mục tiêu Y nếu Y cần phải có mặt, được tạo và cập nhật để X được tạo chính xác. Được tạo có nghĩa là được tạo, xử lý, biên dịch, liên kết, lưu trữ, nén, thực thi hoặc bất kỳ loại tác vụ nào khác thường xảy ra trong bản dựng.

Mục tiêu Xphần phụ thuộc đã khai báo trên mục tiêu Y nếu có cạnh phần phụ thuộc từ X đến Y trong gói X.

Đối với các bản dựng chính xác, biểu đồ của các phần phụ thuộc thực tế A phải là đồ thị con của biểu đồ các phần phụ thuộc đã khai báo D. Tức là mọi cặp nút được kết nối trực tiếp x --> y trong A cũng phải được kết nối trực tiếp trong D. Có thể nói Dgiá trị quá ước của A.

Người viết tệp BUILD phải khai báo rõ ràng tất cả phần phụ thuộc trực tiếp thực tế cho mọi quy tắc đối với hệ thống xây dựng (và không khai báo thêm nữa).

Việc không tuân thủ nguyên tắc này sẽ dẫn đến hành vi không xác định: bản dựng có thể không thành công, nhưng tệ hơn là bản dựng có thể phụ thuộc vào một số thao tác trước đó hoặc phụ thuộc vào các phần phụ thuộc được khai báo bắc cầu mà mục tiêu có. Bazel kiểm tra để tìm các phần phụ thuộc bị thiếu và báo cáo lỗi, nhưng không thể hoàn tất quá trình kiểm tra này trong mọi trường hợp.

Bạn không cần (và không nên) cố gắng liệt kê mọi nội dung được nhập gián tiếp, ngay cả khi A cần có tại thời điểm thực thi.

Trong quá trình tạo bản dựng X mục tiêu, công cụ bản dựng sẽ kiểm tra toàn bộ trạng thái đóng bắc cầu của các phần phụ thuộc của X để đảm bảo mọi thay đổi trong các mục tiêu đó đều được phản ánh trong kết quả cuối cùng, tạo lại các yếu tố trung gian khi cần.

Tính chất bắc cầu của các phần phụ thuộc sẽ dẫn đến một lỗi thường gặp. Đôi khi, mã trong một tệp có thể dùng mã do phần phụ thuộc gián tiếp cung cấp – một thuộc tính bắc cầu chứ không phải cạnh trực tiếp trong biểu đồ phần phụ thuộc đã khai báo. Các phần phụ thuộc gián tiếp không xuất hiện trong tệp BUILD. Vì quy tắc này không phụ thuộc trực tiếp vào trình cung cấp, nên không có cách nào để theo dõi các thay đổi, như minh hoạ trong tiến trình mẫu sau:

1. Các phần phụ thuộc đã khai báo khớp với các phần phụ thuộc thực tế

Lúc đầu, mọi thứ đều hoạt động. Mã trong gói a sử dụng mã trong gói b. Mã trong gói b sử dụng mã trong gói c, và do đó a phụ thuộc bắc cầu vào c.

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
Biểu đồ phần phụ thuộc đã khai báo có các mũi tên kết nối a, b và c
Biểu đồ phần phụ thuộc đã khai báo
Biểu đồ phần phụ thuộc thực tế khớp với biểu đồ phần phụ thuộc đã khai báo có các mũi tên kết nối a, b và c
Biểu đồ phần phụ thuộc thực tế

Các phần phụ thuộc đã khai báo vượt trội so với các phần phụ thuộc thực tế. Mọi thứ đều ổn.

2. Thêm phần phụ thuộc chưa khai báo

Mối nguy hiểm tiềm ẩn xuất hiện khi ai đó thêm mã vào a sẽ tạo ra phần phụ thuộc thực tế trực tiếp trên c, nhưng lại quên khai báo trong tệp bản dựng a/BUILD.

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Biểu đồ phần phụ thuộc đã khai báo có các mũi tên kết nối a, b và c
Biểu đồ phần phụ thuộc đã khai báo
Biểu đồ phần phụ thuộc thực tế có các mũi tên kết nối a, b và c. Một mũi tên giờ đây cũng kết nối A với C. Biểu đồ này không khớp với biểu đồ phần phụ thuộc đã khai báo
Biểu đồ phần phụ thuộc thực tế

Các phần phụ thuộc đã khai báo không còn ước lượng quá mức các phần phụ thuộc thực tế. Điều này có thể vẫn ổn vì trạng thái đóng bắc cầu của 2 biểu đồ là bằng nhau, nhưng che giấu một vấn đề: a có phần phụ thuộc thực tế nhưng không được khai báo trên c.

3. Sự khác biệt giữa biểu đồ phần phụ thuộc được khai báo và thực tế

Mối nguy hiểm sẽ xuất hiện khi ai đó tái cấu trúc b để nó không còn phụ thuộc vào c, vô tình làm hỏng a mà không phải do lỗi của chính họ.

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
Biểu đồ phần phụ thuộc đã khai báo có các mũi tên kết nối a và b.
                  b không còn kết nối với c nữa, điều này sẽ ngắt kết nối của một với c
Biểu đồ phần phụ thuộc đã khai báo
Biểu đồ phần phụ thuộc thực tế cho thấy kết nối với b và c, nhưng b không còn kết nối với c nữa
Biểu đồ phần phụ thuộc thực tế

Biểu đồ phần phụ thuộc đã khai báo hiện là giá trị không đúng của các phần phụ thuộc thực tế, ngay cả khi đóng bắc cầu; bản dựng có thể không thành công.

Vấn đề này có thể được ngăn chặn bằng cách đảm bảo rằng phần phụ thuộc thực tế từ a đến c được giới thiệu ở Bước 2 đã được khai báo đúng cách trong tệp BUILD.

Các loại phần phụ thuộc

Hầu hết các quy tắc xây dựng đều có 3 thuộc tính để chỉ định các loại phần phụ thuộc chung khác nhau: srcs, depsdata. Những điều này được giải thích bên dưới. Để biết thêm thông tin chi tiết, hãy xem phần Các thuộc tính phổ biến cho mọi quy tắc.

Nhiều quy tắc cũng có thuộc tính bổ sung cho các loại phần phụ thuộc dành riêng cho quy tắc, ví dụ: compiler hoặc resources. Bạn có thể xem chi tiết các chủ đề này trong Xây dựng bách khoa toàn thư.

Phần phụ thuộc srcs

Các tệp được sử dụng trực tiếp bởi quy tắc hoặc quy tắc đầu ra tệp nguồn.

Phần phụ thuộc deps

Quy tắc trỏ đến các mô-đun được biên dịch riêng cung cấp tệp tiêu đề, biểu tượng, thư viện, dữ liệu, v.v.

Phần phụ thuộc data

Mục tiêu bản dựng có thể cần một số tệp dữ liệu để chạy chính xác. Các tệp dữ liệu này không phải là mã nguồn: không ảnh hưởng đến cách tạo mục tiêu. Ví dụ: phép kiểm thử đơn vị có thể so sánh kết quả của một hàm với nội dung của tệp. Khi tạo kiểm thử đơn vị, bạn không cần tệp này, nhưng cần thiết khi chạy kiểm thử. Điều này cũng áp dụng với các công cụ được khởi chạy trong quá trình thực thi.

Hệ thống xây dựng chạy kiểm thử trong một thư mục tách biệt nơi chỉ có các tệp được liệt kê là data. Do đó, nếu một tệp nhị phân/thư viện/kiểm thử cần một số tệp để chạy, hãy chỉ định các tệp đó (hoặc quy tắc bản dựng chứa các tệp đó) trong data. Ví dụ:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

Bạn có thể sử dụng các tệp này bằng đường dẫn tương đối path/to/data/file. Trong kiểm thử, bạn có thể tham chiếu đến các tệp này bằng cách kết hợp đường dẫn của thư mục nguồn kiểm thử với đường dẫn tương đối của không gian làm việc, ví dụ: ${TEST_SRCDIR}/workspace/path/to/data/file.

Sử dụng nhãn để tham chiếu các thư mục

Khi xem qua các tệp BUILD của chúng tôi, bạn có thể nhận thấy một số nhãn data tham chiếu đến các thư mục. Các nhãn này kết thúc bằng /. hoặc / như các ví dụ sau (bạn không nên sử dụng nhãn này:

Không nêndata = ["//data/regression:unittest/."]

Không nêndata = ["testdata/."]

Không nêndata = ["testdata/"]

Điều này có vẻ thuận tiện, đặc biệt là đối với chương trình kiểm thử vì nó cho phép kiểm thử sử dụng mọi tệp dữ liệu trong thư mục.

Nhưng hãy cố gắng không làm việc này. Để đảm bảo tạo lại gia tăng chính xác (và thực thi lại kiểm thử) sau khi thay đổi, hệ thống xây dựng phải nhận biết được tập hợp đầy đủ các tệp là dữ liệu đầu vào cho bản dựng (hoặc kiểm thử). Khi bạn chỉ định một thư mục, hệ thống xây dựng chỉ thực hiện tạo lại khi chính thư mục đó thay đổi (do thêm hoặc xoá tệp), nhưng sẽ không thể phát hiện nội dung chỉnh sửa đối với từng tệp riêng lẻ vì những thay đổi đó không ảnh hưởng đến thư mục đính kèm. Thay vì chỉ định các thư mục làm dữ liệu đầu vào cho hệ thống xây dựng, bạn nên liệt kê tập hợp tệp có trong các thư mục đó một cách rõ ràng hoặc bằng cách sử dụng hàm glob(). (Sử dụng ** để buộc glob() mang tính đệ quy.)

Nên dùngdata = glob(["testdata/**"])

Rất tiếc, có một số trường hợp bắt buộc phải sử dụng nhãn thư mục. Ví dụ: nếu thư mục testdata chứa các tệp có tên không phù hợp với cú pháp nhãn, thì việc liệt kê rõ ràng các tệp hoặc việc sử dụng hàm glob() sẽ tạo ra lỗi nhãn không hợp lệ. Bạn phải sử dụng nhãn thư mục trong trường hợp này, nhưng hãy cẩn thận với nguy cơ liên quan đến việc xây dựng lại không chính xác được mô tả ở trên.

Nếu bạn phải sử dụng nhãn thư mục, hãy lưu ý rằng bạn không thể tham chiếu đến gói mẹ bằng đường dẫn ../ tương đối; thay vào đó, hãy sử dụng đường dẫn tuyệt đối như //data/regression:unittest/..

Mọi quy tắc bên ngoài, chẳng hạn như quy tắc kiểm thử, cần sử dụng nhiều tệp, đều phải khai báo rõ ràng sự phụ thuộc vào tất cả các tệp đó. Bạn có thể sử dụng filegroup() để nhóm các tệp lại với nhau trong tệp BUILD:

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

Sau đó, bạn có thể tham chiếu nhãn my_data dưới dạng phần phụ thuộc dữ liệu trong chương trình kiểm thử của mình.

XÂY DỰNG tệp Chế độ hiển thị