Bazel 코드베이스

문제 신고 <ph type="x-smartling-placeholder"></ph> 소스 보기 1박 · 7.2 · 7.1 · 7.0 · 6.5 · 6.4

이 문서는 코드베이스 및 Bazel 구조화 방식에 관한 설명입니다. 그것은 최종 사용자가 아닌 Bazel에 기여하고자 하는 사람들을 대상으로 합니다.

소개

Bazel의 코드베이스는 큽니다 (약 350KLOC 프로덕션 코드 및 약 260 KLOC 테스트). 누구도 전체 환경에 대해 잘 알지 못합니다. 모든 사람이 자신의 매우 잘 어울리지만, 모든 산 중턱에 무엇이 있는지 아는 사람은 거의 없습니다. 방향을 설정할 수 있습니다.

여정의 중간 단계에 있는 사람들이 이 문서에서는 어두운 숲속의 통로가 없어져 보다 쉽게 시작할 수 있도록 코드베이스에 대한 개요를 제공합니다. 작업 중입니다.

Bazel 소스 코드의 공개 버전은 GitHub의 github.com/bazelbuild/bazel 이것은 '정보 소스' Google 내부 소스 트리에서 파생되며 Google 외부에서 유용하지 않은 추가 기능이 포함되어 있습니다. 이 장기적인 목표는 GitHub를 정보 소스로 만드는 것입니다.

참여는 일반적인 GitHub pull 요청 메커니즘을 통해 허용됩니다. 내부 소스 트리로 가져온 후 GitHub로 다시 내보냈습니다.

클라이언트/서버 아키텍처

Bazel의 대부분은 빌드 간에 RAM에 머무르는 서버 프로세스에 있습니다. 이렇게 하면 Bazel이 빌드 간에 상태를 유지할 수 있습니다.

이것이 Bazel 명령줄에 두 가지 옵션, 즉 시작 및 명령어와 함께 사용하면 됩니다 명령줄에서 다음과 같이 합니다.

    bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar

일부 옵션 (--host_jvm_args=)이 실행할 명령어 이름 앞에 있습니다. 일부는 (-c opt) 이후입니다. 전자의 종류는 '시작 옵션'이라고 합니다. 및 서버 프로세스 전체에 영향을 미치는 반면, 후자의 종류는 "명령어는 단일 명령에만 영향을 미칩니다.

각 서버 인스턴스에는 연결된 단일 작업공간 (소스 모음)이 있습니다. '저장소')라는 트리가 있고 일반적으로 각 작업공간에는 단일 활성 가상 머신 인스턴스를 시작합니다 맞춤 출력 기반을 지정하여 우회할 수 있습니다. 자세한 내용은 '디렉터리 레이아웃' 섹션을 참고하세요.

Bazel은 유효한 .zip 파일이기도 한 단일 ELF 실행 파일로 배포됩니다. bazel를 입력하면 위의 ELF 실행 파일이 C++( 제어됩니다. 이것은 올바른 서버 프로세스를 설정하는데, 다음 단계를 따르세요.

  1. 이미 추출되었는지 여부를 확인합니다. 그렇지 않은 경우에는 자동으로 실행됩니다. 이 서버 구현이 시작되는 위치입니다.
  2. 작동 중인 활성 서버 인스턴스가 있는지, 인스턴스가 실행 중인지, 시작 옵션이 적절하고 올바른 작업공간 디렉터리를 사용합니다 그것은 $OUTPUT_BASE/server 디렉터리를 확인하여 실행 중인 서버를 찾습니다. 서버가 수신 대기 중인 포트가 있는 잠금 파일이 있습니다.
  3. 필요한 경우 이전 서버 프로세스를 종료합니다.
  4. 필요한 경우 새 서버 프로세스 시작

적절한 서버 프로세스가 준비되면 실행해야 하는 명령은 gRPC 인터페이스를 통해 통신하면 Bazel의 출력이 터미널에 전달합니다 동시에 하나의 명령어만 실행할 수 있습니다. 이것은 C++로 된 정교한 잠금 메커니즘을 사용하여 Java 여러 명령을 병렬로 실행하기 위한 인프라가 있습니다. 다른 명령어와 동시에 bazel version를 실행할 수 없으므로 다소 당황스러울 수 있습니다 주요 장애는 BlazeModule의 수명 주기입니다. BlazeRuntime에 있습니다.

명령이 끝나면 Bazel 서버가 종료 코드를 클라이언트로 반환합니다. 흥미로운 점은 bazel run의 구현입니다. Bazel이 방금 빌드한 것을 실행하는 것이지만 그렇게 할 수는 없습니다 서버 프로세스에서 얻습니다. 터미널이 없기 때문입니다. 대신 클라이언트에게 ujexec()해야 하는 바이너리와 함께 어떤 인수를 사용해야 하는지를 지정해야 합니다.

Ctrl-C를 누르면 클라이언트는 이를 gRPC의 Cancel 호출로 변환합니다. 가능한 한 빨리 명령어를 종료하려고 시도합니다. 그런 다음 세 번째 Ctrl-C를 누르면 클라이언트가 대신 SIGKILL을 서버로 보냅니다.

클라이언트의 소스 코드는 src/main/cpp 아래에 있으며 서버와의 통신은 src/main/protobuf/command_server.proto에 있습니다 .

서버의 기본 진입점은 BlazeRuntime.main()이며 gRPC 호출 GrpcServerImpl.run()에서 처리됩니다.

디렉터리 레이아웃

Bazel은 빌드 중에 다소 복잡한 디렉터리 집합을 만듭니다. 전체 설명은 출력 디렉터리 레이아웃에서 확인할 수 있습니다.

'기본 저장소' Bazel이 실행되는 소스 트리입니다. 일반적으로 소스 제어에서 체크아웃한 것입니다 이 디렉터리의 루트는 '작업공간 루트'라고 합니다

Bazel은 모든 데이터를 '출력 사용자 루트' 아래에 넣습니다. 일반적으로 $HOME/.cache/bazel/_bazel_${USER}를 지원하지만 --output_user_root 시작 옵션

'설치한 사용자 수' 에서 Bazel이 추출됩니다 이 작업은 자동으로 수행됩니다. 각 Bazel 버전은 설치한 사용자 수 기본값은 $OUTPUT_USER_ROOT/install이며 변경할 수 있습니다. --install_base 명령줄 옵션을 사용하여 쿼리합니다.

'출력 기반' Bazel 인스턴스가 특정 작업공간이 쓰기 대상입니다 각 출력 베이스에는 Bazel 서버 인스턴스가 최대 1개 있습니다. 실행할 수 있습니다 보통 $OUTPUT_USER_ROOT/<checksum of the path to the workspace>에 있습니다. --output_base 시작 옵션을 사용하여 변경할 수 있습니다. 무엇보다도 이 기능은 고가용성을 유지해야 하는 하나의 Bazel 인스턴스가 언제든지 모든 작업공간에서 실행될 수 있습니다.

출력 디렉터리에는 다음 항목이 포함됩니다.

  • $OUTPUT_BASE/external에서 가져온 외부 저장소입니다.
  • exec 루트(모든 소스의 심볼릭 링크가 포함된 디렉터리) 생성합니다. $OUTPUT_BASE/execroot에 있습니다. 기간 중 빌드 시 작업 디렉터리는 $EXECROOT/<name of main repository>입니다. $EXECROOT로 변경할 계획이지만 매우 어울리지 않는 변화이기 때문입니다.
  • 빌드 중에 빌드된 파일

명령을 실행하는 프로세스

Bazel 서버가 제어권을 받고 실행하면 다음과 같은 이벤트 시퀀스가 발생합니다.

  1. BlazeCommandDispatcher이 새 요청에 대한 알림을 받습니다. 결정 명령어를 실행할 작업공간이 필요한지 여부 (다음을 제외한 거의 모든 명령어 버전 또는 소스 코드처럼 소스 코드와 관련이 없는 다른 명령이 실행 중인지 여부를 확인할 수 있습니다.

  2. 올바른 명령어를 찾습니다. 각 명령어는 인터페이스를 구현해야 함 BlazeCommand이며 @Command 주석이 있어야 합니다 (약간의 명령에 필요한 모든 메타데이터가 BlazeCommand의 메서드에서 설명됨)

  3. 명령줄 옵션이 파싱됩니다. 각 명령어는 서로 다른 명령줄을 사용합니다. @Command 주석에 설명되어 있습니다.

  4. 이벤트 버스가 생성됩니다. 이벤트 버스는 발생하는 이벤트의 스트림입니다. 확인할 수 있습니다 이 중 일부는 빌드 이벤트 프로토콜의 기본입니다. 있습니다.

  5. 명령어가 제어할 수 있습니다. 가장 흥미로운 명령어는 빌드, 테스트, 실행, 적용 범위 등입니다. 이 기능은 BuildTool로 구현됩니다.

  6. 명령줄의 타겟 패턴 집합은 파싱되며, //pkg:all//pkg/... 문제가 해결되었습니다. 이는 AnalysisPhaseRunner.evaluateTargetPatterns(), Skyframe에서 다음으로 구체화됨 TargetPatternPhaseValue입니다.

  7. 로딩/분석 단계는 액션 그래프 (방향성 빌드에 대해 실행해야 하는 명령의 비순환 그래프).

  8. 실행 단계가 실행됩니다. 즉, 데이터 레이크에 필요한 모든 요청된 최상위 대상을 빌드합니다.

명령줄 옵션

Bazel 호출의 명령줄 옵션은 OptionsParsingResult 객체: 'option'의 지도를 차례로 포함합니다. 클래스" 옵션 값에 추가합니다 '옵션 클래스' 은 OptionsBase 및 각각과 관련된 명령줄 옵션을 그룹화함 있습니다. 예를 들면 다음과 같습니다.

  1. 프로그래밍 언어와 관련된 옵션입니다 (CppOptions 또는 JavaOptions). FragmentOptions의 서브클래스여야 하며 최종적으로 래핑됩니다. BuildOptions 객체로 변환합니다.
  2. Bazel이 작업을 실행하는 방식과 관련된 옵션 (ExecutionOptions)

이러한 옵션은 분석 단계 및 Java의 RuleContext.getFragment() 또는 Starlark의 ctx.fragments를 통해). 그중 일부 (예: C++ 검사 포함 여부)는 판독됩니다. 항상 명시적인 배관이 필요하기 때문에 그때는 BuildConfiguration을(를) 사용할 수 없습니다. 자세한 내용은 '구성' 섹션으로 이동합니다

경고: OptionsBase 인스턴스는 변경할 수 없으며 이러한 방식으로 사용해야 합니다 (예: SkyKeys의 일부). 이는 사실이 아니며 그것을 수정하는 것은 어렵고 미묘한 방식으로 Bazel을 깨뜨릴 수 있는 좋은 방법입니다. 디버깅할 수 있습니다 불행히도, 이를 실제로 변경할 수 없게 만들기 위해서는 상당한 노력이 필요합니다. (다른 사람보다 먼저 생성 직후에 FragmentOptions 수정 equals() 또는 hashCode()가 를 호출해도 괜찮습니다.)

Bazel은 다음과 같은 방법으로 옵션 클래스에 대해 알아봅니다.

  1. 일부는 Bazel에 내장되어 있습니다 (CommonCommandOptions).
  2. 각 Bazel 명령어의 @Command 주석에서
  3. ConfiguredRuleClassProvider에서 (이는 이와 관련된 명령줄 옵션임) 변환)
  4. Starlark 규칙은 자체 옵션을 정의할 수도 있습니다 (자세한 내용은 여기)

각 옵션 (Starlark에서 정의한 옵션 제외)은 @Option 주석이 있는 FragmentOptions 서브클래스로, 다음을 지정합니다. 몇 가지 도움말 텍스트와 함께 명령줄 옵션의 이름 및 유형을 확인할 수 있습니다.

