Bazel로 프로그램 빌드

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

이 페이지에서는 Bazel을 사용하여 프로그램을 빌드하는 방법, 명령어 명령어 빌드, 대상 패턴 구문을 다룹니다.

빠른 시작

Bazel을 실행하려면 기본 workspace 디렉터리나 하위 디렉터리로 이동한 후 bazel를 입력합니다. 새 작업공간을 만들어야 하는 경우 build를 참조하세요.

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

사용 가능한 명령어

  • analyze-profile: 빌드 프로필 데이터를 분석합니다.
  • aquery: 분석 후 작업 그래프에서 쿼리를 실행합니다.
  • build: 지정된 타겟을 빌드합니다.
  • canonicalize-flags: Bazel 플래그를 표준화합니다.
  • clean: 출력 파일을 삭제하고 선택적으로 서버를 중지합니다.
  • cquery: 분석 후 종속 항목 그래프 쿼리를 실행합니다.
  • dump: Bazel 서버 프로세스의 내부 상태를 덤프합니다.
  • help: 명령어 또는 색인에 대한 도움말을 출력합니다.
  • info: bazel 서버에 대한 런타임 정보를 표시합니다.
  • fetch: 대상의 모든 외부 종속 항목을 가져옵니다.
  • mobile-install: 휴대기기에 앱을 설치합니다.
  • query: 종속 항목 그래프 쿼리를 실행합니다.
  • run: 지정된 타겟을 실행합니다.
  • shutdown: Bazel 서버를 중지합니다.
  • test: 지정된 테스트 대상을 빌드하고 실행합니다.
  • version: Bazel의 버전 정보를 출력합니다.

도움 받기

  • bazel help command: command의 도움말 및 옵션을 출력합니다.
  • bazel helpstartup_options: Bazel을 호스팅하는 JVM 옵션.
  • bazel helptarget-syntax: 대상을 지정하는 문법을 설명합니다.
  • bazel help info-keys: info 명령어에서 사용하는 키 목록을 표시합니다.

bazel 도구는 명령어라고 하는 많은 기능을 실행합니다. 가장 일반적으로 사용되는 옵션은 bazel buildbazel test입니다. bazel help를 사용하여 온라인 도움말 메시지를 탐색할 수 있습니다.

하나의 대상 빌드

빌드를 시작하려면 작업공간이 필요합니다. 작업공간은 애플리케이션을 빌드하는 데 필요한 모든 소스 파일을 포함하는 디렉터리 트리입니다. Bazel을 사용하면 완전히 읽기 전용 볼륨에서 빌드를 수행할 수 있습니다.

Bazel을 사용하여 프로그램을 빌드하려면 bazel build 뒤에 빌드하려는 대상을 입력합니다.

bazel build //foo

//foo 빌드 명령어를 실행하면 다음과 비슷한 출력이 표시됩니다.

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

첫째, Bazel이 대상의 종속 항목 그래프에 있는 모든 패키지를 로드합니다. 여기에는 선언된 종속 항목, 대상의 BUILD 파일에 직접 나열된 파일, 전이 종속 항목, 대상 종속 항목의 BUILD 파일에 나열된 파일이 포함됩니다. 모든 종속 항목을 식별한 후 Bazel이 정확성을 분석하고 빌드 작업을 만듭니다. 마지막으로 Bazel은 빌드의 컴파일러와 기타 도구를 실행합니다.

빌드 실행 단계에서 Bazel이 진행률 메시지를 출력합니다. 진행률 메시지에는 시작 시 현재 빌드 단계 (예: 컴파일러 또는 링커) 및 총 빌드 작업 수에 걸쳐 완료된 수가 포함됩니다. 빌드가 시작되면 Bazel이 전체 작업 그래프를 발견하면서 총 작업 수가 증가하는 경우가 많지만 숫자는 몇 초 내에 안정화됩니다.

빌드가 끝나면 Bazel은 요청된 대상, 성공적으로 빌드되었는지 여부, 빌드 성공 시 출력 파일을 찾을 수 있는 위치를 출력합니다. 빌드를 실행하는 스크립트는 이 출력을 안정적으로 파싱할 수 있습니다. 자세한 내용은 --show_result를 참조하세요.

같은 명령어를 다시 입력하면 빌드가 훨씬 더 빨리 완료됩니다.

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

