Esta página aborda como criar um programa com o Bazel, a sintaxe do comando de build e a sintaxe do padrão de destino.
Guia de início rápido
Para executar o Bazel, acesse o diretório workspace
ou qualquer um dos subdiretórios e digite bazel
. Consulte criar se você
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 pós-análise.build
: cria os destinos especificados.canonicalize-flags
: canonicaliza flags do Bazel.clean
: remove os arquivos de saída e, opcionalmente, interrompe o servidor.cquery
: executa uma consulta de gráfico de dependência de pós-análise.dump
: salva o estado interno do processo do servidor do Bazel.help
: mostra a ajuda de comandos ou o índice.info
: mostra informações de execução sobre o servidor do 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 as informações da 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
: exibe uma lista de chaves usadas pelo comando info.
A ferramenta bazel
executa muitas funções, chamadas comandos. Os mais usados
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 que você execute um build em um volume totalmente 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ê vai 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, arquivos listados nos arquivos BUILD
das
dependências do destino. Depois de identificar todas as dependências, o Bazel as analisou
para verificar se estão corretas e criou as ações de build. Por fim, o Bazel executes
os compiladores e outras ferramentas do build.
Durante a fase de execução do build, o Bazel imprime mensagens de progresso. As mensagens de progresso incluem a etapa de build atual (como o compilador ou o 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 começa, o número total de ações geralmente aumenta à medida que o Bazel descobre todo o gráfico de ações, mas o número se estabiliza em alguns segundos.
No final do build, o Bazel mostra quais destinos foram solicitados, se
foram ou não criados e, em caso afirmativo, onde os arquivos de saída podem ser
encontrados. Os scripts que executam builds podem analisar essa saída com confiabilidade. 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 build nulo. Como nada mudou, não há pacotes para recarregar e nenhuma etapa de build para executar. Se algo mudar em "foo" ou nas dependências, o Bazel vai executar novamente algumas ações de build ou concluir um build incremental.
Como criar vários destinos
O Bazel permite várias maneiras de especificar os destinos a serem criados. Coletivamente,
elas são conhecidas como padrões de destino. Essa sintaxe é usada em comandos como
build
, test
ou query
.
Enquanto os rótulos são usados para especificar destinos
individuais, como 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 de 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 único //foo/bar:wiz . |
//foo/bar |
É equivalente a //foo/bar:bar . |
//foo/bar:all |
Todos os destinos 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 . |
//... |
Todas as segmentações 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 assumem um diretório de trabalho de foo
:
:foo |
É equivalente a //foo:foo . |
bar:wiz |
É equivalente a //foo/bar:wiz . |
bar/wiz |
Equivale 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 criado 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 com o nome a seguir:
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 curinga em alvos, correspondendo a todas as regras em um pacote. Eles podem ser
combinados, como em foo/...:all
, e quando ambos os caracteres curinga são usados, isso pode ser
abreviado para foo/...
.
Além disso, :*
(ou :all-targets
) é um 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 a regras java_binary
.
Isso implica que :*
denota um superconjunto de :all
. Embora possa
ser confuso, essa sintaxe permite que o curinga :all
seja usado para
builds típicos, em que não é necessário criar destinos como _deploy.jar
.
Além disso, o Bazel permite que uma barra inclinada seja usada em vez do dois-pontos exigido pela
sintaxe do rótulo. Isso é conveniente ao usar a expansão de nome de arquivo do 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
alvos do conjunto especificado pelos argumentos anteriores. 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 destinos dessa maneira não
garante que eles não sejam criados, já que podem ser dependências de destinos
que não foram subtraídos. Por exemplo, se houvesse uma //foo:all-apis
de destino
que, entre outras, dependesse de //foo/bar:api
, esta seria criada como
parte da criação da primeira.
Os destinos com tags = ["manual"]
não são incluídos em padrões de destino curinga
(...
, :*
, :all
etc.) quando especificados em comandos como
bazel build
e bazel test
. É necessário especificar esses
destinos de teste com padrões de destino 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 anularia o propósito do
bazel query
.
Como buscar dependências externas
Por padrão, o Bazel faz o download e cria um link simbólico para dependências externas durante o
build. No entanto, isso pode ser indesejável, porque você quer saber
quando novas dependências externas são adicionadas ou porque você quer
"pré-carregar" dependências (por exemplo, antes de um voo em que você estará off-line). Se você
não quiser que novas dependências sejam adicionadas durante os builds, especifique
a flag --fetch=false
. Essa flag só
se aplica a regras de repositório que não apontam para um diretório no sistema de arquivos
local. Mudanças, por exemplo, para local_repository
,
new_local_repository
e regras do repositório do SDK do Android e do NDK
sempre vão entrar em vigor, independentemente do valor --fetch
.
Se você não permitir a busca durante os builds e o Bazel encontrar novas dependências externas, o build vai falhar.
É possível buscar dependências manualmente executando bazel fetch
. Se
você não permitir a busca durante a criação, será necessário executar bazel fetch
:
- Antes de criar pela primeira vez.
- Depois de adicionar uma nova dependência externa.
Depois que ele for executado, não será necessário executá-lo novamente até que o arquivo WORKSPACE seja alterado.
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 //...
Não é necessário executar o bazel fetch 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
O Bazel tenta evitar a busca do mesmo arquivo várias vezes, mesmo que o mesmo
arquivo seja necessário em diferentes espaços de trabalho ou se a definição de um repositório
externo mudou, mas ainda precisa do mesmo arquivo para fazer o 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/
. O
local pode ser alterado pela opção --repository_cache
. O
cache é compartilhado entre todos os espaços de trabalho e versões instaladas do Bazel.
Uma entrada é tirada do cache se
o Bazel souber com certeza 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 de segurança, mas também ajuda a evitar
downloads desnecessários.
Em cada acerto de cache, o tempo de 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 é 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 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 extraí-los. Um arquivo é retirado de um 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 WORKSPACE.
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, a especificação de diretórios de arquivos de distribuição continua eficiente, mesmo que o número de arquivos em um diretório aumente muito.
Como executar o Bazel em um ambiente isolado
Para manter o tamanho binário do Bazel pequeno, as dependências implícitas do Bazel são buscadas pela rede durante a execução pela primeira vez. Essas dependências implícitas contêm toolchains e regras que podem não ser necessárias para todos. Por exemplo, as ferramentas do Android são desempacotadas e buscadas apenas ao criar projetos Android.
No entanto, essas dependências implícitas podem causar problemas ao executar o Bazel em um ambiente isolado, mesmo que você tenha fornecido todas as dependências do WORKSPACE. Para resolver isso, prepare um diretório de distribuição que contenha essas dependências em uma máquina com acesso à rede e, em seguida, transfira-as para o ambiente isolado 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 o checkout 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 que contém as dependências de execução implícitas 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
airgapped. Observe a flag --strip-components
, porque --distdir
pode ser
bastante complicada 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 ambiente isolado, transmita a flag --distdir
apontando para o diretório. Para facilitar, adicione-o 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 é a informação
intrínseca armazenada nos arquivos BUILD
do projeto: a regra de build, os
valores dos atributos e o conjunto completo de 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 da cadeia de ferramentas. Chamamos um conjunto completo
de dados ambientais de 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 vai
requerer a criação de //foo:bin
usando uma cadeia 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
próprio build, por exemplo, ferramentas criadas a partir da origem e usadas
em uma genrule. Elas precisam ser criadas para serem executadas na estação de trabalho. Assim,
podemos identificar duas configurações: a configuração do host, que é usada
para criar ferramentas executadas durante o build, e a configuração de destino
(ou configuração de solicitação, mas dizemos "configuração de destino" com mais frequência, mesmo
que essa palavra já tenha muitos significados), que é usada para criar o
binário que você solicitou.
Normalmente, há muitas bibliotecas que são pré-requisitos do destino de build
solicitado (//foo:bin
) e de uma ou mais das ferramentas de host, por exemplo, algumas
bibliotecas básicas. Essas bibliotecas precisam ser criadas duas vezes, uma para a configuração
do host e outra para a configuração de destino. O Bazel cuida de
garantir que ambas as variantes sejam criadas e que os arquivos derivados sejam mantidos
separados para evitar interferências. Normalmente, esses destinos podem ser criados simultaneamente,
já que são independentes. Se você receber mensagens de progresso
indicando que um determinado destino está sendo criado duas vezes, essa é provavelmente a
explicação.
O Bazel usa uma das duas maneiras de selecionar a configuração do host com base na
opção --distinct_host_configuration
. Essa opção booleana é um pouco sutil,
e a configuração pode melhorar (ou piorar) a velocidade dos seus builds.
--distinct_host_configuration=false
Quando essa opção é falsa, as configurações do host e da solicitação são idênticas: todas as ferramentas necessárias durante o build são criadas exatamente da mesma forma que os programas de destino. Essa configuração significa que nenhuma biblioteca precisa ser criada duas vezes durante um único build.
No entanto, isso significa que qualquer mudança na configuração da solicitação também afeta a configuração do host, fazendo com que todas as ferramentas sejam recriadas e, em seguida, tudo que depende da saída da ferramenta também seja recriado. Assim, por exemplo, basta mudar uma opção de vinculador entre builds para que todas as ferramentas sejam vinculadas novamente, e todas as ações que as usam sejam executadas novamente, e assim por diante, resultando em uma reconstrução muito grande.
--distinct_host_configuration=true
(padrão)
Se essa opção for verdadeira, em vez de usar a mesma configuração para o host e a solicitação, uma configuração de host completamente distinta será usada. A configuração do host é derivada da configuração de destino da seguinte maneira:
- Usa a mesma versão do Crosstool (
--crosstool_top
) especificada 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 do host. - Use o valor de
--host_javabase
para--javabase
- Use o valor de
--host_java_toolchain
para--java_toolchain
- Use builds otimizados para código C++ (
-c opt
). - Não gera informações de depuração (
--copt=-g0
). - Remover 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.
- Supressão da impressão de binários com dados de build (consulte as opções
--embed_*
). - Todos os outros valores permanecem com os valores padrão.
Há muitos motivos para preferir selecionar uma configuração de host diferente da configuração de solicitação. Algumas são muito esotéricas para serem mencionadas aqui, mas duas delas valem a pena destacar.
Primeiro, ao usar binários otimizados e simplificados, você reduz o tempo gasto vinculando e executando 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 o host e solicitar configurações em todos os builds, você evita reconstruções muito caras que resultariam de pequenas mudanças na configuração da solicitação (como mudar as opções de um vinculador), conforme descrito anteriormente.
No entanto, para alguns builds, essa opção pode ser um obstáculo. Especificamente, builds em que as mudanças de configuração são pouco frequentes (principalmente alguns builds Java) e builds em que a quantidade de código que precisa ser criado nas configurações de host e de destino é grande podem não se beneficiar.
Correções de reconstruções incrementais
Uma das principais metas do projeto Bazel é garantir a reconstrução incremental correta. Ferramentas de build anteriores, especialmente as baseadas em Make, fazem várias suposições incorretas na implementação de builds incrementais.
Primeiro, os carimbos de data/hora dos arquivos aumentam de forma monotonicamente. Embora esse seja o caso típico, é muito fácil cair nessa armadilha. A sincronização com uma revisão anterior de um arquivo faz com que o tempo de modificação dele diminua. Os sistemas baseados em make não são reconstruídos.
De modo geral, embora o Make detecte mudanças em arquivos, ele não detecta mudanças
em comandos. Se você alterar as opções transmitidas ao compilador em uma determinada etapa
de build, o Make não vai 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 a interrupção de um dos subprocessos após ele ter começado a gravar no arquivo de saída. Embora a execução atual do Make falhe, a invocação subsequente do Make vai presumir cegamente que o arquivo de saída truncado é válido (porque é mais recente que as entradas) e não será recriado. Da mesma forma, se o processo Make for interrompido, 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 encontrar que o conjunto de arquivos de entrada (e os carimbos de data/hora deles) para essa etapa de build e o comando de compilação para essa etapa de build correspondem exatamente a um no banco de dados e que o conjunto de arquivos de saída (e os carimbos de data/hora deles) para a entrada do banco de dados correspondem exatamente aos carimbos de data/hora dos arquivos no disco. Qualquer mudança nos arquivos de entrada ou de saída ou no próprio comando vai causar a reexecução da etapa de build.
O benefício para os usuários de builds incrementais corretos é: menos tempo desperdiçado devido
à confusão. Além disso, menos tempo de espera para reconstruçõ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 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 assim até a próxima execução da ferramenta de build para conclusão. Descrevemos essa situação como inconsistência instável, porque ela é apenas temporária, e a consistência é restaurada com a execução da ferramenta de build.
Há outro tipo de inconsistência que é perniciosa: a inconsistência
estável. Se o build alcançar um estado estável inconsistente, a invocação
repetida da ferramenta de build não 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 alcançar um build consistente é descartar todas as saídas de build anteriores e começar de novo: faça com que cada build seja um build limpo. Essa abordagem é obviamente muito demorada 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.
A análise de dependência incremental correta é 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, durante a qual você não fez edições, o build vai estar em um estado consistente. Se você editar os arquivos de origem durante uma compilação, o Bazel não garante a consistência do resultado do build atual. No entanto, ele garante que os resultados do próximo build vão restaurar a consistência.)
Como em todas as garantias, há algumas letras miúdas: existem algumas maneiras conhecidas de entrar em um estado estável inconsistente com o Bazel. Não garantimos a investigação desses problemas decorrentes de tentativas deliberadas de encontrar bugs na análise incremental de dependências, mas vamos investigar e fazer o possível para corrigir todos os estados instáveis inconsistentes decorrentes 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 forma hermética e
correta. O Bazel executa spawns (em termos gerais: ações) em sandboxes que
contêm apenas o conjunto mínimo de arquivos necessários para a ferramenta fazer 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 mostrar um aviso se o sistema não oferecer suporte ao sandbox para alertar
que os builds não têm garantia de serem herméticos 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 nós de cluster do Google Kubernetes
Engine ou o Debian,
os namespaces de usuário são desativados por padrão devido a problemas de
segurança. Isso pode ser verificado analisando o arquivo
/proc/sys/kernel/unprivileged_userns_clone
: se ele existir e contiver um 0,
os namespaces de usuário poderão ser ativados com
sudo sysctl kernel.unprivileged_userns_clone=1
.
Em alguns casos, o sandbox do Bazel não consegue 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 geradas com
--strategy=Genrule=standalone
e para outras regras com
--spawn_strategy=standalone
. Além disso, informe um bug no nosso
rastreador de problemas e mencione 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 insights sobre as opções que controlam um build (confira abaixo).
Fase de carregamento
O primeiro é o carregamento, durante o qual todos os arquivos BUILD necessários para os alvos iniciais e o fechamento transitivo das dependências são carregados, analisados, avaliados e armazenados em cache.
Para o primeiro build após a inicialização de um servidor do Bazel, a fase de carregamento normalmente leva muitos segundos, já que muitos arquivos BUILD são carregados do sistema de arquivos. Em builds subsequentes, especialmente se nenhum arquivo BUILD tiver mudado, o carregamento ocorrerá muito rapidamente.
Os erros relatados durante essa fase incluem: pacote não encontrado, destino não encontrado, erros de gramática e ortografia 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 de exatamente o que precisa ser feito em cada etapa do build.
Assim como o carregamento, a análise também leva vários segundos quando computada por completo. No entanto, o Bazel armazena em cache o gráfico de dependências de um build para o próximo e só reanalisam o que precisa, o que pode tornar os builds incrementais extremamente rápidos no caso em que os pacotes não mudaram desde o build anterior.
Os erros informados 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 operações de E/S de arquivo desnecessárias nessa fase, lendo apenas arquivos BUILD para determinar o trabalho a ser realizado. Isso é intencional 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 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. É nessa etapa que 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.