A
đích phụ thuộc vào B
đích nếu A
cần B
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 Đồ 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 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 của các bản dựng, có 2 biểu đồ phần phụ thuộc, biểu đồ phần phụ thuộc thực tế và biểu đồ phần phụ thuộc đã khai báo. Hầu hết thời gian, hai 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 dưới đây.
Phần phụ thuộc thực tế và phần phụ thuộc được khai báo
Một X
đích thực sự phụ thuộc vào đích Y
nếu Y
phải có mặt, được tạo và luôn mới để X
được tạo đúng cách. Đã 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.
X
đích có phần phụ thuộc đã khai báo trên đích Y
nếu có một cạnh phụ thuộc từ X
đến Y
trong gói X
.
Để tạo đúng, biểu đồ các phần phụ thuộc thực tế A phải là một biểu đồ 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 rằng D là một ước tính dư 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, không hơn không ké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 nữa 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 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 đến chúng tại thời điểm thực thi.
Trong quá trình tạo bản dựng của X
đích, công cụ tạo bản 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, tạo lại các thành phần 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 một lỗi thường gặp. Đôi khi, mã trong một tệp có thể sử dụng mã do một 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 đã 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 dòng thời gian ví dụ 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
, do đó a
phụ thuộc một cách 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 một phần phụ thuộc chưa khai báo
Một mối nguy 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 đượ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.
Thao tác này có thể tạo thành công vì bao đó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 đượ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à biểu đồ phần phụ thuộc thực tế
Mối nguy hiểm này xuất hiện khi có người tái cấu trúc b
để nó không còn phụ thuộc vào c
nữa, 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 được khai báo hiện là một ước lượng thấp hơn về các phần phụ thuộc thực tế, ngay cả khi đó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 xây 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 loại này được giải thích dưới đây. Để biết thêm thông tin chi tiết, hãy xem phần Các thuộc tính chung cho 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 theo quy tắc cụ thể, 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 xuất tệp nguồn sử dụng trực tiếp.
Phần phụ thuộc deps
Quy tắc trỏ đến các mô-đun được biên dịch riêng biệt 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. Đây không phải là tệp mã nguồn: chúng không ảnh hưởng đến cách tạo mục tiêu. Ví dụ: một 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 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ê dưới dạng data
. Do đó, nếu một tệp nhị phân/thư viện/thử nghiệm cần một số tệp để chạy, hãy chỉ định các tệp đó (hoặc quy tắc tạo 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ể truy cập các tệp này bằng đường dẫn tương đối path/to/data/file
. Trong các lượt kiểm thử, bạn có thể tham chiếu đến những tệp này bằng cách kết hợp các đường dẫn của thư mục nguồn của lượt 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 các thư mục
Khi xem xét các tệp BUILD
, bạn có thể nhận thấy rằng một số nhãn data
đề cập đến các thư mục. Các nhãn này kết thúc bằng /.
hoặc /
, chẳng hạn như các ví dụ sau mà bạn không nên sử dụng:
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ì nó cho phép một bài kiểm thử 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 việc này. Để đảm bảo quá trình tạo lại gia tăng chính xác (và thực hiện lại các kiểm thử) sau khi có thay đổi, hệ thống xây dựng phải biết toàn bộ các tệp là đầ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 bản dựng chỉ thực hiện quá trình tạo 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 đối với từng tệp vì những thay đổi đó không ảnh hưởng đến thư mục bao quanh.
Thay vì chỉ định các 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 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()
phải đệ quy.)
Đề xuất –
data = glob(["testdata/**"])
Rất tiếc, 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ủ cú 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ẽ 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 lưu ý đến nguy cơ liên quan là bản 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 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ể 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ị |