Prefira arquivos DAMP BUILD em vez de DRY
O princípio DRY ("Não se repita") 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 (frases descritivas e significativas) 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ódigo, mas sim configurações. Eles não são testados como
código, mas precisam ser mantidos por pessoas e ferramentas. Isso torna o DAMP melhor para eles do que o DRY.
Formatação do arquivo 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 automática, o que elimina problemas de formatação durante as revisões de código. Além disso, facilita a compreensão, edição e
geração de arquivos BUILD
pelas ferramentas.
A formatação do arquivo BUILD
precisa corresponder à saída de 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 seguinte ordem (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 depois dele. Essa distinção é importante ao fazer mudanças automáticas, 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 de nível superior, como ..
). Os arquivos gerados precisam ter o prefixo ":
" para indicar que não são fontes. Os arquivos de origem não podem ser prefixados com :
. 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 destinos
Os nomes dos destinos precisam ser descritivos. Se um destino contiver um arquivo de origem, ele geralmente terá um nome derivado dessa origem. Por exemplo, um
cc_library
para chat.cc
pode ser chamado de chat
, ou um java_library
para
DirectMessage.java
pode ser chamado de direct_message
.
O destino epô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 não houver uma meta assim, não crie uma meta epônima.
Prefira usar o nome curto ao se referir a um destino epô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 têm um significado especial. Isso inclui all
, __pkg__
e __subpackages__
, que têm semântica especial e podem causar confusão e comportamentos inesperados quando 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 um
java_library
com umsrc
, isso significa usar um nome que não seja o mesmo que o nome do arquivo sem a extensão. - Para regras Java
*_binary
e*_test
, use "Upper CamelCase". Isso permite que o nome de destino corresponda a um dossrc
s. Parajava_test
, isso permite que o atributotest_class
seja inferido do nome do destino.
- Para um
- Se houver várias variantes de um determinado destino, adicione um sufixo para
desambiguar (por exemplo,
:foo_dev
,:foo_prod
ou:bar_x86
,:bar_x64
) - Sufixo
_test
segmentado com_test
,_unittest
,Test
ouTests
- Evite sufixos sem sentido, como
_lib
ou_library
, a menos que seja necessário para evitar conflitos entre uma meta_library
e o_binary
correspondente. - Para destinos relacionados a proto:
- Os destinos
proto_library
precisam ter nomes que terminam em_proto
- As regras específicas de
*_proto_library
precisam corresponder ao proto subjacente, mas substitua_proto
por um sufixo específico do idioma, como:cc_proto_library
:_cc_proto
java_proto_library
:_java_proto
java_lite_proto_library
:_java_proto_lite
- Os destinos
Visibilidade
A visibilidade precisa ser o mais restrita possível, mas ainda permitir o acesso por testes e dependências inversas. Use __pkg__
e __subpackages__
conforme
necessário.
Evite definir o pacote default_visibility
como //visibility:public
.
//visibility:public
precisa ser definido individualmente apenas para destinos na API pública do projeto. Podem ser bibliotecas projetadas para serem usadas por projetos externos ou binários que podem ser usados no processo de build de um projeto externo.
Dependências
As dependências precisam ser restritas às diretas (necessárias pelas fontes 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 seção Referências a destinos no pacote atual acima (não pelo nome absoluto do pacote).
É melhor 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, impede que as ferramentas mudem as dependências de um destino e pode levar a 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
armazenamento em cache remoto e paralelismo.
É recomendável 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 regra de nível superior ou uma chamada de macro separada. Dê a cada um um parâmetro name
curto para maior clareza.
A compreensão de lista reduz o seguinte:
- Capacidade de manutenção. É difícil ou impossível para 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 usar 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 use 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 o Gazelle e outras ferramentas fazerem isso. 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 strings comuns para tornar as expressões mais concisas ou quebrar linhas longas. No entanto,
É mais difícil ler valores de string divididos rapidamente.
Ferramentas automatizadas, como o buildozer e a Pesquisa de código, têm dificuldade para encontrar valores e atualizá-los corretamente quando eles são divididos.
Em arquivos
BUILD
, a legibilidade é mais importante do que evitar repetições (consulte DAMP versus DRY).Este guia de estilo alerta contra a divisão de strings com valores de rótulo e permite explicitamente linhas longas.
O Buildifier une automaticamente strings concatenadas quando detecta que elas são rótulos.
Portanto, prefira strings explícitas e literais 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 público .bzl
(Starlark). Recomendamos que um arquivo exporte
vários símbolos somente se eles forem usados juntos. Caso contrário, divida
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 reconstruir 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 devem ser divididos, mesmo que tenham mais de 79 caracteres. Os rótulos precisam ser literais de string sempre que possível. Justificativa: facilita a localização e substituição. Além disso, ele melhora a legibilidade.
O valor do atributo "name" precisa ser uma string constante literal, exceto em macros. Justificativa: ferramentas externas usam o atributo "name" para se referir a uma regra. Eles precisam encontrar regras sem ter que interpretar códigos.
Ao definir atributos booleanos, use valores booleanos, não inteiros. Por motivos legados, as regras ainda convertem números inteiros em booleanos conforme necessário, mas isso não é recomendado. Justificativa:
flaky = 1
pode ser interpretado como "remova a instabilidade desse destino executando-o novamente uma vez".flaky = True
diz sem ambiguidade "este teste é instável".
Diferenças em relação ao guia de estilo do Python
Embora a compatibilidade com o guia de estilo do Python seja uma meta, há algumas diferenças:
Sem limite rígido de comprimento de linha. Comentários e strings longos geralmente são divididos em 79 colunas, mas isso não é obrigatório. Não deve ser aplicado em revisões de código ou scripts de pré-envio. Justificativa: os rótulos podem ser longos e exceder esse limite. É comum que arquivos
BUILD
sejam gerados ou editados por ferramentas, o que não combina com um limite de comprimento de linha.Não há suporte para concatenação implícita de strings. Use o operador
+
. Justificativa: os arquivosBUILD
contêm muitas listas de strings. É fácil esquecer uma vírgula, o que leva a um resultado completamente diferente. Isso já causou muitos bugs no passado. Confira também esta discussão.Use espaços ao redor do sinal
=
para argumentos de palavras-chave em regras. Justificativa: os argumentos nomeados são muito mais frequentes do que em Python e estão sempre 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 arquivosBUILD
atuais.Por padrão, use aspas duplas para strings. Justificativa: isso não é especificado no guia de estilo do Python, mas recomenda-se consistência. Por isso, decidimos usar apenas strings entre aspas duplas. Muitas linguagens usam aspas duplas para strings literais.
Use uma única linha em branco entre duas definições de nível superior. Justificativa: a estrutura de um arquivo
BUILD
não é como um arquivo Python típico. Ele tem apenas instruções de nível superior. Usar uma única linha em branco torna os arquivosBUILD
mais curtos.