Dependências

Informar um problema Mostrar fonte Por noite · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Um A de destino depende de um B de destino se o B for necessário para o A no build ou no ambiente de execução. A relação dependente induz uma Gráfico acíclico dirigido (DAG) sobre as metas e é chamado de gráfico de dependência.

As dependências diretas de um destino são os outros destinos acessíveis por um caminho de comprimento 1 no gráfico de dependências. As dependências transitivas de um destino são os destinos dos quais depende por um caminho de qualquer tamanho no gráfico.

Na verdade, no contexto dos builds, há dois gráficos de dependência: o gráfico de dependências reais e o gráfico das dependências declaradas. A maioria os dois gráficos são tão semelhantes que não é necessário fazer essa distinção, ele é útil para a discussão abaixo.

Dependências reais e declaradas

Um X de destino é realmente dependente do Y de destino se o Y precisar estar presente. criados e atualizados para que o X seja criado corretamente. Criado poderia média gerada, processada, compilado, vinculado, arquivado, compactado, executado ou qualquer um dos outros tipos de tarefas que ocorrem rotineiramente durante um build.

Um X de destino tem uma dependência declarada no destino Y caso haja uma dependência. borda de X para Y no pacote de X.

Para os builds corretos, o gráfico das dependências A reais precisa ser um subgráfico do o gráfico das dependências declaradas D. Ou seja, cada par de os nós conectados diretamente x --> y em A também precisam estar diretamente conectados em D) Pode-se dizer que D é uma aproximação excessiva de A.

Os gravadores de arquivos BUILD precisam declarar explicitamente todos os direitos de acesso dependências de cada regra ao sistema de build e nada mais.

A não observação desse princípio causa um comportamento indefinido: o build pode falhar, o que é pior, o build pode depender de algumas operações anteriores ou as dependências declaradas do destino. O Bazel verifica se há erros dependências e relatar erros, mas não é possível que essa verificação seja ser concluída em todos os casos.

Você não precisa (e não deve) tentar listar tudo que foi importado indiretamente, mesmo que seja necessário para A no momento da execução.

Durante um build do X de destino, a ferramenta de build inspeciona todo o fechamento de dependências de X para garantir que qualquer mudança nesses destinos seja refletidas no resultado final, reconstruindo intermediários conforme necessário.

A natureza transitiva das dependências leva a um erro comum. Às vezes, em um arquivo pode usar o código fornecido por uma dependência indireta, que é uma transitiva, mas não direta, no gráfico de dependência declarado. Indireto dependências não aparecem no arquivo BUILD. Como a regra não depender diretamente do provedor, não há como rastrear as alterações, como mostrado no o seguinte exemplo de linha do tempo:

1. Dependências declaradas correspondem às dependências reais

No início, tudo funciona. O código no pacote a usa o código no pacote b. O código no pacote b usa o código no pacote c e, portanto, a de forma transitiva depende de c.

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
Gráfico de dependência declarado com setas conectando a, b e c
Gráfico de dependências declarado
Gráfico de dependência real que corresponde à dependência declarada
                  gráfico com setas conectando a, b e c
Gráfico de dependências real

As dependências declaradas se aproximam das dependências reais. Tudo bem.

2. Como adicionar uma dependência não declarada

Um risco latente é introduzido quando alguém adiciona um código ao a que cria uma dependência real direta em c, mas esquece de declará-la no arquivo de build a/BUILD.

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Gráfico de dependência declarado com setas conectando a, b e c
Gráfico de dependências declarado
Gráfico de dependência real com setas conectando a, b e c. Um
                  seta agora conecta A a C também. Isso não corresponde ao
                  gráfico de dependência declarada
Gráfico de dependências real

As dependências declaradas não superaproximam mais as dependências reais. Isso pode funcionar corretamente, porque os fechamentos transitivos dos dois gráficos são iguais, mas mascara um problema: a tem uma dependência real, mas não declarada, em c.

3. Divergência entre gráficos de dependência declarados e reais

O perigo é revelado quando alguém refatora o b para que ele não dependa mais de c, dividindo inadvertidamente a por não por conta própria.

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
Gráfico de dependência declarado com setas conectando a e b.
                  b não se conecta mais a c, o que interrompe a conexão de a com c