명령줄 옵션 값의 Java 유형은 일반적으로 간단합니다. (문자열, 정수, 부울, 라벨 등) 그러나 더 복잡한 유형의 옵션 이 경우에는 데이터 유형에 대한 명령줄 문자열은 com.google.devtools.common.options.Converter

Bazel에서 보는 소스 트리

Bazel은 소프트웨어 빌드 사업에 종사하고 있습니다. 소스 코드를 해석하는 데 사용됩니다. Bazel이 작동하는 소스 코드 전체는 '작업공간'이라고 합니다 저장소, 패키지, 컨테이너로 구조화되어 있습니다.

저장소

'저장소' 개발자가 작업하는 소스 트리입니다. 보통 는 단일 프로젝트를 나타냅니다. Bazel의 조상 Blaze는 모노레포로 즉 빌드를 실행하는 데 사용되는 모든 소스 코드가 포함된 단일 소스 트리입니다. 반면 Bazel은 소스 코드가 여러 영역에 걸쳐 있는 프로젝트를 저장합니다 Bazel이 호출되는 저장소는 '기본' 저장소라고 합니다. 다른 저장소는 '외부 저장소'라고 합니다.

저장소는 저장소 경계 파일 (MODULE.bazel, REPO.bazel 또는 와 같은 기존 컨텍스트, WORKSPACE 또는 WORKSPACE.bazel)는 루트 디렉터리에 있습니다. 이 main repo는 Bazel을 호출하는 소스 트리입니다. 외부 저장소 다양한 방식으로 정의됩니다 외부 종속 항목 보기 개요를 참조하세요.

외부 저장소의 코드는 아래에 심볼릭 링크되거나 다운로드됩니다. $OUTPUT_BASE/external

빌드를 실행할 때 전체 소스 트리를 결합해야 합니다. 이번 기본 저장소의 모든 패키지를 심볼릭 링크로 연결하는 SymlinkForest에 의해 실행됩니다. $EXECROOT로, 모든 외부 저장소는 $EXECROOT/external 또는 $EXECROOT/..입니다.

패키지

모든 저장소는 패키지, 관련 파일 모음 및 종속 항목 사양을 제공합니다 이는 BUILD 또는 BUILD.bazel 둘 다 있는 경우 Bazel은 BUILD.bazel를 선호합니다. 이유 BUILD 파일이 계속 허용되는 이유는 Bazel의 상위 Blaze에서 파일 이름 그러나 이 경로는 특히 (파일 이름은 대소문자를 구분하지 않음)

패키지는 서로 독립적입니다. 패키지의 BUILD 파일이 변경됩니다. 다른 패키지가 변경되도록 할 수 없습니다. BUILD 파일 추가 또는 삭제 재귀 glob은 패키지 경계에서 중지되므로 다른 패키지를 _변경할 수 있습니다. 따라서 BUILD 파일이 있으면 재귀가 중지됩니다.

BUILD 파일의 평가를 '패키지 로드'라고 합니다. Kubernetes는 PackageFactory 클래스에서 작동하며 Starlark 인터프리터를 호출하고 사용 가능한 규칙 클래스에 대한 지식이 필요합니다. 패키지 결과 로드는 Package 객체입니다. 이것은 주로 문자열( 타겟)를 타겟 자체에 추가합니다.

패키지 로드 중 복잡성이 큰 부분은 글로빙(globbing)입니다. Bazel은 모든 소스 파일을 명시적으로 나열해야 하며 대신 glob (예: glob(["**/*.java"])) 셸과 달리 반복적 glob을 지원합니다. 하위 디렉터리로 내려갑니다 (하위 패키지로는 내려가지 않음). 이 작업을 수행하려면 다음에 액세스해야 합니다. 이 속도가 느려질 수 있으므로 모든 종류의 트릭을 구현하여 가능한 한 효율적으로 동시에 실행할 수 있도록 합니다.

글로빙은 다음 클래스에서 구현됩니다.

  • LegacyGlobber, 빠르고 행복한 Skyframe을 인식하지 못하는 글로버
  • SkyframeHybridGlobber - Skyframe을 사용하고 '스카이프레임 재시작'을 방지하기 위해 (아래에 설명)

Package 클래스 자체에는 'external' 패키지(외부 종속 항목과 관련됨)와 종속되지 않는 패키지(예: 이해하는 것이 중요합니다. 이것은 일반 패키지를 설명하는 객체에는 다른 것을 설명하는 필드입니다. 예를 들면 다음과 같습니다.

  • 저장소 매핑
  • 등록된 도구 모음
  • 등록된 실행 플랫폼

이상적으로는 '외부' 택배 일반 패키지를 파싱하지 않기 때문에 Package가 둘 다 필요합니다 안타깝지만 이 작업은 쉽지 않습니다. 왜냐하면 이 둘은 복잡하게 얽혀 있습니다.

라벨, 대상, 규칙

패키지는 다음과 같은 유형을 가진 타겟으로 구성됩니다.

  1. Files: 빌드의 입력 또는 출력입니다. 포함 Bazel 용어로는 이를 아티팩트라고 부릅니다 (다른 곳에서 설명). 일부 빌드 중에 생성된 파일이 타겟입니다. 인코더-디코더 출력과 Bazel에 연결된 라벨이 없어야 합니다.
  2. 규칙: 입력에서 출력을 얻는 단계를 설명합니다. 그들은 일반적으로 프로그래밍 언어 (예: cc_library, java_library 또는 py_library)을 사용하지만 언어를 가리지 않는 경우도 있습니다. (예: genrule 또는 filegroup)
  3. 패키지 그룹: 공개 상태 섹션에서 설명합니다.

대상의 이름을 라벨이라고 합니다. 라벨의 구문은 @repo//pac/kage:name, 여기서 repo은 라벨이 있는 저장소의 이름입니다. 여기서 pac/kageBUILD 파일이 있는 디렉터리이고 name는 해당 파일 (라벨이 소스 파일을 참조하는 경우)은 패키지에서 찾을 수 있습니다. 명령줄에서 타겟을 언급할 때, 라벨의 일부분은 생략할 수 있습니다.

  1. 저장소가 생략되면 라벨이 기본 저장소
  2. 패키지 부분이 생략되면 (예: name 또는 :name) 라벨이 사용됩니다. 현재 작업 디렉터리의 패키지에 있어야 합니다 (상대 경로). 상위 수준 참조 (..) 포함은 허용되지 않습니다.

규칙의 일종 (예: 'C++ 라이브러리')을 '규칙 클래스'라고 합니다. 규칙 클래스는 Starlark (rule() 함수) 또는 Java (소위)에서 구현되어야 합니다. '네이티브 규칙', 유형 RuleClass) 장기적으로는 모든 언어별 Starlark에서 구현될 예정이지만 Java와 같은 일부 기존 규칙군 또는 C++)는 당분간 여전히 Java에 있습니다.

BUILD 파일의 시작 부분에서 Starlark 규칙 클래스를 가져와야 합니다. load() 문을 사용하는 반면 Java 규칙 클래스는 '원래'입니다. 알려진 사람 Bazel이 ConfiguredRuleClassProvider에 등록되어 있기 때문입니다.

Rule 클래스에는 다음과 같은 정보가 포함됩니다.

  1. 속성 (예: srcs, deps): 유형, 기본값, 제약 조건 등이 있습니다.
  2. 구성 전환 및 각 속성에 연결된 관점(있는 경우)
  3. 규칙 구현
  4. '일반적으로' 규칙은 만들기

용어 참고사항: 코드베이스에서는 종종 '규칙'을 사용합니다. 대상임을 의미하기 위해 할 수 있습니다. 하지만 Starlark와 사용자 대상 문서에서 '규칙' 규칙 클래스 자체를 참조하는 용도로만 사용되어야 합니다. 대상 '타겟'일 뿐입니다. RuleClass에 'class'가 있더라도 를 규칙 클래스와 대상 간에 Java 상속 관계가 없음 있습니다.

스카이프레임

Bazel의 기반이 되는 평가 프레임워크를 Skyframe이라고 합니다. 모델은 빌드 중에 빌드해야 하는 모든 것은 데이터 조각에서 종속 항목을 가리키는 에지가 있는 비순환 그래프 즉, 그것을 구성하기 위해 알아야 하는 다른 데이터 조각이 있습니다.

그래프의 노드를 SkyValue라고 하고 노드 이름을 SkyKey 둘 다 완전히 변경할 수 없습니다. 불변 객체만 연결을 제공합니다. 이 불변량은 거의 항상 유지되고, 그렇지 않은 경우에는 예를 들어BuildOptions BuildConfigurationValueSkyKey)를 변경하지 않고 외부에서 관찰할 수 없는 방식으로만 변경하는 것입니다. 이를 통해 스카이프레임 내에서 계산된 모든 값 (예: 구성된 대상) 또한 변경할 수 없어야 합니다.

스카이프레임 그래프를 관찰하는 가장 편리한 방법은 그래프를 한 줄에 하나씩 SkyValue씩 덤프하는 bazel dump --skyframe=deps를 실행하는 것입니다. 최고 꽤 커질 수 있기 때문에 작은 빌드에 사용합니다.

스카이프레임은 com.google.devtools.build.skyframe 패키지에 포함됩니다. 이 유사한 이름의 패키지 com.google.devtools.build.lib.skyframe에는 Skyframe 위에 Bazel을 구현했습니다. 스카이프레임에 대한 자세한 내용은 여기에서 확인하세요.

지정된 SkyKeySkyValue로 평가하기 위해 스카이프레임은 키 유형에 해당하는 SkyFunction입니다. 함수의 평가가 있는 경우, 다음 메서드를 호출하여 스카이프레임에서 다른 종속 항목을 요청할 수도 있습니다. SkyFunction.Environment.getValue()의 다양한 오버로드 여기에는 종속 항목을 Skyframe의 내부 그래프에 등록하는 부작용이 있으므로 함수를 재평가하는 것을 인지하고 있습니다. 즉, Skyframe의 캐싱과 증분 계산은 SkyFunctionSkyValue의 세부사항

SkyFunction가 사용할 수 없는 종속 항목을 요청할 때마다 getValue() null을 반환합니다. 그러면 함수가 자체가 null을 반환합니다. 이후 언젠가 스카이프레임은 사용할 수 없는 경우 함수를 처음부터 다시 시작하세요. getValue() 호출이 null이 아닌 결과와 함께 성공합니다.

결과적으로 SkyFunction 내부에서 수행되는 모든 계산은 이 반복되어야 할 수도 있습니다. 그러나 여기에는 캐시된 종속 항목 SkyValues를 평가합니다. 따라서 일반적으로 다음과 같은 방법으로 이 문제를 해결합니다.

  1. getValuesAndExceptions()를 사용하여 종속 항목을 일괄로 선언 재시작 횟수를 제한합니다
  2. SkyValue를 다른 방식으로 계산된 별도의 조각으로 분할 SkyFunction를 사용하여 독립적으로 계산하고 캐시할 수 있습니다. 이 전략적으로 수행해야 하며, 이렇게 하면 메모리가 사용합니다
  3. 다음을 사용하여 재시작 간에 상태 저장 SkyFunction.Environment.getState() 또는 임시 정적 캐시 유지 '스카이프레임 뒷면' 복잡한 SkyFunctions로 상태 관리 까다로울 수 있으므로 StateMachine은 논리적 동시 실행에 대해 구조화된 접근 방식을 제공하도록 설계되었습니다. SkyFunction 내에서 계층적 계산을 재개합니다. 예: DependencyResolver#computeDependencies 드림 StateMachinegetState()와 함께 사용하여 잠재적으로 거대한 집합을 계산합니다. 구성된 대상의 직접 종속 항목이며, 그렇지 않을 경우 리소스를 많이 사용합니다

기본적으로 Bazel은 이러한 유형의 해결 방법이 필요합니다. 수천 개의 Skyframe 노드가 일반적이며 Java의 경량형 스레드는 스레드를 능가하지 않으며 2023년 현재 StateMachine 구현

스타라크

