Phần phụ thuộc

Mục tiêu A phụ thuộc vào mục tiêu B nếu B được mục tiêu A cần tại thời gian xây dựng hoặc thực thi. Mối quan hệ phụ thuộc vào tạo ra một Biểu đồ có hướng không có chu kỳ (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ột mục tiêu là những mục tiêu khác có thể tiếp cận được bằng một đường dẫn có độ dài là 1 trong biểu đồ phần phụ thuộc. Các phần phụ thuộc bắc cầu của một mục tiêu là những mục tiêu mà mục tiêu đó phụ thuộc vào thông qua một đường dẫn có độ dài bất kỳ trong biểu đồ.

Trên thực tế, trong bối cảnh xây dựng, có 2 biểu đồ phần phụ thuộc: biểu đồ của phần phụ thuộc thực tế và biểu đồ của phần phụ thuộc được khai báo. Hầu hết thời gian, 2 biểu đồ này giống nhau đến mức không cần phân biệt, nhưng điều này hữu ích cho cuộc thảo luận bên dưới.

Phần phụ thuộc thực tế và phần phụ thuộc đượ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 phải có mặt, được xây dựng và cập nhật để X được xây dựng đúng cách. Đã xây dựng có thể có nghĩa là đã 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 xuyên xảy ra trong quá trình xây dựng.

Mục tiêu Xphần phụ thuộc được khai báo vào mục tiêu Y nếu có một cạnh phần phụ thuộc từ X đến Y trong gói của X.

Để xây dựng đúng cách, biểu đồ phần phụ thuộc thực tế A phải là một biểu đồ con của biểu đồ phần phụ thuộc đượ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 rằng D là một ước tính quá mức của A.

Trình ghi tệp BUILD phải khai báo rõ ràng tất cả cá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 được khai báo thêm.

Việc không tuân thủ nguyên tắc này sẽ gây ra hành vi không xác định: quá trình xây dựng có thể không thành công, nhưng tệ hơn là quá trình xây dựng có thể phụ thuộc vào một số thao tác trước đó hoặc vào các phần phụ thuộc bắc cầu được khai báo mà mục tiêu có. Bazel kiểm tra các phần phụ thuộc bị thiếu và báo cáo lỗi, nhưng không thể kiểm tra đầ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 thứ được nhập gián tiếp, ngay cả khi cần bởi A tại thời gian thực thi.

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

Bản chất bắc cầu của các phần phụ thuộc dẫn đến một lỗi phổ biến. Đôi khi, mã trong một tệp có thể sử dụng mã do phần phụ thuộc gián tiếp cung cấp – một cạnh bắc cầu nhưng không trực tiếp trong biểu đồ phần phụ thuộc đượ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 không phụ thuộc trực tiếp vào nhà cung cấp, nên không có cách nào để theo dõi các thay đổi, như minh hoạ trong dòng thời gian ví dụ sau:

1. Phần phụ thuộc được khai báo khớp với 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, 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đượ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 bằng các mũi tên kết nối a, b và c
Biểu đồ phần phụ thuộcthực tế

Các phần phụ thuộc được khai báo ước tính quá mức 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 được khai báo

Một mối nguy hiểm tiềm ẩn được đưa vào khi ai đó thêm mã vào a để tạo phần phụ thuộc thực tế trực tiếp vào c, nhưng 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đượ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. Giờ đây, một mũi tên cũng kết nối A với C. Điề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ộcthực tế

Các phần phụ thuộc được khai báo không còn ước tính quá mức các phần phụ thuộc thực tế nữa. Điều này có thể xây dựng ok, vì bao đóng bắc cầu của 2 biểu đồ bằng nhau, nhưng che giấu một vấn đề: a có phần phụ thuộc thực tế nhưng chưa được khai báo vào c.

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

Mối nguy hiểm được tiết lộ khi ai đó tái cấu trúc b để 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 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, điều này làm gián đoạn kết nối của a với c
Biểu đồ phần phụ thuộc được khai báo
Biểu đồ phần phụ thuộc thực tế cho thấy a 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 được khai báo hiện là một ước tính dưới mức của các phần phụ thuộc thực tế, ngay cả khi đóng bắc cầu; quá trình xây dựng có thể không thành công.

Vấn đề 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 trong 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: srcs, depsdata. Các thuộc tính này được giải thích bên dưới. Để biết thêm thông tin chi tiết, hãy xem bài viết Thuộc tính chung cho tất cả các quy tắc.

Nhiều quy tắc cũng có cá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. Các thuộc tính này được trình bày chi tiết trong Bách khoa toàn thư về bản dựng.

srcs phần phụ thuộc

Các tệp được quy tắc hoặc quy tắc sử dụng trực tiếp để xuất tệp nguồn.

deps phần phụ thuộc

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

data phần phụ thuộc

Mục tiêu xây dựng có thể cần một số tệp dữ liệu để chạy đúng cách. Các tệp dữ liệu này không phải là mã nguồn: chúng không ảnh hưởng đến cách xây dựng mục tiêu. Ví dụ: một chương trình kiểm thử đơn vị có thể so sánh đầu ra của một hàm với nội dung của một tệp. Khi bạn xây dựng chương trình kiểm thử đơn vị, bạn không cần tệp, nhưng bạn cần tệp đó khi chạy chương trình kiểm thử. Điều tương tự cũng áp dụng cho 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 các chương trình kiểm thử trong một thư mục riêng 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/chương trình kiểm thử cần một số tệp để chạy, hãy chỉ định các tệp đó (hoặc một quy tắc xây 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",
        ...
    ],
)

Các tệp này có sẵn bằng đường dẫn tương đối path/to/data/file. Trong các chương trình 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 của chương trình kiểm thử và đườ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 đến thư mục

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

Không nên dùngdata = ["//data/regression:unittest/."]

Không nên dùngdata = ["testdata/."]

Không nên dùngdata = ["testdata/"]

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

Tuy nhiên, hãy cố gắng không làm như vậy. Để đảm bảo xây dựng lại gia tăng chính xác (và thực thi lại các chương trình kiểm thử) sau khi thay đổi, hệ thống xây dựng phải biết tập hợp đầy đủ các tệp là đầu vào cho bản dựng (hoặc chương trình 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 xây dựng 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 các chỉnh sửa đối với từng tệp vì những thay đổi đó không ảnh hưởng đến thư mục chứa. Thay vì chỉ định thư mục làm đầu vào cho hệ thống xây dựng, bạn nên liệt kê tập hợp các tệp có trong đó, một cách rõ ràng hoặc bằng cách sử dụng glob() hàm. (Sử dụng ** để buộc glob() phải đệ quy.)

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

Rất tiếc, có một số trường hợp 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 tuân theo cú pháp nhãn, thì việc liệt kê rõ ràng các tệp hoặc sử dụng glob() hàm sẽ tạo ra lỗi nhãn không hợp lệ. Trong trường hợp này, bạn phải sử dụng nhãn thư mục, nhưng hãy cẩn thận về nguy cơ liên quan đến việc xây dựng lại không chính xác như mô tả ở trên.

Nếu phải sử dụng nhãn thư mục, hãy nhớ 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/..

Bất kỳ quy tắc bên ngoài nào, chẳng hạn như một chương trình kiểm thử, cần sử dụng nhiều tệp 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 đến nhãn my_data làm phần phụ thuộc dữ liệu trong chương trình kiểm thử.

Tệp BUILD Mức độ hiển thị