Bắt đầu truy vấn nhanh

Phần 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ã 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 sử dụng Tài liệu tham khảo về truy vấn BazelTài liệu tham khảo về truy vấn 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. Đây là tài liệu dành cho những nhà phát triển mới sử dụng 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 Git.

Để trực quan hoá các biểu đồ phần phụ thuộc, bạn có thể sử dụng công cụ có tên là Graphviz. Bạn có thể tải công cụ này xuống để theo dõi.

Tải dự án mẫu

Tiếp theo, hãy truy xuất ứng dụng mẫu từ Kho lưu trữ mẫu 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ả để 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 tuỳ chọn, hãy xem hướng dẫn về truy vấn. Các truy vấn giúp bạn tìm hiểu về các phần phụ thuộc trong dự án có quy mô lớn mà không cần điều hướng qua các tệp BUILD theo cách thủ công.

Để chạy truy vấn, hãy mở dòng lệnh và nhập:

bazel query 'query_function'

Trường hợp

Hãy tưởng tượng một tình huống đào sâu vào mối quan hệ giữa Cafe Bazel và đầu bếp tương ứng. Tiệm cà phê này độc quyền bán pizza, Mac và 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
└── WORKSPACE

Trong suốt hướng dẫn này, trừ phi có hướng dẫn khác, cố gắng không tìm trong các tệp BUILD để tìm thông tin bạn cần và chỉ sử dụng hàm truy vấn.

Một dự án bao gồm các gói khác nhau tạo nên một Quán cà phê. Các tài sản này được phân tách thành: restaurant, ingredients, dishes, customersreviews. Quy tắc trong những gói này xác định các thành phần khác nhau của Cafe bằng các thẻ và phần phụ thuộc khác nhau.

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 ra trình đơn của Cafe. Tạo dự án bằng Bazel với lệnh bazel build và dùng : để báo hiệu rằng mục tiêu có tên là runner. Hãy 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 dự án này, hãy dán lệnh sau vào một cửa sổ dòng lệnh:

bazel build :runner

Kết quả đầu ra của bạn sẽ có dạng như sau nếu quá trình tạo 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 xong ứng dụ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ẽ cho bạn thấy danh sách các món trong trình đơn cùng với một đoạn mô tả ngắn.

Khám phá mục tiêu

Dự án này liệt kê các nguyên liệu và món ăn theo cách 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ể dùng mục này để xem nguyên liệu và món ăn trên quán Cafe này 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 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 kích hoạt hệ thống tệp (có thể không hoạt động đối với các dự án lớn). Cafe Bazel sử dụng quy tắc nào?

Nếu 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 dùng hàm truy vấn deps() để xem từng phần phụ thuộc đầu ra 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 xem đường dẫn dưới dạng hình ảnh đồ thị tuần hoàn có định hướng trái ngược với danh sách đã làm phẳng. Bạn có thể thay đổi giao diện của biểu đồ truy vấn Bazel bằng cách sử dụng các tuỳ chọn dòng lệnh --output. Xem Định dạng đầu ra để biết các tuỳ chọn.

Bắt đầu bằng cách chạy truy vấn mà bạn muốn và thêm cờ --noimplicit_deps để xoá quá nhiều phần phụ thuộc của công cụ. Sau đó, theo truy vấn có cờ đầu ra và lưu trữ biểu đồ vào tệp có tên graph.in để tạo bản trình bày văn bản của biểu đồ.

Cách tìm tất cả phần phụ thuộc của :runner mục tiêu và định dạng kết quả 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 là graph.in, đại diện cho 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á để giúp cho thông tin chi tiết về đường dẫn quan trọng trở nên rõ ràng hơn trong hướng dẫn này.

Sơ đồ thể hiện mối quan hệ từ quán cà phê, đầu bếp đến các món ăn: pizza, Mac và phô mai. Những thành phần này phân chia thành các thành phần riêng biệt: phô mai, cà chua, bột nhào và mì ống.

Điều này rất hữu í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

Nếu bạn có một mục tiêu muốn phân tích xem 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. Quá trình nà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 chưa quen dùng, đồng thời giúp bạn tránh 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 chút đố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 dựa vào cheese.

Để xem những 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ố: 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 một đối số thứ ba không bắt buộc: một giá trị cố định dạng số nguyên xác định giới hạn trên trong nội dung tìm kiếm.

Để tìm các phần phụ thuộc đảo 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