Starlark는 사용자가 구성하고 확장하는 데 사용하는 도메인별 언어입니다. Bazel을 사용하세요. 유형이 훨씬 적은 Python의 제한된 하위 집합으로 생각됩니다. 제어 흐름에 대한 더 많은 제한, 무엇보다도 강력한 불변성 동시 읽기를 사용 설정할 수 있습니다 튜링 완전체가 아닌데, 모든 사용자가 아닌 일부 사용자가 일반적인 목표를 달성하려는 프로그래밍 작업을 수행할 수 있습니다.

Starlark는 net.starlark.java 패키지에서 구현됩니다. 또한 독립적인 Go 구현으로 여기에서 확인할 수 있습니다. Java 현재 인터프리터 구현은 Bazel에서 사용됩니다.

Starlark는 다음과 같은 여러 문맥에서 사용됩니다.

  1. BUILD 파일. 여기에서 새 빌드 대상이 정의됩니다. 스타라크 이 컨텍스트에서 실행되는 코드는 BUILD의 콘텐츠에만 액세스할 수 있습니다. 파일 자체 및 그것에 의해 로드된 .bzl 파일.
  2. MODULE.bazel 파일 여기에 외부 종속 항목이 있습니다. 정의할 수 있습니다 이 컨텍스트에서 실행되는 Starlark 코드는 액세스가 매우 제한적입니다. 몇 가지 사전 정의된 지시어로 전달할 수 있습니다
  3. .bzl 파일. 여기에서 새로운 빌드 규칙, 저장소 규칙, 모듈을 확장 프로그램을 정의합니다 여기의 Starlark 코드는 새 함수를 정의하고 다른 .bzl 파일에서 가져옴

BUILD.bzl 파일에서 사용할 수 있는 언어가 약간 다릅니다. 서로 다른 것을 표현하기 때문입니다. 차이점 목록을 사용할 수 있습니다. 여기에서 확인할 수 있습니다.

Starlark에 관한 자세한 내용은 여기에서 확인할 수 있습니다.

로드/분석 단계

로드/분석 단계에서는 Bazel이 코드 작성에 필요한 작업을 만들 수 있습니다. 기본 단위는 "구성된 대상"으로, (타겟, 구성) 쌍입니다.

이를 '로드/분석 단계'라고 합니다. 두 개의 신경망으로 나눌 수 있으므로 구분되어 있는 부분은 직렬화되어 있으나 이제는 시간이 겹칠 수도 있습니다.

  1. 패키지 로드, 즉 BUILD 파일을 Package 객체로 변환 이를 나타내는
  2. 구성된 타겟을 분석하여 액션 그래프를 생성하는 규칙

구성된 대상의 임시 폐쇄에 포함된 각 대상 상향식으로 분석해야 합니다. 리프 노드, 즉 다음으로 명령줄의 항목까지 선택할 수 있습니다 디바이스에 대한 분석에 구성된 대상은 다음과 같습니다.

  1. 구성. ('방법')을 지정합니다. 예를 들어 사용자가 원하는 명령줄 옵션과 같은 요소도 포함합니다. C++ 컴파일러로 전달됨)
  2. 직접 종속 항목. 전이 정보 제공자를 사용할 수 있습니다. 추가할 수 있습니다. 인코더 디코더 블록은 "롤업" 구성된 예를 들어 클래스 경로의 모든 .jar 파일이나 C++ 바이너리에 연결해야 함)
  3. 타겟 자체. 대상 패키지를 로드한 결과입니다. 이(가) 있는 경우 규칙의 경우 여기에는 속성이 포함됩니다. 일반적으로 중요합니다
  4. 구성된 타겟의 구현. 규칙의 경우 Starlark 또는 Java에 있습니다. 규칙으로 구성되지 않은 모든 대상이 구현되었습니다. Java로 변환할 수 있습니다.

구성된 대상을 분석한 결과는 다음과 같습니다.

  1. 여기에 종속된 표적을 구성한 전이 정보 제공자는 접근
  2. 생성할 수 있는 아티팩트와 이를 생성하는 작업

Java 규칙에 제공되는 API는 RuleContext이며 Starlark 규칙의 ctx 인수입니다. API는 더 강력하지만 동시에 Bad ThingsTM를 수행하기가 더 쉽습니다. 예를 들어 시간이나 코드가 공간의 복잡성은 2차 이상으로 나오기 때문에 (또는 그보다 더 심하게) Bazel 서버가 1초 안에 Java 예외를 사용하거나 불변을 위반하는 경우 (예: Options 인스턴스를 사용하거나 구성된 타겟을 변경 가능하도록 만들어)

구성된 타겟의 직접적인 종속 항목을 결정하는 알고리즘 DependencyResolver.dependentNodeMap()에 거주합니다.

구성

구성은 데이터를 '어떻게' 어떤 플랫폼으로, 어떤 것을 이용해 명령줄 옵션 등이 있습니다

동일한 빌드의 여러 구성에 대해 동일한 타겟을 빌드할 수 있습니다. 이 예를 들어, 실행되는 동안 실행되는 도구에 같은 코드를 사용할 때 대상 코드에 대해 크로스 컴파일 중이거나 fat Android 앱 (여러 CPU를 위한 네이티브 코드가 포함된 앱)을 아키텍처)

개념적으로 구성은 BuildOptions 인스턴스입니다. 하지만 BuildOptions는 다음을 제공하는 BuildConfiguration로 래핑됩니다. 기능을 추가했습니다. 이 함수는 포드의 상단에서 종속 항목 그래프를 아래로 스크롤하면 됩니다. 변경되는 경우 빌드는 다음과 같아야 합니다. 재분석했습니다.

이로 인해 전체 빌드를 재분석해야 하는 등의 이상치가 발생합니다. 예를 들어 요청된 테스트 실행 횟수는 테스트 대상에 영향을 미칩니다 (이렇게 하기 위해 구성을 '삭제'할 계획입니다. 아직 준비가 되지 않았습니다).

규칙 구현에 구성의 일부가 필요한 경우 이를 선언해야 하는 경우 RuleClass.Builder.requiresConfigurationFragments()를 사용하여 에서 자세한 내용을 확인하실 수 있습니다. 이는 실수 (예: Java 프래그먼트를 사용하는 Python 규칙)를 방지하기 위함과 동시에 Python 옵션이 변경되는 경우 C++로 구성 자르기를 용이하게 하여 재분석할 필요가 없습니다

