빌드 이벤트 프로토콜의 전체 사양은 프로토콜 버퍼 정의에서 확인할 수 있습니다. 하지만 사양을 살펴보기 전에 약간의 직관을 쌓는 것이 도움이 될 수 있습니다.
빈 셸 스크립트 foo.sh
및 foo_test.sh
와 다음 BUILD
파일로 구성된 간단한 Bazel 워크스페이스를 생각해 보겠습니다.
sh_library(
name = "foo_lib",
srcs = ["foo.sh"],
)
sh_test(
name = "foo_test",
srcs = ["foo_test.sh"],
deps = [":foo_lib"],
)
이 프로젝트에서 bazel test ...
를 실행하면 생성된 빌드 이벤트의 빌드 그래프가 아래 그래프와 유사합니다. 화살표는 앞서 언급한 상위 및 하위 관계를 나타냅니다. 간결성을 위해 일부 빌드 이벤트와 대부분의 필드는 생략되었습니다.
그림 1. BEP 그래프
처음에는 BuildStarted
이벤트가 게시됩니다. 이 이벤트는 빌드가 bazel test
명령어를 통해 호출되었음을 알리고 하위 이벤트를 공지합니다.
OptionsParsed
WorkspaceStatus
CommandLine
UnstructuredCommandLine
BuildMetadata
BuildFinished
PatternExpanded
Progress
처음 세 이벤트는 Bazel이 호출된 방식에 관한 정보를 제공합니다.
PatternExpanded
빌드 이벤트는 ...
패턴이 //foo:foo_lib
및 //foo:foo_test
로 확장된 특정 타겟에 대한 통계를 제공합니다. 두 개의 TargetConfigured
이벤트를 하위 요소로 선언하여 이를 실행합니다. TargetConfigured
이벤트는 Configuration
이벤트가 TargetConfigured
이벤트 전에 게시되었더라도 Configuration
이벤트를 하위 이벤트로 선언합니다.
이벤트는 상위 및 하위 관계 외에도 빌드 이벤트 식별자를 사용하여 서로 참조할 수도 있습니다. 예를 들어 위 그래프에서 TargetComplete
이벤트는 fileSets
필드의 NamedSetOfFiles
이벤트를 참조합니다.
파일을 참조하는 빌드 이벤트는 일반적으로 이벤트에 파일 이름과 경로를 삽입하지 않습니다. 대신 NamedSetOfFiles
이벤트의 빌드 이벤트 식별자가 포함되며 여기에는 실제 파일 이름과 경로가 포함됩니다. NamedSetOfFiles
이벤트를 사용하면 일련의 파일을 한 번 보고하고 여러 대상에서 참조할 수 있습니다. 이 구조가 필요한 이유는 빌드 이벤트 프로토콜의 출력 크기가 파일 수에 따라 4차례로 늘어나기 때문입니다. NamedSetOfFiles
이벤트에 모든 파일이 삽입되지 않고 빌드 이벤트 식별자를 통해 다른 NamedSetOfFiles
이벤트를 참조할 수도 있습니다.
다음은 위 그래프의 //foo:foo_lib
타겟에 대한 TargetComplete
이벤트의 인스턴스로, 프로토콜 버퍼의 JSON 표현으로 출력됩니다.
빌드 이벤트 식별자는 불투명 문자열로 타겟을 포함하고 빌드 이벤트 식별자를 사용하여 Configuration
이벤트를 참조합니다. 이 이벤트는 하위 이벤트를 공지하지 않습니다. 페이로드에는 타겟이 빌드되었는지 여부, 출력 파일 세트, 빌드된 타겟 유형에 관한 정보가 포함됩니다.
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
}
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "0"
}]
}],
"targetKind": "sh_library rule"
}
}
BEP의 측정항목 결과
일반 빌드는 (target, configuration)
쌍과 연결된 작업을 평가합니다. 애스펙트가 사용 설정된 상태로 빌드하면 Bazel은 사용 설정된 특정 애스펙트의 영향을 받는 각 타겟에 대해 (target, configuration,
aspect)
트리플과 연결된 타겟을 추가로 평가합니다.
측정항목별 이벤트 유형이 없더라도 BEP에서 측정항목의 평가 결과를 확인할 수 있습니다. 적용 가능한 측면이 있는 각 (target, configuration)
쌍에 대해 Bazel은 측면을 타겟에 적용한 결과를 포함하는 추가 TargetConfigured
및 TargetComplete
이벤트를 게시합니다. 예를 들어 //:foo_lib
가 --aspects=aspects/myaspect.bzl%custom_aspect
로 빌드된 경우 이 이벤트는 BEP에도 표시됩니다.
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
},
"aspect": "aspects/myaspect.bzl%custom_aspect"
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "1"
}]
}]
}
}
NamedSetOfFiles
소비
특정 타겟(또는 측면)에서 생성된 아티팩트를 결정하는 것은 약간의 준비만으로 효율적으로 수행할 수 있는 일반적인 BEP 사용 사례입니다. 이 섹션에서는 Starlark Depset의 구조와 일치하는 NamedSetOfFiles
이벤트가 제공하는 재귀 공유 구조를 설명합니다.
대규모 빌드에는 이러한 이벤트가 수만 개 포함될 수 있으므로 NamedSetOfFiles
이벤트를 처리할 때는 이차 알고리즘을 피해야 합니다. 이차 복잡도의 트래버설에는 수억 개의 작업이 필요합니다.
그림 2. NamedSetOfFiles
BEP 그래프
NamedSetOfFiles
이벤트는 항상 이를 참조하는 TargetComplete
또는 NamedSetOfFiles
이벤트 앞에 BEP 스트림에 표시됩니다. 이는 '상위-하위' 이벤트 관계의 역순으로, 첫 번째 이벤트를 제외한 모든 이벤트가 이를 알리는 이벤트가 하나 이상 발생한 후에 표시됩니다. NamedSetOfFiles
이벤트는 시맨틱이 없는 Progress
이벤트에 의해 공지됩니다.
이러한 순서 지정 및 공유 제약조건을 고려할 때 일반적인 소비자는 BEP 스트림이 소진될 때까지 모든 NamedSetOfFiles
이벤트를 버퍼링해야 합니다. 다음 JSON 이벤트 스트림 및 Python 코드는 '기본' 출력 그룹에서 타겟/측면과 빌드된 아티팩트 간의 매핑을 채우는 방법과 빌드된 타겟/측면의 하위 집합에 대한 출력을 처리하는 방법을 보여줍니다.
named_sets = {} # type: dict[str, NamedSetOfFiles]
outputs = {} # type: dict[str, dict[str, set[str]]]
for event in stream:
kind = event.id.WhichOneof("id")
if kind == "named_set":
named_sets[event.id.named_set.id] = event.named_set_of_files
elif kind == "target_completed":
tc = event.id.target_completed
target_id = (tc.label, tc.configuration.id, tc.aspect)
outputs[target_id] = {}
for group in event.completed.output_group:
outputs[target_id][group.name] = {fs.id for fs in group.file_sets}
for result_id in relevant_subset(outputs.keys()):
visit = outputs[result_id].get("default", [])
seen_sets = set(visit)
while visit:
set_name = visit.pop()
s = named_sets[set_name]
for f in s.files:
process_file(result_id, f)
for fs in s.file_sets:
if fs.id not in seen_sets:
visit.add(fs.id)
seen_sets.add(fs.id)