Kết quả trả về cho thấy rằng cả pizza và macAndCheese đều 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. Không có bất cứ điều gì về chúng ngoài tên của chúng. May mắn thay, các đơn đặt hàng của họ đã được gắn thẻ trong tệp BUILD của "khách hàng". Làm cách nào để bạn có thể truy cập vào thẻ này?

Các nhà phát triển có thể gắn thẻ các 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ụ: các 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 chương trình kiểm thử này thiếu khả năng chú thích trong thời gian chạy. Việc sử dụng thẻ và phần tử kích thước mang lại sự linh hoạt trong việc 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, thẻ là một trong 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ả các mục tiêu trong gói "khách hàng" có thẻ "pizza".

Tự kiểm tra

Hãy dùng cụm từ tìm kiếm này để tìm hiểu xem Jenny muốn gọi món gì.

Câu trả lời

Bánh mì và 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 Sinh tố! Loại sinh tố cụ thể này bao gồm các thành phần StrawberryBanana.

Trước tiên, hãy thêm các nguyên liệu tuỳ thuộc vào loại sinh tố: Strawberry.javaBanana.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 cả tên, chế độ hiển thị công khai và tệp "src" mới được tạo. Bạn sẽ kết thúc với tệp BUILD đã cập nhật sau:

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 cần thêm quy tắc mới cho Smoothie. Thao tá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 ra cho từng nguyên liệu làm 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 đưa sinh tố vào tệp phụ thuộc trong 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 kênh được tạo thành công thì xin chúc mừng! Bạn đã thêm phần phụ thuộc mới cho "Cafe". Nếu không, hãy chú ý đến lỗi chính tả và cách đặt tên gói. Để biết thêm thông tin về cách viết tệp BUILD, hãy xem Hướng dẫn xây dựng kiểu.

Bây giờ, hãy trực quan hoá biểu đồ phần phụ thuộc mới có thêm Smoothie để so sánh với biểu đồ trước đó. Để cho rõ ràng, hãy đặt tên cho dữ liệu đầu vào của biểu đồ là graph2.ingraph2.png.

bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png

Biểu đồ tương tự như biểu đồ đầu tiên, ngoại trừ việc có lời nói bắt nguồn từ mục tiêu đầu bếp với sinh tố dẫn đến chuối và dâu tây

Nhìn vào graph2.png, bạn có thể thấy Smoothie không có phần phụ thuộc 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 một gó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 loại này sẽ cho ra 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()allpaths(). Cho sẵn 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 các mục tiêu "Chef" và "Cheese". Có nhiều đường dẫn để chuyển 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 duy nhất trong số 2 tuỳ chọn, trong khi "allpaths()" sẽ cho ra mọi đường dẫn khả thi.

Dùng Cafe Bazel làm ví dụ, 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ả xuất ra tuân theo đường dẫn đầu tiên là Cafe → Chef → MacAndCheese → Phô mai. 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

Đầu ra đường dẫn từ quán cà phê đến đầu bếp, pizza, Mac và phô mai đến phô mai

Kết quả đầu ra của allpaths() khó đọc hơn một chút vì đó là danh sách phần phụ thuộc đã làm phẳng. Việc trực quan hoá biểu đồ này bằng Graphviz giúp mối quan hệ 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 đã đưa ra bài đánh giá đầu tiên về nhà hàng này! Rất tiếc, bài đánh giá thiếu một số thông tin chi tiết như danh tính của người đánh giá và món ăn mà bài đánh giá đề cập. Thật may là bạn có thể truy cập thông tin này cùng với 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 bằng:

bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review

Nếu bạn chỉ truy vấn các cụm từ tìm kiếm của Bazel, hãy cố gắng tìm hiểu xem ai đã viết bài đánh giá và tìm hiểu xem họ đang mô tả về 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.

Câu trả lời

Bài đánh giá này mô tả về pizza và Amir là người đánh giá. Nếu bạn xem các 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 xem xét! 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 xem Amir có thẻ nào trong tệp "BUILD" để xem món ăn nào ở đó. 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à cũng chính là người đánh giá đã đưa ra câu trả lời.

Kết thúc

Xin chúc mừng! Bạn hiện đã chạy một số truy vấn cơ bản mà bạn có thể 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 truy vấn. Bạn muốn các truy vấn nâng cao hơn? Hướng dẫn truy vấn cung cấp danh sách chi tiết về nhiều trường hợp sử dụng hơn được đề cập trong hướng dẫn này.