규칙의 구성은 '상위' 규칙의 구성과 반드시 동일하지는 않습니다. 있습니다. 종속 항목 에지에서 구성을 변경하는 프로세스를 '구성 전환'을 선택합니다. 다음과 같은 두 가지 위치에서 발생할 수 있습니다.

  1. 종속 항목 에지에서 이러한 전환은 Attribute.Builder.cfg()Rule의 함수입니다 (여기서 BuildOptions (원래 구성)을 1로 이상 BuildOptions (출력 구성)
  2. 구성된 대상에 대한 모든 수신 에지 이들은 RuleClass.Builder.cfg()

관련 클래스는 TransitionFactoryConfigurationTransition입니다.

구성 전환이 사용됩니다. 예를 들면 다음과 같습니다.

  1. 빌드 중에 특정 종속 항목이 사용된다고 선언하고 따라서 컨테이너는 실행 아키텍처에
  2. 특정 종속 항목이 여러 애플리케이션을 위해 빌드되어야 한다고 선언 아키텍처 (예: fat Android APK의 네이티브 코드)

구성 전환으로 인해 여러 구성이 발생하는 경우 이를 분할 전환도 가능합니다

구성 전환은 Starlark에서도 구현할 수 있습니다 (문서 여기)

임시 정보 제공업체

구성된 타겟의 _유일한 _방법인 임시 정보 제공자 이에 종속된 다른 구성된 대상에 대해 알려줍니다. 그 이유 '전환' 이것은 일반적으로 일종의 롤업이며 구성된 타겟의 임시 종료

Java 전이 정보 제공자 간에는 일반적으로 1:1 대응이 있습니다. 및 Starlark 항목 (단, DefaultInfoFileProvider, FilesToRunProvider, RunfilesProvider입니다. 해당 API가 자바를 직접 음역한 것보다 스타라크어에 더 가깝다고 간주합니다. 키는 다음 중 하나입니다.

  1. Java 클래스 객체입니다. 이 기능은 Starlark에서 액세스할 수 있습니다. 이러한 제공자는 TransitiveInfoProvider
  2. 문자열. 이는 레거시이며 공격에 민감하므로 사용하지 않는 것이 좋습니다. 이름이 충돌합니다. 이러한 과도적 정보 제공자는 build.lib.packages.Info
  3. 제공업체 기호입니다. provider()를 사용하여 Starlark에서 만들 수 있습니다. 함수를 사용하는 것이 좋으며, 새 제공업체를 만드는 데 권장되는 방법입니다. 기호는 Java에서 Provider.Key 인스턴스로 표현됩니다.

Java로 구현된 새 제공자는 BuiltinProvider를 사용하여 구현해야 합니다. NativeProvider는 지원 중단되었으며 (아직 삭제할 시간이 없음) Starlark에서 TransitiveInfoProvider 서브클래스에 액세스할 수 없습니다.

구성된 대상

구성된 대상은 RuleConfiguredTargetFactory로 구현됩니다. 이 서브클래스를 만듭니다. Starlark 구성된 대상 StarlarkRuleConfiguredTargetUtil.buildRule()를 통해 생성됩니다 .

구성된 대상 팩토리는 RuleConfiguredTargetBuilder를 사용하여 다음을 수행해야 합니다. 반환 값을 구성합니다. 다음으로 구성됩니다.

  1. '이 규칙의 파일 집합'이라는 모호한 개념인 filesToBuild 나타냅니다. 이들은 구성된 타겟이 구성될 때 빌드되는 파일입니다 명령줄 또는 genrule의 src에 있습니다.
  2. 실행 파일, 일반 및 데이터.
  3. 출력 그룹 이러한 파일은 다양한 '기타 파일 집합'입니다. 규칙은 있습니다. 이러한 객체는 BUILD의 파일 그룹 규칙 및 Java에서 OutputGroupInfo 제공자 사용

실행 파일

일부 바이너리는 실행하려면 데이터 파일이 필요합니다. 대표적인 예로 입력 파일 이는 Bazel에서 '실행 파일'이라는 개념으로 표현됩니다. 가 "실행 파일 트리" 특정 바이너리에 대한 데이터 파일의 디렉터리 트리입니다. 개별 심볼릭 링크가 있는 심볼릭 링크 트리로 파일 시스템에서 생성됩니다. 출력 트리의 소스에 있는 파일을 가리킵니다.

실행 파일 집합은 Runfiles 인스턴스로 표시됩니다. Kubernetes는 개념적으로 실행 파일 트리의 파일 경로에서 실행 중인 Artifact 인스턴스로 매핑 나타냅니다. 2에 단일 Map를 사용하는 것보다 조금 더 복잡합니다. 이유:

  • 대부분의 경우 파일의 실행 파일 경로는 execpath와 동일합니다. 이것을 사용하여 약간의 RAM을 절약합니다.
  • 실행 파일 트리에는 다양한 레거시 항목이 있는데 나타냅니다.

실행 파일은 이 클래스의 인스턴스인 RunfilesProvider를 사용하여 수집됩니다. 구성된 타겟 (예: 라이브러리)의 실행 파일과 전이 파일을 나타냅니다. 중첩된 세트처럼 수집됩니다 (실제로는 커버 아래에 중첩된 세트를 사용하여 구현됨): 각 타겟은 실행 파일을 통합합니다. 자체 종속 항목을 추가하고 그 중 일부를 추가한 다음 종속 항목 그래프에 포함되어 있습니다. RunfilesProvider 인스턴스에는 두 개의 Runfiles가 포함됩니다. 인스턴스, 즉 'data' 속성 및 각 종속 항목마다 하나씩 설정해야 합니다 이는 타겟이 데이터 속성을 통해 종속될 때 다른 실행 파일을 표시하는 경우가 있음 그렇지 않은 경우에 그렇습니다. 이는 Google에서 아직 다루지 않은 바람직하지 않은 기존 동작입니다. 아직 삭제되지 않았습니다.

바이너리의 실행 파일은 RunfilesSupport의 인스턴스로 표현됩니다. 이 은 Runfiles와 다릅니다. RunfilesSupport에는 다음과 같은 기능이 있기 때문입니다. (단순 매핑인 Runfiles과 다름) 이 에는 다음과 같은 추가 구성요소가 필요합니다.

  • 입력 실행 파일 매니페스트 이 내용은 실행 파일 트리에 저장됩니다. 실행 파일 트리 콘텐츠의 프록시로 사용됩니다. Bazel은 실행 파일 트리가 매니페스트 변경사항을 확인합니다
  • 출력 실행 파일 매니페스트 이는 스테이트리스(Stateless) 컨테이너를 실행하는 특히 윈도우즈에서 실행 파일 트리를 처리할 수 있습니다. 심볼릭 링크를 제공합니다.
  • 실행 파일 미들맨 실행 파일 트리가 존재하려면 심볼릭 링크 트리와 심볼릭 링크가 가리키는 아티팩트를 빌드합니다. 순서 종속 항목 에지의 수를 줄이기 위해 실행 파일 미들맨은 이 모든 것을 나타내는 데 사용됩니다.
  • 명령줄 인수: RunfilesSupport 객체가 나타냅니다.

관점

관점은 '종속 항목 그래프를 따라 계산을 전파'하는 방법입니다. 그들은 설명: Bazel 사용자 여기에서 확인할 수 있습니다. 좋음 프로토콜 버퍼를 예로 들 수 있습니다. proto_library 규칙은 프로토콜의 구현을 빌드하는 것에 대해 모든 프로그래밍에서 버퍼 메시지 (프로토콜 버퍼의 '기본 단위')를 두 개의 대상 언어가 proto_library 규칙에 연결되어 같은 언어가 동일한 프로토콜 버퍼에 종속되므로 한 번만 빌드됩니다.

구성된 타겟과 마찬가지로 스카이프레임에 SkyValue로 표시됩니다. 구성되는 방식은 구성된 타겟이 구성되는 방식과 다음과 같은 ConfiguredAspectFactory라는 팩토리 클래스가 있습니다. RuleContext에 액세스할 수 있지만 구성된 대상 팩토리와 달리 연결된 대상 및 해당 제공업체에 대한 정보

종속 항목 그래프로 전파되는 관점 집합은 각 특성에 대해 지정됩니다. Attribute.Builder.aspects() 함수를 사용하여 속성을 반환할 수 있습니다. 몇 가지 클래스에 참여하는 혼란을 야기하는 이름의 클래스를 생성합니다.

  1. AspectClass는 관점의 구현입니다. Java 형식 또는 (이 경우 서브클래스임) 또는 Starlark (이 경우 StarlarkAspectClass 인스턴스) Kubernetes는 RuleConfiguredTargetFactory입니다.
  2. AspectDefinition은 관점의 정의입니다. 여기에는 제공하는 제공업체와 이에 대한 참조가 포함되어 있습니다. 적절한 AspectClass 인스턴스와 같이 구현되어야 합니다. 그것은 RuleClass와 유사합니다.
  3. AspectParameters는 아래로 전파되는 관점을 매개변수화하는 방법입니다. 종속 항목 그래프를 확인할 수 있습니다 현재 문자열 대 문자열 매핑입니다. 좋은 예 프로토콜 버퍼가 유용한 이유 중 하나입니다. 한 언어에 여러 API가 있으면 어떤 API에 대해 프로토콜 버퍼를 빌드해야 하는지와 관련된 정보가 있어야 합니다. 종속 항목 그래프로 전파됩니다
  4. Aspect는 특정 측면을 계산하는 데 필요한 모든 데이터를 나타냅니다. 종속 항목 그래프를 아래로 전파합니다 관점 클래스로 구성됩니다. 정의할 수 있습니다
  5. RuleAspect는 특정 규칙을 적용할 관점을 결정하는 함수입니다. 전파되어야 합니다. Rule입니다 -> Aspect 함수.

다소 예상치 못한 문제는 여러 측면이 다른 측면과 연결될 수 있다는 점입니다. 예를 들어 Java IDE의 클래스 경로를 수집하는 측면은 아마도 클래스 경로의 모든 .jar 파일에 대해 알고 싶지만 이 중 일부는 프로토콜 버퍼의 정의와 관련이 있습니다. 이 경우 IDE 측면은 (proto_library 규칙 + Java proto 관점) 쌍.

관점의 복잡성은 클래스에 캡처됨 AspectCollection

플랫폼 및 도구 모음

Bazel은 다중 플랫폼 빌드, 다시 말해 빌드 작업이 실행되는 여러 아키텍처와 확인할 수 있습니다 Bazel에서는 이러한 아키텍처를 플랫폼이라고 합니다. 용어 (전체 문서) 여기)

플랫폼은 제약조건 설정의 키-값 매핑 (예: 'CPU 아키텍처'의 개념을 제한 (예: 특정 CPU) x86_64). '사전'이 있습니다. 가장 일반적으로 사용되는 제약조건의 @platforms 저장소의 설정 및 값에서 확인할 수 있습니다.

도구 모음의 개념은 어떤 플랫폼에 사용되는지에 따라 대상 플랫폼에 맞게 빌드하려면 다른 컴파일러에 대해서 예를 들어 특정 C++ 툴체인이 다른 OS를 표적으로 삼을 수 있습니다. Bazel은 항상 C++로 설정된 실행 및 타겟 플랫폼에 기반하여 사용되는 컴파일러 (도구 모음 문서 여기)에서 확인할 수 있습니다.

이를 위해 도구 모음에 실행 집합으로 주석이 추가되고 지원하는 타겟 플랫폼 제약 조건을 적용할 수 있습니다 이를 위해 도구 모음은 두 부분으로 나뉩니다.

  1. 실행 및 대상 집합을 설명하는 toolchain() 규칙 툴체인이 지원하는 제약 조건을 지정하고 어떤 종류 (C++ 또는 Java)가 지원되는지를 해당하는 도구 모음에 있습니다 (후자는 toolchain_type() 규칙으로 표시됨).
  2. 실제 도구 모음을 설명하는 언어별 규칙 (예: cc_toolchain())

모든 모델의 제약 조건을 알아야 하기 때문에 툴체인 해상도와 언어별 작업을 수행하기 위한 툴체인 *_toolchain() 규칙에는 이보다 훨씬 더 많은 정보가 포함되어 있으므로 시간이 오래 걸립니다

실행 플랫폼은 다음 방법 중 하나로 지정됩니다.

  1. MODULE.bazel 파일에서 register_execution_platforms() 함수 사용
  2. 명령줄에서 --extra_execution_platforms 명령줄 사용 옵션

사용 가능한 실행 플랫폼 집합은 RegisteredExecutionPlatformsFunction

구성된 대상의 대상 플랫폼은 다음에 의해 결정됩니다. PlatformOptions.computeTargetPlatform() 플랫폼의 목록입니다. 결국 여러 타겟 플랫폼을 지원할 예정이지만 아직 구현되지 않았습니다. 있습니다.

구성된 대상에 사용되는 도구 모음 집합은 ToolchainResolutionFunction 이는 다음과 같은 함수입니다.

  • 등록된 툴체인 집합 (MODULE.bazel 파일 및 구성)
  • 원하는 실행 및 대상 플랫폼 (구성)
  • 구성된 대상에 필요한 도구 모음 유형 집합( UnloadedToolchainContextKey)
  • 구성된 타겟의 실행 플랫폼 제약 조건 세트( exec_compatible_with 속성) 및 구성 (--experimental_add_exec_constraints_to_targets), - <ph type="x-smartling-placeholder">UnloadedToolchainContextKey</ph>

그 결과는 UnloadedToolchainContext입니다. 기본적으로 도구 모음 유형 (ToolchainTypeInfo 인스턴스로 표시됨)을 선택한 도구 모음에 포함되어야 합니다. 이를 '언로드됨'이라고 합니다. 왜냐하면 툴체인 자체와 해당 라벨만 지원합니다.

그러면 도구 모음이 실제로 ResolvedToolchainContext.load()를 사용하여 로드됩니다. 이를 요청한 구성된 타겟의 구현에 의해 사용됩니다.

또한 단일 '호스트'가 있는 기존 시스템도 있습니다. 여러 대상 구성으로 표시되는 구성 플래그(예: --cpu) Google은 위의 버전으로 점진적으로 전환하고 있습니다. 있습니다. 사람들이 기존 구성을 사용하는 경우를 처리하기 위해 인코더-디코더 아키텍처를 플랫폼 매핑 사용하여 기존 플래그와 새로운 스타일의 플랫폼 제약 조건 간에 변환할 수 있습니다. 코드는 PlatformMappingFunction에 있으며 Starlark가 아닌 'little 언어'로 시작해야 합니다.

제약조건

일부 대상과 일부 대상과만 호환되는 것으로 지정하고자 할 때가 있습니다. 지원합니다 Bazel에는 이러한 목적을 달성하기 위한 여러 가지 메커니즘이 있습니다.

  • 규칙별 제약조건
  • environment_group()/environment()
  • 플랫폼 제약조건

규칙별 제약 조건은 주로 Google 내에서 Java 규칙에 사용됩니다. 그들은 Bazel에서 사용할 수 없지만 소스 코드는 이에 대한 참조를 포함합니다. 이를 제어하는 속성을 constraints=

환경_그룹() 및 환경()

이러한 규칙은 레거시 메커니즘이며 널리 사용되지 않습니다.

모든 빌드 규칙은 어떤 '환경'을 선언할 수 있는지 애플리케이션을 빌드할 수 있는 'environment' environment() 규칙의 인스턴스입니다.

규칙에 대해 지원되는 환경을 지정하는 방법에는 여러 가지가 있습니다.

  1. restricted_to= 속성을 통해 이것은 가장 직접적인 형태의 사양 규칙에서 지원하는 정확한 환경 집합을 선언합니다. 이 그룹에 적용됩니다.
  2. compatible_with= 속성을 통해 이렇게 하면 환경이 규칙이 선언됩니다. 'standard' 외에도 Google Kubernetes Engine, 기본값입니다.
  3. 패키지 수준 속성 default_restricted_to=default_compatible_with=입니다.
  4. environment_group() 규칙의 기본 사양을 통해 반복 주기 환경은 테마적으로 관련된 동종 업계 그룹 (예: "CPU" 아키텍처', 'JDK 버전' 또는 '모바일 운영체제'). 이 다음 중 어떤 환경이 포함되는지 정의하기 '기본값'으로 지원되어야 합니다. '계약'에 의해 달리 명시되지 않은 경우 restricted_to= / environment() 속성. 이러한 항목이 없는 규칙 속성은 모든 기본값을 상속합니다.
  5. 규칙 클래스 기본값을 통해 이렇게 하면 지정된 규칙 클래스의 인스턴스입니다. 예를 들어 이를 사용하여 모든 *_test 규칙을 각 인스턴스를 명시적으로 테스트하지 않고도 테스트할 수 있습니다. 이 기능을 선언해야 합니다

environment()는 일반 규칙으로 구현되는 반면 environment_group()Target의 서브클래스이지만 Rule (EnvironmentGroup)이 아니며 기본적으로 Starlark에서 제공하는 함수입니다. (StarlarkLibrary.environmentGroup())는 최종적으로 같은 이름을 가진 있습니다. 이는 각 종속 항목이 다시 로드되기 때문에 발생하는 각 환경은 해당 환경이 속한 환경 그룹을 선언해야 하며 기본 환경을 선언해야 합니다

빌드는 특정 환경에만 허용되도록 --target_environment 명령줄 옵션

제약조건 검사 구현은 RuleContextConstraintSemanticsTopLevelConstraintSemantics.

플랫폼 제약조건

현재 '공식' 타겟이 호환되는 플랫폼을 설명하는 방법 도구 모음과 플랫폼을 설명하는 데 사용되는 것과 동일한 제약조건을 사용하는 것입니다. pull 요청에서 검토 중입니다. #10945

공개 상태

Google과 같이 많은 개발자와 함께 대규모 코드베이스를 작업하는 경우 자신의 목표에 따라 다른 모든 사람이 임의로 생성합니다. 그렇지 않은 경우 Hyrum의 법칙에 따라 사람들은 내가 구현한다고 생각하는 행동에 따르게 될 것입니다. 확인하세요.

Bazel은 가시성이라는 메커니즘을 통해 이를 지원합니다. 특정 표적이 되는 것은 공개 상태 속성에 대해 자세히 알아보세요. 이 속성은 약간 특별합니다. 라벨 목록이 포함되어 있지만 이러한 속성은 라벨은 특정 객체를 가리키는 포인터가 아니라 패키지 이름 위에 있는 패턴을 인코딩할 수 있습니다. 확인할 수 있습니다 (예, 디자인 결함이 있습니다.)

이는 다음 위치에서 구현됩니다.

  • RuleVisibility 인터페이스는 공개 상태 선언을 나타냅니다. 가능 상수 (완전히 공개 또는 완전 비공개)이거나 라벨 목록이어야 합니다.
  • 라벨은 패키지 그룹 (사전 정의된 패키지 목록) 또는 패키지 직접 (//pkg:__pkg__) 또는 패키지의 하위 트리 (//pkg:__subpackages__). 이는 명령줄 문법과는 다르며 이는 //pkg:* 또는 //pkg/...를 사용합니다.
  • 패키지 그룹은 자체 타겟 (PackageGroup)으로 구현됩니다. 구성된 대상 (PackageGroupConfiguredTarget) 아마도 원하는 경우 간단한 규칙으로 바꿀 수 있습니다. 로직이 구현됨 PackageSpecification를 사용하면 //pkg/...와 같은 단일 패턴 PackageGroupContents: 단일 package_grouppackages 속성에 추가합니다. 및 PackageSpecificationProvider: package_group 및 전이 includes입니다.
  • 공개 상태 라벨 목록에서 종속 항목으로의 전환은 DependencyResolver.visitTargetVisibility 외 기타 활동 있습니다.
  • 실제 검사는 CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()

중첩된 세트

구성된 대상은 종속 항목에서 파일 세트를 집계하는 경우가 많습니다. 자체를 추가하고, 집계 세트를 전이 정보 제공자로 래핑하여 해당 대상에 종속된 구성 대상도 동일한 작업을 수행할 수 있습니다. 예:

  • 빌드에 사용되는 C++ 헤더 파일
  • cc_library의 전이적 클로저를 나타내는 객체 파일
  • Java 규칙이 실행되기 위해 클래스 경로에 있어야 하는 컴파일하거나 실행
  • Python 규칙의 전이적 클로저에 있는 Python 파일 집합

예를 들어 List 또는 Set를 사용하여 간단한 방식으로 이 작업을 했다면 다음과 같이 됩니다. 이차 메모리 사용량: N개의 규칙 체인이 있고 각 규칙이 파일에서는 1+2+...+N개의 컬렉션 회원이 될 것입니다.

이 문제를 해결하기 위해 우리는 데이터 레이크와 NestedSet 다른 NestedSet로 구성된 데이터 구조입니다. 일부 자체 인스턴스를 생성하여 방향성 비순환 그래프(DAG)를 형성함 집합으로 나뉩니다. 이들은 변경할 수 없으며 멤버를 반복할 수 있습니다. 우리는 여러 반복 순서 (NestedSet.Order): preorder, postorder, 토폴로지 (노드는 항상 상위 노드 뒤에 와야 함) 및 "상관없지만 같아야 한다는 것입니다.

Starlark에서는 동일한 데이터 구조를 depset라고 합니다.

아티팩트 및 작업

실제 빌드는 이미지를 생성하기 위해 실행해야 하는 명령어 세트로 구성됩니다. 출력됩니다 명령어는 Action 클래스이며 파일은 클래스의 인스턴스로 표현됩니다. Artifact입니다. 이 그래프는 방향성 비순환 그래프인 'actiongraph'라고 합니다.

아티팩트는 두 가지 종류가 있습니다. 소스 아티팩트는 파생된 아티팩트 (실행해야 할 아티팩트)와 합니다. 파생된 아티팩트 자체는 여러 종류일 수 있습니다.

  1. **일반 아티팩트. **이러한 항목은 계산을 통해 체크섬과 함께 mtime을 단축어로 사용합니다. 파일이 체크섬을 하지 않고 ctime은 변경되지 않았습니다.
  2. 해결되지 않은 심볼릭 링크 아티팩트. 이러한 항목은 DAG에 의해 readlink()를 호출합니다. 일반적인 아티팩트와 달리 사용합니다. 일반적으로 일부 파일을 한 개의 컴퓨터에 한데 묶을 때 일종의 아카이브입니다.
  3. 나무 아티팩트. 단일 파일이 아니라 디렉터리 트리입니다. 그들은 파일 세트와 해당 파일의 최신 상태를 확인하여 있습니다. TreeArtifact로 표시됩니다.
  4. 상시 메타데이터 아티팩트. 이러한 아티팩트가 변경되어도 있습니다 이것은 빌드 스탬프 정보에만 사용되며 현재 시간이 변경되었기 때문에 다시 빌드합니다.

소스 아티팩트가 트리 아티팩트가 될 수 없거나 아직 구현되지 않은 것뿐입니다( 하지만 BUILD 파일에서 소스 디렉터리를 참조하는 것은 Bazel과 관련하여 알려진 오래된 부정확성 문제가 거의 없습니다. 우리는 작업을 수행하는 데 필요한 모든 작업을 BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM 속성)

주목할 만한 종류의 Artifact는 중개자입니다. Artifact로 표시됩니다. MiddlemanAction의 출력인 인스턴스입니다. Kubernetes는 다음과 같은 몇 가지 예외가 있습니다.

  • 집계 중개자는 아티팩트를 함께 그룹화하는 데 사용됩니다. 이렇게 하면 많은 작업이 동일한 큰 입력 세트를 사용하는 경우 N*M은 없습니다. 종속 항목 에지, N+M만 (중첩된 세트로 대체됨)
  • 종속 항목 중간자를 예약하면 한 작업이 다른 작업보다 먼저 실행되도록 할 수 있습니다. 주로 린트 작업에 사용되지만 C++ 컴파일에도 사용됩니다( CcCompilationContext.createMiddleman())
  • 실행 파일 미들맨은 실행 파일 트리의 존재를 보장하므로 출력 매니페스트에 별도로 의존할 필요가 없고 단일 아티팩트일 수 있습니다.

작업은 실행해야 하는 명령, 환경 생성하는 출력 집합이 주요 기능은 다음과 같습니다. 작업 설명의 구성요소입니다.

  • 실행해야 하는 명령줄은
  • 필요한 입력 아티팩트
  • 설정해야 하는 환경 변수입니다.
  • 실행해야 하는 환경 (예: 플랫폼)을 설명하는 주석 \

또한 파일이 작성된 파일을 작성하는 경우와 같이 알게 되었습니다. AbstractAction의 서브클래스입니다. 대부분의 작업은 SpawnAction 또는 StarlarkAction (동일하지만 다음과 같아서는 안 됨) 별도의 클래스) Java와 C++에는 고유한 작업 유형이 있지만 (JavaCompileAction, CppCompileAction, CppLinkAction).

최종적으로는 모든 항목을 SpawnAction로 이동하려고 합니다. JavaCompileAction: C++는 .d 파일의 파싱과 검사가 포함됩니다

액션 그래프가 대부분 '삽입'되어 있음 살펴볼 수 있습니다. 개념적으로 작업 실행은 ActionExecutionFunction 작업 그래프 종속 항목 에지에서 Skyframe 종속 항목 에지에 관한 설명은 다음과 같습니다. ActionExecutionFunction.getInputDeps(), Artifact.key(), 최적화 하는 것이 좋습니다.

  • 파생된 아티팩트에는 자체 SkyValue가 없습니다. 대신 Artifact.getGeneratingActionKey()는 이를 생성하는 작업이
  • 중첩된 세트에는 자체 스카이프레임 키가 있습니다.

공유된 액션

일부 작업은 여러 개의 구성된 대상에 의해 생성됩니다. Starlark 규칙은 파생된 액션을 디렉토리가 그 구성 및 패키지에 의해 결정됩니다 (그러나 그렇더라도 충돌할 수 있지만 Java로 구현된 규칙은 파생된 아티팩트를 사용합니다

잘못된 특성으로 간주되지만 제거하기란 매우 어렵습니다. 예를 들어, CPU 인스턴스 또는 소스 파일이 처리되어야 하며 해당 파일을 여러 규칙 (예: 손 흔들기) 이를 위해서는 약간의 RAM 비용이 소요됩니다. 공유 작업의 인스턴스는 메모리에 별도로 저장되어야 합니다.

두 작업이 동일한 출력 파일을 생성하는 경우 정확히 동일해야 합니다. 동일한 입력, 출력이 있고 동일한 명령줄을 실행할 수 있습니다. 이 등가 관계가 Actions.canBeShared()에서 구현되고 모든 액션을 살펴보고 분석 단계와 실행 단계 사이에 검증된 결과입니다 SkyframeActionExecutor.findAndStoreArtifactConflicts()에서 구현됩니다. 는 Bazel에서 '전역' Kubernetes의 있습니다.

실행 단계

이때 Bazel이 빌드 작업(예: 여러 가지 방법이 있습니다

분석 단계 이후 Bazel이 가장 먼저 하는 일은 아티팩트를 빌드해야 합니다. 이에 대한 로직은 TopLevelArtifactHelper; 대략적으로 말하자면 filesToBuild 명령줄에 구성된 대상과 특수 출력의 내용을 "이 표적이 명령에 있는 경우"를 표현하기 위한 명시적인 목적으로 줄, 빌드 이 아티팩트를 빌드해야 합니다.'라는 메시지가 표시됩니다.

다음 단계는 실행 루트를 만드는 것입니다. Bazel은 SQL을 읽을 수 있는 옵션이 있으므로 파일 시스템의 여러 위치에서 소스 패키지 (--package_path) 전체 소스 트리가 있는 로컬에서 실행된 작업을 제공해야 합니다. 이것은 SymlinkForest 클래스에서 처리하며 모든 타겟을 메모하는 방식으로 작동합니다. 심볼릭 링크를 제공하는 단일 디렉터리 트리를 구축하여 실제 위치에서 사용된 타겟이 있는 모든 패키지가 생성됩니다. 대안은 올바른 경로를 명령어에 전달해야 합니다 (--package_path 고려). 이는 다음과 같은 이유로 바람직하지 않습니다.

  • 패키지가 패키지 경로에서 이동할 때 작업 명령줄을 변경합니다. 다른 항목으로 항목 전송 (이전에는 자주 발생함)
  • 작업을 원격으로 실행하면 명령줄이 달라집니다. 로컬에서 실행되며
  • 사용 중인 도구에 맞는 명령줄 변환이 필요함 (예: Java 클래스 경로와 C++ 포함 경로 간의 차이점을 고려하세요.)
  • 작업의 명령줄을 변경하면 작업 캐시 항목이 무효화됩니다.
  • --package_path이(가) 천천히 그리고 점진적으로 지원 중단됨

그런 다음 Bazel이 작업 그래프 (이분할 방향성 그래프)를 순회하기 시작합니다. 동작과 입력 및 출력 아티팩트) 및 실행 중인 작업으로 구성됩니다. 각 작업의 실행은 SkyValue의 인스턴스로 표현됩니다. 클래스 ActionExecutionValue.

작업 실행은 비용이 많이 들기 때문에 문제가 발생할 수 있습니다.

  • ActionExecutionFunction.stateMap에는 Skyframe을 다시 시작하기 위한 데이터가 포함되어 있습니다. /ActionExecutionFunction 저렴
  • 로컬 작업 캐시에는 파일 시스템의 상태에 대한 데이터가 포함되어 있습니다.
  • 또한 원격 실행 시스템에는 일반적으로 자체 캐시가 포함되어 있습니다.

로컬 작업 캐시

이 캐시는 Skyframe 뒤에 있는 또 다른 레이어입니다. 특정 작업이 여전히 로컬 작업 캐시에서 적중일 수 있습니다. 그것은 로컬 파일 시스템의 상태를 나타내며 로컬 파일 시스템의 상태를 나타내는 즉, 새 Bazel 서버를 시작할 때 로컬 작업 캐시를 가져올 수 있습니다. 조회가 발생하지 않습니다.

이 캐시는 ActionCacheChecker.getTokenIfNeedToExecute()

이름과 달리 파생된 아티팩트의 경로에서 배웁니다. 작업은 다음과 같이 설명됩니다.

  1. 입력 및 출력 파일 집합과 체크섬
  2. 일반적으로 실행된 명령줄인 "작업 키"이지만 은 일반적으로 포드의 체크섬으로 캡처되지 않은 모든 것을 입력 파일 (예: FileWriteAction의 경우 데이터의 체크섬) 작성)

고도로 실험적인 '하향식 작업 캐시'도 있습니다. 아직 하위 수준인 캐시로 이동하는 것을 방지하기 위해 전이 해시를 사용하는 표시됩니다.

입력 검색 및 입력 프루닝

일부 작업은 단순한 입력 세트를 갖는 것보다 더 복잡합니다. 변경사항 작업의 입력 세트는 두 가지 형태로 제공됩니다.

  • 작업은 실행 전에 새로운 입력을 발견하거나 실제로는 필요하지 않습니다. 표준 예는 C++입니다. 어떤 헤더 파일이 C++에서 어떤 헤더 파일에 대해 모든 파일을 전송하는 데 집중하지 않도록 원격 실행자에게 보내는 것입니다. 따라서 특정 페이지에만 등록할 수도 있습니다. 헤더 파일을 '입력'으로 설정하고, 소스 파일에서 전이적으로 스캔합니다. 포함하고 있는 헤더 파일을 #include 문에서 언급됨 (이를 과대평가하여 구현) 이 옵션은 현재 'false' Google에서만 사용됩니다.
  • 작업에서 일부 파일이 실행 중에 사용되지 않았음을 인식할 수도 있습니다. 포함 C++에서는 이를 ".d 파일"이라고 합니다. 컴파일러는 어떤 헤더 파일이 있었는지 알려줍니다. 문제가 발생할 때 당황스러워지는 것을 피하기 위해 성과 증분을 감안할 때 Bazel은 이 사실을 활용합니다. 이렇게 하면 컴파일러에 의존하기 때문에 포함 스캐너보다 추정치입니다.

이는 Action의 메서드를 사용하여 구현됩니다.

  1. Action.discoverInputs()가 호출됩니다. 이 함수는 필수 항목으로 판정된 아티팩트. 소스 아티팩트여야 합니다. 따라서 작업 그래프에 구성된 대상 그래프에서 동등합니다.
  2. 작업은 Action.execute()를 호출하여 실행됩니다.
  3. Action.execute()가 끝나면 작업은 다음을 호출할 수 있습니다. Action.updateInputs(): Bazel에게 모든 입력이 아님을 알립니다. 확인할 수 있습니다 사용된 입력이 다음에 해당하는 경우 잘못된 증분 빌드가 발생할 수 있습니다. 사용되지 않은 것으로 보고됩니다.

작업 캐시가 새로운 작업 인스턴스에 조회를 반환하는 경우 (예: Bazel은 updateInputs() 자체를 호출하여 'input'은 이전에 수행된 입력 탐색 및 프루닝의 결과를 반영합니다.

Starlark 작업은 기능을 사용하여 일부 입력을 미사용으로 선언할 수 있음 unused_inputs_list= 인수 사용 ctx.actions.run()입니다.

다양한 작업 실행 방법: 전략/ActionContexts

일부 작업은 다양한 방식으로 실행할 수 있습니다. 예를 들어, 명령줄을 사용하면 로컬, 로컬, 다양한 종류의 샌드박스에서 또는 원격으로 실행됩니다. 이 이를 구현하는 개념을 ActionContext (또는 Strategy)라고 합니다. 절반 정도만 성공적으로 진행되었고...)

작업 컨텍스트의 수명 주기는 다음과 같습니다.

  1. 실행 단계가 시작되면 BlazeModule 인스턴스에 확인할 수 있습니다 이는 ExecutionTool 작업 컨텍스트 유형은 Java Class로 식별됩니다. ActionContext의 하위 인터페이스를 참조하는 인스턴스이며 작업 컨텍스트가 구현해야 하는 인터페이스입니다.
  2. 사용 가능한 작업 컨텍스트 중에서 적절한 작업 컨텍스트가 선택되고 ActionExecutionContextBlazeExecutor에 전달되었습니다 .
  3. 작업은 ActionExecutionContext.getContext()BlazeExecutor.getStrategy() (정말 한 가지 방법만 있어야 함) 그것...)

전략은 다른 전략에 따라 작업을 수행할 수 있음 이를 위해 사용되는 예를 들어 로컬과 원격으로 작업을 시작하는 동적 전략에서는 둘 중 먼저 완료된 항목을 사용합니다.

주목할 만한 전략은 영구 작업자 프로세스를 구현하고 (WorkerSpawnStrategy). 일부 도구는 시작하는 데 시간이 오래 걸리기 때문에 따라서 작업을 새로 시작하는 대신 작업 간에 재사용해야 합니다. 모든 작업 (Bazel은 이전에 생성한 데이터 세트 대신에 작업자 프로세스가 관측 가능한 상태)

도구가 변경되면 작업자 프로세스를 다시 시작해야 합니다. 작업자가 체크섬을 계산하여 재사용 가능 여부를 WorkerFilesHash 동작의 입력 중에서 입력값을 나타내는 부분 크리에이터가 결정합니다 Spawn.getToolFiles() 작업 및 Spawn의 실행 파일은 다음과 같습니다. 도구의 일부로 간주됩니다.

전략 (또는 작업 컨텍스트)에 관한 추가 정보:

  • 작업 실행을 위한 다양한 전략에 관한 정보가 제공됩니다. 여기에서 확인할 수 있습니다.
  • 동적 전략에 관한 정보(작업을 실행하는 전략과 전략을 둘 다 실행하는 전략) 먼저 완료된 항목을 확인할 수 있도록 로컬 및 원격으로 여기에서 확인할 수 있습니다.
  • 로컬에서 작업을 실행하는 복잡성에 대한 정보를 사용할 수 있음 여기에서 확인할 수 있습니다.

로컬 리소스 관리자

Bazel은 많은 작업을 동시에 실행할 수 있습니다. 발생한 오프라인 액션의 수 실행해야 합니다. 즉, 실행에 필요한 리소스가 많을수록 작업별로 필요한 경우 보다 적은 수의 인스턴스가 동시에 실행되어야 로컬 머신에 과부하가 걸릴 수 있습니다

이는 ResourceManager 클래스에서 구현됩니다. 각 작업은 다음과 같아야 합니다. 필요한 지역 자원의 추정치를 ResourceSet 인스턴스 (CPU 및 RAM). 그런 다음 작업 컨텍스트가 로컬 리소스가 필요한 경우 ResourceManager.acquireResources()를 호출합니다. 필요한 리소스를 사용할 수 있을 때까지 차단됩니다

로컬 리소스 관리에 관한 자세한 설명 확인 가능 여기에서 확인할 수 있습니다.

출력 디렉터리의 구조

각 작업은 출력 디렉터리에 배치되는 별도의 위치가 필요합니다. 살펴봤습니다 파생된 아티팩트의 위치는 일반적으로 다음과 같습니다.

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

특정 데이터베이스와 연결된 디렉터리의 이름은 어떻게 해야 할까요? 충돌하는 바람직한 속성 두 가지는 다음과 같습니다.

  1. 동일한 빌드에서 두 개의 구성이 발생할 수 있는 경우 두 개의 동일한 디렉터리가 action; 두 구성이 서로 일치하지 않는 경우 작업의 줄이 있는 경우 Bazel은 어떤 작업이 선택할 작업('작업 충돌')
  2. 두 구성이 '대략적인' 마찬가지로 같은 이름을 사용해야 예를 들어 명령줄 옵션을 변경하여 Java 컴파일러로 인해 C++ 컴파일 작업이 다시 실행되지 않아야 합니다.

지금까지는 이 문제를 해결할 원칙적인 방법을 마련하지 못했습니다. 구성 트리밍 문제와 유사성이 있습니다. 자세한 설명 개 중 개의 옵션이 제공됩니다. 여기에서 확인할 수 있습니다. 주요 문제 영역은 Starlark 규칙입니다 (저자들은 일반적으로 몇 가지 측면과 유사하여 접근 방식에 새로운 차원을 더해줍니다. '동일한' 것으로 생산할 수 있는 사물의 공간이 출력 파일을 선택합니다.

현재의 접근 방식은 구성의 경로 세그먼트를 다양한 접미사가 추가된 <CPU>-<compilation mode> Java에서 구현된 전환은 작업 충돌을 일으키지 않습니다. 또한 Starlark 구성 전환 세트의 체크섬이 추가되어 사용자가 작업 충돌을 일으킬 수 없습니다. 아직 완벽하지 않습니다. 이는 OutputDirectories.buildMnemonic(), 각 구성 프래그먼트에 의존함 출력 디렉터리의 이름에 자체 부분을 추가합니다.

테스트

Bazel은 테스트 실행을 풍부하게 지원합니다. 지원되는 옵션은 다음과 같습니다.

  • 원격으로 테스트 실행 (원격 실행 백엔드를 사용할 수 있는 경우)
  • 테스트를 동시에 여러 번 실행 (디플레이킹 또는 타이밍 수집) 데이터)
  • 샤딩 테스트 (동일한 테스트에서 여러 프로세스에 걸쳐 테스트 사례 분할) )
  • 불안정한 테스트 재실행
  • 테스트를 테스트 모음으로 그룹화

