쿼리 빠른 시작

문제 신고 소스 보기 Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

이 튜토리얼에서는 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'

시나리오

Cafe 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
└── WORKSPACE

달리 지시가 없는 한 이 튜토리얼에서는 필요한 정보를 찾기 위해 BUILD 파일을 보지 말고 쿼리 함수만 사용합니다.

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

빌드 실행

이 프로젝트에는 Runner.java 내에 실행하여 카페 메뉴를 출력할 수 있는 main 메서드가 포함되어 있습니다. 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_scopetarget)를 사용합니다. 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 Cafe에 들어갑니다. 이름 외에는 알려진 것이 없습니다. 다행히 'customers' BUILD 파일에 주문 태그가 지정되어 있습니다. 이 태그에 액세스하려면 어떻게 해야 하나요?

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

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

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

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

자가 진단

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

답변

마카로니 앤 치즈

새 종속 항목 추가

카페 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 {

}

그런 다음 적절한 디렉터리(dishes)에 Smoothie.java를 추가합니다.

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를 다시 빌드하여 오류가 없는지 확인합니다. 빌드가 완료되면 축하합니다. '카페'에 새 종속 항목을 추가했습니다. 그렇지 않은 경우 맞춤법 오류와 패키지 이름 지정을 확인하세요. 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
  • Chef → Pizza → 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를 사용하여 이 그래프를 시각화하면 관계를 더 명확하게 이해할 수 있습니다.

직접 테스트하기

카페 바젤의 고객이 레스토랑에 첫 리뷰를 남겼습니다. 리뷰에 리뷰 작성자의 신원, 언급된 음식 등 일부 세부정보가 누락되었습니다. 다행히 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/...)' 명령어는 아미르가 피자를 주문한 유일한 고객이며 답변을 제공한 리뷰 작성자임을 출력합니다.

요약

축하합니다. 이제 몇 가지 기본 쿼리를 실행했으며, 자체 프로젝트에서 사용해 볼 수 있습니다. 쿼리 언어 구문에 대한 자세한 내용은 쿼리 참조 페이지를 참고하세요. 더 많은 고급 쿼리를 원하십니까? 쿼리 가이드에서는 이 가이드에서 다루는 것보다 더 많은 사용 사례의 심층 목록을 확인할 수 있습니다.