쿼리 빠른 시작

이 튜토리얼에서는 Bazel을 사용하여 미리 만든 Bazel 프로젝트를 사용하여 코드의 종속 항목을 추적하는 방법을 설명합니다.

언어 및 --output 플래그 세부정보는 Bazel 쿼리 참조Bazel cquery 참조 매뉴얼을 참고하세요. 명령줄에 bazel help query 또는 bazel help cquery를 입력하여 IDE에서 도움말을 확인하세요.

목표

이 가이드에서는 프로젝트의 파일 종속 항목을 자세히 알아보는 데 사용할 수 있는 일련의 기본 쿼리를 설명합니다. 이 가이드는 Bazel 및 BUILD 파일의 작동 방식에 관한 기본 지식을 갖춘 신규 Bazel 개발자를 대상으로 합니다.

기본 요건

아직 Bazel을 설치하지 않았다면 먼저 설치합니다. 이 튜토리얼에서는 소스 제어에 Git을 사용하므로 최상의 결과를 얻으려면 Git도 설치하세요.

종속 항목 그래프를 시각화하기 위해 Graphviz라는 도구가 사용됩니다. 이 도구는 다운로드하여 따라할 수 있습니다.

샘플 프로젝트 가져오기

그런 다음 원하는 명령줄 도구에서 다음을 실행하여 Bazel의 예시 저장소에서 샘플 앱을 가져옵니다.

git clone https://github.com/bazelbuild/examples.git

이 가이드의 샘플 프로젝트는 examples/query-quickstart 디렉터리에 있습니다.

시작하기

Bazel 쿼리란 무엇인가요?

쿼리를 사용하면 BUILD 파일 간의 관계를 분석하고 결과 출력에서 유용한 정보를 확인하여 Bazel 코드베이스에 대해 알아볼 수 있습니다. 이 가이드에서는 몇 가지 기본 쿼리 함수를 미리 보여줍니다. 자세한 옵션은 쿼리 가이드를 참고하세요. 쿼리를 사용하면 BUILD 파일을 수동으로 탐색하지 않고도 대규모 프로젝트의 종속 항목에 관해 알아볼 수 있습니다.

쿼리를 실행하려면 명령줄 터미널을 열고 다음을 입력합니다.

bazel query 'query_function'

시나리오

카페 Bazel과 각 셰프 간의 관계를 자세히 살펴보는 시나리오를 생각해 보세요. 이 카페는 피자와 맥 앤 치즈만 판매합니다. 아래에서 프로젝트의 구조를 살펴보세요.

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

이 튜토리얼에서는 달리 지시되지 않는 한 BUILD 파일에서 필요한 정보를 찾지 말고 대신 쿼리 함수만 사용하세요.

프로젝트는 카페를 구성하는 여러 패키지로 구성됩니다. restaurant, ingredients, dishes, customers, reviews로 구분됩니다. 이러한 패키지 내의 규칙은 다양한 태그와 종속 항목으로 카페의 다양한 구성요소를 정의합니다.

빌드 실행

이 프로젝트에는 카페의 메뉴를 출력하기 위해 실행할 수 있는 Runner.java의 기본 메서드가 포함되어 있습니다. bazel build 명령어와 함께 Bazel을 사용하여 프로젝트를 빌드하고 :를 사용하여 대상 이름이 runner임을 알립니다. 타겟을 참조하는 방법은 타겟 이름을 참고하세요.

이 프로젝트를 빌드하려면 터미널에 다음 명령어를 붙여넣습니다.

bazel build :runner

빌드가 성공하면 다음과 같은 출력이 표시됩니다.

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

성공적으로 빌드되면 다음 명령어를 붙여넣어 애플리케이션을 실행합니다.

bazel-bin/runner
--------------------- MENU -------------------------

Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner

----------------------------------------------------

그러면 간단한 설명과 함께 제공된 메뉴 항목 목록이 표시됩니다.

타겟 탐색

프로젝트는 재료와 요리를 자체 패키지로 나열합니다. 쿼리를 사용하여 패키지의 규칙을 보려면 bazel query package/… 명령어를 실행합니다.

이 경우 다음을 실행하여 이 카페의 재료와 요리를 살펴볼 수 있습니다.

bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/ingredients/...

구성요소 패키지의 타겟을 쿼리하면 다음과 같은 출력이 표시됩니다.

//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

종속 항목 찾기

러너가 실행하는 데 사용하는 타겟은 무엇인가요?

파일 시스템으로 이동하지 않고 프로젝트 구조를 더 자세히 알고 싶다고 가정해 보겠습니다 (대규모 프로젝트에서는 불가능할 수 있음). Cafe Bazel에서는 어떤 규칙을 사용하나요?

이 예와 같이 런너의 대상이 runner인 경우 다음 명령어를 실행하여 대상의 기본 종속 항목을 찾습니다.

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

대부분의 경우 쿼리 함수 deps()를 사용하여 특정 타겟의 개별 출력 종속 항목을 확인합니다.

종속 항목 그래프 시각화(선택사항)