테스트는 다음을 설명하는 TestProvider가 있는 일반 구성 타겟입니다. 테스트 실행 방법:

  • 빌드 결과 테스트가 실행되는 아티팩트입니다. 이를 '캐시'라고 하며 상태' 직렬화된 TestResultData 메시지가 포함된 파일
  • 테스트를 실행해야 하는 횟수입니다.
  • 테스트를 분할해야 하는 샤드 수입니다.
  • 테스트 실행 방법에 관한 일부 매개변수 (예: 테스트 제한 시간)

실행할 테스트 결정

어떤 테스트를 실행할지 결정하는 것은 정교한 프로세스입니다.

첫째, 대상 패턴 파싱 중에 테스트 모음이 재귀적으로 확장됩니다. 이 확장은 TestsForTargetPatternFunction에서 구현됩니다. 다소 한 가지 놀라운 점은 테스트 모음이 테스트를 선언하지 않는 경우 모든 테스트를 실행합니다. 이는 Package.beforeBuild()에서 다음을 통해 구현됩니다. $implicit_tests라는 암시적 속성을 테스트 모음 규칙에 추가합니다.

그런 다음 명령줄 옵션을 제공합니다 이는 TestFilter에서 구현되며 다음에서 호출됩니다. 대상 파싱 도중 TargetPatternPhaseFunction.determineTests(), 결과가 TargetPatternPhaseValue.getTestsToRunLabels()에 입력됩니다. 이유 필터링할 수 있는 규칙 속성을 구성할 수 없는 이유는 발생하므로 구성은 분석 단계 전에 있습니다.

