Guia de estilo do build

A formatação de arquivo BUILD segue a mesma abordagem do Go, em que uma ferramenta padronizada lida com a maioria dos problemas de formatação. O Buildifier é uma ferramenta que analisa e emite o código-fonte em um estilo padrão. Portanto, cada arquivo BUILD é formatado da mesma maneira automatizada, o que torna a formatação um problema durante as revisões de código. Ele também facilita o entendimento, a edição e a 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 a 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. A distinção é importante ao fazer alterações 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 correspondentes ao diretório do pacote (sem nunca usar referências, como ..). Os arquivos gerados precisam ter o prefixo ":" para indicar que eles não são origens. Os arquivos de origem não podem ter o prefixo :. As regras precisam ser prefixadas com :. 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();' > $@",
)

Nomenclatura do destino

Os nomes dos destinos devem 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 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 essa segmentação não existir, não crie outra que tenha o mesmo nome.

Prefira usar o nome curto 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 segmentações "reservados" que tenham um significado especial. Isso inclui all, __pkg__ e __subpackages__. Esses nomes têm semânticas especiais 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 um java_library com um src, isso significa usar um nome que não seja igual ao nome do arquivo sem a extensão.
    • Para regras *_binary e *_test do Java, use "CamelCase Upper" (link em inglês). Isso permite que o nome do destino corresponda a um dos srcs. Para java_test, isso possibilita que o atributo test_class seja inferido do nome do destino.
  • Se houver diversas variantes de um destino específico, adicione um sufixo para desambiguar (como :foo_dev, :foo_prod ou :bar_x86, :bar_x64).
  • Sufixo: destinos _test com _test, _unittest, Test ou Tests
  • Evite sufixos sem sentido, como _lib ou _library, a menos que seja necessário para evitar conflitos entre um destino _library e o _binary correspondente
  • Para destinos relacionados a proto:
    • Os destinos proto_library precisam ter nomes terminados em _proto
    • As regras *_proto_library específicas das linguagens precisam corresponder ao protótipo subjacente, mas substituem _proto por um sufixo específico de idioma, como:
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

Visibilidade

O escopo da visibilidade precisa ser o mais definido possível, mas ainda permitir o acesso por testes e dependências reversas. Use __pkg__ e __subpackages__ conforme adequado.

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 depender de projetos externos ou binários que podem ser usados pelo processo de compilação de um projeto externo.

Dependências

As dependências precisam ser restritas a dependências diretas, ou seja, aquelas necessárias às origens listadas na regra. Não liste as dependências transitivas.

As dependências de pacote local precisam ser listadas primeiro e referenciadas de maneira compatível com a seção Referências a destinos no pacote atual 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 levar a dependências não utilizadas.

Globs

Indique "nenhuma meta" com []. Não use um glob que não corresponda a nada: ele é mais propenso a erros e menos óbvia 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 a análise dos arquivos BUILD porque eles pulam 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 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

Globos não recursivos geralmente são aceitáveis.

Outras convenções

  • Use letras maiúsculas e sublinhados para declarar constantes (como GLOBAL_CONSTANT). Use letras minúsculas e sublinhados para declarar variáveis (como my_variable).

  • Os rótulos nunca devem ser divididos, mesmo que tenham mais de 79 caracteres. Os rótulos devem ser literais de string sempre que possível. Lógica: facilita a localização e a substituição. Também melhora a legibilidade.

  • O valor do atributo de nome precisa ser uma string constante literal (exceto em macros). Lógica: as ferramentas externas usam o atributo de nome para se referir a uma regra. Eles precisam encontrar regras sem ter que interpretar o código.

  • Ao definir atributos do tipo booleano, 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. Lógica: flaky = 1 pode ser interpretado incorretamente como dizer "desflar este destino novamente executando-o uma vez". flaky = True diz inequivocamente "este teste é instável".

Diferenças com o guia de estilo do Python

Embora a compatibilidade com o Guia de estilo do Python seja um objetivo, existem algumas diferenças:

  • Não há um limite estrito de comprimento de linha. Comentários longos e strings longas geralmente são divididos em 79 colunas, mas isso não é obrigatório. Ela não deve ser aplicada em revisões de código ou scripts de pré-envio. Lógica: os rótulos podem ser longos e exceder esse limite. É comum que arquivos BUILD sejam gerados ou editados por ferramentas, o que não funciona bem com um limite de comprimento de linha.

  • Não há suporte para a concatenação implícita de strings. Use o operador +. Lógica: os arquivos BUILD contêm muitas listas de strings. É fácil esquecer uma vírgula, o que leva a um resultado totalmente diferente. Isso criou muitos bugs no passado. Veja também esta discussão.

  • Use espaços ao redor do sinal = para argumentos de palavras-chave nas regras. Lógica: os argumentos nomeados são muito mais frequentes do que no 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 arquivos BUILD existentes.

  • Por padrão, use aspas duplas para as strings. Lógica: isso não está especificado no guia de estilo Python, mas recomenda consistência. Então, decidimos usar apenas strings entre 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. Lógica: a estrutura de um arquivo BUILD não é como um arquivo Python típico. Ele contém apenas declarações de nível superior. Usar uma única linha em branco torna os arquivos BUILD mais curtos.