Criar programas com o Bazel

Informar um problema Acessar fonte

Nesta página, você verá como criar um programa com o Bazel, a sintaxe do comando de build e a sintaxe de padrão de destino.

Guia de início rápido

Para executar o Bazel, acesse o diretório base workspace ou qualquer um dos subdiretórios dele e digite bazel. Consulte build se precisar criar um novo espaço de trabalho.

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

Comandos disponíveis

  • analyze-profile: analisa os dados do perfil do build.
  • aquery: executa uma consulta no gráfico de ações de pós-análise.
  • build: cria os destinos especificados.
  • canonicalize-flags: canonizar as sinalizações do Bazel.
  • clean: remove arquivos de saída e, opcionalmente, interrompe o servidor.
  • cquery: executa uma consulta do gráfico de dependências pós-análise.
  • dump: despeja o estado interno do processo do servidor do Bazel.
  • help: mostra a ajuda para comandos ou o índice.
  • info: mostra informações de tempo de execução sobre o servidor Bazel.
  • fetch: busca todas as dependências externas de um destino.
  • mobile-install: instala apps em dispositivos móveis.
  • query: executa uma consulta do gráfico de dependência.
  • run: executa o destino especificado.
  • shutdown: interrompe o servidor do Bazel.
  • test: cria e executa os destinos de teste especificados.
  • version: imprime informações de versão para o Bazel.

Receber ajuda

  • bazel help command: mostra ajuda e opções para command.
  • bazel helpstartup_options: opções para a JVM que hospeda o Bazel.
  • bazel helptarget-syntax: explica a sintaxe para especificar destinos.
  • bazel help info-keys: mostra uma lista de chaves usadas pelo comando de informações.

A ferramenta bazel executa muitas funções, chamadas de comandos. As mais usadas são bazel build e bazel test. É possível navegar pelas mensagens de ajuda on-line usando bazel help.

Como criar um destino

Antes de iniciar um build, você precisa de um espaço de trabalho. Um espaço de trabalho é uma árvore de diretórios que contém todos os arquivos de origem necessários para criar seu aplicativo. O Bazel permite executar um build a partir de um volume completamente somente leitura.

Para criar um programa com o Bazel, digite bazel build seguido do destino que você quer criar.

bazel build //foo

Depois de emitir o comando para criar //foo, você verá uma saída semelhante a esta:

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

Primeiro, o Bazel carrega todos os pacotes no gráfico de dependências do destino. Isso inclui dependências declaradas, arquivos listados diretamente no arquivo BUILD do destino e dependências transitivas, que são arquivos listados nos arquivos BUILD das dependências do destino. Depois de identificar todas as dependências, o Bazel as analisa para garantir a precisão e cria as ações de build. Por fim, o Bazel executa os compiladores e outras ferramentas do build.

Durante a fase de execução da compilação, o Bazel imprime mensagens de progresso. As mensagens de progresso incluem a etapa de build atual (como compilador ou vinculador) conforme ela é iniciada e o número concluído em relação ao número total de ações de build. À medida que o build é iniciado, o número total de ações geralmente aumenta à medida que o Bazel descobre o gráfico de ações inteiro, mas o número se estabiliza em alguns segundos.

No final da compilação, o Bazel imprime quais destinos foram solicitados, se foram criados ou não e, em caso afirmativo, onde os arquivos de saída podem ser encontrados. Os scripts que executam builds podem analisar essa saída de modo confiável. Consulte --show_result para mais detalhes.

Se você digitar o mesmo comando novamente, o build será concluído muito mais rápido.

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

Este é um null build. Como nada mudou, não há pacotes para recarregar nem etapas de build para executar. Se algo mudasse em "foo" ou nas dependências, o Bazel executaria algumas ações de build novamente ou concluiria uma versão incremental.

Como criar vários destinos

O Bazel permite várias maneiras de especificar os destinos a serem criados. Coletivamente, eles são conhecidos como padrões de destino. Essa sintaxe é usada em comandos como build, test ou query.

