BazelCon 2022는 11월 16~17일에 뉴욕과 온라인에서 개최됩니다.
지금 등록하기

Bazel 가이드: 자바 프로젝트 빌드

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

이 튜토리얼에서는 Bazel을 사용하여 자바 애플리케이션을 빌드하는 기본 사항을 설명합니다. 작업공간을 설정하고 대상 및 BUILD 파일과 같은 주요 Bazel 개념을 보여주는 간단한 자바 프로젝트를 빌드합니다.

예상 완료 시간: 30분

실습 내용

이 튜토리얼에서는 다음 방법을 알아봅니다.

  • 타겟 빌드
  • 프로젝트의 종속 항목 시각화
  • 프로젝트를 여러 타겟과 패키지로 분할
  • 패키지 전체의 타겟 공개 상태 제어
  • 라벨을 통한 타겟 참조
  • 대상 배포

시작하기 전에

Bazel 설치

튜토리얼을 준비하려면 먼저 Bazel이 아직 설치되지 않은 경우 Bazel을 설치합니다.

JDK 설치

  1. 자바 JDK를 설치합니다 (권장 버전은 11이지만 8~15 사이의 버전이 지원됨).

  2. JDK를 가리키도록 JAVA_HOME 환경 변수를 설정합니다.

    • Linux/macOS의 경우:

      export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
      
    • Windows:

      1. Control Panel(제어판)을 엽니다.
      2. '시스템' 및 '보안' '고급' 설정 탭으로 이동하여 .
      3. 상단에 있는 '사용자 변수' 목록에서 '새로 만들기...'를 클릭합니다.
      4. '변수 이름' 필드에 JAVA_HOME를 입력합니다.
      5. '디렉터리 찾아보기...'를 클릭합니다.
      6. JDK 디렉터리 (예: C:\Program Files\Java\jdk1.8.0_152)로 이동합니다.
      7. 모든 대화상자 창에서 '확인'을 클릭합니다.

샘플 프로젝트 가져오기

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이 특별한 것으로 인식하는 파일도 포함됩니다.

  • WORKSPACE 파일은 디렉터리와 해당 콘텐츠를 Bazel 작업공간으로 식별하고 프로젝트의 디렉터리 구조 루트에 있습니다.

  • Bazel에게 프로젝트의 여러 부분을 빌드하는 방법을 알려주는 하나 이상의 BUILD 파일. 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 규칙을 인스턴스화합니다. 규칙은 .jar 파일과 래퍼 셸 스크립트를 빌드하도록 Bazel에 지시합니다 (둘 다 타겟 이름을 따라 지정함).

타겟의 속성은 종속 항목과 옵션을 명시적으로 나타냅니다. name 속성은 필수이지만 대부분은 선택사항입니다. 예를 들어 ProjectRunner 규칙 대상에서 name은 대상 이름이고 srcs는 Bazel이 대상을 빌드하는 데 사용하는 소스 파일을 지정하고 main_class은 기본 메서드가 포함된 클래스를 지정합니다. (이 예시에서는 glob을 사용하여 소스 파일을 하나씩 나열하는 대신 Bazel에 전달합니다.)

프로젝트 빌드

샘플 프로젝트를 빌드하려면 java-tutorial 디렉터리로 이동하여 다음을 실행합니다.

bazel build //:ProjectRunner

타겟 라벨에서 // 부분은 작업공간의 루트 (이 경우 루트 자체)를 기준으로 한 BUILD 파일의 위치입니다. ProjectRunnerBUILD 파일의 타겟 이름입니다. 대상 라벨은 이 튜토리얼의 끝부분에서 자세히 알아봅니다.

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에 붙여넣습니다.

보시다시피 프로젝트에는 추가 종속 항목 없이 소스 파일 두 개를 빌드하는 단일 타겟이 있습니다.

타겟 'ProjectRunner'의 종속 항목 그래프

작업공간을 설정하고, 프로젝트를 빌드하고, 종속 항목을 검사한 후에는 복잡성을 추가할 수 있습니다.

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_binarydeps 속성은 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'의 종속 항목 그래프

이제 대상 두 개로 프로젝트를 빌드했습니다. ProjectRunner 타겟은 두 개의 소스 파일을 빌드하고 하나의 다른 대상(:greeter)에 종속되어 하나의 추가 소스 파일을 빌드합니다.

여러 패키지 사용

이제 프로젝트를 여러 패키지로 분할하겠습니다. src/main/java/com/example/cmdline 디렉터리를 살펴보면 이 디렉터리에 BUILD 파일과 일부 소스 파일도 포함된 것을 확인할 수 있습니다. 따라서 Bazel의 경우 이제 작업공간의 루트에 BUILD 파일이 있으므로 작업공간에 //src/main/java/com/example/cmdline//라는 두 개의 패키지가 포함됩니다.

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/BUILDrunner 대상을 //BUILD의 대상에 명시적으로 지정해야 합니다. 기본적으로 대상은 동일한 BUILD 파일의 다른 대상에만 표시되기 때문입니다. (Bazel은 대상 공개 상태를 사용하여 공개 API에 유출되는 구현 세부정보가 포함된 라이브러리와 같은 문제를 방지합니다.)

이렇게 하려면 아래와 같이 visibility 속성을 java-tutorial/BUILDgreeter 대상에 추가합니다.

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/packageBUILD 파일이 포함된 디렉터리 경로이고 target-nameBUILD 파일 (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_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

추가 자료

자세한 내용은 다음을 참고하세요.

즐겁게 빌드하세요!