Gráfico de dependências declarado
Gráfico de dependência real que mostra uma conexão com b e c,
                  mas b não se conecta mais a c
Gráfico de dependências real

O gráfico de dependência declarado agora é uma subaproximação do gráfico de dependência real dependências, mesmo quando transitivamente fechados; o build provavelmente vai falhar.

O problema poderia ter sido evitado garantindo que a dependência real O a ao c introduzido na etapa 2 foi corretamente declarado no arquivo BUILD.

Tipos de dependências

A maioria das regras de compilação tem três atributos para especificar diferentes tipos de dependências genéricas: srcs, deps e data. Eles são explicados abaixo. Para mais detalhes, consulte Atributos comuns a todas as regras.

Muitas regras também têm atributos adicionais para tipos específicos de regras de dependências, por exemplo, compiler ou resources. Eles estão detalhados na Criar enciclopédia.

srcs dependências

Arquivos consumidos diretamente pelas regras que geram arquivos de origem.

deps dependências

Regra que aponta para módulos compilados separadamente que fornecem arquivos de cabeçalho. símbolos, bibliotecas, dados etc.

data dependências

Um destino de build pode precisar de alguns arquivos de dados para ser executado corretamente. Esses arquivos de dados não são código-fonte, eles não afetam como o destino é criado. Por exemplo, O teste de unidade pode comparar a saída de uma função com o conteúdo de um arquivo. Quando você criar o teste de unidade. Você não precisa do arquivo, mas precisa dele ao executar o teste. O mesmo se aplica a ferramentas iniciadas durante a execução.

O sistema de compilação executa testes em um diretório isolado em que apenas os arquivos listados como data estão disponíveis. Assim, se um binário/biblioteca/teste precisar de alguns arquivos para serem executados, especificá-los (ou uma regra de build que os contenha) em data. Exemplo:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

Esses arquivos estão disponíveis usando o caminho relativo path/to/data/file. Em testes, você pode referenciar esses arquivos mesclando os caminhos do código-fonte do teste e o caminho relativo ao espaço de trabalho, por exemplo, ${TEST_SRCDIR}/workspace/path/to/data/file:

Como usar rótulos para fazer referência a diretórios

Ao analisar nossos arquivos BUILD, talvez você perceba que alguns marcadores data aos diretórios. Esses rótulos terminam com /. ou /, como nos exemplos a seguir. que você não deve usar:

Não recomendado: data = ["//data/regression:unittest/."]

Não recomendado: data = ["testdata/."]

Não recomendado: data = ["testdata/"]

Isso parece conveniente, especialmente para testes, porque permite que um teste use todos os arquivos de dados do diretório.

Mas tente não fazer isso. Para garantir recriações incrementais corretas (e reexecução de testes) após uma alteração, o sistema de compilação precisa estar ciente conjunto completo de arquivos que são entradas para o build (ou teste). Quando você especifica diretório, o sistema de compilação realiza uma recriação somente quando o próprio diretório alterações (devido à adição ou exclusão de arquivos), mas não detectam edições em arquivos individuais, pois essas alterações não afetam o diretório de inclusão. Em vez de especificar diretórios como entradas para o sistema de build, recomendamos enumerar o conjunto de arquivos contidos neles, explicitamente ou usando o método função glob(). Use ** para forçar a glob() sejam recursivos.

Recomendadodata = glob(["testdata/**"])

Infelizmente, há alguns cenários em que é necessário usar rótulos de diretório. Por exemplo, se o diretório testdata contiver arquivos com nomes que não estejam em conformidade com a sintaxe de rótulo, então, a enumeração explícita de arquivos ou o uso da A função glob() produz rótulos inválidos. erro. Use os marcadores de diretório nesse caso, mas tenha cuidado com os o risco associado de recriações incorretas descritas acima.

Se você precisar usar marcadores de diretório, tenha em mente que não é possível consultar o pacote pai com um caminho ../ relativo. Em vez disso, use um caminho absoluto como //data/regression:unittest/..

Qualquer regra externa, como um teste, que precise usar vários arquivos precisa declarar explicitamente a dependência de todos eles. Você pode usar filegroup() para agrupar arquivos no arquivo BUILD:

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

Em seguida, é possível referenciar o rótulo my_data como a dependência de dados no teste.

Arquivos BUILD Visibilidade