Nesta página, você verá como criar um programa com o Bazel, a sintaxe de comandos 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 do workspace
ou qualquer um dos subdiretórios 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 sinalizações do Bazel.clean
: remove os 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 de 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
: mostra informações de versão do Bazel.
Receber ajuda
bazel help command
: mostra ajuda e opções paracommand
.bazel help
startup_options
: opções para a JVM que hospeda o Bazel.bazel help
target-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. Os mais usados são bazel build
e bazel test
. Navegue 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 que você execute um build a partir de um volume completamente somente leitura.
Para criar um programa com o Bazel, digite bazel build
seguido pelo
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 listadas 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 último, o Bazel executa
os compiladores e outras ferramentas do build.
Durante a fase de execução da versão, o Bazel imprime mensagens de progresso. As mensagens de progresso incluem a etapa de build atual (como compilador ou vinculador) conforme ela começa e o número total de ações de build. À medida que o build começa, o número total de ações aumenta à medida que o Bazel descobre todo o gráfico de ações, mas o número é estabilizado em alguns segundos.
No final do build, o Bazel imprime quais destinos foram solicitados, se
foram criados com sucesso e, em caso afirmativo, onde os arquivos de saída podem ser
encontrados. Os scripts que executam builds podem analisar essa saída de maneira confiável. Consulte
--show_result
para mais detalhes.
Se você digitar o mesmo comando novamente, a compilação será concluída muito mais rapidamente.
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 um build 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, como na declaração de 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 destino //foo/bar:wiz . |
//foo/bar |
É equivalente a //foo/bar:bar . |
//foo/bar:all |
Todos os destinos de regras no pacote foo/bar . |
//foo/... |
Todas as segmentações de regras em todos os pacotes abaixo do diretório foo . |
//foo/...:all |
Todas as segmentações 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 de regras em pacotes no repositório principal. Não inclui destinos de repositórios externos. |
//:all |
Todas as segmentações de regras 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. Os exemplos abaixo 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:
|
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 em 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, correspondendo a todas as regras em um pacote. Esses dois caracteres podem ser
combinados, como em foo/...:all
, e quando ambos os 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
confuso, essa sintaxe permite que o conhecido caractere curinga :all
seja usado para
builds comuns, em que a criação de destinos como o _deploy.jar
não é desejada.
Além disso, o Bazel permite que uma barra seja usada em vez 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
é equivalente 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 destinos 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.
É importante ressaltar que subtrair metas dessa maneira não
garante que elas não sejam criadas, já que podem ser dependências de destinos
que não foram subtraídos. Por exemplo, se houver um //foo:all-apis
de destino
que entre outros dependa de //foo/bar:api
, o último será criado como
parte da criação do primeiro.
Destinos com tags = ["manual"]
não estão incluídos em padrões de destino curinga
(...
, :*
, :all
etc.) quando especificados em comandos como
bazel build
e bazel test
. Mas eles estão incluídos em
padrões de destino de caracteres curinga negativos, ou seja, são subtraídos. Especifique
esses destinos de teste com padrões explícitos na linha de comando se
você quiser que o Bazel os crie/teste. Por outro lado, o bazel query
não realiza
essa filtragem automaticamente, o que invalidaria a finalidade de
bazel query
.
Como buscar dependências externas
Por padrão, o Bazel faz o download e cria links 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 porque quer
"pré-buscar" dependências (por exemplo, antes de um voo em que você vai ficar off-line). Se você
quiser evitar a adição de novas dependências durante a criação, especifique a sinalização --fetch=false
. Observe que 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 as 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, sua versão falhará.
É possível buscar dependências manualmente executando bazel fetch
. Se
você não permitir a busca durante o build, execute bazel fetch
:
- Antes de criar pela primeira vez.
- Depois de adicionar uma dependência externa,
Depois de ser executado, não será necessário executá-lo novamente até que o arquivo do ESPAÇO DE TRABALHO seja modificado.
fetch
usa uma lista de destinos para buscar dependências. 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 //...
Com o Bazel 7 ou mais recente, se o Bzlmod estiver ativado, você também poderá buscar todas as dependências externas executando
bazel fetch
Não é necessário executar o bazel fetch se você tiver todas as ferramentas que estiver
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 buscar o mesmo arquivo várias vezes, mesmo que ele
seja necessário em diferentes espaços de trabalho ou se a definição de um repositório
externo tiver mudado, mas ele ainda precisa do mesmo arquivo para download. Para 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/
. O local 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 o cache manualmente. O cache nunca é limpado automaticamente, já que pode conter uma cópia de um arquivo que não está mais disponível para upstream.
[Descontinuado] Diretórios de arquivos de distribuição
Descontinuado: é recomendável usar o cache do repositório para ter um build off-line.
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
, é possível especificar outros diretórios somente leitura para procurar arquivos
em vez de buscá-los. Um arquivo é extraído 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ó vai funcionar se o hash do arquivo for especificado na declaração do WORKSPACE.
Embora a condição no nome do arquivo não seja necessária para precisã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 continua 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 o tamanho do binário do Bazel pequeno, 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 durante a criação de projetos Android.
No entanto, essas dependências implícitas podem causar problemas ao executar o Bazel em um ambiente com airgapp, mesmo que você tenha fornecido todas as dependências externas. Para resolver isso, você pode preparar um cache de repositório (com o Bazel 7 ou mais recente) ou um diretório de distribuição (com o Bazel anterior ao 7) contendo essas dependências em uma máquina com acesso à rede e, em seguida, transferi-las para o ambiente com airgapped com uma abordagem off-line.
Cache do repositório (com o Bazel 7 ou mais recente)
Para preparar o cache do repositório, use a
sinalização
--repository_cache
. 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 buscar essas dependências fora do ambiente protegido, primeiro crie um espaço de trabalho vazio:
mkdir empty_workspace && cd empty_workspace
touch MODULE.bazel
touch WORKSPACE
Para buscar dependências Bzlmod integradas, execute
bazel fetch --repository_cache="path/to/repository/cache"
Se você ainda depende do arquivo WORKSPACE legado, para buscar dependências integradas do WORKSPACE, execute
bazel sync --repository_cache="path/to/repository/cache"
Por fim, quando você usar o Bazel no seu ambiente controlado por isolamento, transmita a mesma
sinalização --repository_cache
. Por conveniência, você pode adicioná-lo como uma entrada
.bazelrc
:
common --repository_cache="path/to/repository/cache"
Além disso, talvez seja necessário clonar o
BCR localmente e usar a
sinalização --registry
para apontar sua cópia local e impedir que o Bazel acesse o
BCR pela Internet. Adicione a seguinte linha a .bazelrc
:
common --registry="path/to/local/bcr/registry"
Diretório de distribuição (com Bazel anterior a 7)
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 coberto por isolamento, primeiro faça o 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 do ambiente 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 controlado. Observe a sinalização --strip-components
, porque --distdir
pode ser bastante 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 controlado por isolamento, transmita a sinalização --distdir
que aponta para o diretório. Por conveniência, você pode adicioná-lo como uma entrada
.bazelrc
:
build --distdir=path/to/directory
Configurações de 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.
O segundo tipo são os dados externos ou ambientais 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. Obviamente, 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 compilação também precisa criar várias ferramentas usadas durante a
compilação, por exemplo, ferramentas criadas a partir da origem e, posteriormente,
usadas em uma regra geral, e elas precisam ser criadas para execução na estação de trabalho. Assim,
podemos identificar duas configurações: a configuração de exec, que é usada
para criar ferramentas que são executadas durante a criação, e a configuração de destino
(ou configuração de 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, como, por exemplo, algumas
bibliotecas base. Essas bibliotecas precisam ser criadas duas vezes: uma para a configuração
exec e outra para a configuração de destino. O Bazel garante 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 entre si. Se você receber mensagens de progresso
indicando que um determinado destino está sendo criado duas vezes, essa provavelmente é 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
e, se--host_crosstool_top
for usado, o valor de--host_cpu
será usado para procurar umdefault_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.
- Suprime o carimbo de binários com dados de build. Consulte as opções do
--embed_*
. - Todos os outros valores permanecem nos padrões.
Há muitos motivos pelos quais pode ser preferível selecionar uma configuração de execução distinta da configuração da solicitação. Mais importante:
Primeiro, ao usar binários otimizados que podem ser removidos, você reduz o tempo gasto na vinculação e execução das 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 todos os builds, você evita recriações muito caras que resultariam de pequenas mudanças na configuração da solicitação (como alterar uma opção do vinculador), conforme descrito anteriormente.
Corrigir recriações incrementais
Um dos principais objetivos do projeto do Bazel é garantir recompilações incrementais corretas. As ferramentas de build anteriores, especialmente aquelas baseadas no Make, fazem várias suposições incorretas na implementação de builds incrementais.
Primeiro, os carimbos de data/hora dos arquivos aumentam monotonicamente. Embora esse seja o caso típico, é muito fácil cair em descumprimento dessa suposição. Sincronizar com uma revisão anterior de um arquivo diminui o tempo de modificação dele. Os sistemas baseados em Make não são recriados.
De modo mais geral, embora o Make detecte alterações em arquivos, ele não detecta mudanças
nos comandos. Se você mudar as opções transmitidas ao compilador em uma determinada etapa
de build, o Make não executará o compilador novamente, e 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 supõe cegamente que o arquivo de saída truncado é válido (por ser mais recente que as entradas) e não é recriado. Da mesma forma, se o processo Make for eliminado, uma situação semelhante poderá ocorrer.
O Bazel evita essas e outras suposições. O Bazel mantém um banco de dados de todo o trabalho feito anteriormente e só vai omitir uma etapa de build se descobrir que o conjunto de arquivos de entrada (e os carimbos de data/hora) dessa 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á uma nova execução da etapa de compilação.
O benefício para os usuários de builds incrementais corretos é: menos tempo perdido devido à
confusão. Além disso, menos tempo gasto esperando recriações causadas pelo uso de make
clean
, seja necessário ou antecipado.
Criar consistência e builds incrementais
Formalmente, definimos o estado de um build como consistente quando todos os arquivos de saída esperados existem e o conteúdo deles está 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 com sucesso. 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 perceptiva: a inconsistência estável. Se o build atingir um estado inconsistente e estável, a invocação
bem-sucedida repetida da ferramenta de build não vai restaurar a consistência: o build
ficará "travado" e as saídas permanecerão incorretas. Estados inconsistentes estáveis
são o principal motivo pelo qual os usuários do Make e outras ferramentas de build digitam 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 ter um build consistente é descartar todas as saídas de build anteriores e começar de novo: tornar cada build um build 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 conseguir executar builds incrementais sem comprometer a consistência.
A análise correta das dependências incrementais é difícil e, conforme descrito acima, muitas outras ferramentas de build não conseguem 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 vai ficar 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. mas garante que os resultados do próximo build restaurarão a consistência.
Como acontece com todas as garantias, há alguns detalhes: há algumas maneiras conhecidas de entrar em um estado inconsistente e estável com o Bazel. Não podemos investigar esses 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 e estável com o Bazel, informe o bug.
Execução no modo 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 vazadas) em sandboxes que
contêm apenas o conjunto mínimo de arquivos que a ferramenta exige para realizar o 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 imprimir um aviso se o sistema não for compatível com o sandbox para alertar
você de que não há garantia de que as versões são herméticas e 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 do usuário são desativados por padrão devido a preocupações com a segurança. Isso pode ser verificado analisando o arquivo
/proc/sys/kernel/unprivileged_userns_clone
: se ele existir e contiver 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 falha ao executar 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 (veja abaixo).
Carregando fase
O primeiro é o carregamento, em que todos os arquivos BUILD necessários para os destinos iniciais e o fechamento transitivo de dependências são carregados, analisados, avaliados e armazenados em cache.
Para o primeiro build após um servidor do Bazel ser iniciado, a fase de carregamento normalmente leva muitos segundos porque muitos arquivos BUILD são 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 léxicos 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 do trabalho exato que precisa ser feito em cada etapa.
Assim como o carregamento, a análise também leva alguns segundos quando calculada na íntegra. No entanto, ele armazena em cache o gráfico de dependências de um build para o próximo e analisa novamente apenas 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 informados nessa etapa incluem: dependências inadequadas, entradas inválidas em uma regra e todas as mensagens de erro específicas dela.
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 é por projeto e torna o Bazel uma boa base para ferramentas de análise, como o comando query do Bazel, que é implementado acima da fase de carregamento.
Fase de execução
A terceira e última fase do build é a execução. Essa fase garante que as saídas de cada etapa do build sejam consistentes com as entradas, executando novamente ferramentas de compilação/vinculação etc. conforme necessário. Essa etapa é em que o build gasta 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 executados por alguma ação de build ou falha de uma ferramenta para produzir o conjunto esperado de saídas.