null 빌드입니다. 아무것도 변경되지 않았으므로 새로고침할 패키지와 실행할 빌드 단계가 없습니다. 'foo' 또는 종속 항목에서 변경된 사항이 있으면 Bazel이 일부 빌드 작업을 다시 실행하거나 증분 빌드를 완료합니다.

여러 타겟 빌드

Bazel을 사용하면 다양한 방법으로 빌드할 대상을 지정할 수 있습니다. 모두 대상 패턴이라고 합니다. 이 구문은 build, test 또는 query와 같은 명령어에서 사용됩니다.

라벨은 개별 대상을 지정하는 데 사용되지만(예: BUILD 파일에서 종속 항목 선언) Bazel의 대상 패턴은 여러 대상을 지정합니다. 대상 패턴은 와일드 카드를 사용하여 대상 세트의 라벨 구문 일반화입니다. 가장 간단한 경우 모든 유효한 라벨도 유효한 대상 패턴이며 정확히 1개의 대상 세트를 식별합니다.

//로 시작하는 모든 대상 패턴은 현재 작업공간을 기준으로 확인됩니다.

//foo/bar:wiz 단일 타겟 //foo/bar:wiz.
//foo/bar //foo/bar:bar과 같습니다.
//foo/bar:all foo/bar 패키지의 모든 규칙 대상
//foo/... foo 디렉터리에 있는 모든 패키지의 모든 규칙 대상
//foo/...:all foo 디렉터리에 있는 모든 패키지의 모든 규칙 대상
//foo/...:* foo 디렉터리에 있는 모든 패키지의 모든 대상 (규칙 및 파일)
//foo/...:all-targets foo 디렉터리에 있는 모든 패키지의 모든 대상 (규칙 및 파일)
//... 작업공간에 있는 패키지의 모든 대상 외부 저장소의 대상은 여기에 포함되지 않습니다.
//:all 작업공간의 루트에 'BUILD' 파일이 있는 경우, 최상위 패키지의 모든 대상

//로 시작하지 않는 대상 패턴은 현재 작업 디렉터리를 기준으로 확인됩니다. 이 예시에서는 foo의 작업 디렉터리를 가정합니다.

:foo //foo:foo과 같습니다.
bar:wiz //foo/bar:wiz과 같습니다.
bar/wiz 다음 데이터에 해당합니다.
  • foo/bar/wiz가 패키지인 경우 //foo/bar/wiz:wiz
  • foo/bar가 패키지인 경우 //foo/bar:wiz
  • 그렇지 않은 경우 //foo:bar/wiz
bar:all //foo/bar:all과 같습니다.
:all //foo:all과 같습니다.
...:all //foo/...:all과 같습니다.
... //foo/...:all과 같습니다.
bar/...:all //foo/bar/...:all과 같습니다.

기본적으로 디렉터리 심볼릭 링크는 재귀 타켓팅 패턴을 따르는 데 사용됩니다. 단, 작업공간의 루트 디렉터리에서 생성되는 편의 심볼릭 링크와 같이 출력 기반에서 가리키는 심볼릭 링크는 제외됩니다.

또한 Bazel은 다음과 같은 이름의 파일을 포함하는 디렉터리에서 재귀 대상 패턴을 평가할 때 심볼릭 링크를 따르지 않습니다. DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/...패키지 위에 있는 와일드 카드로, foo에서 재귀적으로 모든 패키지를 나타냅니다(패키지 경로의 모든 루트). :all대상에 대한 와일드 카드이며 패키지 내의 모든 규칙과 일치합니다. foo/...:all과 같이 이 둘을 결합할 수 있으며 두 와일드 카드를 모두 사용하면 foo/...로 축약할 수 있습니다.

또한 :* (또는 :all-targets)는 일치된 패키지의 모든 대상과 일치하는 와일드 카드이며, 일례로 java_binary 규칙에 연결된 _deploy.jar 파일과 같이 어떠한 규칙에 의해 빌드되지 않은 파일도 포함됩니다.

이는 :*:all상위 집합을 의미한다는 것을 의미합니다. 혼동을 일으킬 수 있지만 이 문법은 일반적인 :all 와일드 카드를 사용하여 _deploy.jar과 같은 타겟 빌드가 바람직하지 않은 일반적인 빌드에 사용할 수 있도록 합니다.