Enquanto rótulos são usados para especificar destinos individuais, por exemplo, para declarar dependências em arquivos BUILD, os padrões de destino do Bazel especificam vários destinos. Os padrões de destino são uma generalização da sintaxe do rótulo para conjuntos de destinos, usando caracteres curinga. No caso mais simples, qualquer rótulo válido também é um padrão de destino válido, identificando um conjunto de exatamente um destino.

Todos os padrões de destino que começam com // são resolvidos em relação ao espaço de trabalho atual.

//foo/bar:wiz Apenas o único destino //foo/bar:wiz.
//foo/bar É equivalente a //foo/bar:bar.
//foo/bar:all Todas as segmentações de regras no pacote foo/bar.
//foo/... Todos os destinos de regras em todos os pacotes abaixo do diretório foo.
//foo/...:all Todos os destinos de regras em todos os pacotes abaixo do diretório foo.
//foo/...:* Todos os destinos (regras e arquivos) em todos os pacotes abaixo do diretório foo.
//foo/...:all-targets Todos os destinos (regras e arquivos) em todos os pacotes abaixo do diretório foo.
//... Todos os destinos em pacotes no espaço de trabalho. Isso não inclui destinos de repositórios externos.
//:all Todos os destinos no pacote de nível superior, se houver um arquivo "BUILD" na raiz do espaço de trabalho.

Os padrões de destino que não começam com // são resolvidos em relação ao diretório de trabalho atual. Estes exemplos pressupõem um diretório de trabalho de foo:

:foo Equivalente a //foo:foo.
bar:wiz É equivalente a //foo/bar:wiz.
bar/wiz Equivalente a:
  • //foo/bar/wiz:wiz se foo/bar/wiz for um pacote
  • //foo/bar:wiz se foo/bar for um pacote
  • //foo:bar/wiz caso contrário
bar:all Equivalente a //foo/bar:all.
:all Equivalente a //foo:all.
...:all Equivalente a //foo/...:all.
... Equivalente a //foo/...:all.
bar/...:all Equivalente a //foo/bar/...:all.

Por padrão, os links simbólicos de diretório são seguidos para padrões de destino recursivos, exceto aqueles que apontam para a base de saída, como os links simbólicos de conveniência criados no diretório raiz do espaço de trabalho.

Além disso, o Bazel não segue links simbólicos ao avaliar padrões de destino recursivos em qualquer diretório que contenha um arquivo nomeado da seguinte maneira: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/... é um caractere curinga sobre pacotes, indicando todos os pacotes recursivamente abaixo do diretório foo (para todas as raízes do caminho do pacote). :all é um caractere curinga em destinos, que corresponde a todas as regras em um pacote. Os dois podem ser combinados, como em foo/...:all, e quando os dois caracteres curinga são usados, isso pode ser abreviado como foo/....

Além disso, :* (ou :all-targets) é um caractere curinga que corresponde a todos os destinos nos pacotes correspondentes, incluindo arquivos que normalmente não são criados por nenhuma regra, como arquivos _deploy.jar associados às regras java_binary.

Isso implica que :* denota um superconjunto de :all. Embora seja potencialmente confusa, essa sintaxe permite que o caractere curinga :all conhecido seja usado para builds típicos, em que a criação de destinos como _deploy.jar não é desejada.

Além disso, o Bazel permite que uma barra seja usada no lugar dos dois pontos exigidos pela sintaxe do rótulo. Isso geralmente é conveniente ao usar a expansão de nome de arquivo Bash. Por exemplo, foo/bar/wiz equivale a //foo/bar:wiz (se houver um pacote foo/bar) ou a //foo:bar/wiz (se houver um pacote foo).

Muitos comandos do Bazel aceitam uma lista de padrões de destino como argumentos e todos respeitam o operador de negação de prefixo -. Isso pode ser usado para subtrair um conjunto de metas do conjunto especificado pelos argumentos anteriores. Observe que isso significa que a ordem é importante. Por exemplo,