이 섹션에서는 특정 쿼리의 종속 항목 경로를 시각화하는 방법을 설명합니다. Graphviz를 사용하면 경로를 평면화된 목록이 아닌 방향성 비순환 그래프 이미지로 볼 수 있습니다. 다양한 --output 명령줄 옵션을 사용하여 Bazel 쿼리 그래프의 표시를 변경할 수 있습니다. 옵션은 출력 형식을 참조하세요.

먼저 원하는 쿼리를 실행하고 --noimplicit_deps 플래그를 추가하여 불필요한 도구 종속 항목을 삭제합니다. 그런 다음 출력 플래그가 있는 쿼리를 따르고 graph.in 파일에 그래프를 저장하여 그래프의 텍스트 표현을 만듭니다.

타겟 :runner의 모든 종속 항목을 검색하고 출력 형식을 그래프로 지정하려면 다음 안내를 따르세요.

bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in

그러면 빌드 그래프의 텍스트 표현인 graph.in라는 파일이 생성됩니다. Graphviz는 텍스트를 시각화로 처리하는 도구인 dot 를 사용하여 png를 만듭니다.

dot -Tpng < graph.in > graph.png

graph.png를 열면 다음과 같이 표시됩니다. 이 가이드에서 필수 경로 세부정보를 보다 명확히 하기 위해 아래 그래프가 간소화되었습니다.

카페에서 셰프, 요리에 대한 관계를 보여주는 다이어그램(피자, 맥 앤 치즈, 즉 치즈, 토마토, 도우, 마카로니)는 각각 다른 재료로 나뉩니다.

이렇게 하면 이 가이드 전체에서 다양한 쿼리 함수의 출력을 확인할 때 도움이 됩니다.

역 종속 항목 찾기

대신 다른 대상이 사용하는 대상을 분석하려는 경우 쿼리를 사용하여 특정 규칙에 종속되는 대상을 검사할 수 있습니다. 이를 '역 종속 항목'이라고 합니다. rdeps()를 사용하면 익숙하지 않은 코드베이스의 파일을 수정할 때 유용하며, 이 파일에 종속된 다른 파일을 모르는 사이에 손상시키지 않을 수 있습니다.

예를 들어 cheese 구성요소를 수정하려고 합니다. Cafe Bazel에 문제가 발생하지 않도록 하려면 cheese를 사용하는 요리를 확인해야 합니다.

특정 타겟/패키지에 종속되는 타겟을 확인하려면 rdeps(universe_scope, target)를 사용하면 됩니다. rdeps() 쿼리 함수는 universe_scope(관련 디렉터리)와 target라는 인수를 두 개 이상 사용합니다. Bazel은 제공된 universe_scope 내에서 대상의 역 종속 항목을 검색합니다. rdeps() 연산자는 세 번째 인수(선택사항)를 허용합니다. 이 인수는 검색 심도의 상한값을 지정하는 정수 리터럴입니다.

전체 프로젝트 ‘//...’ 범위 내에서 타겟 cheese의 역 종속 항목을 찾으려면 다음 명령어를 실행하세요.

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

쿼리 반환 결과에 따르면 피자와 치즈 맥앤치즈 모두 치즈를 사용합니다. 깜짝 놀랐습니다.

태그를 기반으로 타겟 찾기

아미르와 제니라는 두 고객이 Bazel 카페에 들어옵니다. 이름 외에는 알려진 것이 없습니다. 다행히 '고객' BUILD 파일에 주문 태그가 지정되어 있습니다. 이 태그에 액세스하려면 어떻게 해야 하나요?

개발자는 종종 테스트 목적으로 Bazel 타겟에 서로 다른 식별자로 태그를 지정할 수 있습니다. 예를 들어, 테스트 태그는 디버그 및 출시 프로세스에서 테스트의 역할에 주석을 달 수 있습니다. 특히 런타임 주석 기능이 없는 C++ 및 Python 테스트의 경우 더욱 그렇습니다. 태그와 크기 요소를 사용하면 코드베이스의 체크인 정책에 따라 테스트 모음을 유연하게 조합할 수 있습니다.

이 예에서 태그는 메뉴 항목을 나타내는 pizza 또는 macAndCheese 중 하나입니다. 이 명령어는 특정 패키지 내에서 식별자와 일치하는 태그가 있는 타겟을 쿼리합니다.

bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'

이 쿼리는 'pizza' 태그가 있는 'customers' 패키지의 모든 타겟을 반환합니다.

자가 진단

이 쿼리를 사용하여 제니가 주문하려는 제품을 알아봅니다.

답변

마카로니 앤 치즈

새 종속 항목 추가

카페 Bazel의 메뉴가 확장되었습니다. 이제 고객이 스무디를 주문할 수 있습니다. 이 스무디는 StrawberryBanana 재료로 구성됩니다.

먼저 스무디에 종속되는 재료인 Strawberry.javaBanana.java를 추가합니다. 빈 Java 클래스를 추가합니다.

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 {

}

그런 다음 Smoothie.java를 적절한 디렉터리(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";
}

