Visão geral das dependências externas

O Bazel oferece suporte a dependências externas, arquivos de origem (texto e binários) usados no build que não são do seu espaço de trabalho. Por exemplo, eles podem ser um conjunto de regras hospedado em um repo do GitHub, um artefato do Maven ou um diretório na sua máquina local fora do espaço de trabalho atual.

A partir do Bazel 6.0, há duas maneiras de gerenciar dependências externas com o Bazel: o sistema tradicional WORKSPACE focado em repositórios, e o sistema MODULE.bazel mais recente focado em módulos (com o codinome Bzlmod, e ativado com a flag --enable_bzlmod). Os dois sistemas podem ser usados juntos, mas o Bzlmod está substituindo o sistema WORKSPACE em versões futuras do Bazel. Consulte o guia de migração do Bzlmod para saber como migrar.

Este documento explica os conceitos relacionados ao gerenciamento de dependências externas no Bazel, antes de entrar em mais detalhes sobre os dois sistemas em ordem.

Conceitos

Repositório

Uma árvore de diretórios com um arquivo de marcador de limite na raiz, contendo arquivos de origem que podem ser usados em um build do Bazel. Geralmente abreviado para apenas repo.

Um arquivo de marcador de limite de repo pode ser MODULE.bazel (indicando que esse repo representa um módulo do Bazel), REPO.bazel (confira abaixo) ou, em contextos legados, WORKSPACE ou WORKSPACE.bazel. Qualquer arquivo de marcador de limite de repo indica o limite de um repo. Vários arquivos desse tipo podem coexistir em um diretório.

Repositório principal

O repositório em que o comando atual do Bazel está sendo executado.

A raiz do repositório principal também é conhecida como raiz do espaço de trabalho.

Espaço de trabalho

O ambiente compartilhado por todos os comandos do Bazel executados no mesmo repositório principal. Ele abrange o repo principal e o conjunto de todos os repos externos definidos.

Historicamente, os conceitos de "repositório" e "espaço de trabalho" foram confundidos. O termo "espaço de trabalho" costuma ser usado para se referir ao repositório principal e, às vezes, até como sinônimo de "repositório".

Nome canônico do repositório

O nome canônico pelo qual um repositório pode ser acessado. No contexto de um espaço de trabalho, cada repositório tem um único nome canônico. Um destino dentro de um repo cujo nome canônico é canonical_name pode ser acessado pelo rótulo @@canonical_name//pac/kage:target (observe o @ duplo).

O repositório principal sempre tem a string vazia como nome canônico.

Nome aparente do repositório

O nome pelo qual um repositório pode ser acessado no contexto de outro repo. Isso pode ser considerado como um "apelido" de um repo: o repo com o nome canônico michael pode ter o nome aparente mike no contexto do repo alice, mas pode ter o nome aparente mickey no contexto do repo bob. Nesse caso, um destino dentro de michael pode ser acessado pelo rótulo @mike//pac/kage:target no contexto de alice (observe o @ único).

Por outro lado, isso pode ser entendido como um mapeamento de repositório: cada repo mantém um mapeamento de "nome aparente do repo" para um "nome canônico do repo".

Regra de repositório

Um esquema para definições de repositório que informa ao Bazel como materializar um repositório. Por exemplo, pode ser "fazer o download de um arquivo zip de um determinado URL e extraí-lo", "buscar um determinado artefato do Maven e disponibilizá-lo como um destino java_import" ou simplesmente "criar um link simbólico para um diretório local". Cada repo é definido chamando uma regra de repo com um número adequado de argumentos.

Consulte Regras de repositório para mais informações sobre como gravar suas próprias regras de repositório.

As regras de repo mais comuns são http_archive, que faz o download de um arquivo de um URL e o extrai, e local_repository, que cria um link simbólico para um diretório local que já é um repositório do Bazel.

Buscar um repositório

A ação de disponibilizar um repo no disco local executando a regra de repo associada a ele. Os repos definidos em um espaço de trabalho não ficam disponíveis no disco local antes de serem buscados.

Normalmente, o Bazel só busca um repo quando precisa de algo dele e o repo ainda não foi buscado. Se o repo já tiver sido buscado antes, o Bazel só o buscará novamente se a definição dele tiver mudado.

O comando fetch pode ser usado para iniciar uma busca prévia de um repositório, destino ou todos os repositórios necessários para realizar qualquer build. Essa capacidade permite builds off-line usando a opção --nofetch.

A opção --fetch serve para gerenciar o acesso à rede. O valor padrão é "true". No entanto, quando definido como "false" (--nofetch), o comando usa qualquer versão armazenada em cache da dependência e, se não houver nenhuma, o comando vai falhar.

Consulte Opções de busca para mais informações sobre como controlar a busca.

Layout do diretório

Depois de buscado, o repo pode ser encontrado no subdiretório external na base de saída, com o nome canônico.

