Hướng dẫn này trình bày cách làm việc với Bazel để theo dõi các phần phụ thuộc trong mã của bạn bằng cách sử dụ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 tài liệu hướng dẫn Tài liệu tham khảo về truy vấn Bazel và Tài liệu tham khảo về cquery Bazel. Yêu cầu trợ giúp trong IDE bằng cách nhập bazel help query
hoặc bazel help cquery
vào dòng lệnh.
Mục tiêu
Hướng dẫn này sẽ trình bày một tập hợp các truy vấn cơ bản mà bạn có thể dùng để tìm hiểu thêm về các phần phụ thuộc tệp của dự án. Ứng dụng này dành cho các nhà phát triển mới ở Bazel có kiến thức cơ bản về cách hoạt động của tệp Bazel và 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 cả Git.
Để trực quan hoá các biểu đồ phần phụ thuộc, công cụ có tên là Graphviz được sử dụng. Bạn có thể tải xuống công cụ này để theo dõi.
Nhận dự án mẫu
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.git
Dự án mẫu cho hướng dẫn này nằm trong thư mục examples/query-quickstart
.
Bắt đầu
Cụm từ tìm kiếm về Bazel là gì?
Các 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 để có thêm lựa chọn, hãy xem hướng dẫn về 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 có quy mô lớn mà không cần điều hướng theo cách thủ công thông qua các tệp BUILD
.
Để chạy truy vấn, hãy mở thiết bị đầu cuối dòng lệnh rồi nhập:
bazel query 'query_function'
Trường hợp
Hãy tưởng tượng một tình huống đ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 chuyên bán pizza, Mac và phô mai. Hãy xem phần dưới đây để biết cấu trúc của dự án:
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
└── WORKSPACE
Trong suốt hướng dẫn này, trừ phi có hướng dẫn khác, đừng tìm kiếm trong các tệp BUILD
để tìm thông tin bạn cần và chỉ 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ê. Các tài sản này được tách thành: restaurant
, ingredients
, dishes
, customers
và reviews
. Quy tắc trong các gói này xác định các thành phần khác nhau của Cafe 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 dự án bằng Bazel bằng lệnh
bazel build
và dùng :
để ra 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
mục tiêu tham chiếu.
Để tạo dự án này, hãy dán lệnh sau vào một thiết bị đầu cuối:
bazel build :runner
Kết quả của bạn 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 ứng dụng thành công, hãy chạy ứng dụng bằng cách dán lệnh sau:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Thao tác này sẽ cung cấp cho bạn một danh sách các món trong trình đơn được cung cấp cùng với một đoạn mô tả ngắn.
Khám phá các mục tiêu
Dự án này liệt kê các nguyên liệu và món ăn trong gói riêng. Nếu muốn dùng truy vấn để 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ể dùng biểu đồ này để xem qua 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, kết quả 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
Người chạy của bạn dựa vào 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 thúc đẩy hệ thống tệp (có thể không dùng được cho các dự án lớn). Cafe Bazel sử dụng quy tắc nào?
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ể.
Trực quan hoá 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ể trực quan hoá 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 đồ không chu trình có hướng trái ngược với danh sách đã làm phẳng. Bạn có thể thay đổi cách hiển thị của biểu đồ truy vấn Bazel bằng cách sử dụng nhiều tuỳ chọn dòng lệnh --output
. Xem phần Định dạng đầu ra để biết các lựa chọn.
Hãy bắt đầu bằng cách chạy truy vấn bạn muốn và thêm cờ --noimplicit_deps
để xoá những phần phụ thuộc thừa của công cụ. Sau đó, theo dõi truy vấn có cờ đầu ra và lưu trữ biểu đồ vào một tệp có tên là graph.in
để tạo nội dung biểu diễn bằng văn bản của biểu đồ.
Cách tìm kiếm tất cả phần phụ thuộc của :runner
mục tiêu và định dạng đầu ra dưới dạng biểu đồ:
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in
Thao tác này sẽ tạo một tệp có tên graph.in
, đại diện bằng 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 tệp png:
dot -Tpng < graph.in > graph.png
Nếu mở graph.png
, bạn sẽ thấy giao diện như sau. Biểu đồ bên dưới đã được đơn giản hoá để làm rõ hơn các chi tiết về lộ trình thiết yếu trong hướng dẫn này.
Điều này giúp ích khi bạn muốn xem kết quả 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
Thay vào đó, nếu bạn có mục tiêu và muốn phân tích các mục tiêu khác sử dụng mục tiêu đó, bạn có thể sử dụng truy vấn để kiểm tra xem mục tiêu nào 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". rdeps()
có thể hữu ích khi bạn chỉnh sửa tệp trong cơ sở mã mà bạn không quen thuộc, đồng thời có thể giúp bạn không vô tình làm hỏng các tệp khác phụ thuộc vào cơ sở mã đó.
Ví dụ: bạn muốn chỉnh sửa một số điểm đối với nguyên liệu cheese
. Để tránh gây ra vấn đề cho Cafe Bazel, bạn cần kiểm tra xem những món ăn nào sử dụng cheese
.
Để xem mục tiêu nào 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()
có ít nhất 2 đối số: một universe_scope
(thư mục có liên quan) và một 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ị cố định số nguyên chỉ định giới hạn trên về độ sâu của nội dung tìm kiếm.
Để tìm các phần phụ thuộc ngược của cheese
mục tiêu 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
Cụm từ tìm kiếm trả về cho thấy cả pizza và macAndCheese sử dụng 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. Chẳng biết gì về họ ngoài tên của họ. May mắn thay, đơn đặt hàng của họ đã được gắn thẻ trong mục 'khách hàng' Tệp BUILD
. Làm cách nào để truy cập vào thẻ này?
Nhà phát triển có thể gắn thẻ mục tiêu Bazel bằng nhiều giá trị nhận dạng, thường là cho mục đích thử nghiệm. Ví dụ: thẻ trên các chương trình kiểm thử có thể chú thích vai trò của chương trình kiểm thử trong quy trình gỡ lỗi và phát hành, đặc biệt là đối với các chương trình kiểm thử C++ và Python. Các thẻ này không có khả năng chú thích trong thời gian chạy. Khi sử dụng thẻ và phần tử kích thước, bạn có thể linh hoạt tập hợp các bộ kiểm thử dựa trên chính sách đăng ký của cơ sở mã.
Trong ví dụ này, các thẻ là một trong hai thẻ pizza
hoặc macAndCheese
để đại diện cho các mục trong trình đơn. Lệnh này truy vấn các mục tiêu có thẻ khớp với giá trị 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ả mục tiêu trong thuộc tính 'khách hàng' gói hàng có thẻ "pizza".
Tự kiểm tra
Hãy sử dụng cụm từ tìm kiếm này để biết Jenny muốn gọi món gì.
Trả lời
Mac và phô mai
Thêm phần phụ thuộc mới
Cafe Bazel đã mở rộng thực đơn của mình — giờ đây khách hàng có thể gọi sinh tố! Món sinh tố đặc trưng này làm từ các nguyên liệu Strawberry
và Banana
.
Trước tiên, hãy thêm các nguyên liệu mà ly sinh tố cần có: 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 thành phần mới, bao gồm cả tên, chế độ hiển thị công khai và "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 món ăn, bạn muốn thêm một quy tắc mới cho Smoothie
. Thao tác này sẽ bao gồm tệp Java được tạo cho Smoothie
dưới dạng "src" và các quy tắc mới mà bạn đã đặt 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 nên thêm sinh tố dưới dạng phần phụ thuộc vào tệp BUILD
của Chef.
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 cafe
để xác nhận rằng không có lỗi. Nếu bản dựng được tạo thành công thì 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 kiểm tra lỗi chính tả và cách đặt tên gói. Để biết thêm thông tin về cách ghi tệp BUILD
, hãy xem XÂY DỰNG Hướng dẫn về quy tắc lập trình.
Bây giờ, hãy trực quan hoá biểu đồ phần phụ thuộc mới với việc thêm Smoothie
để so sánh với biểu đồ trước đó. Để cho rõ ràng, hãy đặt tên cho đầu vào trong biểu đồ là graph2.in
và graph2.png
.
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png
Nhìn vào graph2.png
, bạn có thể thấy Smoothie
không có phần phụ thuộc dùng chung 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()
Nếu bạn muốn truy vấn lý do tại sao một gói này lại phụ thuộc vào một gói khác thì sao? Việc hiển thị đường dẫn phần phụ thuộc giữa hai lớp sẽ cung cấp câu trả lời.
2 hàm có thể giúp bạn tìm thấy đường dẫn phần phụ thuộc: somepath()
và allpaths()
. Giả sử mục tiêu bắt đầu S và điểm cuối E, hãy tìm đường đi 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 chức năng này bằng cách xem xét mối quan hệ giữa "Chef" và "Cheese" mục tiêu. Có nhiều cách để đi từ mục tiêu này sang mục tiêu khác:
- Đầu bếp → MacAndCheese → Phô mai
- Đầu bếp → Pizza → Phô mai
somepath()
cung cấp cho bạn một đường dẫn trong số hai tuỳ chọn, trong khi đó "allpaths()" cho ra mọi đường dẫn khả thi.
Lấy Cafe Bazel làm ví dụ, chạy đoạn mã 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
Dữ liệu đầu ra đi theo đường dẫn đầu tiên là Cafe → Chef → MacAndCheese → phô mai. Thay vào đó, nếu 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ả 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 trực quan hoá biểu đồ này bằng Graphviz giúp cho mối quan hệ này trở nên rõ ràng hơn.
Tự kiểm tra
Một trong những khách hàng của Cafe Bazel đã đánh giá nhà hàng đầu tiên! Rất tiếc, bài đánh giá còn thiếu một số thông tin như danh tính của người đánh giá và món ăn mà người đánh giá đề cập. Thật may là bạn có thể truy cập vào thông tin này nhờ 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 và chạy công cụ này bằng:
bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review
Chỉ dừng các cụm từ tìm kiếm về Bazel, 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 ý
Hãy 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ả về nhà hàng Pizza và Amir là người đánh giá. Nếu bạn xem những phần phụ thuộc mà quy tắc này đã 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, bạn có thể sử dụng hàm truy vấn để tìm kiếm Amir có thẻ nào trong tệp "BUILD" để xem món ăn nào đang ở đó.
Lệnh bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
cho ra kết quả rằng Amir là khách hàng duy nhất đã đặt pizza và là người đánh giá đưa ra câu trả lời cho chúng ta.
Kết thúc
Xin chúc mừng! Bạn hiện đã chạy một số truy vấn cơ bản và có thể dùng thử trong 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 tham khảo về truy vấn. Bạn muốn truy vấn nâng cao hơn? Hướng dẫn truy vấn trình bày một danh sách chi tiết về nhiều trường hợp sử dụng hơn so với được đề cập trong hướng dẫn này.