A especificação completa do Build Event Protocol pode ser encontrada na definição do buffer de protocolo. No entanto, pode ser útil criar 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 foo.sh
e foo_test.sh
e no arquivo BUILD
a seguir:
sh_library(
name = "foo_lib",
srcs = ["foo.sh"],
)
sh_test(
name = "foo_test",
srcs = ["foo_test.sh"],
deps = [":foo_lib"],
)
Ao executar bazel test ...
neste projeto, o gráfico de compilação dos eventos de build
gerados será semelhante ao gráfico abaixo. As setas indicam a relação pai e filho mencionada acima. Alguns eventos de compilação e
a maioria dos campos foram omitidos para fins de praticidade.
Figura 1. Gráfico BEP.
Inicialmente, um evento BuildStarted
é publicado. O evento nos 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 ...
foi expandido para:
//foo:foo_lib
e //foo:foo_test
. Para fazer isso, declare dois
eventos TargetConfigured
como filhos. Observe que o evento TargetConfigured
declara o evento Configuration
como um evento filho, mesmo que Configuration
tenha sido postado antes do evento TargetConfigured
.
Além do relacionamento pai e filho, os eventos também podem se referir uns aos outros usando identificadores de evento de build. Por exemplo, no gráfico acima, o evento TargetComplete
refere-se ao evento NamedSetOfFiles
no campo fileSets
.
Os eventos de compilação que se referem a arquivos geralmente não incorporam
nomes e caminhos de arquivo no evento. Eles contêm o identificador de eventos de build de um evento NamedSetOfFiles
, que conterá os nomes e caminhos dos arquivos. O evento NamedSetOfFiles
permite que um conjunto de arquivos seja relatado uma vez e referenciado por muitos destinos. Essa estrutura é necessária porque, em outros casos,
o tamanho de saída do protocolo de evento de build cresceria quadráticamente
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
pelos identificadores de evento de build.
Veja abaixo uma instância do evento TargetComplete
para o destino //foo:foo_lib
do gráfico acima, mostrado 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 nenhum evento filho. O payload contém informações sobre se o
destino foi criado, o conjunto de arquivos de saída e o tipo
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 aspecto no BEP
Builds comuns avaliam ações associadas a pares (target, configuration)
. Ao criar com aspects ativado, o Bazel também avalia os destinos associados a (target, configuration,
aspect)
triplos para cada destino afetado por um determinado aspecto ativado.
Os resultados da avaliação de aspectos estão disponíveis no BEP, apesar da ausência de tipos de eventos específicos do aspecto. Para cada par de (target, configuration)
com um aspecto aplicável, o Bazel publica um evento TargetConfigured
e TargetComplete
adicional contendo o resultado da aplicação do aspecto ao destino. Por exemplo, se //:foo_lib
fosse criado com
--aspects=aspects/myaspect.bzl%custom_aspect
, esse evento também apareceria
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 maneira eficiente com alguma preparação. Nesta seção, discutimos a estrutura compartilhada recursiva oferecida pelo evento NamedSetOfFiles
, que corresponde à estrutura de um Depset do Starlark.
Os consumidores precisam tomar cuidado para evitar algoritmos quadráticos ao processar
eventos NamedSetOfFiles
, porque grandes builds 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 NamedSetOfFiles
.
Um evento NamedSetOfFiles
sempre aparece no stream BEP antes de um evento TargetComplete
ou NamedSetOfFiles
que faz referência a ele. Esse é o
inverso da relação do evento "pai-filho", em que todos, exceto o primeiro,
aparecem após o anúncio de pelo menos um evento. Um evento NamedSetOfFiles
é
anunciado por um evento Progress
sem semântica.
Considerando essas restrições de compartilhamento e ordem, um consumidor típico precisa armazenar em buffer todos os
eventos NamedSetOfFiles
até que o fluxo do BEP seja esgotado. O fluxo 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 para um subconjunto de destinos/aspectos de versão:
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)