bazel build foo/... bar/...

significa "criar todos os destinos abaixo de foo e todos os destinos abaixo de bar", enquanto

bazel build -- foo/... -foo/bar/...

significa "criar todos os destinos abaixo de foo, exceto aqueles abaixo de foo/bar". O argumento -- é necessário para evitar que os argumentos subsequentes que começam com - sejam interpretados como opções adicionais.

No entanto, é importante ressaltar que subtrair metas dessa maneira não garante que elas não sejam criadas, já que podem ser dependências de metas que não foram subtraídas. Por exemplo, se houvesse um //foo:all-apis de destino que, entre outros, dependesse de //foo/bar:api, o último seria criado como parte da criação do primeiro.

Os destinos com tags = ["manual"] não são incluídos nos padrões de destino de caracteres curinga (..., :*, :all etc.) quando especificados em comandos como bazel build e bazel test. No entanto, eles estão incluídos em padrões de destino de caracteres curinga negativos, ou seja, serão subtraídos. Especifique esses destinos de teste com padrões de destino explícitos na linha de comando se quiser que o Bazel os crie/teste. Por outro lado, bazel query não realiza essa filtragem automaticamente (isso anularia a finalidade de bazel query).

Como buscar dependências externas

Por padrão, o Bazel faz o download e vincula simbólicos de dependências externas durante o build. No entanto, isso pode ser indesejável, seja porque você quer saber quando novas dependências externas são adicionadas ou quer "pré-buscar" dependências (por exemplo, antes de um voo em que você estará off-line). Se você quiser evitar a adição de novas dependências durante a criação, especifique a sinalização --fetch=false. Essa sinalização se aplica apenas a regras de repositório que não apontam para um diretório no sistema de arquivos local. Mudanças, por exemplo, em local_repository, new_local_repository e nas regras de repositório do SDK do Android e do NDK sempre vão entrar em vigor, independente do valor --fetch .

Se você proibir a busca durante builds e o Bazel encontrar novas dependências externas, seu build falhará.

É possível buscar dependências manualmente executando bazel fetch. Se você não permitir durante a busca de build, será necessário executar bazel fetch:

  • Antes de começar a criar.
  • Depois de adicionar uma nova dependência externa.

Depois da execução, não será necessário executá-lo novamente até que o arquivo do ESPAÇO DE TRABALHO seja modificado.

fetch pega uma lista de destinos para os quais as dependências precisam ser buscadas. Por exemplo, isso buscaria as dependências necessárias para criar //foo:bar e //bar:baz:

bazel fetch //foo:bar //bar:baz

Para buscar todas as dependências externas de um espaço de trabalho, execute:

bazel fetch //...

No Bazel 7.1 ou posterior, se o Bzlmod estiver ativado, também é possível buscar todas as dependências externas executando

bazel fetch

Não será necessário executar a busca do bazel se você tiver todas as ferramentas que está usando (de jars de biblioteca ao próprio JDK) na raiz do espaço de trabalho. No entanto, se você estiver usando algo fora do diretório do espaço de trabalho, o Bazel vai executar automaticamente bazel fetch antes de executar bazel build.

O cache do repositório

Ele tenta evitar a busca do mesmo arquivo várias vezes, mesmo que o mesmo arquivo seja necessário em espaços de trabalho diferentes ou se a definição de um repositório externo tenha mudado, mas ele ainda precisa do mesmo arquivo para download. Para fazer isso, o Bazel armazena em cache todos os arquivos transferidos por download no cache do repositório que, por padrão, está localizado em ~/.cache/bazel/_bazel_$USER/cache/repos/v1/. Ele pode ser alterado pela opção --repository_cache. O cache é compartilhado entre todos os espaços de trabalho e as versões instaladas do Bazel. Uma entrada será extraída do cache se o Bazel tiver certeza de que tem uma cópia do arquivo correto, ou seja, se a solicitação de download tiver uma soma SHA256 do arquivo especificado e um arquivo com esse hash estiver no cache. Portanto, especificar um hash para cada arquivo externo não é apenas uma boa ideia do ponto de vista da segurança, mas também ajuda a evitar downloads desnecessários.