또한 Bazel을 사용하면 라벨 구문에 필요한 콜론 대신 슬래시를 사용할 수 있으며, Bash 파일 이름 확장을 사용할 때 편리합니다. 예를 들어 foo/bar/wiz//foo/bar:wiz(패키지 foo/bar가 있는 경우) 또는 //foo:bar/wiz(패키지 foo가 있는 경우)와 같습니다.

많은 Bazel 명령어는 대상 패턴 목록을 인수로 허용하고 모두 접두어 부정 연산자 -를 따릅니다. 이전 인수에서 지정한 집합에서 타겟 집합을 빼는 데 사용할 수 있습니다. 즉, 순서가 중요합니다. 예를 들면 다음과 같습니다.

bazel build foo/... bar/...

'foo 아래의 모든 타겟 bar 아래의 모든 타겟 빌드'를 의미합니다.

bazel build -- foo/... -foo/bar/...

'foo/bar 아래의 타겟을 제외한 모든 타겟 빌드'를 의미합니다. -로 시작하는 후속 인수가 추가 옵션으로 해석되지 않도록 하려면 -- 인수가 필요합니다.

단, 이 방법으로 타겟을 빼도 빌드되지 않는다고 보장할 수는 없습니다. 빼지 않은 대상의 종속 항목일 수 있기 때문입니다. 예를 들어 다른 것 중 //foo/bar:api에 종속된 타겟 //foo:all-apis가 있다면 후자는 빌드 작업의 일부로 빌드됩니다.

tags = ["manual"] 및 타겟은 와일드 카드 대상 패턴(..., :*, :all 등)에 포함되지 않습니다. bazel buildbazel test 같은 명령어에 지정되면 Bazel이 이러한 대상을 빌드/테스트하도록 하려면 명령줄에서 명시적인 대상 패턴을 사용하여 지정해야 합니다. 반면에 bazel query는 이러한 필터링을 자동으로 실행하지 않습니다 (bazel query의 용도에 어긋남).

외부 종속 항목 가져오기

기본적으로 Bazel은 빌드 중에 외부 종속 항목을 다운로드하고 심볼릭 링크로 만듭니다. 그러나 이는 새로운 외부 종속 항목이 언제 추가되는지 알고 싶거나 종속 항목을 '미리 가져오기'(예: 오프라인으로 전환되기 전에)하려고 하기 때문에 바람직하지 않을 수 있습니다. 빌드 중에 새 종속 항목이 추가되지 않도록 하려면 --fetch=false 플래그를 지정하면 됩니다. 이 플래그는 로컬 파일 시스템의 디렉터리를 가리키지 않는 저장소 규칙에만 적용됩니다. 예를 들어 local_repository, new_local_repository, Android SDK 및 NDK 저장소 규칙의 변경사항은 --fetch 값과 관계없이 항상 적용됩니다 .

빌드 중에 가져오기를 허용하지 않고 Bazel이 새 외부 종속 항목을 찾으면 빌드가 실패합니다.

bazel fetch를 실행하여 종속 항목을 수동으로 가져올 수 있습니다. 빌드 가져오기 중에 금지하는 경우 bazel fetch를 실행해야 합니다.

  • 처음으로 빌드하기 전에
  • 새 외부 종속 항목을 추가한 후

실행 후에는 WORKSPACE 파일이 변경될 때까지 다시 실행할 필요가 없습니다.

fetch는 종속 항목을 가져올 타겟 목록을 가져옵니다. 예를 들어 이렇게 하면 //foo:bar//bar:baz를 빌드하는 데 필요한 종속 항목을 가져옵니다.

bazel fetch //foo:bar //bar:baz

작업공간의 모든 외부 종속 항목을 가져오려면 다음을 실행합니다.

bazel fetch //...

라이브러리 jar에서 JDK 자체까지 사용 중인 모든 도구가 작업공간 루트 아래에 있는 경우 bazel 가져오기를 전혀 실행할 필요가 없습니다. 그러나 작업공간 디렉터리 외부의 항목을 사용하는 경우 Bazel은 bazel build를 실행하기 전에 자동으로 bazel fetch를 실행합니다.

저장소 캐시