Execute o comando a seguir para conferir o conteúdo do repo com o nome canônico canonical_name:

ls $(bazel info output_base)/external/ canonical_name 

Arquivo REPO.bazel

O arquivo REPO.bazel é usado para marcar o limite superior da árvore de diretórios que constitui um repo. Ele não precisa conter nada para servir como um arquivo de limite de repo. No entanto, ele também pode ser usado para especificar alguns atributos comuns para todos os destinos de build dentro do repo.

A sintaxe de um arquivo REPO.bazel é semelhante aos arquivos BUILD, exceto que nenhuma instrução load é aceita, e apenas uma única função, repo(), está disponível. repo() usa os mesmos argumentos que a package() função em arquivos BUILD. Enquanto package() especifica atributos comuns para todos os destinos de build dentro do pacote, repo() faz isso de maneira análoga para todos os destinos de build dentro do repo.

Por exemplo, é possível especificar uma licença comum para todos os destinos no repo com o seguinte arquivo REPO.bazel:

repo(
    default_package_metadata = ["//:my_license"],
)

Gerenciar dependências externas com o Bzlmod

O Bzlmod, o novo subsistema de dependência externa, não funciona diretamente com definições de repo. Em vez disso, ele cria um gráfico de dependência de módulos, executa extensões na parte de cima do gráfico e define repos de acordo.

Um módulo do Bazel é um projeto do Bazel que pode ter várias versões, cada uma delas publicando metadados sobre outros módulos de que depende. Um módulo precisa ter um arquivo MODULE.bazel na raiz do repo, ao lado do arquivo WORKSPACE. Esse arquivo é o manifesto do módulo, declarando o nome, a versão, a lista de dependências, entre outras informações. Confira abaixo um exemplo básico:

module(name = "my-module", version = "1.0")

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

Um módulo só precisa listar as dependências diretas, que o Bzlmod procura em um registro do Bazel. Por padrão, o Registro central do Bazel. O registro fornece os arquivos MODULE.bazel das dependências, o que permite que o Bazel descubra todo o gráfico de dependência transitiva antes de realizar a resolução de versão.

Após a resolução de versão, em que uma versão é selecionada para cada módulo, o Bazel consulta o registro novamente para saber como definir um repo para cada módulo (na maioria dos casos, usando http_archive).

Os módulos também podem especificar partes personalizadas de dados chamadas tags, que são consumidas por extensões de módulo após a resolução do módulo para definir repos adicionais. Essas extensões têm recursos semelhantes às regras de repo, permitindo que elas executem ações como E/S de arquivos e envio de solicitações de rede. Entre outras coisas, elas permitem que o Bazel interaja com outros sistemas de gerenciamento de pacotes, respeitando o gráfico de dependência criado com módulos do Bazel.

Definir repos com WORKSPACE

Historicamente, é possível gerenciar dependências externas definindo repos no WORKSPACE (ou WORKSPACE.bazel) arquivo. Esse arquivo tem uma sintaxe semelhante aos arquivos BUILD, usando regras de repo em vez de regras de build.

O snippet a seguir é um exemplo de como usar a regra de repo http_archive no arquivo WORKSPACE:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "foo",
    urls = ["https://example.com/foo.zip"],
    sha256 = "c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96",
)

O snippet define um repo cujo nome canônico é foo. No sistema WORKSPACE, por padrão, o nome canônico de um repo também é o nome aparente para todos os outros repos.

Consulte a lista completa de funções disponíveis em WORKSPACE arquivos.

Limitações do sistema WORKSPACE

Nos anos desde que o sistema WORKSPACE foi introduzido, os usuários relataram muitos pontos fracos, incluindo:

  • O Bazel não avalia os arquivos WORKSPACE de nenhuma dependência. Portanto, todas as dependências transitivas precisam ser definidas no arquivo WORKSPACE do repo principal, além das dependências diretas.
  • Para contornar isso, os projetos adotaram o padrão "deps.bzl", em que definem uma macro que, por sua vez, define vários repos e pede aos usuários que chamem essa macro nos arquivos WORKSPACE.
    • Isso tem problemas próprios: as macros não podem load outros arquivos .bzl. Portanto, esses projetos precisam definir as dependências transitivas nessa macro "deps" ou contornar esse problema fazendo com que o usuário chame várias macros "deps" em camadas.
    • O Bazel avalia o arquivo WORKSPACE sequencialmente. Além disso, as dependências são especificadas usando http_archive com URLs, sem informações de versão. Isso significa que não há uma maneira confiável de realizar a resolução de versão no caso de dependências de diamante (A depende de B e C; B e C dependem de versões diferentes de D).

Devido às limitações do WORKSPACE, o Bzlmod vai substituir o sistema legado do WORKSPACE em versões futuras do Bazel. Leia o guia de migração do Bzlmod para saber como migrar para o Bzlmod.