Após cada ocorrência em cache, o horário da modificação do arquivo no cache é atualizado. Dessa forma, o último uso de um arquivo no diretório de cache pode ser facilmente determinado, por exemplo, para limpar manualmente o cache. O cache nunca é limpo automaticamente, porque pode conter uma cópia de um arquivo que não está mais disponível upstream.

Diretórios de arquivos de distribuição

O diretório de distribuição é outro mecanismo do Bazel para evitar downloads desnecessários. O Bazel pesquisa os diretórios de distribuição antes do cache do repositório. A principal diferença é que o diretório de distribuição requer preparação manual.

Usando a opção --distdir=/path/to-directory, você pode especificar outros diretórios somente leitura para procurar arquivos em vez de buscá-los. Um arquivo será retirado desse diretório se o nome do arquivo for igual ao nome base do URL e, além disso, o hash do arquivo for igual ao especificado na solicitação de download. Isso só funciona se o hash do arquivo for especificado na declaração do ESPAÇO DE TRABALHO.

Embora a condição no nome do arquivo não seja necessária para a correção, ela reduz o número de arquivos candidatos a um por diretório especificado. Dessa forma, especificar diretórios de arquivos de distribuição vai continuar eficiente, mesmo que o número de arquivos nesses diretórios cresça.

Como executar o Bazel em um ambiente com isolamento físico

Para manter pequeno o tamanho do binário do Bazel, as dependências implícitas do Bazel são buscadas na rede durante a execução pela primeira vez. Essas dependências implícitas contêm conjuntos de ferramentas e regras que podem não ser necessários para todos. Por exemplo, as ferramentas do Android são desagrupadas e buscadas apenas ao criar projetos Android.

No entanto, essas dependências implícitas podem causar problemas ao executar o Bazel em um ambiente fechado, mesmo que você tenha fornecido todas as dependências do ESPAÇO DE TRABALHO. Para resolver isso, prepare um diretório de distribuição contendo essas dependências em uma máquina com acesso à rede e transfira-as para o ambiente controlado com uma abordagem off-line.

Para preparar o diretório de distribuição, use a sinalização --distdir. Você precisará fazer isso uma vez para cada nova versão binária do Bazel, já que as dependências implícitas podem ser diferentes para cada versão.

Para criar essas dependências fora do ambiente isolado, primeiro faça check-out da árvore de origem do Bazel na versão correta:

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

Em seguida, crie o tarball contendo as dependências implícitas de tempo de execução para essa versão específica do Bazel:

bazel build @additional_distfiles//:archives.tar

Exporte esse tarball para um diretório que possa ser copiado para o ambiente afetado. Observe a flag --strip-components, porque --distdir pode ser bem específico com o nível de aninhamento do diretório:

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

Por fim, ao usar o Bazel no seu ambiente isolado, transmita a sinalização --distdir que aponta para o diretório. Por conveniência, é possível adicioná-lo como uma entrada .bazelrc:

build --distdir=path/to/directory

Configurações do build e compilação cruzada

Todas as entradas que especificam o comportamento e o resultado de um determinado build podem ser divididas em duas categorias distintas. O primeiro tipo são as informações intrínsecas armazenadas nos arquivos BUILD do projeto: a regra de build, os valores dos atributos e o conjunto completo das dependências transitivas dele. O segundo tipo são os dados externos ou do ambiente, fornecidos pelo usuário ou pela ferramenta de build: a escolha da arquitetura de destino, opções de compilação e vinculação e outras opções de configuração do conjunto de ferramentas. Chamamos um conjunto completo de dados ambientais como uma configuração.