Bazel은 동일한 파일을 여러 작업공간에서 필요로 하거나 외부 저장소의 정의가 변경되었지만 동일한 파일을 다운로드할 필요가 있는 경우에도 동일한 파일을 여러 번 가져오는 것을 방지하려고 합니다. 이렇게 하기 위해 bazel은 기본적으로 ~/.cache/bazel/_bazel_$USER/cache/repos/v1/에 있는 저장소 캐시에 다운로드한 모든 파일을 캐시합니다. 위치는 --repository_cache 옵션으로 변경할 수 있습니다. 캐시는 모든 작업공간과 설치된 bazel 버전 간에 공유됩니다. Bazel이 올바른 파일의 사본이 있는지 확인한 경우, 즉 다운로드 요청에 지정된 파일의 SHA256 sum이 있고 해당 해시가 있는 파일이 캐시에 있는 경우 캐시에서 항목이 가져옵니다. 따라서, 각 외부 파일에 해시를 지정하는 것은 보안 측면에서 좋은 아이디어일 뿐 아니라 불필요한 다운로드를 방지하는 데도 도움이 됩니다.

캐시 적중 시 캐시에 있는 파일의 수정 시간이 업데이트됩니다. 이러한 방식으로 캐시 디렉터리에 있는 파일의 마지막 사용은 쉽게 확인할 수 있습니다(예: 캐시를 수동으로 정리). 캐시에는 더 이상 사용할 수 없는 업스트림 파일의 사본이 포함되어 있을 수 있으므로 자동으로 정리되지 않습니다.

배포 파일 디렉터리

배포 디렉터리는 불필요한 다운로드를 방지하는 또 다른 Bazel 메커니즘입니다. Bazel은 저장소 캐시 전에 배포 디렉터리를 검색합니다. 주요 차이점은 배포 디렉터리에 수동 준비가 필요하다는 것입니다.

--distdir=/path/to-directory 옵션을 사용하면 파일을 가져오는 대신 추가 읽기 전용 디렉터리를 지정하여 파일을 찾을 수 있습니다. 파일 이름이 URL의 기본 이름과 동일하고 추가로 파일의 해시가 다운로드 요청에 지정된 것과 동일한 경우 해당 디렉터리에서 파일을 가져옵니다. WORKSPACE 선언에 파일 해시가 지정된 경우에만 작동합니다.

정확성을 위해 파일 이름의 조건이 필요하지는 않지만 특정 디렉터리당 후보 파일 수를 1개로 줄입니다. 이러한 방식으로 배포 파일 디렉터리를 지정하는 것은 디렉터리의 디렉터리 수가 많아지더라도 효율적으로 유지됩니다.

에어간격적인 환경에서 Bazel 실행

Bazel의 바이너리 크기를 작게 유지하기 위해 Bazel의 암시적 종속 항목을 처음으로 실행하는 동안 네트워크를 통해 가져옵니다. 이러한 암시적 종속 항목에는 모두에게 필요하지 않을 수 있는 도구 모음과 규칙이 포함되어 있습니다. 예를 들어 Android 도구는 번들 해제되며 Android 프로젝트를 빌드할 때만 가져옵니다.

그러나 이러한 암시적 종속 항목은 모든 Workspace 종속 항목을 벤더링한 경우에도 에어간격적인 환경에서 Bazel을 실행할 때 문제를 일으킬 수 있습니다. 이 문제를 해결하려면 네트워크 액세스 권한이 있는 머신에서 이러한 종속 항목이 포함된 배포 디렉터리를 준비한 후 오프라인 방식으로 에어간트 환경으로 전송합니다.

배포 디렉터리를 준비하려면 --distdir 플래그를 사용합니다. 출시 버전마다 암시적 종속 항목이 다를 수 있으므로 모든 새 Bazel 바이너리 버전에 대해 한 번씩 이 작업을 수행해야 합니다.

에어간격된 환경 외부에서 이러한 종속 항목을 빌드하려면 먼저 올바른 버전에서 Bazel 소스 트리를 결제합니다.

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

그런 다음 특정 Bazel 버전의 암시적 런타임 종속 항목을 포함하는 tarball을 빌드합니다.

bazel build @additional_distfiles//:archives.tar

