Exemplos de protocolo de evento de build

7.3 · 7.2 · 7.1 · 7.0 · 6.5

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 e filho mencionada acima. Alguns eventos de build e a maioria dos campos foram omitidos para encurtar o texto.

bep-graph

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 ... se expandiu: //foo:foo_lib e //foo:foo_test. Para isso, ele declara 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 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, elas contêm o identificador de evento de versão de um evento NamedSetOfFiles, que passa a conter os nomes e caminhos reais dos arquivos. O evento NamedSetOfFiles permite que um conjunto de arquivos seja informado uma vez e referenciados por vários destinos. Essa estrutura é necessária porque, em alguns casos, o tamanho da saída do Build Event Protocol aumenta de forma 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 pelos identificadores de evento de build.

Veja 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 de aspectos no BEP

Os builds comuns avaliam ações associadas a pares de (target, configuration). Ao criar com aspectos ativados, o Bazel também avalia destinos associados a triplos de (target, configuration, aspect) 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 (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"
      }]
    }]
  }
}

Consumindo 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.

namedsetoffiles-bep-graph

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 depois de 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 abaixo 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)