마지막으로 이러한 파일을 적절한 BUILD 파일에 규칙으로 추가합니다. 이름, 공개 상태, 새로 생성된 'src' 파일을 포함하여 새 구성요소마다 새 Java 라이브러리를 만듭니다. 다음과 같이 업데이트된 BUILD 파일을 완성해야 합니다.

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"],
)

요리의 BUILD 파일에서 Smoothie의 새 규칙을 추가하려고 합니다. 여기에는 Smoothie용으로 생성된 Java 파일이 'src' 파일로 포함되며, 스무디의 각 재료에 설정한 새로운 규칙이 포함됩니다.

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",
    ],
)

마지막으로 Chef의 BUILD 파일에 스무디를 종속 항목으로 포함합니다.

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",
    ],
)

cafe를 다시 빌드하여 오류가 없는지 확인합니다. 성공적으로 빌드되면 축하드립니다! 'Cafe'의 새로운 종속 항목을 추가했습니다. 그렇지 않은 경우 철자 실수 및 패키지 이름 지정에 주의하세요. BUILD 파일 작성에 관한 자세한 내용은 BUILD 스타일 가이드를 참고하세요.

이제 Smoothie를 추가하여 새 종속 항목 그래프를 시각화하여 이전 그래프와 비교합니다. 명확성을 위해 그래프 입력의 이름을 graph2.ingraph2.png로 지정합니다.

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

첫 번째 그래프와 동일하지만 이제 스무디가 있는 셰프 타겟에서 시작되어 바나나와 딸기로 이어지는 스포크가 있습니다.

graph2.png를 보면 Smoothie는 다른 요리와 공유 종속 항목이 없으며 Chef가 사용하는 다른 타겟일 뿐입니다.

somepath() 및 allpaths()

한 패키지가 다른 패키지에 종속되는 이유를 쿼리하려면 어떻게 해야 하나요? 두 항목 간의 종속 항목 경로를 표시하면 답변을 얻을 수 있습니다.

somepath()allpaths(), 두 가지 함수를 사용하여 종속 항목 경로를 찾을 수 있습니다. 시작 타겟 S와 엔드포인트 E가 주어지면 somepath(S,E)를 사용하여 S와 E 사이의 경로를 찾습니다.

'Chef' 및 'Cheese' 타겟 간의 관계를 살펴보고 이 두 함수의 차이점을 살펴봅니다. 한 타겟에서 다른 타겟으로 이동할 수 있는 다양한 경로가 있습니다.

  • Chef → MacAndCheese → Cheese
  • 요리사 → 피자 → 치즈

somepath()는 두 옵션 중 단일 경로를 제공하는 반면 'allpaths()'는 가능한 모든 경로를 출력합니다.

예를 들어 Cafe Bazel을 사용하여 다음을 실행합니다.

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

출력은 Cafe → Chef → MacAndCheese → Cheese의 첫 번째 경로를 따릅니다. 대신 allpaths()를 사용하면 다음과 같은 이점이 있습니다.

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

카페에서 셰프, 피자, 맥앤치즈, 치즈로 이어지는 경로를 출력합니다.

allpaths()의 출력은 종속 항목의 평면화된 목록이므로 읽기가 약간 더 어렵습니다. Graphviz를 사용하여 이 그래프를 시각화하면 관계를 더 명확하게 이해할 수 있습니다.

직접 테스트하기

Cafe Bazel의 고객 중 한 명이 식당에 대한 첫 리뷰를 작성했습니다. 리뷰에 리뷰 작성자의 신원, 언급된 음식 등 일부 세부정보가 누락되었습니다. 다행히 Bazel을 사용하면 이 정보에 액세스할 수 있습니다. reviews 패키지에는 미스터리 고객의 리뷰를 출력하는 프로그램이 포함되어 있습니다. 다음을 사용하여 빌드하고 실행합니다.

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

Bazel 쿼리만 하는 대신 리뷰를 작성한 사람이 누구이고 어떤 요리를 묘사했는지 알아봐야 합니다.

힌트

유용한 정보는 태그 및 종속 항목을 확인하세요.

답변

이 리뷰는 피자를 설명하는 내용이며, 아미르는 리뷰 작성자입니다. bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'를 사용하여 이 규칙의 종속 항목을 살펴보면 이 명령어의 결과에서 아미르가 검토자임을 알 수 있습니다. 다음으로 리뷰 작성자가 아미르라는 것을 알고 있으므로 쿼리 함수를 사용하여 아미르가 `BUILD` 파일에 있는 태그를 찾아 어떤 요리가 있는지 확인할 수 있습니다. bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' 명령어는 아미르가 피자를 주문한 유일한 고객이며 답변을 제공한 리뷰 작성자임을 출력합니다.

요약

축하합니다. 이제 몇 가지 기본 쿼리를 실행했으며, 자체 프로젝트에서 사용해 볼 수 있습니다. 쿼리 언어 구문에 대한 자세한 내용은 쿼리 참조 페이지를 참조하세요. 더 많은 고급 쿼리를 원하시나요? 쿼리 가이드에서는 이 가이드에서 다루는 것보다 더 많은 사용 사례를 자세히 보여줍니다.