Trabalhar com dependências externas

Ele pode depender de destinos de outros projetos. As dependências desses outros projetos são chamadas de dependências externas.

O arquivo WORKSPACE (ou WORKSPACE.bazel) no diretório do espaço de trabalho informa ao Bazel como acessar as origens de outros projetos. Esses outros projetos podem conter um ou mais arquivos BUILD com os próprios destinos. Os arquivos BUILD no projeto principal podem depender desses destinos externos usando o nome deles no arquivo WORKSPACE.

Por exemplo, suponha que haja dois projetos em um sistema:

/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/

Se project1 quisesse depender de um destino, :foo, definido em /home/user/project2/BUILD, ele poderia especificar que um repositório chamado project2 poderia ser encontrado em /home/user/project2. Assim, os destinos em /home/user/project1/BUILD podem depender de @project2//:foo.

O arquivo WORKSPACE permite que os usuários dependam de destinos de outras partes do sistema de arquivos ou transferidos por download da Internet. Ele usa a mesma sintaxe dos arquivos BUILD, mas permite um conjunto diferente de regras chamadas regras de repositório (às vezes conhecidas como regras do espaço de trabalho). O Bazel vem com algumas regras de repositório integradas e um conjunto de regras de repositório do Starlark incorporadas. Os usuários também podem escrever regras de repositório personalizadas para ter um comportamento mais complexo.

Tipos compatíveis de dependências externas

Alguns tipos básicos de dependências externas podem ser usados:

Depende de outros projetos do Bazel

Se você quiser usar destinos de um segundo projeto do Bazel, use local_repository, git_repository ou http_archive para vinculá-lo simbolicamente a partir do sistema de arquivos local, referenciar um repositório git ou fazer o download dele (respectivamente).

Por exemplo, suponha que você esteja trabalhando em um projeto, my-project/, e queira depender dos destinos do projeto de seu colega de trabalho, coworkers-project/. Ambos os projetos usam o Bazel, para que você possa adicionar o projeto do colega de trabalho como uma dependência externa e usar qualquer destino que ele tenha definido a partir de seus próprios arquivos BUILD. Você adicionaria o seguinte a my_project/WORKSPACE:

local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

Se sua colega de trabalho tiver um //foo:bar de destino, seu projeto poderá se referir a ele como @coworkers_project//foo:bar. Nomes de projetos externos precisam ser nomes de espaços de trabalho válidos.

Dependendo de projetos que não sejam do Bazel

As regras prefixadas com new_, como new_local_repository, permitem criar destinos de projetos que não usam o Bazel.

Por exemplo, suponha que você esteja trabalhando em um projeto, my-project/, e queira depender do projeto de seu colega de trabalho, coworkers-project/. O projeto do seu colega de trabalho usa make para criar, mas você gostaria de depender de um dos arquivos .so que ele gera. Para fazer isso, adicione o seguinte a my_project/WORKSPACE:

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)

build_file especifica um arquivo BUILD a ser sobreposto no projeto existente, por exemplo:

cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)

Então, você pode depender de @coworkers_project//:some-lib dos arquivos BUILD do seu projeto.

Dependendo dos pacotes externos,

Artefatos e repositórios Maven

Use o conjunto de regras rules_jvm_external para fazer o download de artefatos de repositórios Maven e disponibilizá-los como dependências Java.

Como buscar dependências

Por padrão, as dependências externas são buscadas conforme necessário durante bazel build. Caso você queira pré-buscar as dependências necessárias para um conjunto específico de destinos, use bazel fetch. Para buscar incondicionalmente todas as dependências externas, use bazel sync. À medida que os repositórios buscados são armazenados na base de saída, a busca ocorre por espaço de trabalho.

Dependências de acompanhamento

Sempre que possível, recomenda-se ter uma única política de versão no projeto. Isso é necessário para dependências que você compila e termina no binário final. No entanto, nos casos em que isso não é verdade, é possível descobrir as dependências. Pense no seguinte:

projeto/espaço de trabalho

workspace(name = "myproject")

local_repository(
    name = "A",
    path = "../A",
)
local_repository(
    name = "B",
    path = "../B",
)

A/WORKSPACE

workspace(name = "A")

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

B/ESPAÇO DE TRABALHO

workspace(name = "B")

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

As dependências A e B dependem de testrunner, mas dependem de versões diferentes de testrunner. Não há motivo para esses executores de testes não coexistirem pacificamente em myproject. No entanto, eles vão entrar em conflito já que têm o mesmo nome. Para declarar as duas dependências, atualize myproject/WORKSPACE:

workspace(name = "myproject")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "testrunner-v1",
    urls = ["https://github.com/testrunner/v1.zip"],
    sha256 = "..."
)
http_archive(
    name = "testrunner-v2",
    urls = ["https://github.com/testrunner/v2.zip"],
    sha256 = "..."
)
local_repository(
    name = "A",
    path = "../A",
    repo_mapping = {"@testrunner" : "@testrunner-v1"}
)
local_repository(
    name = "B",
    path = "../B",
    repo_mapping = {"@testrunner" : "@testrunner-v2"}
)

Esse mecanismo também pode ser usado para unir diamantes. Por exemplo, se A e B tinham a mesma dependência, mas a chamavam por nomes diferentes, essas dependências podem ser unidas em myproject/WORKSPACE.

Como substituir repositórios pela linha de comando

Para substituir um repositório declarado por um repositório local na linha de comando, use a sinalização --override_repository. O uso dessa flag muda o conteúdo de repositórios externos sem alterar o código-fonte.

Por exemplo, para substituir @foo no diretório local /path/to/local/foo, transmita a sinalização --override_repository=foo=/path/to/local/foo.

