Preferir arquivos BUILD DAMP em vez de DRY
O princípio DRY ("Don't Repeat Yourself") incentiva a exclusividade ao introduzir abstrações, como variáveis e funções, para evitar redundância no código.
Em contraste, o princípio DAMP ("Descriptive and Meaningful Phrases") incentiva a legibilidade em vez da exclusividade para facilitar a compreensão e a manutenção dos arquivos.
Os arquivos BUILD não são códigos, mas configurações. Eles não são testados como códigos, mas precisam ser mantidos por pessoas e ferramentas. Isso torna o DAMP melhor para eles do que o DRY.
Formatação de arquivos BUILD.bazel
A formatação de arquivos BUILD segue a mesma abordagem do Go, em que uma ferramenta padronizada cuida da maioria dos problemas de formatação.
O Buildifier é uma ferramenta que analisa e
emite o código-fonte em um estilo padrão. Portanto, todos os arquivos BUILD são formatados da mesma maneira automatizada, o que torna a formatação um problema durante as revisões de código. Isso também facilita a compreensão, edição e geração de arquivos BUILD pelas ferramentas.
A formatação de arquivos BUILD precisa corresponder à saída do buildifier.
Exemplo de formatação
# Test code implementing the Foo controller.
package(default_testonly = True)
py_test(
name = "foo_test",
srcs = glob(["*.py"]),
data = [
"//data/production/foo:startfoo",
"//foo",
"//third_party/java/jdk:jdk-k8",
],
flaky = True,
deps = [
":check_bar_lib",
":foo_data_check",
":pick_foo_port",
"//pyglib",
"//testing/pybase",
],
)
Estrutura do arquivo
Recomendação: use a ordem a seguir (todos os elementos são opcionais):
Descrição do pacote (um comentário)
Todas as instruções
load()A função
package().Chamadas para regras e macros
O Buildifier faz uma distinção entre um comentário independente e um comentário anexado a um elemento. Se um comentário não estiver anexado a um elemento específico, use uma linha vazia após ele. A distinção é importante ao fazer mudanças automatizadas (por exemplo, para manter ou remover um comentário ao excluir uma regra).
# Standalone comment (such as to make a section in a file)
# Comment for the cc_library below
cc_library(name = "cc")
Referências a destinos no pacote atual
Os arquivos precisam ser referenciados pelos caminhos relativos ao diretório do pacote (sem usar referências, como ..). Os arquivos gerados precisam ter o prefixo ":" para indicar que não são origens. Os arquivos de origem não podem ter o prefixo :. As regras precisam ter o prefixo :. Por exemplo, supondo que x.cc seja um arquivo de origem:
cc_library(
name = "lib",
srcs = ["x.cc"],
hdrs = [":gen_header"],
)
genrule(
name = "gen_header",
srcs = [],
outs = ["x.h"],
cmd = "echo 'int x();' > $@",
)
Nomeação de destino
Os nomes de destino precisam ser descritivos. Se um destino contiver um arquivo de origem,
o destino geralmente terá um nome derivado dessa origem (por exemplo, uma
cc_library para chat.cc pode ser chamada de chat, ou uma java_library para
DirectMessage.java pode ser chamada de direct_message).
O destino homônimo de um pacote (o destino com o mesmo nome do diretório que o contém) precisa fornecer a funcionalidade descrita pelo nome do diretório. Se esse destino não existir, não crie um novo com o mesmo nome.
Prefira usar o nome abreviado ao se referir a um destino homônimo (//x
em vez de //x:x). Se você estiver no mesmo pacote, prefira a referência local (:x em vez de //x).
Evite usar nomes de destino "reservados" que tenham um significado especial. Isso inclui all, __pkg__ e __subpackages__. Esses nomes têm semântica especial e podem causar confusão e comportamentos inesperados quando são usados.
Na ausência de uma convenção de equipe predominante, estas são algumas recomendações não vinculativas que são amplamente usadas no Google:
- Em geral, use "snake_case"
- Para uma
java_librarycom umsrc, isso significa usar um nome que não seja o mesmo que o nome do arquivo sem a extensão - Para regras Java
*_binarye*_test, use "Upper CamelCase". Isso permite que o nome do destino corresponda a um dossrcs. Parajava_test, isso possibilita que o atributotest_classseja inferido do nome do destino.
- Para uma
- Se houver várias variantes de um destino específico, adicione um sufixo para desambiguar (como.
:foo_dev,:foo_prodou:bar_x86,:bar_x64) - O sufixo
_testé direcionado a nomes que tenham_test,_unittest,TestouTests - Evite sufixos sem sentido, como
_libou_library(a menos que seja necessário evitar conflitos entre um destino_librarye o_binarycorrespondente) - Para destinos relacionados a protos:
- Os destinos
proto_libraryprecisam ter nomes que terminam em_proto - As regras
*_proto_libraryespecíficas do idioma precisam corresponder ao proto subjacente , mas substituir_protopor um sufixo específico do idioma, como:cc_proto_library:_cc_protojava_proto_library:_java_protojava_lite_proto_library:_java_proto_lite
- Os destinos
Visibilidade
A visibilidade precisa ser definida o mais estritamente possível, mas ainda permitindo o acesso por testes e dependências inversas. Use __pkg__ e __subpackages__ conforme apropriado.
Evite definir default_visibility do pacote como //visibility:public.
//visibility:public só precisa ser definido individualmente para destinos na API pública do projeto. Essas podem ser bibliotecas projetadas para serem dependentes de projetos externos ou binários que podem ser usados pelo processo de build de um projeto externo.
Dependências
As dependências precisam ser restritas a dependências diretas (dependências necessárias pelas origens listadas na regra). Não liste dependências transitivas.
As dependências locais do pacote precisam ser listadas primeiro e referenciadas de uma maneira compatível com a Referências a destinos no pacote atual seção acima (não pelo nome absoluto do pacote).
Prefira listar as dependências diretamente, como uma única lista. Colocar as dependências "comuns" de vários destinos em uma variável reduz a capacidade de manutenção, impossibilita que as ferramentas mudem as dependências de um destino e pode gerar dependências não utilizadas.
Globs
Indique "sem destinos" com []. Não use um glob que não corresponda a nada: ele é mais propenso a erros e menos óbvio do que uma lista vazia.
Recursivo
Não use globs recursivos para corresponder a arquivos de origem (por exemplo,
glob(["**/*.java"])).
Os globs recursivos dificultam o raciocínio sobre arquivos BUILD porque ignoram subdiretórios que contêm arquivos BUILD.
Os globs recursivos geralmente são menos eficientes do que ter um arquivo BUILD por diretório com um gráfico de dependência definido entre eles, já que isso permite um melhor paralelismo e armazenamento em cache remoto.
É uma boa prática criar um arquivo BUILD em cada diretório e definir um gráfico de dependência entre eles.
Não recursivo
Os globs não recursivos geralmente são aceitáveis.
Evitar comprehensions de lista
Evite usar comprehensions de lista no nível superior de um arquivo BUILD.bazel.
Automatize chamadas repetitivas criando cada destino nomeado com uma chamada de regra ou macro de nível superior separada. Dê a cada um um parâmetro name curto para fins de esclarecimento.
A comprehension de lista reduz o seguinte:
- Capacidade de manutenção. É difícil ou impossível para os mantenedores humanos e mudanças automatizadas em grande escala atualizar as comprehensions de lista corretamente.
- Facilidade de descoberta Como o padrão não tem parâmetros
name, é difícil encontrar a regra pelo nome.
Uma aplicação comum do padrão de compreensão de lista é gerar testes. Por exemplo:
[[java_test(
name = "test_%s_%s" % (backend, count),
srcs = [ ... ],
deps = [ ... ],
...
) for backend in [
"fake",
"mock",
]] for count in [
1,
10,
]]
Recomendamos o uso de alternativas mais simples. Por exemplo, defina uma macro que gere um teste e invoque-o para cada name de nível superior:
my_java_test(name = "test_fake_1",
...)
my_java_test(name = "test_fake_10",
...)
...
Não usar variáveis de dependências
Não use variáveis de lista para encapsular dependências comuns:
COMMON_DEPS = [
"//d:e",
"//x/y:z",
]
cc_library(name = "a",
srcs = ["a.cc"],
deps = COMMON_DEPS + [ ... ],
)
cc_library(name = "b",
srcs = ["b.cc"],
deps = COMMON_DEPS + [ ... ],
)
Da mesma forma, não use um destino de biblioteca com
exports para agrupar dependências.
Em vez disso, liste as dependências separadamente para cada destino:
cc_library(name = "a",
srcs = ["a.cc"],
deps = [
"//a:b",
"//x/y:z",
...
],
)
cc_library(name = "b",
srcs = ["b.cc"],
deps = [
"//a:b",
"//x/y:z",
...
],
)
Deixe Gazelle e outras ferramentas manterem essas dependências. Haverá repetição, mas você não precisará pensar em como gerenciar as dependências.
Preferir strings literais
Embora o Starlark forneça operadores de string para concatenação (+) e formatação (%), use-os com cuidado. É tentador fatorar partes de string comuns para tornar as expressões mais concisas ou quebrar linhas longas. No entanto,
É mais difícil ler valores de string quebrados rapidamente.
Ferramentas automatizadas, como buildozer e a Pesquisa de código, têm dificuldade para encontrar valores e atualizá-los corretamente quando os valores são quebrados.
Em arquivos
BUILD, a legibilidade é mais importante do que evitar a repetição (consulte DAMP versus DRY).Este guia de estilo alerta contra a divisão de strings com valor de rótulo e permite explicitamente linhas longas.
O Buildifier mescla automaticamente strings concatenadas quando detecta que elas são rótulos.
Portanto, prefira strings literais explícitas em vez de strings concatenadas ou formatadas, especialmente em atributos do tipo rótulo, como name e deps. Por exemplo, este fragmento BUILD:
NAME = "foo"
PACKAGE = "//a/b"
proto_library(
name = "%s_proto" % NAME,
deps = [PACKAGE + ":other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:" +
"extravagantly_long_target_name",
)
seria melhor reescrito como
proto_library(
name = "foo_proto",
deps = ["//a/b:other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)
Limitar os símbolos exportados por cada arquivo .bzl
Minimize o número de símbolos (regras, macros, constantes, funções) exportados por cada arquivo .bzl público (Starlark). Recomendamos que um arquivo exporte vários símbolos apenas se eles forem usados juntos. Caso contrário, divida
o em vários arquivos .bzl, cada um com sua própria bzl_library.
O excesso de símbolos pode fazer com que os arquivos .bzl se transformem em "bibliotecas" amplas de símbolos, fazendo com que as mudanças em arquivos únicos forcem o Bazel a recompilar muitos destinos.
Outras convenções
Use letras maiúsculas e sublinhados para declarar constantes (como
GLOBAL_CONSTANT) e letras minúsculas e sublinhados para declarar variáveis (comomy_variable).Os rótulos nunca podem ser divididos, mesmo que tenham mais de 79 caracteres. Os rótulos precisam ser literais de string sempre que possível. Razão: isso facilita a localização e a substituição. Também melhora a legibilidade.
O valor do atributo de nome precisa ser uma string de constante literal, exceto em macros. Razão: as ferramentas externas usam o atributo de nome para se referir a uma regra. Eles precisam encontrar regras sem precisar interpretar o código.
Ao definir atributos do tipo booleano, use valores booleanos, não valores inteiros. Por motivos de compatibilidade, as regras ainda convertem inteiros em valores booleanos quando necessário, mas isso não é recomendado. Razão:
flaky = 1pode ser interpretado incorretamente como "deflake this target by rerunning it once".flaky = Truediz de forma inequívoca "este teste é instável".
Diferenças em relação ao guia de estilo Python
Embora a compatibilidade com o Guia de estilo Python (em inglês) seja uma meta, há algumas diferenças:
Não há limite de comprimento de linha. Comentários e strings longos geralmente são divididos em 79 colunas, mas isso não é obrigatório. Essa prática não deve ser aplicada em revisões de código nem em scripts de pré-envio. Razão: os rótulos podem ser longos e exceder esse limite. É comum que os arquivos
BUILDsejam gerados ou editados por ferramentas, o que não combina com um limite de comprimento de linha.Não há suporte para concatenação de strings implícitas. Use o operador
+. Razão: os arquivosBUILDcontêm muitas listas de strings. É fácil esquecer uma vírgula, o que leva a um resultado completamente diferente. Isso criou muitos bugs no passado. Confira também esta discussão. (em inglês)Use espaços em torno do sinal
=para argumentos de palavras-chave nas regras. Razão: os argumentos nomeados são muito mais frequentes do que no Python e sempre estão em uma linha separada. Os espaços melhoram a legibilidade. Essa convenção existe há muito tempo, e não vale a pena modificar todos os arquivosBUILDexistentes.Por padrão, use aspas duplas para strings. Razão: isso não está especificado no guia de estilo Python, mas ele recomenda manter consistência. Então, decidimos usar apenas strings com aspas duplas. Muitas linguagens usam aspas duplas para literais de string.
Use uma única linha em branco entre duas definições de nível superior. Razão: a estrutura de um arquivo
BUILDnão é como um arquivo Python típico. Ele tem apenas instruções de nível superior. Usar uma única linha em branco encurta os arquivosBUILD.