이 튜토리얼에서는 Bazel로 Java 애플리케이션을 빌드하는 기본사항을 다룹니다. 작업공간을 설정하고 타겟, BUILD
파일과 같은 주요 Bazel 개념을 보여주는 간단한 Java 프로젝트를 빌드합니다.
예상 완료 시간: 30분
학습할 내용
이 튜토리얼에서는 다음을 수행하는 방법을 알아봅니다.
- 타겟 빌드
- 프로젝트의 종속 항목 시각화
- 프로젝트를 여러 타겟과 패키지로 분할
- 패키지 간 타겟 공개 상태 제어
- 라벨을 통해 타겟 참조
- 타겟 배포
시작하기 전에
Bazel 설치
튜토리얼을 준비하려면 아직 설치하지 않은 경우 먼저 Bazel을 설치하세요.
JDK 설치
Java JDK를 설치합니다 (기본 버전은 11이지만 8~15 버전이 지원됨).
JDK를 가리키도록 JAVA_HOME 환경 변수를 설정합니다.
Linux/macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
Windows:
- 제어판을 엽니다.
- '시스템 및 보안' > '시스템' > '고급 시스템 설정' > '고급' 탭 > '환경 변수...'로 이동합니다. .
- '사용자 변수' 목록 (상단에 있는 목록)에서 '새로 만들기...'를 클릭합니다.
- '변수 이름' 필드에
JAVA_HOME
를 입력합니다. - '디렉터리 찾아보기...'를 클릭합니다.
- JDK 디렉터리 (예:
C:\Program Files\Java\jdk1.8.0_152
)로 이동합니다. - 모든 대화상자 창에서 '확인'을 클릭합니다.
샘플 프로젝트 가져오기
Bazel의 GitHub 저장소에서 샘플 프로젝트를 가져옵니다.
git clone https://github.com/bazelbuild/examples
이 가이드의 샘플 프로젝트는 examples/java-tutorial
디렉터리에 있으며 다음과 같이 구성됩니다.
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
Bazel을 사용하여 빌드
작업공간 설정
프로젝트를 빌드하려면 먼저 작업공간을 설정해야 합니다. 작업공간은 프로젝트의 소스 파일과 Bazel의 빌드 출력을 보유하는 디렉터리입니다. 또한 Bazel에서 특수 파일로 인식하는 파일도 포함되어 있습니다.
디렉터리와 그 콘텐츠를 Bazel 작업공간으로 식별하고 프로젝트 디렉터리 구조의 루트에 있는
WORKSPACE
파일하나 이상의
BUILD
파일. Bazel에 프로젝트의 여러 부분을 빌드하는 방법을 알려줍니다. 작업공간 내에서BUILD
파일을 포함하는 디렉터리는 패키지입니다. 패키지에 대해서는 이 튜토리얼의 뒷부분에서 알아봅니다.
디렉터리를 Bazel 작업공간으로 지정하려면 해당 디렉터리에 WORKSPACE
라는 빈 파일을 만듭니다.
Bazel이 프로젝트를 빌드할 때 모든 입력과 종속 항목은 동일한 워크스페이스에 있어야 합니다. 다른 워크스페이스에 있는 파일은 연결되지 않는 한 서로 독립적입니다. 연결은 이 튜토리얼의 범위를 벗어납니다.
BUILD 파일 이해하기
BUILD
파일에는 Bazel에 관한 여러 유형의 명령어가 포함되어 있습니다.
가장 중요한 유형은 빌드 규칙으로, 실행 가능한 바이너리나 라이브러리 등 원하는 출력을 빌드하는 방법을 Bazel에 알려줍니다. BUILD
파일에 있는 각 빌드 규칙 인스턴스를 타겟이라고 하며 특정 소스 파일 및 종속 항목 집합을 가리킵니다. 타겟은 다른 타겟을 가리킬 수도 있습니다.
java-tutorial/BUILD
파일을 확인합니다.
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
이 예에서 ProjectRunner
타겟은 Bazel의 내장 java_binary
규칙을 인스턴스화합니다. 이 규칙은 Bazel에 .jar
파일과 래퍼 셸 스크립트 (둘 다 타겟의 이름을 따서 지정됨)를 빌드하도록 지시합니다.
타겟의 속성은 종속 항목과 옵션을 명시적으로 명시합니다.
name
속성은 필수이지만, 대부분은 선택사항입니다. 예를 들어 ProjectRunner
규칙 타겟에서 name
은 타겟의 이름이고 srcs
은 Bazel이 타겟을 빌드하는 데 사용하는 소스 파일을 지정하며 main_class
은 main 메서드를 포함하는 클래스를 지정합니다. (예시에서는 소스 파일 집합을 하나씩 나열하는 대신 glob을 사용하여 Bazel에 전달합니다.)
프로젝트 빌드
샘플 프로젝트를 빌드하려면 java-tutorial
디렉터리로 이동하여 다음을 실행합니다.
bazel build //:ProjectRunner
타겟 라벨에서 //
부분은 작업공간의 루트 (이 경우 루트 자체)를 기준으로 한 BUILD
파일의 위치이고 ProjectRunner
는 BUILD
파일의 타겟 이름입니다. (이 튜토리얼의 끝부분에서 타겟 라벨에 대해 자세히 알아봅니다.)
Bazel은 다음과 비슷한 출력을 생성합니다.
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
축하합니다. 첫 번째 Bazel 타겟을 빌드했습니다. Bazel은 작업공간의 루트에 있는 bazel-bin
디렉터리에 빌드 출력을 배치합니다. 콘텐츠를 탐색하여 Bazel의 출력 구조를 파악합니다.
이제 새로 빌드한 바이너리를 테스트합니다.
bazel-bin/ProjectRunner
종속 항목 그래프 검토
Bazel에서는 빌드 종속 항목이 BUILD 파일에 명시적으로 선언되어야 합니다. Bazel은 이러한 문을 사용하여 프로젝트의 종속성 그래프를 생성하므로 정확한 증분 빌드가 가능합니다.
샘플 프로젝트의 종속 항목을 시각화하려면 워크스페이스 루트에서 다음 명령어를 실행하여 종속 항목 그래프의 텍스트 표현을 생성하면 됩니다.
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
위 명령어는 Bazel에 //:ProjectRunner
타겟의 모든 종속 항목 (호스트 및 암시적 종속 항목 제외)을 찾고 출력을 그래프로 포맷하도록 지시합니다.
그런 다음 텍스트를 GraphViz에 붙여넣습니다.
보시다시피 프로젝트에는 추가 종속 항목 없이 두 개의 소스 파일을 빌드하는 단일 타겟이 있습니다.
작업공간을 설정하고 프로젝트를 빌드하고 종속 항목을 검사한 후 복잡성을 추가할 수 있습니다.
Bazel 빌드 개선
작은 프로젝트에는 하나의 타겟으로 충분하지만, 더 큰 프로젝트에서는 빠른 증분 빌드 (즉, 변경된 부분만 다시 빌드)를 허용하고 프로젝트의 여러 부분을 한 번에 빌드하여 빌드 속도를 높이기 위해 여러 타겟과 패키지로 분할하는 것이 좋습니다.
여러 빌드 타겟 지정
샘플 프로젝트 빌드를 두 타겟으로 분할할 수 있습니다. java-tutorial/BUILD
파일의 내용을 다음으로 바꿉니다.
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
이 구성에서는 Bazel이 먼저 greeter
라이브러리를 빌드한 다음 ProjectRunner
바이너리를 빌드합니다. java_binary
의 deps
속성은 ProjectRunner
바이너리를 빌드하는 데 greeter
라이브러리가 필요하다고 Bazel에 알려줍니다.
이 새 버전의 프로젝트를 빌드하려면 다음 명령어를 실행합니다.
bazel build //:ProjectRunner
Bazel은 다음과 비슷한 출력을 생성합니다.
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
이제 새로 빌드한 바이너리를 테스트합니다.
bazel-bin/ProjectRunner
이제 ProjectRunner.java
를 수정하고 프로젝트를 다시 빌드하면 Bazel은 해당 파일만 다시 컴파일합니다.
종속 항목 그래프를 보면 ProjectRunner
가 이전과 동일한 입력에 종속되지만 빌드 구조는 다릅니다.
이제 타겟이 두 개인 프로젝트를 빌드했습니다. ProjectRunner
타겟은 두 개의 소스 파일을 빌드하고 하나의 추가 소스 파일을 빌드하는 다른 타겟 (:greeter
)에 종속됩니다.
여러 패키지 사용
이제 프로젝트를 여러 패키지로 분할해 보겠습니다. src/main/java/com/example/cmdline
디렉터리를 살펴보면 BUILD
파일과 일부 소스 파일도 포함되어 있습니다. 따라서 Bazel에게는 이제 작업공간에 //src/main/java/com/example/cmdline
와 //
라는 두 패키지가 포함됩니다 (작업공간의 루트에 BUILD
파일이 있으므로).
src/main/java/com/example/cmdline/BUILD
파일을 확인합니다.
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
runner
타겟은 //
패키지의 greeter
타겟에 종속됩니다 (따라서 타겟 라벨은 //:greeter
). Bazel은 deps
속성을 통해 이를 알고 있습니다.
종속 항목 그래프를 살펴보세요.
하지만 빌드가 성공하려면 visibility
속성을 사용하여 //src/main/java/com/example/cmdline/BUILD
의 runner
타겟에 //BUILD
의 타겟에 대한 공개 상태를 명시적으로 부여해야 합니다. 기본적으로 타겟은 동일한 BUILD
파일의 다른 타겟에만 표시되기 때문입니다. (Bazel은 타겟 공개 상태를 사용하여 구현 세부정보가 공개 API로 유출되는 등의 문제를 방지합니다.)
이렇게 하려면 아래와 같이 java-tutorial/BUILD
의 greeter
타겟에 visibility
속성을 추가합니다.
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
이제 작업공간의 루트에서 다음 명령어를 실행하여 새 패키지를 빌드할 수 있습니다.
bazel build //src/main/java/com/example/cmdline:runner
Bazel은 다음과 비슷한 출력을 생성합니다.
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
이제 새로 빌드한 바이너리를 테스트합니다.
./bazel-bin/src/main/java/com/example/cmdline/runner
이제 타겟이 하나씩 포함된 두 패키지로 빌드되도록 프로젝트를 수정하고 패키지 간의 종속 항목을 이해했습니다.
라벨을 사용하여 타겟 참조
BUILD
파일과 명령줄에서 Bazel은 타겟 라벨을 사용하여 타겟을 참조합니다(예: //:ProjectRunner
또는 //src/main/java/com/example/cmdline:runner
). 구문은 다음과 같습니다.
//path/to/package:target-name
타겟이 규칙 타겟인 경우 path/to/package
은 BUILD
파일이 포함된 디렉터리의 경로이고 target-name
은 BUILD
파일에서 타겟의 이름 (name
속성)입니다. 타겟이 파일 타겟인 경우 path/to/package
는 패키지 루트의 경로이고 target-name
는 전체 경로를 포함한 타겟 파일의 이름입니다.
저장소 루트에서 타겟을 참조할 때는 패키지 경로가 비어 있으므로 //:target-name
만 사용하세요. 동일한 BUILD
파일 내에서 타겟을 참조할 때는 //
작업공간 루트 식별자를 건너뛰고 :target-name
만 사용할 수도 있습니다.
예를 들어 java-tutorial/BUILD
파일의 타겟의 경우 작업공간 루트 자체가 패키지 (//
)이고 두 타겟 라벨이 //:ProjectRunner
및 //:greeter
이므로 패키지 경로를 지정하지 않아도 되었습니다.
하지만 //src/main/java/com/example/cmdline/BUILD
파일의 타겟의 경우 //src/main/java/com/example/cmdline
의 전체 패키지 경로를 지정해야 했고 타겟 라벨은 //src/main/java/com/example/cmdline:runner
였습니다.
배포를 위해 Java 타겟 패키징
이제 모든 런타임 종속 항목을 사용하여 바이너리를 빌드하여 배포할 Java 타겟을 패키징해 보겠습니다. 이렇게 하면 개발 환경 외부에서 바이너리를 실행할 수 있습니다.
java_binary 빌드 규칙은 .jar
와 래퍼 셸 스크립트를 생성합니다. 다음 명령어를 사용하여 runner.jar
의 콘텐츠를 확인합니다.
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
콘텐츠는 다음과 같습니다.
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
runner.jar
에는 Runner.class
이 포함되어 있지만 종속 항목인 Greeting.class
은 포함되어 있지 않습니다. Bazel이 생성하는 runner
스크립트는 greeter.jar
를 클래스 경로에 추가하므로 이대로 두면 로컬에서 실행되지만 다른 머신에서는 독립형으로 실행되지 않습니다. 다행히 java_binary
규칙을 사용하면 자체 포함된 배포 가능한 바이너리를 빌드할 수 있습니다. 이를 빌드하려면 타겟 이름에 _deploy.jar
를 추가합니다.
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel은 다음과 비슷한 출력을 생성합니다.
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
필수 런타임 종속 항목이 포함되어 있으므로 개발 환경에서 벗어나 독립적으로 실행할 수 있는 runner_deploy.jar
를 빌드했습니다. 이전과 동일한 명령어를 사용하여 이 독립형 JAR의 콘텐츠를 살펴봅니다.
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
콘텐츠에는 다음을 실행하는 데 필요한 모든 클래스가 포함됩니다.
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
추가 자료
자세한 내용은 다음을 참고하세요.
전이적 Maven 종속 항목을 관리하는 규칙을 위한 rules_jvm_external
외부 종속 항목을 참고하여 로컬 및 원격 저장소 작업에 대해 자세히 알아보세요.
Bazel에 대해 자세히 알아볼 수 있는 기타 규칙
C++ 빌드 튜토리얼을 통해 Bazel로 C++ 프로젝트 빌드를 시작하세요.
Android 애플리케이션 튜토리얼 및 iOS 애플리케이션 튜토리얼)을 참고하여 Bazel로 Android 및 iOS용 모바일 애플리케이션을 빌드하는 방법을 알아보세요.
즐거운 빌드 되세요!