A especificação completa do protocolo de evento de build pode ser encontrada na definição do buffer de protocolo. No entanto, pode ser útil desenvolver um pouco de intuição antes de consultar 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 de build dos eventos
de build gerados será semelhante ao gráfico abaixo. As setas indicam a
relação pai-filho mencionada. Alguns eventos de build e
a maioria dos campos foram omitidos para encurtar o texto.
Figura 1. Gráfico 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 quais destinos específicos o padrão ...
expandiu para:
//foo:foo_lib
e //foo:foo_test
. Para isso, ele declara dois
eventos TargetConfigured
como filhos. O evento TargetConfigured
declara o evento Configuration
como um evento 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 uns aos outros
usando os identificadores de eventos 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 dos arquivos no evento. Em vez disso, eles contêm o identificador do 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 informado uma vez e
referido por muitos destinos. Essa estrutura é necessária porque, em alguns
casos, o tamanho da saída do Build Event Protocol aumentaria de maneira quadrática com
o número de arquivos. Um evento NamedSetOfFiles
também pode não ter todos os arquivos
incorporados, mas se referir a outros eventos NamedSetOfFiles
usando os
identificadores de evento de build.
Confira abaixo uma instância do evento TargetComplete
para o destino //foo:foo_lib
do gráfico acima, impresso 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 identificador de evento de build. O evento não
anuncia eventos filhos. O payload contém informações sobre se o
destino foi criado, o conjunto de arquivos de saída e o tipo de destino
criado.
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
}
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "0"
}]
}],
"targetKind": "sh_library rule"
}
}
Resultados do Aspect em BEP
Os builds comuns avaliam ações associadas a pares de
(target, configuration)
. Ao criar com aspectos ativados, o Bazel
também avalia os destinos associados aos trios (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 (target, configuration)
com um
aspecto aplicável, o Bazel publica um evento TargetConfigured
e
TargetComplete
adicional 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 vai 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"
}]
}]
}
}
Como consumir NamedSetOfFiles
Determinar os artefatos produzidos por um determinado alvo (ou aspecto) é um caso de uso comum
do BEP que pode ser feito de maneira eficiente com alguma preparação. Esta seção
discute a estrutura compartilhada e recursiva oferecida pelo evento NamedSetOfFiles
,
que corresponde à estrutura de um Depset do Starlark.
Os consumidores precisam 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 na transmissão de BEP antes de um
evento TargetComplete
ou NamedSetOfFiles
que o referencia. Esse é o
inverso da relação de eventos "pai-filho", em que todos os eventos, exceto o primeiro,
aparecem após pelo menos um evento que o anuncia. Um evento NamedSetOfFiles
é
anunciado por um evento Progress
sem semântica.
Considerando 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 fluxo de eventos JSON
e o código Python a seguir demonstram como preencher um mapa de
alvo/aspecto para artefatos criados no grupo de saída "padrão" e como
processar as saídas para um subconjunto de alvos/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)