Alguns dos casos de uso incluem:

  • Depuração de problemas. Por exemplo, é possível substituir um repositório http_archive para um diretório local em que seja possível fazer alterações com mais facilidade.
  • Fornecedores. Se você estiver em um ambiente em que não é possível fazer chamadas de rede, modifique as regras de repositório baseadas em rede para apontar para diretórios locais.

Como usar proxies

O Bazel seleciona endereços de proxy das variáveis de ambiente HTTPS_PROXY e HTTP_PROXY e as usa para fazer o download de arquivos HTTP/HTTPS (se especificado).

Suporte para IPv6

Em máquinas somente IPv6, o Bazel consegue fazer o download de dependências sem alterações. No entanto, em máquinas IPv4/IPv6 de pilha dupla, o Bazel segue a mesma convenção do Java: se o IPv4 estiver ativado, o IPv4 será preferível. Em algumas situações, por exemplo, quando a rede IPv4 não consegue resolver/alcançar endereços externos, isso pode causar exceções Network unreachable e falhas de build. Nesses casos, é possível substituir o comportamento do Bazel para preferir o IPv6 usando a propriedade do sistema java.net.preferIPv6Addresses=true (link em inglês). Mais especificamente:

  • Use a opção de inicialização --host_jvm_args=-Djava.net.preferIPv6Addresses=true, por exemplo, adicionando a seguinte linha ao arquivo .bazelrc:

    startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true

  • Se você estiver executando destinos de build Java que também precisam se conectar à Internet (testes de integração às vezes precisam disso), use também a sinalização de ferramenta --jvmopt=-Djava.net.preferIPv6Addresses=true. Por exemplo, usando a linha abaixo no arquivo .bazelrc:

    build --jvmopt=-Djava.net.preferIPv6Addresses

  • Se você estiver usando rules_jvm_external, por exemplo, para resolução da versão da dependência, adicione também -Djava.net.preferIPv6Addresses=true à variável de ambiente COURSIER_OPTS para fornecer opções da JVM para a Coursier.

Dependências transitivas

O Bazel só lê as dependências listadas no seu arquivo WORKSPACE. Se o projeto (A) depender de outro projeto (B) que lista uma dependência de um terceiro projeto (C) no arquivo WORKSPACE, será necessário adicionar B e C ao arquivo WORKSPACE do projeto. Esse requisito pode aumentar o tamanho do arquivo WORKSPACE, mas limita as chances de que uma biblioteca inclua C na versão 1.0 e outra inclua C na 2.0.

Armazenamento em cache de dependências externas

Por padrão, o Bazel só faz um novo download das dependências externas quando a definição delas é alterada. As alterações nos arquivos referenciados na definição (como patches ou arquivos BUILD) também são consideradas pelo Bazel.

Para forçar um novo download, use bazel sync.

Layout

As dependências externas são todas transferidas para um diretório no subdiretório external na base de saída. No caso de um repositório local, um link simbólico será criado lá em vez de criar um novo diretório. Para ver o diretório external, execute:

ls $(bazel info output_base)/external

Observe que executar bazel clean não excluirá o diretório externo. Para remover todos os artefatos externos, use bazel clean --expunge.

Compilações off-line

Às vezes, é desejável ou necessário executar um build de modo off-line. Para casos de uso simples, como em viagens de avião, a pré-busca dos repositórios necessários com bazel fetch ou bazel sync pode ser suficiente. Além disso, usando a opção --nofetch, a busca de outros repositórios pode ser desativada durante o build.

Para builds off-line verdadeiros, em que o fornecimento dos arquivos necessários é feito por uma entidade diferente do Bazel, o Bazel oferece suporte à opção --distdir. Sempre que uma regra de repositório solicitar que o Bazel busque um arquivo por ctx.download ou ctx.download_and_extract e forneça uma soma de hash do arquivo necessário, o Bazel vai procurar primeiro nos diretórios especificados por essa opção em busca de um arquivo correspondente ao nome de base do primeiro URL fornecido e usar essa cópia local se o hash corresponder.

O próprio Bazel usa essa técnica para fazer o bootstrap off-line do artefato de distribuição (link em inglês). Ele faz isso coletando todas as dependências externas necessárias em uma distdir_tar interna.

No entanto, o Bazel permite a execução de comandos arbitrários em regras de repositório, sem saber se eles chamam a rede. Portanto, o Bazel não tem a opção de fazer com que os builds fiquem totalmente off-line. Portanto, testar se um build funciona corretamente off-line requer bloqueio externo da rede, assim como o Bazel faz no teste de bootstrap.

Práticas recomendadas

Regras do repositório

Uma regra de repositório geralmente é responsável por:

  • Detectar as configurações do sistema e gravá-las em arquivos.
  • Encontrar recursos em outro lugar do sistema.
  • Fazendo o download de recursos de URLs.
  • gerar ou fazer a vinculação simbólica de arquivos BUILD no diretório do repositório externo;

Evite usar repository_ctx.execute sempre que possível. Por exemplo, ao usar uma biblioteca C++ que não seja do Bazel e tenha um build usando o Make, é preferível usar repository_ctx.download() e escrever um arquivo BUILD para criá-la, em vez de executar ctx.execute(["make"]).

Use http_archive em vez de git_repository e new_git_repository. Estes são os motivos:

  • As regras de repositório Git dependem do sistema git(1), enquanto o downloader HTTP é criado no Bazel e não tem dependências do sistema.
  • http_archive oferece suporte a uma lista de urls como espelhos, e git_repository oferece suporte a apenas um único remote.
  • http_archive funciona com o cache do repositório, mas não com git_repository. Consulte o No 5116 (link em inglês) para mais informações.

Não use bind(). Consulte "Considerar remover vinculação" para ver uma longa discussão sobre os problemas e as alternativas.