A especificação completa do Build Event Protocol pode ser encontrada na definição do buffer de protocolo. No entanto, pode ser útil desenvolver alguma intuição antes de analisar a especificação.
Considere um espaço de trabalho simples do Bazel que consiste em dois scripts de shell vazios
foo.sh
e foo_test.sh
e o seguinte arquivo BUILD
:
sh_library(
name = "foo_lib",
srcs = ["foo.sh"],
)
sh_test(
name = "foo_test",
srcs = ["foo_test.sh"],
deps = [":foo_lib"],
)
Ao executar bazel test ...
nesse projeto, o gráfico dos eventos de build
gerados vai ser semelhante ao gráfico abaixo. As setas indicam o relacionamento pai e filho mencionado acima. Observe que alguns eventos de build e a maioria dos campos foram omitidos para simplificar.
Figura 1. Gráfico de BEP.
Inicialmente, um evento BuildStarted
é publicado. O evento informa que o
build foi invocado pelo comando bazel test
e anuncia eventos filhos:
OptionsParsed
WorkspaceStatus
CommandLine
UnstructuredCommandLine
BuildMetadata
BuildFinished
PatternExpanded
Progress
Os três primeiros eventos fornecem informações sobre como o Bazel foi invocado.
O evento de build PatternExpanded
fornece insights
sobre para quais destinos específicos o padrão ...
foi expandido para:
//foo:foo_lib
e //foo:foo_test
. Isso é feito declarando dois
eventos TargetConfigured
como filhos. O evento TargetConfigured
declara o evento Configuration
como filho, mesmo que Configuration
tenha sido postado antes do evento TargetConfigured
.
Além da relação pai e filho, os eventos também podem se referir entre si
usando os respectivos identificadores de evento de build. Por exemplo, no gráfico acima, o evento
TargetComplete
se refere ao evento NamedSetOfFiles
no campo
fileSets
.
Eventos de build que se referem a arquivos geralmente não incorporam os nomes e caminhos
de arquivos no evento. Em vez disso, eles contêm o identificador de evento de build de um evento NamedSetOfFiles
, que conterá os nomes e caminhos de arquivo reais. O evento NamedSetOfFiles
permite que um conjunto de arquivos seja relatado uma vez e referenciado por vários destinos. Essa estrutura é necessária porque, caso contrário, em alguns casos, o tamanho de saída do Build Event Protocol cresceria quadráticamente com o número de arquivos. Um evento NamedSetOfFiles
também pode não ter todos os arquivos
incorporados. Em vez disso, ele se refere a outros eventos NamedSetOfFiles
com os
identificadores de evento de build.
Veja abaixo uma instância do evento TargetComplete
para o destino //foo:foo_lib
do gráfico acima, impressa na representação JSON do buffer de protocolo.
O identificador de evento de build contém o destino como uma string opaca e se refere ao
evento Configuration
usando o respectivo identificador de evento de build. O evento não anuncia nenhum evento filho. O payload contém informações sobre a criação do destino, o conjunto de arquivos de saída e o tipo de build de destino.
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
}
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "0"
}]
}],
"targetKind": "sh_library rule"
}
}
Resultados de aspectos em BEP
As versões comuns avaliam ações associadas a pares de
(target, configuration)
. Ao criar com aspects ativado, o Bazel também avalia os destinos associados a triplos de (target, configuration,
aspect)
para cada destino afetado por um determinado aspecto ativado.
Os resultados da avaliação dos aspectos estão disponíveis no BEP, apesar da ausência de tipos de evento específicos. Para cada par de (target, configuration)
com um
aspecto aplicável, o Bazel publica outro evento TargetConfigured
e
TargetComplete
com o resultado da aplicação do aspecto ao
destino. Por exemplo, se //:foo_lib
for criado com --aspects=aspects/myaspect.bzl%custom_aspect
, esse evento também aparecerá no 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"
}]
}]
}
}
Consumindo NamedSetOfFiles
Determinar os artefatos produzidos por um determinado destino (ou aspecto) é um caso de uso comum
de BEP que pode ser feito de forma eficiente com certa preparação. Esta seção
discute a estrutura recursiva compartilhada oferecida pelo evento NamedSetOfFiles
,
que corresponde à estrutura de um Depset do Starlark.
Os consumidores precisam ter cuidado para evitar algoritmos quadráticos ao processar eventos NamedSetOfFiles
, porque builds grandes podem conter dezenas de milhares desses eventos, exigindo centenas de milhões de operações em uma travessia com complexidade quadrática.
Figura 2. Gráfico de BEP de NamedSetOfFiles
.
Um evento NamedSetOfFiles
sempre aparece no fluxo do BEP antes de um evento TargetComplete
ou NamedSetOfFiles
que o referencie. Isso é o inverso da relação de evento "pai-filho", em que todos, exceto o primeiro, aparece depois de pelo menos um evento que o anuncia. Um evento NamedSetOfFiles
é
anunciado por um evento Progress
sem semântica.
Devido a essas restrições de ordenação e compartilhamento, um consumidor típico precisa armazenar todos os eventos NamedSetOfFiles
em buffer até que o fluxo de BEP seja esgotado. O stream de eventos JSON
e o código Python a seguir demonstram como preencher um mapa de
destino/aspecto para artefatos criados no grupo de saída "padrão" e como
processar as saídas de um subconjunto de destinos/aspectos criados:
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)