이 tarball을 공백 환경에 복사할 수 있는 디렉터리로 내보냅니다. --distdir가 디렉터리 중첩 수준으로 매우 까다로울 수 있으므로 --strip-components 플래그를 확인합니다.

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

마지막으로 에어간격 환경에서 Bazel을 사용할 때 디렉터리를 가리키는 --distdir 플래그를 전달합니다. 편의를 위해 .bazelrc 항목으로 추가할 수 있습니다.

build --distdir=path/to/directory

빌드 구성 및 크로스 컴파일

특정 빌드의 동작과 결과를 지정하는 모든 입력은 두 개의 서로 다른 카테고리로 나눌 수 있습니다. 첫 번째 종류는 프로젝트의 BUILD 파일에 저장되는 고유 정보(빌드 규칙, 속성 값, 완전한 전이 종속 항목)입니다. 두 번째 종류는 사용자 또는 빌드 도구에서 제공하는 외부 또는 환경 데이터입니다. 대상 아키텍처, 컴파일 및 링크 옵션, 기타 도구 모음 구성 옵션을 선택하면 됩니다. 완전한 환경 데이터 세트를 구성이라고 합니다.

주어진 빌드에는 두 개 이상의 구성이 있을 수 있습니다. 64비트 아키텍처용 //foo:bin 실행 파일을 빌드하는 크로스 컴파일을 가정해 보겠습니다. 하지만 워크스테이션은 32비트 머신입니다. 빌드에는 64비트 실행 파일을 만들 수 있는 도구 모음을 사용하여 //foo:bin를 빌드해야 하지만, 빌드 시스템은 빌드 자체에서 사용되는 다양한 도구(예: 소스에서 빌드되고 이후에 genrule에서 사용되는 도구)도 빌드해야 하며 이러한 도구는 워크스테이션에서 실행되도록 빌드되어야 합니다. 따라서 두 가지 구성을 식별할 수 있습니다. 빌드 중에 실행되는 도구를 빌드하는 데 사용되는 호스트 구성과 최종적으로 요청하는 바이너리를 빌드하는 데 사용되는 대상 구성(단어가 이미 많은 의미를 지니고 있지만 더 자주 '대상 구성'이라고 함)을 확인할 수 있습니다.