그런 다음 BuildView.createResult()에서 추가로 처리됩니다. 실패한 분석은 제외되고 테스트는 전용 및 비독점 테스트를 실행합니다. 그런 다음 AnalysisResult에 입력되며 다음과 같습니다. ExecutionTool는 실행할 테스트를 알고 있습니다.

이 정교한 프로세스를 투명하게 전달하기 위해 tests() 쿼리 연산자 (TestsFunction에 구현됨)를 사용하여 어떤 테스트를 확인할 수 있는지 알 수 있습니다. 특정 타겟이 명령줄에 지정되면 실행됩니다. 그것은 아쉽게도 재구현이 필요한 부분이기 때문에 방법을 알아볼 것입니다.

테스트 실행

테스트는 캐시 상태 아티팩트를 요청하는 방식으로 실행됩니다. 그러면 결과적으로 TestRunnerAction가 실행되어 최종적으로 --test_strategy 명령줄 옵션으로 선택된 TestActionContext 요청된 방식으로 테스트를 실행합니다.

환경 변수를 사용하는 정교한 프로토콜에 따라 테스트가 실행됩니다. 테스트에 예상할 수 있는 내용을 알려줘야 합니다 Bazel에 대한 자세한 설명 Bazel에서 예상할 수 있는 테스트가 무엇인지 여기에서 확인할 수 있습니다. 가장 단순한 종료 코드 0은 성공을, 나머지는 실패를 의미합니다.

