Phụ thuộc

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

Mục tiêu A phụ thuộc vào mục tiêu B nếu B cần A tại thời điểm tạo hoặc thực thi. Mối quan hệ phụ thuộc vào sẽ tạo ra Biểu đồ tuần hoàn có hướng dẫn (DAG) so với mục tiêu và được gọi là biểu đồ phần phụ thuộ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 dài 1 trong biểu đồ phần phụ thuộc. Phần phụ thuộc bắc cầu của mục tiêu là các mục tiêu phụ thuộc thông qua đường dẫn có độ dài bất kỳ thông qua biểu đồ.

Trên thực tế, trong bối cảnh bản dựng, có hai biểu đồ phần phụ thuộc, biểu đồ về các phần phụ thuộc thực tế và biểu đồ về các phần phụ thuộc đã khai báo. Trong hầu hết các trường hợp, 2 biểu đồ tương tự nhau nên không cần phân biệt, nhưng hữu ích cho cuộc thảo luận dưới đây.

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

X mục tiêu thực sự phụ thuộc vào mục tiêu Y nếu Y 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ó thể có nghĩa là việc đượ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 diễn ra trong một bản dựng.

X mục tiêu có một phần phụ thuộc được khai báo trên Y mục tiêu nếu có một cạnh 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à biểu đồ con của biểu đồ các phần phụ thuộc đã khai báo D. Điều này có nghĩa là mọi cặp nút 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 Dsự ướ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òn quy tắc nào khác.

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: bản dựng có thể không hoạt động, nhưng tệ hơn, bản dựng có thể phụ thuộc vào một số hoạt động trước đó hoặc khi các phần phụ thuộc bắc cầu được khai báo mà mục tiêu xảy ra. 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 được) cố gắng liệt kê mọi thứ đã nhập gián tiếp, ngay cả khi A cần vào thời điểm thực thi.

Trong quá trình tạo bản dựng của mục tiêu X, công cụ bản dựng sẽ kiểm tra toàn bộ quá trình đóng tạm thời 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 đó sẽ được phản ánh trong kết quả cuối cùng, tạo lại các trung gian khi cần.

Tính chất bắc cầu của các phần phụ thuộc dẫn đến 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 — cạnh tạm thời nhưng không 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 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 tiến trình mẫu sau đây:

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ế

Ban đầ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 với 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
                  với 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 quá phần phụ thuộc thực tế. Tất cả đều ổn.

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

Sẽ có một mối nguy hiểm tiềm ẩn khi người dùng thêm mã vào a để tạo phần phụ thuộc thực tế trực tiếp trên c, nhưng quên khai báo phần tử đó 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 với 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ế với các mũi tên kết nối a, b và c. Mũi tên
 hiện 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 chiếm ưu thế các phần phụ thuộc thực tế. Điều này có thể tạo ra vấn đề vì các trạng thái đóng bắc cầu của hai 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 không được khai báo trên c.

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

Mối nguy hiểm này được phát hiện khi ai đó tái cấu trúc b để không còn phụ thuộc vào c, vô tình phá vỡ a mà không gây ra lỗi nào.

  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 với 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, việc này làm hỏng kết nối của c đến c
Biểu đồ phần phụ thuộcđã khai báo
Biểu đồ phần phụ thuộc thực tế cho thấy đường 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 đã bị đánh giá thấp các phần phụ thuộc thực tế, ngay cả khi đã đóng bắc cầu; bản dựng này có thể không hoạt động.

Bạn có thể khắc phục sự cố này 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 tạo bản dựng đều có 3 thuộc tính để chỉ định nhiều loại phần phụ thuộc chung: srcs, depsdata. Những điều này được giải thích bên dưới. Để biết thêm thông tin, hãy xem phần Các thuộc tính phổ biến đối với tất 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 cụ thể cho quy tắc, ví dụ: compiler hoặc resources. Chúng được trình bày chi tiết trong bài viết Bách khoa toàn thư về bản dựng.

Phần phụ thuộc srcs

Tệp được sử dụng trực tiếp bởi quy tắc hoặc các quy tắc tạo 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 các 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 mã nguồn: chúng không ảnh hưởng đến cách xây dựng mục tiêu. Ví dụ: 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 một tệp. Khi tạo kiểm thử đơn vị, bạn không cần tệp đó, nhưng bạn cần tệp đó khi chạy kiểm thử. Quy tắc này cũng áp dụng cho các công 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 tách biệt, nơi chỉ có các tệp được liệt kê là data. Do đó, nếu tệp nhị phân/thư viện/kiểm thử cần chạy một số tệp, hãy chỉ định các tệp đó (hoặc quy tắc tạo 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",
        ...
    ],
)

Các tệp này có sẵn bằng cách sử dụ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 tham gia các đường dẫn của thư mục nguồn kiểm thử và đường dẫn tương đối không gian làm việc, chẳng hạn như ${TEST_SRCDIR}/workspace/path/to/data/file.

Sử dụng nhãn để tham chiếu 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 rằng 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ư những ví dụ mà bạn không nên sử dụng:

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 các thử nghiệm vì nó cho phép thử nghiệm sử dụng tất cả các tệp dữ liệu trong thư mục.

Nhưng hãy cố gắng không làm điều này. Để đảm bảo tạo lại bản dựng tăng dần (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 nhận biết được tập hợp đầy đủ các tệp đầ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 việc xây dựng lại khi chính thư mục đó thay đổi (do việc thêm hoặc xoá tệp), nhưng sẽ không thể phát hiện các nội dung chỉnh sửa cho từng tệp riêng lẻ vì những thay đổi đó không ảnh hưởng đến thư mục đi 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 các tệp chứa trong đó một cách rõ ràng hoặc sử dụng hàm glob(). (Sử dụng ** để buộc đệ quy glob().)

Nêndata = glob(["testdata/**"])

Rất tiếc, có một số trường hợp mà bạn 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ê tệp rõ ràng hoặ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 các nhãn thư mục trong trường hợp này, nhưng hãy cảnh giác rủi ro liên quan đến các bản 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 gốc có đườ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ư kiểm thử) cần sử dụng nhiều tệp đều phải khai báo rõ ràng phần phụ thuộc của tất cả các tệp đó. Bạn có thể sử dụng filegroup() để nhóm các tệp 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 là phần phụ thuộc dữ liệu trong quy trình kiểm thử.

Tạo tệp Chế độ hiển thị