이 튜토리얼에서는 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
로 구분됩니다. 이러한 패키지 내의 규칙은 다양한 태그와 종속 항목으로 Cafe의 다양한 구성요소를 정의합니다.
빌드 실행
이 프로젝트에는 카페 내부 메뉴를 출력하기 위해 실행할 수 있는 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
라는 2개 이상의 인수를 사용합니다. 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
쿼리 반환에는 피자와 macAndCheese가 모두 사용되는 치즈가 표시됩니다. 놀라운 소식입니다.
태그를 기반으로 대상 찾기
두 명의 고객, 아미르와 제니라는 Bazel Cafe가 들어서 있습니다. 이름 이외에는 알려진 바가 없습니다. 다행히 '고객' BUILD
파일에 태그가 지정되어 있습니다. 이 태그에 액세스하려면 어떻게 해야 하나요?
개발자는 테스트를 위해 다양한 식별자로 Bazel 대상에 태그를 지정할 수 있습니다. 예를 들어 테스트의 태그는 특히 런타임 주석 기능이 없는 C++ 및 Python 테스트에서 디버그 및 출시 프로세스에서 테스트의 역할에 주석을 달 수 있습니다. 태그 및 크기 요소를 사용하면 코드베이스의 체크인 정책을 기반으로 테스트 모음을 유연하게 구성할 수 있습니다.
이 예에서 태그는 메뉴 항목을 나타내는 pizza
또는 macAndCheese
중 하나입니다. 이 명령어는 특정 패키지 내에서 식별자와 일치하는 태그가 있는 타겟을 쿼리합니다.
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
이 쿼리는 '고객' 패키지의 '피자' 태그가 있는 모든 타겟을 반환합니다.
테스트
이 쿼리를 사용해 제니가 주문하려는 것을 알아보세요.
답변
맥앤치즈
새 종속 항목 추가
Cafe Bazel이 메뉴를 확대하여 이제 고객이 스무디를 주문할 수 있습니다. 이 특정 스무디는 Strawberry
및 Banana
재료로 구성됩니다.
먼저 스무디에 사용되는 재료인 Strawberry.java
과 Banana.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' 파일을 포함하여 새로운 각 요소마다 새로운 자바 라이브러리를 만듭니다. 업데이트된 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
용으로 생성된 자바 파일이 '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
파일 작성에 관한 자세한 내용은 빌드 스타일 가이드를 참고하세요.
이제 이전 종속 항목과 비교할 수 있도록 Smoothie
가 추가된 새 종속 항목 그래프를 시각화합니다. 명확하게 하려면 그래프 입력의 이름을 graph2.in
및 graph2.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 사이의 경로를 찾습니다.
'요리' 타겟과 '치즈' 타겟 간의 관계를 확인하여 이 두 기능의 차이를 알아보세요. 한 대상에서 다른 대상으로 이동할 수 있는 다양한 경로가 있습니다.
- 요리사 → MacAndCheese → 치즈
- 요리사 → 피자 → 치즈
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 쿼리만 사용하고 리뷰를 작성한 사람과 리뷰에서 어떤 요리를 제공했는지 알아보세요.
힌트
태그와 종속 항목을 확인하여 유용한 정보를 확인하세요.
답변
이 리뷰에서는 Pizza에 대해 설명하고 Amir는 리뷰 작성자입니다. bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'
를 사용하여 이 규칙에 적용된 종속 항목을 살펴보면 Amir가 리뷰어임을 알 수 있습니다.
다음으로 리뷰 작성자가 Amir임을 알고 있으므로 쿼리 함수를 사용하여 `mir` 파일에 어떤 요리가 있는지 살펴보고 어떤 요리가 있는지 확인합니다.
명령어 bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
은 아미르가 피자를 주문한 유일한 사용자이며 답변을 제공하는 리뷰어라는 것을 출력합니다.
요약
축하합니다. 이제 몇 가지 기본 쿼리를 실행하여 자체 프로젝트에서 사용해 볼 수 있습니다. 쿼리 언어 구문에 대한 자세한 내용은 쿼리 참조 페이지를 참조하세요. 고급 쿼리를 사용해 보고 싶으신가요? 쿼리 가이드에는 이 가이드에서 다룬 것보다 더 많은 사용 사례가 자세히 나와 있습니다.