Hướng dẫn này trình bày cách sử dụng Bazel để theo dõi các phần phụ thuộc trong mã bằng một dự án Bazel được tạo sẵn.
Để biết thông tin chi tiết về ngôn ngữ và cờ --output, hãy xem hướng dẫn tham khảo về truy vấn Bazel và hướng dẫn tham khảo về truy vấn Bazel cquery. Nhận trợ giúp trong IDE bằng cách nhập bazel help query hoặc bazel help cquery trên dòng lệnh.
Mục tiêu
Hướng dẫn này sẽ hướng dẫn bạn thực hiện một tập hợp các truy vấn cơ bản mà bạn có thể sử dụng để tìm hiểu thêm về các phần phụ thuộc của tệp trong dự án. Hướng dẫn này dành cho các nhà phát triển mới sử dụng Bazel và có kiến thức cơ bản về cách hoạt động của Bazel và tệp BUILD.
Điều kiện tiên quyết
Bắt đầu bằng cách cài đặt Bazel (nếu bạn chưa cài đặt). Hướng dẫn này sử dụng Git để kiểm soát nguồn, vì vậy, để có kết quả tốt nhất, hãy cài đặt Git as well.
Để hình dung các biểu đồ phần phụ thuộc, hãy sử dụng công cụ có tên là Graphviz. Bạn có thể tải công cụ này xuống để làm theo.
Tải dự án mẫu xuống
Tiếp theo, hãy truy xuất ứng dụng mẫu từ kho lưu trữ Ví dụ của Bazel bằng cách chạy lệnh sau trong công cụ dòng lệnh mà bạn chọn:
git clone https://github.com/bazelbuild/examples.gitDự án mẫu cho hướng dẫn này nằm trong thư mục examples/query-quickstart.
Bắt đầu
Truy vấn Bazel là gì?
Truy vấn giúp bạn tìm hiểu về cơ sở mã Bazel bằng cách phân tích mối quan hệ giữa các tệp BUILD và kiểm tra kết quả đầu ra để biết thông tin hữu ích. Hướng dẫn này xem trước một số hàm truy vấn cơ bản, nhưng để biết thêm các lựa chọn, hãy xem hướng dẫn truy vấn. Truy vấn giúp bạn tìm hiểu về các phần phụ thuộc trong các dự án quy mô lớn mà không cần điều hướng thủ công qua các tệp BUILD.
Để chạy một truy vấn, hãy mở cửa sổ dòng lệnh và nhập:
bazel query 'query_function'Trường hợp
Hãy tưởng tượng một trường hợp đi sâu vào mối quan hệ giữa Cafe Bazel và đầu bếp tương ứng. Quán cà phê này chỉ bán pizza và nui phô mai. Hãy xem cấu trúc của dự án bên dưới:
bazelqueryguide
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── customers
│ │ ├── Jenny.java
│ │ ├── Amir.java
│ │ └── BUILD
│ ├── dishes
│ │ ├── Pizza.java
│ │ ├── MacAndCheese.java
│ │ └── BUILD
│ ├── ingredients
│ │ ├── Cheese.java
│ │ ├── Tomatoes.java
│ │ ├── Dough.java
│ │ ├── Macaroni.java
│ │ └── BUILD
│ ├── restaurant
│ │ ├── Cafe.java
│ │ ├── Chef.java
│ │ └── BUILD
│ ├── reviews
│ │ ├── Review.java
│ │ └── BUILD
│ └── Runner.java
└── MODULE.bazel
Trong suốt hướng dẫn này, trừ phi có hướng dẫn khác, hãy cố gắng không tìm trong các tệp BUILD để tìm thông tin bạn cần mà chỉ sử dụng hàm truy vấn.
Một dự án bao gồm nhiều gói tạo nên một quán cà phê. Chúng được chia thành: restaurant, ingredients, dishes, customers, và reviews. Các quy tắc trong các gói này xác định các thành phần khác nhau của quán cà phê với nhiều thẻ và phần phụ thuộc.
Chạy bản dựng
Dự án này chứa một phương thức chính bên trong Runner.java mà bạn có thể thực thi
để in thực đơn của quán cà phê. Tạo bản dựng dự án bằng Bazel bằng lệnh
bazel build và sử dụng : để báo hiệu rằng mục tiêu có tên là runner. Xem
tên mục tiêu để tìm hiểu cách
tham chiếu đến các mục tiêu.
Để tạo bản dựng dự án này, hãy dán lệnh này vào một cửa sổ dòng lệnh:
bazel build :runnerKết quả đầu ra sẽ có dạng như sau nếu bản dựng thành công.
INFO: Analyzed target //:runner (49 packages loaded, 784 targets configured).
INFO: Found 1 target...
Target //:runner up-to-date:
bazel-bin/runner.jar
bazel-bin/runner
INFO: Elapsed time: 16.593s, Critical Path: 4.32s
INFO: 23 processes: 4 internal, 10 darwin-sandbox, 9 worker.
INFO: Build completed successfully, 23 total actions
Sau khi tạo bản dựng thành công, hãy chạy ứng dụng bằng cách dán lệnh này:
bazel-bin/runner--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Lệnh này sẽ cung cấp cho bạn một danh sách các món trong thực đơn cùng với nội dung mô tả ngắn.
Khám phá các mục tiêu
Dự án liệt kê các nguyên liệu và món ăn trong các gói riêng. Để sử dụng truy vấn nhằm xem các quy tắc của một gói, hãy chạy lệnh bazel query package/…
Trong trường hợp này, bạn có thể sử dụng lệnh này để xem các nguyên liệu và món ăn mà quán cà phê này có bằng cách chạy:
bazel query //src/main/java/com/example/dishes/...bazel query //src/main/java/com/example/ingredients/...Nếu bạn truy vấn các mục tiêu của gói nguyên liệu, thì kết quả đầu ra sẽ có dạng như sau:
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
Tìm phần phụ thuộc
Trình chạy của bạn dựa vào những mục tiêu nào để chạy?
Giả sử bạn muốn tìm hiểu sâu hơn về cấu trúc của dự án mà không cần tìm hiểu hệ thống tệp (điều này có thể không khả thi đối với các dự án lớn). Cafe Bazel sử dụng những quy tắc nào?
Nếu, giống như trong ví dụ này, mục tiêu cho trình chạy của bạn là runner, hãy khám phá các phần phụ thuộc cơ bản của mục tiêu bằng cách chạy lệnh:
bazel query --noimplicit_deps "deps(target)"bazel query --noimplicit_deps "deps(:runner)"//:runner
//:src/main/java/com/example/Runner.java
//src/main/java/com/example/dishes:MacAndCheese.java
//src/main/java/com/example/dishes:Pizza.java
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:Cheese.java
//src/main/java/com/example/ingredients:Dough.java
//src/main/java/com/example/ingredients:Macaroni.java
//src/main/java/com/example/ingredients:Tomato.java
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
//src/main/java/com/example/restaurant:Cafe.java
//src/main/java/com/example/restaurant:Chef.java
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
Trong hầu hết các trường hợp, hãy sử dụng hàm truy vấn deps() để xem các phần phụ thuộc đầu ra riêng lẻ của một mục tiêu cụ thể.
Hình dung biểu đồ phần phụ thuộc (không bắt buộc)
Phần này mô tả cách bạn có thể hình dung các đường dẫn phần phụ thuộc cho một truy vấn cụ thể. Graphviz giúp bạn xem đường dẫn dưới dạng hình ảnh biểu đồ có hướng không có chu kỳ thay vì danh sách được làm phẳng. Bạn có thể thay đổi cách hiển thị biểu đồ truy vấn Bazel bằng cách sử dụng nhiều tuỳ chọn dòng lệnh --output. Xem Định dạng đầu ra để biết các lựa chọn.
Bắt đầu bằng cách chạy truy vấn mong muốn và thêm cờ --noimplicit_deps để xoá các phần phụ thuộc công cụ dư thừa. Sau đó, hãy làm theo truy vấn bằng cờ đầu ra và lưu trữ biểu đồ vào một tệp có tên là graph.in để tạo biểu diễn văn bản của biểu đồ.
Cách tìm kiếm tất cả các phần phụ thuộc của mục tiêu :runner và định dạng kết quả đầu ra dưới dạng biểu đồ:
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.inLệnh này sẽ tạo một tệp có tên là graph.in, đây là biểu diễn văn bản của biểu đồ bản dựng. Graphviz sử dụng dot – một công cụ xử lý văn bản thành hình ảnh trực quan – để tạo png:
dot -Tpng < graph.in > graph.pngNếu mở graph.png, bạn sẽ thấy một hình ảnh như sau. Biểu đồ bên dưới đã được đơn giản hoá để làm rõ các thông tin chi tiết về đường dẫn cần thiết trong hướng dẫn này.