일반적으로 요청된 빌드 대상(//foo:bin)과 하나 이상의 호스트 도구(예: 일부 기본 라이브러리)의 기본 요건인 라이브러리가 많습니다. 이러한 라이브러리는 호스트 구성을 위해 한 번, 타겟 구성을 위해 한 번, 총 두 번 빌드해야 합니다. Bazel은 두 변형이 모두 빌드되도록 하고 파생된 파일이 간섭을 피하기 위해 별도로 유지되도록 합니다. 이러한 타겟은 일반적으로 서로 독립적이므로 동시에 빌드할 수 있습니다. 특정 대상이 두 번 빌드되었음을 나타내는 진행률 메시지가 표시되는 경우 설명이 원인일 수 있습니다.

Bazel은 --distinct_host_configuration 옵션에 따라 두 가지 방법 중 하나를 사용하여 호스트 구성을 선택합니다. 이 불리언 옵션은 다소 미묘하며 설정이 빌드 속도를 개선하거나 악화시킬 수 있습니다.

--distinct_host_configuration=false

이 옵션을 false로 설정하면 호스트와 요청 구성이 동일합니다. 빌드 중에 필요한 모든 도구는 대상 프로그램과 정확히 동일한 방식으로 빌드됩니다. 이 설정은 단일 빌드 시 라이브러리를 두 번 빌드할 필요가 없음을 의미합니다.

하지만 요청 구성을 변경하면 호스트 구성에도 영향을 미쳐 모든 도구가 다시 빌드되고, 도구 출력에 의존하는 모든 부분이 다시 빌드됩니다. 따라서 예를 들어 빌드 간에 링커 옵션을 변경하면 모든 도구가 다시 연결되고 이 도구를 사용하는 모든 작업이 다시 실행될 수 있으므로 매우 큰 재빌드가 발생할 수 있습니다.

--distinct_host_configuration=true(기본값)

이 옵션이 참이면 호스트와 요청에 동일한 구성을 사용하는 대신 완전히 다른 호스트 구성이 사용됩니다. 호스트 구성은 다음과 같이 타겟 구성에서 파생됩니다.

  • --host_crosstool_top가 지정되지 않은 한 요청 구성에 지정된 것과 동일한 버전의 교차 도구 (--crosstool_top)를 사용합니다.
  • --cpu--host_cpu 값을 사용합니다(기본값: k8).
  • 요청 구성에 지정된 것과 동일한 값(--compiler, --use_ijars)을 사용하고 --host_crosstool_top를 사용할 경우 --host_cpu의 값을 사용하여 교차 구성(--compiler 무시)에서 호스트 구성을 찾습니다.
  • --javabase--host_javabase 값을 사용합니다.
  • --java_toolchain--host_java_toolchain 값을 사용합니다.
  • C++ 코드 (-c opt)에 최적화된 빌드를 사용합니다.
  • 디버깅 정보 없음 (--copt=-g0)
  • 실행 파일 및 공유 라이브러리에서 디버그 정보를 제거합니다(--strip=always).
  • 파생된 모든 파일은 가능한 요청 구성에서 사용하는 것과 구별되는 특별한 위치에 배치합니다.
  • 빌드 데이터로 바이너리의 스탬핑을 표시하지 않습니다 (--embed_* 옵션 참고).
  • 다른 모든 값은 기본값으로 유지됩니다.

요청 구성에서 고유한 호스트 구성을 선택하는 것이 더 좋을 수 있는 이유는 다양합니다. 여기서 언급하기에는 너무 난해한 내용도 있지만 그중 두 가지를 지적해 보죠.

첫째, 제거된 최적화된 바이너리를 사용하여 도구를 링크하고 실행하는 데 소요되는 시간, 도구가 차지하는 디스크 공간, 분산 빌드의 네트워크 I/O 시간을 줄입니다.

두 번째로, 모든 빌드에서 호스트와 요청 구성을 분리하면 앞서 설명한 대로 요청 구성을 약간 변경한 결과 (예: 링커 옵션 변경)로 인해 발생하는 매우 비싼 재빌드를 피할 수 있습니다.

그러나 특정 빌드의 경우 이 옵션이 방해가 될 수 있습니다. 특히 구성 변경이 자주 발생하지 않는 빌드 (특히 특정 자바 빌드)와 호스트 및 대상 구성 모두에서 빌드해야 하는 코드 양이 많은 빌드는 도움이 되지 않을 수 있습니다.

증분 재빌드 수정

Bazel 프로젝트의 주요 목표 중 하나는 올바른 점진적 재빌드를 보장하는 것입니다. 이전 빌드 도구, 특히 Make 기반의 빌드 도구는 증분 빌드 구현에서 몇 가지 잘못된 가정을 합니다.

첫째, 파일의 타임스탬프가 단조롭게 증가합니다. 이 경우는 일반적이지만 가정이 매우 쉽습니다. 파일의 이전 버전에 동기화하면 파일의 수정 시간이 단축됩니다. Make 기반 시스템은 다시 빌드되지 않습니다.

일반적으로 Make는 파일 변경사항을 감지하지만 명령어 변경사항은 감지하지 않습니다. 특정 빌드 단계에서 컴파일러에 전달된 옵션을 변경하면 Make가 컴파일러를 다시 실행하지 않습니다. make clean를 사용하여 이전 빌드의 잘못된 출력을 수동으로 삭제해야 합니다.

또한 하위 프로세스가 출력 파일에 쓰기를 시작한 후 하위 프로세스 중 하나가 실패해도 강력하지 않습니다. 현재 Make의 실행은 실패하지만 이후의 Make 호출은 잘린 출력 파일이 유효하다고 가정하며 (입력 파일보다 최신 버전이므로) 다시 빌드되지 않습니다. 마찬가지로 Make 프로세스가 종료되면 비슷한 상황이 발생할 수 있습니다.

Bazel은 이러한 가정과 기타 가정을 피합니다. Bazel은 이전에 완료된 모든 작업의 데이터베이스를 유지관리하며, 빌드 단계에 대한 입력 파일 세트 (및 타임스탬프)와 해당 빌드 단계의 컴파일 명령어가 데이터베이스에 있는 것과 정확히 일치하고, 데이터베이스 항목의 출력 파일 (및 타임스탬프)이 디스크에 있는 파일의 타임스탬프와 정확히 일치하는 경우에만 빌드 단계를 생략합니다. 입력 파일 또는 출력 파일을 변경하거나 명령어 자체를 변경하면 빌드 단계가 다시 실행됩니다.

올바른 증분 빌드 사용자가 얻는 이점은 혼란으로 인해 낭비되는 시간이 줄어든다는 것입니다. (또한 필요한 경우 또는 선제 여부에 관계없이 make clean를 사용하여 발생하는 재빌드를 기다리는 데 드는 시간이 적습니다.)

빌드 일관성 및 증분 빌드

공식적으로는 예상되는 모든 출력 파일이 존재할 때 빌드 상태를 일관된 상태로 정의하고 해당 콘텐츠를 만드는 데 필요한 단계 또는 규칙에 따라 그 콘텐츠가 올바르다고 정의합니다. 소스 파일을 수정하면 빌드 상태가 비일관이라고 하며 다음번 빌드 완료 시 성공적인 완료가 될 때까지 빌드 상태가 일관되지 않습니다. 이 상황은 불안정할 수 있습니다. 이는 일시적인 현상이며 빌드 도구를 실행하여 일관성을 복원하기 때문입니다.

또 다른 종류의 불일치로는 안정적인 불일치가 있습니다. 빌드가 안정적으로 일관되지 않은 상태에 도달하면 빌드 도구를 반복적으로 호출해도 일관성을 복원하지 못합니다. 즉, 빌드가 '중단'되고 출력이 잘못된 상태로 유지됩니다. 안정화되지 않은 상태가 Make 및 기타 빌드 도구 사용자가 make clean을 입력하는 주요 이유입니다. 빌드 도구가 이러한 방식으로 실패한 것을 발견하고 복구하는 것은 시간이 많이 걸리고 매우 좌절할 수 있습니다.

개념적으로, 일관된 빌드를 달성하는 가장 간단한 방법은 이전 빌드 출력을 모두 삭제하고 다시 시작하는 것입니다. 즉, 모든 빌드를 클린 빌드로 만듭니다. 이 방법은 (출시 엔지니어를 제외한) 실질적으로 시간이 너무 오래 걸리므로 유용할 수 있습니다. 따라서 빌드 도구는 일관성을 저하시키지 않으면서 증분 빌드를 실행할 수 있어야 합니다.

올바른 증분 종속 항목 분석은 어렵고, 위에서 설명한 것처럼 다른 많은 빌드 도구는 증분 빌드 중 안정적인 일관되지 않은 상태를 방지하는 역할을 하지 못합니다. 반면에 Bazel은 다음과 같은 보장을 합니다. 즉, 사용자가 빌드 도구를 수정하지 않은 상태로 빌드한 후 빌드가 일관된 상태를 유지하게 됩니다. (빌드 중에 소스 파일을 수정하는 경우 Bazel은 현재 빌드의 결과 일관성에 대해 보장하지 않습니다. 그러나 다음 빌드의 결과가 일관성을 복원한다고 보장할 수 있습니다.)

모든 보장과 마찬가지로 몇 가지 작은 세부 사항이 있습니다. Bazel과 안정적으로 일관되지 않은 상태를 유지하는 몇 가지 알려진 방법이 있습니다. Google은 점진적 종속 항목 분석에서 버그를 찾기 위한 의도적인 시도로 인해 발생하는 문제를 조사한다고 보장하지 않지만, 빌드 도구의 일반적 또는 '합리적인' 사용으로 인해 발생하는 모든 일관되지 않은 모든 상태를 조사하고 해결하기 위해 최선을 다합니다.

Bazel과 일치하지 않는 상태가 안정적으로 감지되는 경우 버그를 신고하세요.

샌드박스 실행

Bazel은 샌드박스가 작업을 밀폐되고 올바르게 실행되도록 보장합니다. Bazel은 도구가 작업을 수행하는 데 필요한 최소한의 파일 집합만 포함하는 샌드박스에서 생성 (느슨하게: 작업)을 실행합니다. 현재 샌드박스는 CONFIG_USER_NS 옵션이 사용 설정된 Linux 3.12 이상과 macOS 10.11 이상에서 작동합니다.

빌드가 밀폐되지 않을 수 있고 알 수 없는 방식으로 호스트 시스템에 영향을 줄 수 있다는 사실을 알려주기 위해 시스템에서 샌드박스를 지원하지 않는 경우 Bazel이 경고를 출력합니다. 이 경고를 사용 중지하려면 --ignore_unsupported_sandboxing 플래그를 Bazel에 전달하면 됩니다.

Google Kubernetes Engine 클러스터 노드 또는 Debian과 같은 일부 플랫폼에서는 보안 문제로 인해 기본적으로 사용자 네임스페이스가 비활성화됩니다. /proc/sys/kernel/unprivileged_userns_clone 파일을 살펴보면 이를 확인할 수 있습니다. 파일이 있고 0이 포함된 경우 사용자 네임스페이스를 sudo sysctl kernel.unprivileged_userns_clone=1으로 활성화할 수 있습니다.

시스템 설정으로 인해 Bazel 샌드박스가 규칙을 실행하지 못하는 경우가 있습니다. 일반적으로 namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory와 유사한 메시지를 출력하는 증상입니다. 이 경우 --strategy=Genrule=standalone가 있는 genrule을 위한 샌드박스와 --spawn_strategy=standalone를 사용하는 다른 규칙에 대한 샌드박스를 비활성화해 보세요. 또한 Issue Tracker에서 버그를 신고하고 사용 중인 Linux 배포 버전을 언급하여 Google에서 조사한 후 후속 버전에서 수정 사항을 제공할 수 있도록 하세요.

빌드 단계

Bazel에서 빌드는 세 단계로 구분됩니다. 사용자는 빌드 간 차이를 이해하면 빌드를 제어하는 옵션에 대한 유용한 정보를 얻을 수 있습니다(아래 참고).

로드 단계

첫 번째는 초기 로드에 필요한 모든 BUILD 파일과 종속 항목의 전이적 클로저가 로드, 파싱, 평가, 캐시되는 로드입니다.

Bazel 서버가 시작된 후 첫 번째 빌드의 경우 로드 시스템에서는 일반적으로 파일 시스템에서 많은 BUILD 파일이 로드되므로 수 초가 걸립니다. 특히 BUILD 파일이 변경되지 않은 경우 후속 빌드에서 로드 속도가 매우 빨라집니다.

이 단계 동안 보고된 오류에는 패키지를 찾을 수 없음, 타겟을 찾을 수 없음, BUILD 파일의 어휘 및 문법 오류, 평가 오류가 포함됩니다.

분석 단계

두 번째 단계인 분석에서는 각 빌드 규칙의 시맨틱 분석과 검증, 빌드 종속 항목 그래프 구성, 빌드의 각 단계에서 해야 할 작업을 정확하게 결정합니다.

로드와 마찬가지로 전체 분석도 수행하는 데 몇 초가 걸립니다. 그러나 Bazel은 종속 항목 그래프를 한 빌드에서 다음 빌드로 캐시하고 필요한 항목만 재분석하므로 이전 빌드 이후 패키지가 변경되지 않은 경우 증분 빌드가 매우 빨라질 수 있습니다.

이 단계에서 보고된 오류에는 부적절한 종속 항목, 규칙에 대한 잘못된 입력, 모든 규칙별 오류 메시지가 포함됩니다.

이 단계에서 Bazel이 불필요한 파일 I/O를 피하고 수행할 작업을 결정하기 위해 BUILD 파일만 읽으므로 로드 및 분석 단계가 빠릅니다. 이는 Bazel이 설계 단계이므로 로드 단계 위에서 구현되는 Bazel의 query 명령어와 같은 분석 도구의 좋은 기반이 됩니다.

실행 단계

빌드의 세 번째이자 마지막 단계는 실행입니다. 이 단계를 통해 빌드 내 각 단계의 출력이 입력과 일관되도록 하여 필요한 경우 컴파일/링크 등을 다시 실행합니다. 이 단계에서는 빌드가 대규모 빌드의 대부분을 소비합니다. 대규모 빌드의 경우 몇 초에서 1시간 이상까지 소요됩니다. 이 단계 중에 보고된 오류에는 소스 파일 누락, 일부 빌드 작업으로 실행된 도구 오류, 도구에서 예상 출력 세트를 생성하는 오류가 포함됩니다.