Em qualquer build, pode haver mais de uma configuração. Considere uma compilação cruzada, em que você cria um executável //foo:bin para uma arquitetura de 64 bits, mas sua estação de trabalho é uma máquina de 32 bits. Claramente, o build exigirá a criação de //foo:bin usando um conjunto de ferramentas capaz de criar executáveis de 64 bits, mas o sistema de build também precisa criar várias ferramentas usadas durante o build, por exemplo, ferramentas criadas a partir da origem e, posteriormente, usadas, por exemplo, em uma regra geral. Elas precisam ser criadas para funcionar na estação de trabalho. Assim, podemos identificar duas configurações: a configuração "exec", usada para criar ferramentas executadas durante a criação, e a configuração de destino (ou configuração da solicitação, mas usamos "configuração de destino" com mais frequência, mesmo que essa palavra já tenha muitos significados), que é usada para criar o binário solicitado.

Normalmente, há muitas bibliotecas que são pré-requisitos do destino de build solicitado (//foo:bin) e de uma ou mais ferramentas exec. Por exemplo, algumas bibliotecas de base. Essas bibliotecas precisam ser criadas duas vezes: uma para a configuração exec e outra para a configuração de destino. Ele cuida de garantir que ambas as variantes sejam criadas e que os arquivos derivados sejam mantidos separados para evitar interferência. Geralmente, esses destinos podem ser criados simultaneamente, porque são independentes um do outro. Se você receber mensagens de progresso indicando que um determinado destino está sendo criado duas vezes, provavelmente essa é a explicação.

A configuração "exec" é derivada da configuração de destino da seguinte maneira:

  • Use a mesma versão do Crosstool (--crosstool_top), conforme especificado na configuração da solicitação, a menos que --host_crosstool_top seja especificado.
  • Use o valor de --host_cpu para --cpu (padrão: k8).
  • Use os mesmos valores dessas opções, conforme especificado na configuração da solicitação: --compiler, --use_ijars. Se --host_crosstool_top for usado, o valor de --host_cpu será usado para procurar uma default_toolchain no Crosstool (ignorando --compiler) para a configuração exec.
  • Use o valor de --host_javabase para --javabase.
  • Use o valor de --host_java_toolchain para --java_toolchain.
  • Usar builds otimizados para código C++ (-c opt).
  • Não gerar informações de depuração (--copt=-g0).
  • Remova informações de depuração de executáveis e bibliotecas compartilhadas (--strip=always).
  • Coloque todos os arquivos derivados em um local especial, diferente daquele usado por qualquer configuração de solicitação possível.
  • Suprimir a formatação de binários com dados de build (consulte as opções do --embed_*).
  • Os demais valores permanecem nos padrões.

Há muitos motivos pelos quais pode ser preferível selecionar uma configuração de execução diferente na configuração da solicitação. Mais importante:

Em primeiro lugar, ao usar binários otimizados e removidos, você reduz o tempo gasto para vincular e executar as ferramentas, o espaço em disco ocupado pelas ferramentas e o tempo de E/S de rede em builds distribuídos.

Em segundo lugar, ao desacoplar as configurações de execução e solicitação em todas as versões, você evita recriações muito caras que resultariam de pequenas alterações na configuração da solicitação (como alterar uma opção do vinculador), conforme descrito anteriormente.

Corrigir recriações incrementais

Uma das principais metas do projeto do Bazel é garantir recompilações incrementais corretas. As ferramentas de build anteriores, especialmente aquelas baseadas no Make, fazem várias suposições falsas ao implementar builds incrementais.

Primeiro, os carimbos de data/hora dos arquivos aumentam monotonicamente. Embora esse seja o caso típico, é muito fácil cair em desacordo com essa suposição. A sincronização com uma revisão anterior de um arquivo diminui o tempo de modificação dele e os sistemas baseados em Make não são recriados.

De modo mais geral, o Make detecta alterações nos arquivos, mas não nos comandos. Se você mudar as opções passadas para o compilador em uma determinada etapa de build, o Make não executará o compilador novamente. Além disso, será necessário descartar manualmente as saídas inválidas do build anterior usando make clean.

Além disso, o Make não é robusto contra o encerramento malsucedido de um dos subprocessos depois que ele começou a gravar no arquivo de saída. Embora a execução atual do Make falhe, a invocação subsequente do Make vai assumir cegamente que o arquivo de saída truncado é válido (por ser mais recente do que as entradas) e não será recriado. Da mesma forma, se o processo Make for encerrado, uma situação semelhante poderá ocorrer.

O Bazel evita essas e outras suposições. Ele mantém um banco de dados de todo o trabalho realizado anteriormente e só omitirá uma etapa de compilação se descobrir que o conjunto de arquivos de entrada (e os respectivos carimbos de data/hora) para essa etapa e o comando de compilação dessa etapa correspondem exatamente a um no banco de dados e se o conjunto de arquivos de saída (e os carimbos de data/hora) da entrada do banco de dados correspondem exatamente aos carimbos de data/hora dos arquivos no disco. Qualquer alteração nos arquivos de entrada ou de saída, ou no próprio comando, causará a nova execução da etapa de build.

O benefício para os usuários de builds incrementais corretos é que há menos tempo perdido devido à confusão. Além disso, há menos tempo gasto aguardando recriações causadas pelo uso de make clean, seja necessário ou preventivo.

Criar consistência e builds incrementais

Formalmente, definimos o estado de um build como consistente quando todos os arquivos de saída esperados existirem e o conteúdo deles estiver correto, conforme especificado pelas etapas ou regras necessárias para criá-los. Quando você edita um arquivo de origem, o estado do build é considerado inconsistente e permanece inconsistente até que você execute a ferramenta de build até a conclusão. Descrevemos essa situação como inconsistência instável, porque ela é apenas temporária, e a consistência é restaurada executando a ferramenta de build.

Há outro tipo de inconsistência que também é perceptiva: a inconsistência estável. Se o build atinge um estado inconsistente estável, a invocação bem-sucedida repetida da ferramenta de build não restaura a consistência: o build fica "travado" e as saídas permanecem incorretas. Estados inconsistentes estáveis são o principal motivo pelo qual os usuários do Make (e de outras ferramentas de build) tipo make clean. Descobrir que a ferramenta de build falhou dessa maneira (e depois se recuperar dela) pode ser demorado e muito frustrante.

Conceitualmente, a maneira mais simples de alcançar um build consistente é descartar todas as saídas de build anteriores e começar de novo: fazer com que cada build seja limpo. Obviamente, essa abordagem é demorada demais para ser prática (exceto talvez para engenheiros de lançamento). Portanto, para ser útil, a ferramenta de build precisa ser capaz de executar builds incrementais sem comprometer a consistência.

Corrigir a análise de dependências incremental é difícil e, conforme descrito acima, muitas outras ferramentas de build não são eficientes para evitar estados inconsistentes estáveis durante builds incrementais. Por outro lado, o Bazel oferece a seguinte garantia: após uma invocação bem-sucedida da ferramenta de build em que você não fez edições, o build estará em um estado consistente. Se você editar os arquivos de origem durante uma versão, o Bazel não garante a consistência do resultado do build atual. No entanto, isso garante que os resultados da próxima compilação restaurarão a consistência.

Como em todas as garantias, há alguns detalhes: há algumas maneiras conhecidas de entrar em um estado inconsistente estável com o Bazel. Não garantimos investigação desses problemas decorrentes de tentativas deliberadas de encontrar bugs na análise de dependência incremental, mas vamos investigar e fazer o possível para corrigir todos os estados inconsistentes estáveis resultantes do uso normal ou "razoável" da ferramenta de build.

Se você detectar um estado inconsistente estável com o Bazel, informe um bug.

Execução em sandbox

O Bazel usa sandboxes para garantir que as ações sejam executadas de maneira hermética e correta. O Bazel executa spawns (ações) em sandboxes que contêm apenas o conjunto mínimo de arquivos necessários para a execução do trabalho. Atualmente, o sandbox funciona no Linux 3.12 ou mais recente com a opção CONFIG_USER_NS ativada e também no macOS 10.11 ou mais recente.

O Bazel vai gerar um aviso se o sistema não for compatível com sandbox para alertar você de que não há garantia de que as versões são herméticas e que podem afetar o sistema host de maneiras desconhecidas. Para desativar esse aviso, transmita a flag --ignore_unsupported_sandboxing para o Bazel.

Em algumas plataformas, como os nós de cluster do Google Kubernetes Engine ou o Debian, os namespaces de usuário são desativados por padrão devido a questões de segurança. Isso pode ser verificado no arquivo /proc/sys/kernel/unprivileged_userns_clone: se ele existir e contiver um 0, os namespaces do usuário poderão ser ativados com sudo sysctl kernel.unprivileged_userns_clone=1.

Em alguns casos, o sandbox do Bazel não executa regras devido à configuração do sistema. O sintoma geralmente é uma falha que gera uma mensagem semelhante a namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory. Nesse caso, tente desativar o sandbox para regras gerais com --strategy=Genrule=standalone e para outras regras com --spawn_strategy=standalone. Além disso, informe um bug no nosso Issue Tracker e informe qual distribuição do Linux você está usando para que possamos investigar e fornecer uma correção em uma versão posterior.

Fases de um build

No Bazel, um build ocorre em três fases distintas. Como usuário, entender a diferença entre elas fornece informações sobre as opções que controlam um build (confira abaixo).

Fase de carregamento

O primeiro é o carregamento, em que todos os arquivos BUILD necessários para os destinos iniciais e o fechamento transitivo das dependências são carregados, analisados, avaliados e armazenados em cache.

Para a primeira compilação após a inicialização de um servidor do Bazel, a fase de carregamento normalmente leva vários segundos do número de arquivos BUILD carregados do sistema de arquivos. Nas versões subsequentes, especialmente se nenhum arquivo BUILD tiver sido alterado, o carregamento ocorre muito rapidamente.

Os erros relatados durante essa fase incluem: pacote não encontrado, destino não encontrado, erros lexicos e gramaticais em um arquivo BUILD e erros de avaliação.

Fase de análise

A segunda fase, análise, envolve a análise semântica e a validação de cada regra de build, a construção de um gráfico de dependência de build e a determinação exatamente de qual trabalho precisa ser feito em cada etapa do build.

Assim como o carregamento, a análise também leva vários segundos quando calculada na totalidade. No entanto, o Bazel armazena em cache o gráfico de dependência de um build para o próximo e analisa novamente o que é necessário, o que pode tornar os builds incrementais extremamente rápidos caso os pacotes não tenham mudado desde o build anterior.

Os erros relatados nesta fase incluem: dependências inadequadas, entradas inválidas para uma regra e todas as mensagens de erro específicas da regra.

As fases de carregamento e análise são rápidas porque o Bazel evita E/S de arquivo desnecessária nesta fase, lendo apenas arquivos BUILD para determinar o trabalho a ser feito. Isso ocorre por projeto e torna o Bazel uma boa base para ferramentas de análise, como o comando query do Bazel, que é implementado na fase de carregamento.

Fase de execução

A terceira e última fase da compilação é a execução. Essa fase garante que as saídas de cada etapa do build sejam consistentes com as entradas, executando novamente as ferramentas de compilação/vinculação/etc. conforme necessário. Essa etapa é onde o build passa a maior parte do tempo, variando de alguns segundos a mais de uma hora para um build grande. Os erros relatados durante essa fase incluem: arquivos de origem ausentes, erros em uma ferramenta executada por alguma ação de build ou falha de uma ferramenta para produzir o conjunto esperado de saídas.