A
mục tiêu phụ thuộc vào B
mục tiêu nếu A
cần B
tại thời điểm tạo bản dựng hoặc thời gian thực thi. Mối quan hệ phụ thuộc vào (depends upon) sẽ tạo ra một Đồ thị không chu trình 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ột mục tiêu là những mục tiêu khác có thể truy cập được bằng một đường dẫn có độ dài 1 trong biểu đồ phần phụ thuộc. Các phần phụ thuộc chuyển tiếp 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ó bất kỳ độ dài nào thông qua biểu đồ.
Trên thực tế, trong bối cảnh của các bản dựng, có hai biểu đồ phần phụ thuộc, biểu đồ các phần phụ thuộc thực tế và biểu đồ các phần phụ thuộc đã khai báo. Hầu hết thời gian, hai biểu đồ này rất giống nhau nên không cần phân biệt, nhưng điều này sẽ hữu ích cho phần thảo luận bên dưới.
Các phần phụ thuộc thực tế và phần phụ thuộc đã khai báo
X
mục tiêu thực sự phụ thuộc vào Y
mục tiêu nếu Y
phải có mặt, được tạo và cập nhật để X
được tạo chính xác. Đã tạo 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 tạo bản dựng.
Một X
mục tiêu có một phần phụ thuộc đã khai báo trên mục tiêu Y
nếu có cạnh phụ thuộc từ X
đến Y
trong gói X
.
Để có bản dựng chính xác, biểu đồ các phần phụ thuộc thực tế A phải là đồ thị con của biểu đồ của các phần phụ thuộc đã khai báo D. Tức là mọi cặp nút x --> y
được kết nối trực tiếp 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 giá trị ước chừng quá cao 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 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: 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 đã khai báo bắc cầu 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ể 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 thứ được nhập gián tiếp,
ngay cả khi A
cần dữ liệu đó tại 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ụ tạo bản dựng sẽ kiểm tra toàn bộ phần đóng chuyển đổ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 đó đều được phản ánh trong kết quả cuối cùng, đồng thời tạo lại các phần trung gian nếu 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 phải là 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 không trực tiếp phụ thuộc 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:
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
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 gián tiếp 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(); } |
Các phần phụ thuộ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 khai báo
Một mối nguy hiểm tiềm ẩn sẽ xuất hiện khi có người thêm mã vào a
để tạo một phần phụ thuộc thực tế trực tiếp trên c
, nhưng quên khai báo phần phụ thuộc đó trong tệp bản dựng a/BUILD
.
a / a.in |
|
---|---|
import b; import c; b.foo(); c.garply(); |
|
Các phần phụ thuộc đã khai báo không còn ước tính quá nhiều các phần phụ thuộc thực tế.
Việc này có thể tạo được, vì các tập hợp đóng bắc cầu của hai biểu đồ bằng nhau, nhưng lại che giấu một vấn đề: a
có một phần phụ thuộc thực tế nhưng chưa 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à thực tế
Mối nguy hiểm sẽ lộ ra khi người nào đó 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 xảy ra 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 hiện là một giá trị ước chừng không chính xác của các phần phụ thuộc thực tế, ngay cả khi được đóng một cách bắc cầu; bản dựng có thể không thành công.
Bạn có thể tránh được vấn đề 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 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 bản dựng đều có 3 thuộc tính để chỉ định các loại phần phụ thuộc chung: srcs
, deps
và data
. Các trường hợp 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 chung cho mọi 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ể theo quy tắc, ví dụ: compiler
hoặc resources
. Các thông tin này được trình bày chi tiết trong Bách khoa toàn thư về bản dựng.
Phần phụ thuộc srcs
Các tệp được quy tắc hoặc các quy tắc trực tiếp sử dụng để xuất 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 đú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 tạo mục tiêu. Ví dụ: một quy 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 tạo kiểm thử đơn vị, bạn không cần tệp này, nhưng bạn cần tệp này khi chạy 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 bài kiểm thử trong một thư mục tách biệt, trong đó 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 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 nối các đường dẫn của thư mục nguồn của kiểm thử và đường dẫn tương ứng với không gian làm việc, ví dụ: ${TEST_SRCDIR}/workspace/path/to/data/file
.
Sử dụng nhãn để tham chiếu thư mục
Khi xem xét các tệp BUILD
, bạn có thể nhận thấy một số nhãn data
tham chiếu đến các thư mục. Bạn không nên sử dụng các nhãn kết thúc bằng /.
hoặc /
như các ví dụ sau:
Không nên — data = ["//data/regression:unittest/."]
Không nên — data = ["testdata/."]
Không nên — data = ["testdata/"]
Điều này có vẻ thuận tiện, đặc biệt là đối với các bài kiểm thử vì cho phép kiểm thử sử dụng tất cả tệp dữ liệu trong thư mục.
Tuy nhiên, hãy cố gắng không làm việc này. Để đảm bảo việc tạo lại tăng dần (và thực thi lại các chương trình kiểm thử) chính xác sau khi thay đổi, hệ thống xây dựng phải biết về 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 việc tạo lại khi chính thư mục đó
thay đổi (do việc thêm hoặc xoá các tệp), nhưng sẽ không thể phát hiện
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 đính kèm.
Thay vì chỉ định 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 có trong đó, một cách rõ ràng hoặc sử dụng hàm glob()
. (Sử dụng **
để buộc glob()
phải đệ quy.)
Nên — data = glob(["testdata/**"])
Tuy nhiên, có một số trường hợp 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 tuân thủ ngữ pháp nhãn, thì việc liệt kê rõ ràng các tệp hoặc sử dụng hàm glob()
sẽ gây 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 rủi ro liên quan đến việc tạo lại không chính xác như 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 phải khai báo rõ ràng phần phụ thuộc của quy tắc đó trên tất 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
làm phần phụ thuộc dữ liệu trong kiểm thử.
Tệp BUILD | Chế độ hiển thị |