캐시 상태 파일 외에도 각 테스트 프로세스는 할 수 있습니다. ‘테스트 로그 디렉터리’에 저장됩니다 이 디렉터리는 대상 구성 출력 디렉터리의 testlogs:

  • test.xml는 개별 테스트 사례를 자세히 설명하는 JUnit 형식의 XML 파일입니다. 테스트 샤드
  • test.log: 테스트의 콘솔 출력 stdout 및 stderr는 분리되어 있습니다.
  • test.outputs: '선언되지 않은 출력 디렉터리' 이는 테스트에서 사용됩니다. 터미널에 출력하는 내용 외에 파일을 출력하려고 하는 클라이언트 라이브러리입니다.

테스트 실행 도중에 발생할 수 있는 두 가지 상황은 일반 타겟 빌드: 독점적인 테스트 실행 및 출력 스트리밍

일부 테스트는 배타적 모드(예: 실행할 수 있습니다 이는 다음 중 하나에 tags=["exclusive"]를 추가하여 실행할 수 있습니다. 테스트 규칙을 적용하거나 --test_strategy=exclusive로 테스트를 실행할 수 있습니다 . 각각의 배타적인 테스트는 'main' 테스트 후에 있습니다. 이는 SkyframeExecutor.runExclusiveTest()

일반 작업과 달리 작업 실행 시 터미널 출력이 덤프됩니다. 완료되면 사용자는 테스트 결과를 스트리밍하여 장기 실행 테스트의 진행 상황에 대한 정보를 얻을 수 있습니다. 이는 --test_output=streamed 명령줄 옵션 및 배타적 테스트를 의미함 다른 테스트의 출력이 산재되지 않도록 합니다.

이는 적절한 이름의 StreamedTestOutput 클래스에서 구현되며 다음에서 작동합니다. 문제가 되는 테스트의 test.log 파일에 대한 변경사항을 폴링하고 새 파일을 덤프합니다. Bazel 규칙이 있는 터미널에 바이트를 추가합니다.

실행된 테스트 결과는 이벤트 버스에서 다양한 이벤트 (예: TestAttempt, TestResult, TestingCompleteEvent) 빌드 이벤트 프로토콜로 덤프되고 콘솔로 내보내집니다. 아티스트: AggregatingTestListener

커버리지 수집

적용 범위는 파일에 LCOV 형식의 테스트를 통해 보고됩니다. bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

적용 범위를 수집하기 위해 각 테스트 실행은 collect_coverage.sh

이 스크립트는 적용 범위 수집을 사용 설정하기 위해 테스트 환경을 설정합니다. 커버리지 런타임에서 커버리지 파일을 작성하는 위치를 파악할 수 있습니다. 그런 다음 테스트를 실행합니다. 테스트는 자체적으로 여러 하위 프로세스를 실행할 수 있으며 여러 다른 프로그래밍 언어로 작성된 여러 파트의 커버리지 수집 런타임). 래퍼 스크립트는 필요한 경우 결과 파일을 LCOV 형식으로 변환하고 하나의 파일로 병합 파일에서 참조됩니다.

collect_coverage.sh의 삽입은 테스트 전략에 의해 실행됩니다. 테스트의 입력에 collect_coverage.sh가 있어야 합니다. 이것은 다음으로 확인되는 암시적 속성 :coverage_support에 의해 수행됩니다. 구성 플래그 --coverage_support의 값입니다( TestConfiguration.TestOptions.coverageSupport)

일부 언어는 오프라인 계측을 수행합니다. 즉, 적용 범위 계측은 컴파일 시간에 추가되고 (예: C++) 다른 것은 온라인으로 수행 계측: 실행 시 적용 범위 계측이 추가됨 있습니다.

또 다른 핵심 개념은 기준 범위입니다. 이것은 라이브러리의 커버리지입니다. 또는 실행된 코드가 없는지 테스트합니다. 이 방법으로 해결되는 문제는 테스트 커버리지를 계산하려는 경우, 바이너리의 테스트 커버리지를 병합하는 것으로는 테스트를 모두 커버하는 것은 가능하지 않습니다. 왜냐하면 바이너리에는 링크를 클릭해도 됩니다. 따라서 우리가 하는 일은 커버되지 않는 커버리지를 수집하는 파일만 포함하는 바이너리 있습니다. 대상의 기준 범위 파일은 다음과 같습니다. bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat 또한 Kubernetes는 바이너리 및 라이브러리에 대한 테스트뿐 아니라 Bazel에 --nobuild_tests_only 플래그를 지정합니다.

현재 기준 적용 범위가 손상되었습니다.

Google은 각 규칙에 대한 적용 범위 수집을 위해 두 가지 파일 그룹, 즉 계측 메타데이터 파일 세트로 구성됩니다.

계측된 파일 집합은 말 그대로 계측할 파일의 집합입니다. 대상 런타임 시 어떤 파일을 사용할지 결정하는 데 사용할 수 있습니다. 사용됩니다. 기준 범위를 구현하는 데도 사용됩니다.

계측 메타데이터 파일 세트는 테스트에 필요한 추가 파일의 집합입니다. LCOV 파일을 생성합니다. 실제로 이것은 런타임별 파일 예를 들어 gcc는 컴파일 중에 .gcno 파일을 내보냅니다. 적용 범위 모드가 다음과 같은 경우 테스트 작업의 입력 세트에 추가됩니다. 사용 설정되어 있습니다.