Điều này sẽ hữu ích khi bạn muốn xem kết quả đầu ra của các hàm truy vấn khác nhau trong suốt hướng dẫn này.
Tìm phần phụ thuộc ngược
Nếu thay vào đó, bạn có một mục tiêu mà bạn muốn phân tích những mục tiêu khác sử dụng mục tiêu đó, thì bạn có thể sử dụng truy vấn để kiểm tra những mục tiêu phụ thuộc vào một quy tắc nhất định. Đây được gọi là "phần phụ thuộc ngược". Việc sử dụng rdeps() có thể hữu ích khi chỉnh sửa một tệp trong cơ sở mã mà bạn không quen thuộc và có thể giúp bạn tránh vô tình làm hỏng các tệp khác phụ thuộc vào tệp đó.
Ví dụ: bạn muốn chỉnh sửa một số nguyên liệu cheese. Để tránh gây ra vấn đề cho Cafe Bazel, bạn cần kiểm tra những món ăn phụ thuộc vào cheese.
Để xem những mục tiêu phụ thuộc vào một mục tiêu/gói cụ thể, bạn có thể sử dụng rdeps(universe_scope, target). Hàm truy vấn rdeps() nhận ít nhất 2 đối số: universe_scope (thư mục có liên quan) và target. Bazel tìm kiếm các phần phụ thuộc ngược của mục tiêu trong universe_scope được cung cấp. Toán tử rdeps() chấp nhận đối số thứ ba không bắt buộc: một giá trị nguyên chỉ định giới hạn trên về độ sâu của tìm kiếm.
Để tìm các phần phụ thuộc ngược của mục tiêu cheese trong phạm vi của toàn bộ dự án "//…", hãy chạy lệnh:
bazel query "rdeps(universe_scope, target)"ex) bazel query "rdeps(//... , //src/main/java/com/example/ingredients:cheese)"
//:runner
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
Kết quả trả về của truy vấn cho thấy cả pizza và macAndCheese đều dựa vào phô mai. Thật bất ngờ!
Tìm mục tiêu dựa trên thẻ
Hai khách hàng bước vào Bazel Cafe: Amir và Jenny. Không có thông tin nào về họ ngoài tên. May mắn là họ đã gắn thẻ đơn đặt hàng trong tệp 'customers' BUILD. Làm cách nào để bạn truy cập vào thẻ này?
Nhà phát triển có thể gắn thẻ các mục tiêu Bazel bằng nhiều mã nhận dạng, thường là cho mục đích kiểm thử. Ví dụ: thẻ trên các bài kiểm thử có thể chú thích vai trò của bài kiểm thử trong quy trình gỡ lỗi và phát hành, đặc biệt là đối với các bài kiểm thử C++ và Python, vốn thiếu khả năng chú thích thời gian chạy. Việc sử dụng thẻ và phần tử kích thước giúp bạn linh hoạt trong việc tập hợp các bộ kiểm thử dựa trên chính sách kiểm tra của cơ sở mã.
Trong ví dụ này, các thẻ là một trong pizza hoặc macAndCheese để biểu thị các món trong thực đơn. Lệnh này truy vấn các mục tiêu có thẻ khớp với mã nhận dạng của bạn trong một gói nhất định.
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
Truy vấn này trả về tất cả các mục tiêu trong gói "customers" có thẻ "pizza".
Tự kiểm tra
Sử dụng truy vấn này để tìm hiểu xem Jenny muốn đặt món gì.
Trả lời
Nui phô mai
Thêm phần phụ thuộc mới
Cafe Bazel đã mở rộng thực đơn – giờ đây, khách hàng có thể đặt món Sinh tố! Món sinh tố cụ thể này bao gồm các nguyên liệu Strawberry và Banana.
Trước tiên, hãy thêm các nguyên liệu mà món sinh tố phụ thuộc vào: Strawberry.java và Banana.java. Thêm các lớp Java trống.
src/main/java/com/example/ingredients/Strawberry.java
package com.example.ingredients;
public class Strawberry {
}
src/main/java/com/example/ingredients/Banana.java
package com.example.ingredients;
public class Banana {
}
Tiếp theo, hãy thêm Smoothie.java vào thư mục thích hợp: dishes.
src/main/java/com/example/dishes/Smoothie.java
package com.example.dishes;
public class Smoothie {
public static final String DISH_NAME = "Smoothie";
public static final String DESCRIPTION = "Yummy and Refreshing";
}
Cuối cùng, hãy thêm các tệp này làm quy tắc trong các tệp BUILD thích hợp. Tạo một thư viện java mới cho mỗi nguyên liệu mới, bao gồm tên, khả năng hiển thị công khai và tệp "src" mới tạo. Bạn sẽ kết thúc bằng tệp BUILD đã cập nhật này:
src/main/java/com/example/ingredients/BUILD
java_library(
name = "cheese",
visibility = ["//visibility:public"],
srcs = ["Cheese.java"],
)
java_library(
name = "dough",
visibility = ["//visibility:public"],
srcs = ["Dough.java"],
)
java_library(
name = "macaroni",
visibility = ["//visibility:public"],
srcs = ["Macaroni.java"],
)
java_library(
name = "tomato",
visibility = ["//visibility:public"],
srcs = ["Tomato.java"],
)
java_library(
name = "strawberry",
visibility = ["//visibility:public"],
srcs = ["Strawberry.java"],
)
java_library(
name = "banana",
visibility = ["//visibility:public"],
srcs = ["Banana.java"],
)
Trong tệp BUILD cho các món ăn, bạn muốn thêm một quy tắc mới cho Smoothie. Việc này bao gồm tệp Java được tạo cho Smoothie dưới dạng tệp "src" và các quy tắc mới mà bạn đã tạo cho từng nguyên liệu của món sinh tố.
src/main/java/com/example/dishes/BUILD
java_library(
name = "macAndCheese",
visibility = ["//visibility:public"],
srcs = ["MacAndCheese.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:macaroni",
],
)
java_library(
name = "pizza",
visibility = ["//visibility:public"],
srcs = ["Pizza.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:dough",
"//src/main/java/com/example/ingredients:tomato",
],
)
java_library(
name = "smoothie",
visibility = ["//visibility:public"],
srcs = ["Smoothie.java"],
deps = [
"//src/main/java/com/example/ingredients:strawberry",
"//src/main/java/com/example/ingredients:banana",
],
)
Cuối cùng, bạn muốn đưa món sinh tố vào làm phần phụ thuộc trong tệp BUILD của Đầu bếp.
src/main/java/com/example/restaurant/BUILD
java\_library(
name = "chef",
visibility = ["//visibility:public"],
srcs = [
"Chef.java",
],
deps = [
"//src/main/java/com/example/dishes:macAndCheese",
"//src/main/java/com/example/dishes:pizza",
"//src/main/java/com/example/dishes:smoothie",
],
)
java\_library(
name = "cafe",
visibility = ["//visibility:public"],
srcs = [
"Cafe.java",
],
deps = [
":chef",
],
)
Tạo lại bản dựng cafe để xác nhận rằng không có lỗi. Nếu bản dựng thành công, xin chúc mừng! Bạn đã thêm một phần phụ thuộc mới cho "Cafe". Nếu không, hãy tìm lỗi chính tả và tên gói. Để biết thêm thông tin về cách viết BUILD tệp, hãy xem Hướng dẫn về kiểu BUILD.
Bây giờ, hãy hình dung biểu đồ phần phụ thuộc mới khi thêm Smoothie để so sánh với biểu đồ trước đó. Để rõ ràng hơn, hãy đặt tên cho đầu vào biểu đồ là graph2.in và graph2.png.
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.indot -Tpng < graph2.in > graph2.pngNhìn vào graph2.png, bạn có thể thấy rằng Smoothie không có phần phụ thuộc nào được chia sẻ với các món ăn khác mà chỉ là một mục tiêu khác mà Chef dựa vào.
somepath() và allpaths()
Điều gì xảy ra nếu bạn muốn truy vấn lý do một gói phụ thuộc vào một gói khác? Việc hiển thị đường dẫn phần phụ thuộc giữa hai gói sẽ cung cấp câu trả lời.
Hai hàm có thể giúp bạn tìm đường dẫn phần phụ thuộc: somepath() và allpaths(). Cho trước mục tiêu bắt đầu S và điểm kết thúc E, hãy tìm đường dẫn giữa S và E bằng cách sử dụng somepath(S,E).
Khám phá sự khác biệt giữa hai hàm này bằng cách xem mối quan hệ giữa các mục tiêu "Đầu bếp" và "Phô mai". Có nhiều đường dẫn có thể để chuyển từ một mục tiêu sang mục tiêu khác:
- Đầu bếp → Nui phô mai → Phô mai
- Đầu bếp → Pizza → Phô mai
somepath() cung cấp cho bạn một đường dẫn duy nhất trong hai lựa chọn, trong khi 'allpaths()' xuất mọi đường dẫn có thể.
Sử dụng Cafe Bazel làm ví dụ, hãy chạy lệnh sau:
bazel query "somepath(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/ingredients:cheese
Kết quả đầu ra tuân theo đường dẫn đầu tiên của Cafe → Đầu bếp → Nui phô mai → Phô mai. Nếu thay vào đó, bạn sử dụng allpaths(), bạn sẽ nhận được:
bazel query "allpaths(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef

Kết quả đầu ra của allpaths() khó đọc hơn một chút vì đây là danh sách các phần phụ thuộc được làm phẳng. Việc hình dung biểu đồ này bằng Graphviz giúp mối quan hệ trở nên rõ ràng hơn để hiểu.
Tự kiểm tra
Một trong những khách hàng của Cafe Bazel đã đưa ra bài đánh giá đầu tiên về nhà hàng! Rất tiếc, bài đánh giá này thiếu một số thông tin chi tiết, chẳng hạn như danh tính của người đánh giá và món ăn mà bài đánh giá đang tham chiếu. May mắn là bạn có thể truy cập vào thông tin này bằng Bazel. Gói reviews chứa một chương trình in bài đánh giá của một khách hàng bí ẩn. Tạo bản dựng và chạy chương trình này bằng:
bazel build //src/main/java/com/example/reviews:reviewbazel-bin/src/main/java/com/example/reviews/reviewChỉ dựa vào các truy vấn Bazel, hãy cố gắng tìm hiểu xem ai đã viết bài đánh giá và họ đang mô tả món ăn nào.
Gợi ý
Kiểm tra các thẻ và phần phụ thuộc để biết thông tin hữu ích.
Trả lời
Bài đánh giá này mô tả món Pizza và Amir là người đánh giá. Nếu bạn xem xét những phần phụ thuộc mà quy tắc này có bằng cách sử dụng
bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'
Kết quả của lệnh này cho thấy Amir là người đánh giá!
Tiếp theo, vì bạn biết người đánh giá là Amir, nên bạn có thể sử dụng hàm truy vấn để tìm thẻ mà Amir có trong tệp `BUILD` để xem món ăn nào có ở đó.
Lệnh bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' cho thấy Amir là khách hàng duy nhất đã đặt pizza và là người đánh giá, điều này cung cấp cho chúng ta câu trả lời.
Kết thúc
Xin chúc mừng! Bây giờ, bạn đã chạy một số truy vấn cơ bản mà bạn có thể thử trên các dự án của riêng mình. Để tìm hiểu thêm về cú pháp ngôn ngữ truy vấn, hãy tham khảo trang Tài liệu tham khảo về truy vấn. Bạn muốn có các truy vấn nâng cao hơn? Hướng dẫn Truy vấn giới thiệu danh sách chi tiết về nhiều trường hợp sử dụng hơn so với những trường hợp được đề cập trong hướng dẫn này.