적용 범위가 수집되고 있는지 여부는 BuildConfiguration 이렇게 하면 테스트를 쉽게 변경할 수 있으므로 편리합니다. 액션 그래프도 확인할 수 있습니다. 하지만 이 부분이 뒤집어지면 모든 대상을 다시 분석해야 합니다 (예: C++에서는 적용 범위를 수집할 수 있는 코드를 내보내려면 다양한 컴파일러 옵션이 필요합니다. 어차피 재분석이 필요하기 때문에 이 문제를 어느 정도 완화할 수 있습니다.

적용 범위 지원 파일은 종속 항목이 삭제되도록 해야 합니다. Bazel 버전 간에 다를 수 있습니다 이상적으로는 이러한 차이를 없애고 그 중 하나를 표준화했습니다.

또한 '노출 범위 보고서'와 이렇게 하면 다음에 대해 수집된 적용 범위가 모든 테스트에 적용됩니다 이 작업은 CoverageReportActionFactory이며 BuildView.createResult()에서 호출됩니다 . 그것은 필요한 도구에 액세스하려면 :coverage_report_generator 속성 값을 설정합니다.

쿼리 엔진

Bazel은 작은 언어 다양한 그래프에 대해 다양한 것을 질문하는 데 익숙합니다. 다음 쿼리 종류는 제공됩니다.

  • bazel query는 타겟 그래프를 조사하는 데 사용됩니다.
  • bazel cquery는 구성된 대상 그래프를 조사하는 데 사용됩니다.
  • bazel aquery는 작업 그래프를 조사하는 데 사용됩니다.

각각은 AbstractBlazeQueryEnvironment를 서브클래스로 분류하여 구현됩니다. QueryFunction를 서브클래스로 분류하여 추가 쿼리 함수를 실행할 수 있습니다. 에서 자세한 내용을 확인하실 수 있습니다. 쿼리 결과를 스트리밍하여 쿼리 결과를 query2.engine.CallbackQueryFunction에 전달됩니다. 반환하려는 결과에 대해 이를 호출합니다.

쿼리 결과는 라벨, 라벨, 규칙 등 다양한 방식으로 내보낼 수 있습니다. 클래스, XML, protobuf 등입니다. 이들은 Kubernetes가 제공하는 OutputFormatter

일부 쿼리 출력 형식 (물론 proto)의 미묘한 요구사항은 Bazel은 패키지 로드가 제공하는 _모든_정보를 내보내야 출력을 달리하여 특정 타겟이 변경되었는지 확인할 수 있습니다. 따라서 속성 값은 직렬화 가능해야 합니다. 복잡한 Starlark를 가진 속성이 없는 속성 유형은 거의 없습니다. 값으로 사용됩니다. 일반적인 해결 방법은 라벨을 사용하고 복잡한 해당 라벨이 있는 규칙에 추가합니다. 그것은 그다지 만족스럽지 않은 해결 방법입니다 이 요구사항을 없애는 것이 좋습니다.

모듈 시스템

Bazel에 모듈을 추가하여 확장할 수 있습니다. 각 모듈은 서브클래스로 분류해야 함 BlazeModule (이름은 Bazel 역사의 유물입니다. Blaze)에서 데이터를 수집하고, 실행되는 동안 실행할 수 있습니다

주로 다양한 '비 코어'를 구현하는 데 사용됩니다. 기능 일부 버전의 Bazel (예: Google에서 사용하는 버전)에만 다음 사항이 요구됩니다.

  • 원격 실행 시스템에 대한 인터페이스
  • 새로운 명령어

BlazeModule에서 제공하는 확장 포인트 세트는 다소 모호합니다. 하지 말아야 할 일 좋은 디자인 원칙의 예로 사용합니다.

이벤트 버스

BlazeModule이 나머지 Bazel과 통신하는 주요 방법은 이벤트 버스를 사용하는 것입니다. (EventBus): 모든 빌드, Bazel의 다양한 부분에 대해 새 인스턴스가 생성됩니다. 모듈은 이벤트를 게시할 수 있고, 모듈은 이벤트를 발생시키는 리스너를 있습니다. 예를 들어 다음은 이벤트로 표현됩니다.

  • 빌드할 빌드 대상 목록이 결정되었습니다. (TargetParsingCompleteEvent)
  • 최상위 구성이 결정되었습니다. (BuildConfigurationEvent)
  • 대상이 빌드되었는지 여부 (TargetCompleteEvent)
  • 테스트를 실행했습니다 (TestAttempt, TestSummary).

이러한 이벤트 중 일부는 이벤트 프로토콜 빌드 (이는 BuildEvent입니다.) 이렇게 하면 BlazeModule뿐만 아니라 Bazel 프로세스 외부에서 빌드를 관찰합니다. 이러한 포드는 또는 Bazel이 서버( 빌드 이벤트 서비스)을 사용하여 이벤트를 스트리밍해야 합니다.

이는 build.lib.buildeventservice에서 구현되며 build.lib.buildeventstream Java 패키지

외부 저장소

반면 Bazel은 원래 모노레포 (단일 소스 모든 것을 포함하는 트리)에 사는 바젤은 꼭 사실은 아닙니다 '외부 저장소' 추상화를 사용하여 빌드에 필요한 코드를 나타내지만 기본 소스 트리에 없습니다.

WORKSPACE 파일

외부 저장소 세트는 WORKSPACE 파일을 파싱하여 결정됩니다. 예를 들어 다음과 같이 선언할 수 있습니다.

    local_repository(name="foo", path="/foo/bar")

@foo라는 저장소의 결과를 사용할 수 있습니다. 출처 Starlark 파일에서 새 저장소 규칙을 정의할 수 있다는 점이 복잡합니다. 그런 다음 새 Starlark 코드를 로드하는 데 사용할 수 있습니다. 이 코드는 저장소 규칙 등...

이 경우 WORKSPACE 파일의 파싱( WorkspaceFileFunction)는 load()로 구분되는 청크로 분할됩니다. 합니다. 청크 색인은 WorkspaceFileKey.getIndex()로 표시되며 색인 X가 정답이 될 때까지 WorkspaceFileFunction을 계산한다는 것을 의미합니다. X번째 load()

저장소를 가져오는 중

Bazel에서 저장소 코드를 사용하려면 코드가 다음과 같아야 합니다. fetched입니다. 그러면 Bazel이 아래에 디렉터리를 $OUTPUT_BASE/external/<repository name>

저장소 가져오기는 다음 단계로 진행됩니다.

  1. PackageLookupFunction는 저장소가 필요하다는 것을 깨닫고 RepositoryLoaderFunction를 호출하는 SkyKey로서의 RepositoryName
  2. RepositoryLoaderFunction는 요청을 불분명한 이유의 RepositoryDelegatorFunction (코드에 Skyframe이 다시 시작될 경우 다시 다운로드하지 않아도 되지만 매우 확실한 이유)
  3. RepositoryDelegatorFunction는 요청된 저장소 규칙을 찾습니다. WORKSPACE 파일의 청크를 반복하여 가져올 수 있습니다. 저장소가 발견되었습니다
  4. 저장소를 구현하는 적절한 RepositoryFunction가 발견됨 fetching; 저장소의 Starlark 구현이거나 Java로 구현된 저장소에 대한 하드 코딩된 맵입니다.

저장소를 가져오는 것은 매우 중요할 수 있으므로 다양한 캐싱 레이어가 있습니다. 비쌈:

  1. 체크섬으로 키가 지정된 다운로드된 파일의 캐시가 있음 (RepositoryCache). 이를 위해서는 WORKSPACE 파일을 지원하지만 이는 어차피 밀폐성에 좋습니다. 공유자: 동일한 워크스테이션에 있는 모든 Bazel 서버 인스턴스를 출력 기반을 생성할 수 있습니다
  2. '마커 파일' $OUTPUT_BASE/external 아래에 있는 각 저장소에 대해 작성됨 체크섬을 포함하고 있습니다. Bazel이 체크섬이 변경되지 않고 다시 가져오지 않습니다. 이 RepositoryDelegatorFunction.DigestWriter에서 구현됩니다 .
  3. --distdir 명령줄 옵션은 다운로드할 아티팩트를 조회합니다. 기업 설정에서 유용합니다. 즉, Bazel이 인터넷에서 임의의 것을 가져오지 않아야 합니다. 이것은 DownloadManager로 구현됩니다 .

저장소가 다운로드되면 저장소의 아티팩트가 소스로 처리됩니다. 아티팩트를 생성합니다. 이는 문제가 됩니다. Bazel이 일반적으로 최신 상태를 확인하기 때문입니다. stat()을 호출하여 소스 아티팩트를 삭제할 수 있으며, 이러한 아티팩트 또한 현재 저장소의 정의가 변경되면 무효화됩니다. 따라서 외부 저장소에 있는 아티팩트의 FileStateValue는 외부 저장소에 저장합니다 이 작업은 ExternalFilesHelper에서 처리합니다.

저장소 매핑

여러 저장소가 동일한 저장소에 종속되기를 원할 수 있습니다. 다른 버전('diamond 종속 항목'의 문제'). 예를 들어 빌드에서 별도의 저장소에 두 개의 바이너리가 있는 경우 두 사람 모두 Guava를 의미하는데 @guava//의 다른 버전을 의미할 것으로 예상됩니다.

따라서 Bazel은 외부 저장소 라벨을 재매핑하여 문자열 @guava//은 다음 위치에서 하나의 Guava 저장소 (예: @guava1//)를 참조할 수 있습니다. 다른 하나는 Guava 저장소 (예: @guava2//)의 저장소이며 다른 한 개의 저장소가 있습니다

또는 다이아몬드를 조인할 때도 사용할 수 있습니다. 저장소가 @guava1//에 종속되고 다른 하나는 @guava2//에 종속됨, 저장소 매핑 표준 @guava// 저장소를 사용하도록 두 저장소를 다시 매핑할 수 있습니다.

매핑은 WORKSPACE 파일에 repo_mapping 속성으로 지정됩니다. 정의할 수 있습니다 그런 다음 스카이프레임에 WorkspaceFileValue는 다음과 같이 연결됩니다.

  • Package.Builder.repositoryMapping: 라벨 값을 변환하는 데 사용됩니다. 패키지의 규칙 속성을 RuleClass.populateRuleAttributeValues()
  • 분석 단계에서 사용되는 Package.repositoryMapping입니다( 로드 시 파싱되지 않는 $(location)와 같은 문제 해결 단계)
  • BzlLoadFunction: load() 문의 라벨 해결

JNI 비트

Bazel 서버는 대부분 자바로 작성됩니다. 단, Java는 단독으로 수행할 수 없거나 Java를 구현할 때 자체적으로 수행할 수 없습니다. 이 대부분 파일 시스템과의 상호 작용, 프로세스 제어 및 기타 여러 하위 수준의 것들을 살펴보겠습니다.

C++ 코드는 src/main/native와 네이티브가 있는 Java 클래스 아래에 있습니다. 메서드는 다음과 같습니다.

  • NativePosixFilesNativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperationsWindowsFileProcesses
  • com.google.devtools.build.lib.platform

콘솔 출력

콘솔 출력을 내보내는 것은 간단한 일처럼 보이지만 여러 프로세스 (때로는 원격으로), 세밀한 캐싱, 멋지고 다채로운 터미널 출력이 있고 장기 실행 서버가 있으면 간단하지 않습니다.

클라이언트에서 RPC 호출을 받은 직후에 두 개의 RpcOutputStream가 인스턴스가 생성되고 (stdout 및 stderr의 경우) 출력된 데이터를 전달합니다. 전송합니다. 그런 다음 OutErr(stdout, stderr)에 래핑됩니다. 페어링합니다. 콘솔에 출력해야 하는 모든 항목은 있습니다. 그런 다음 이 스트림은 BlazeCommandDispatcher.execExclusively()

출력은 기본적으로 ANSI 이스케이프 시퀀스로 인쇄됩니다. 사용 가능하지 않은 원하는 경우 (--color=no) AnsiStrippingOutputStream에 의해 제거됩니다. 포함 또한 System.outSystem.err는 이러한 출력 스트림으로 리디렉션됩니다. 이는 다음을 사용하여 디버깅 정보를 출력할 수 있도록 하기 위함입니다. System.err.println()이며 여전히 클라이언트의 터미널 출력에 있습니다. (서버의 것과는 다름) 프로세스가 복구되지 않는 바이너리 출력 (예: bazel query --output=proto)을 생성하며 stdout을 정리하지 않음 발생할 수 있습니다.

짧은 메시지 (오류, 경고 등)는 EventHandler 인터페이스 특히 이것은 다른 사람이 볼 수 있는 EventBus (이는 혼동을 야기함) 각 Event에는 EventKind (오류, 경고, 정보 등)이 있으며 Location( 이벤트를 발생시킨 소스 코드).

일부 EventHandler 구현은 수신한 이벤트를 저장합니다. 이 코드는 다양한 종류의 캐시된 처리로 인해 발생하는 정보를 UI로 재생하기 위해 캐시된 구성 타겟에서 발생한 경고를 예로 들 수 있습니다.

일부 EventHandler는 궁극적으로 이벤트 버스 (일반적인 Event는 표시되지 _않음_) 이 두 가지는 ExtendedEventHandler의 구현이며 주요 용도는 캐시된 데이터를 재생하는 것입니다 이벤트 EventBus개 이러한 EventBus 이벤트는 모두 Postable를 구현하지만 EventBus에 게시되는 모든 항목은 반드시 이 인터페이스를 구현합니다. ExtendedEventHandler에 의해 캐시된 리소스만 대부분 하는 일들이죠. 시행되지는 않습니다.)

터미널 출력은 대부분 다음과 같은 UiEventHandler을 통해 내보내집니다. 고급 출력 형식 지정 및 진행 상황 보고를 담당하며, Bazel 합니다. 여기에는 두 가지 입력이 있습니다.

  • 이벤트 버스
  • 이벤트 스트림이 Reporter를 통해 파이핑됨

유일하게 명령 실행 기계 (예: Bazel)가 Reporter.getOutErr()를 통해 RPC 스트림에 연결해야 합니다. 이러한 스트림에 직접 액세스할 수 있습니다. 명령어를 실행하려고 할 때만 가능한 바이너리 데이터를 대량으로 덤프하는 방법 (예: bazel query)

Bazel 프로파일링

Bazel은 빠릅니다. Bazel은 또한 느립니다. 그 이유는 견딜 수 있는 한계에 도달할 수 있습니다. 따라서 Bazel은 프로파일러를 사용하여 빌드 및 Bazel 자체를 프로파일링하는 데 사용됩니다. 다음과 같은 클래스에서 구현됩니다. 적절한 이름은 Profiler입니다. 기본적으로 사용 설정되어 있지만 오버헤드를 감당할 수 있도록 요약한 데이터 명령줄 --record_full_profiler_data: 가능한 모든 항목을 녹화하도록 합니다.

Chrome 프로파일러 형식으로 프로필을 내보냅니다. Chrome에서 가장 잘 보입니다. 데이터 모델은 작업 스택의 데이터 모델입니다. 작업을 시작하고 종료할 수 있으며 서로 깔끔하게 중첩되어야 합니다. 각 Java 스레드는 자체 작업 스택을 표시합니다 TODO: 작업 및 연속 전달 스타일을 사용할까요?

프로파일러는 BlazeRuntime.initProfiler()에서 시작 및 중지됩니다. BlazeRuntime.afterCommand() 각각 그리고 오랫동안 활성 상태를 유지하려고 시도합니다. 모든 것을 프로파일링할 수 있습니다. 프로필에 항목을 추가하려면 Profiler.instance().profile()를 호출합니다. 닫힌 Closeable를 반환합니다. 태스크의 끝을 나타냅니다. try-with-resources와 함께 사용하는 것이 가장 좋습니다. 합니다.

MemoryProfiler에서 기본 메모리 프로파일링도 실행합니다. 또한 항상 켜져 있습니다. 대부분 최대 힙 크기와 GC 동작을 기록합니다.

Bazel 테스트

Bazel에는 두 가지 주요 테스트가 있습니다. Bazel을 '블랙 박스'로 관찰하는 테스트입니다. 및 분석 단계만 실행하는 애플리케이션입니다. 이전 코드를 '통합 테스트'라고 합니다. 후자의 '단위 테스트'는 덜 통합되어 있습니다. 또한 실제 단위 테스트도 있습니다. 있습니다.

통합 테스트에는 두 가지 종류가 있습니다.

  1. 매우 정교한 bash 테스트 프레임워크를 사용하여 구현된 테스트 src/test/shell
  2. Java로 구현된 메서드 이들은 Kubernetes가 제공하는 BuildIntegrationTestCase

BuildIntegrationTestCase는 선호되는 통합 테스트 프레임워크입니다. 대부분의 테스트 시나리오에 잘 대비할 수 있습니다. Java 프레임워크이므로 디버그 가능성 및 많은 일반적인 개발 서비스와의 원활한 통합 제공 있습니다. 여기에는 BuildIntegrationTestCase 클래스의 많은 예가 있습니다. Bazel 저장소

분석 테스트는 BuildViewTestCase의 서브클래스로 구현됩니다. 이 BUILD 파일을 작성하는 데 사용할 수 있는 스크래치 파일 시스템과 다양한 도우미 메서드는 구성된 대상을 요청하고, 구성을 변경하며, 분석 결과에 대한 다양한 것을 할 수 있습니다.