Guia de estilo do arquivo .bzl

Esta página aborda as diretrizes básicas de estilo do Starlark e também inclui informações sobre macros e regras.

Starlark é uma linguagem que define como o software é criado e, por isso, é uma linguagem de programação e uma configuração.

Você usará o Starlark para escrever arquivos BUILD, macros e regras de build. Macros e regras são essencialmente metalinguagens. Elas definem como os arquivos BUILD são gravados. Os arquivos BUILD foram criados para serem simples e repetitivos.

Todos os softwares são lidos com mais frequência do que os escritos. Isso é especialmente verdade para o Starlark, porque os engenheiros leem os arquivos BUILD para entender as dependências dos destinos e os detalhes das builds. Essa leitura geralmente acontecerá ao passar, com pressa ou em paralelo para realizar outra tarefa. Consequentemente, a simplicidade e a legibilidade são muito importantes para que os usuários possam analisar e compreender arquivos BUILD rapidamente.

Quando um usuário abre um arquivo BUILD, ele quer saber rapidamente a lista de destinos no arquivo, revisar a lista de origens dessa biblioteca C++ ou remover uma dependência desse binário do Java. Cada vez que você adiciona uma camada de abstração, você dificulta a execução dessas tarefas por um usuário.

Arquivos BUILD também são analisados e atualizados por muitas ferramentas diferentes. Talvez as ferramentas não conseguem editar seu arquivo BUILD se ele usar abstrações. Com a simplicidade dos arquivos BUILD, você tem acesso a ferramentas melhores. À medida que uma base de código cresce, torna-se cada vez mais frequente fazer mudanças em muitos arquivos BUILD para atualizar uma biblioteca ou fazer uma limpeza.

Recomendações gerais

Estilo

Estilo Python

Em caso de dúvida, siga o Guia de estilo PEP 8 sempre que possível. Em especial, use quatro espaços em vez de dois para que o recuo siga a convenção do Python.

Como o Starlark não é Python, alguns aspectos do estilo Python não se aplicam. Por exemplo, o PEP 8 recomenda que as comparações com singletons sejam feitas com is, que não é um operador no Starlark.

String do documento

Documente arquivos e funções usando docstrings. Use um docstring na parte superior de cada arquivo .bzl e um docstring para cada função pública.

Documentar regras e aspectos

Regras e aspectos, além dos respectivos atributos, provedores e campos, precisam ser documentados usando o argumento doc.

Convenção de nomenclatura

  • As variáveis e os nomes de função usam letras minúsculas com palavras separadas por sublinhados ([a-z][a-z0-9_]*), como cc_library.
  • Os valores privados de nível superior começam com um sublinhado. Ele impede que valores particulares sejam usados em outros arquivos. Variáveis locais não podem usar o prefixo sublinhado.

Comprimento de linha

Como em arquivos BUILD, não há um limite estrito de comprimento de linha, já que os marcadores podem ser longos. Sempre que possível, tente usar no máximo 79 caracteres por linha, seguindo o guia de estilo de Python, PEP 8 (link em inglês). Essa diretriz não deve ser aplicada de forma estrita: os editores precisam exibir mais de 80 colunas, as mudanças automáticas geralmente apresentam linhas mais longas, e os humanos não precisam gastar tempo dividindo linhas que já são legíveis.

Argumentos de palavra-chave

Em argumentos de palavra-chave, prefira espaços em torno do sinal de igual:

def fct(name, srcs):
    filtered_srcs = my_filter(source = srcs)
    native.cc_library(
        name = name,
        srcs = filtered_srcs,
        testonly = True,
    )

Valores booleanos

Prefira valores True e False (em vez de 1 e 0) para valores booleanos, como ao usar um atributo booleano em uma regra.

Não use a função print() no código de produção. Ela se destina apenas à depuração e vai enviar spam para todos os usuários diretos e indiretos do arquivo .bzl. A única exceção é que você pode enviar um código que use print() se ele estiver desativado por padrão e só poderá ser ativado editando a origem. Por exemplo, se todos os usos de print() forem protegidos por if DEBUG:, em que DEBUG está fixado no código como False. Verifique se essas declarações são úteis o suficiente para justificar o impacto delas na legibilidade.

Macros

Uma macro é uma função que instancia uma ou mais regras durante a fase de carregamento. Em geral, use regras sempre que possível em vez de macros. O gráfico de build visto pelo usuário não é o mesmo usado pelo Bazel durante o build. As macros são expandidas antes de o Bazel fazer qualquer análise do gráfico do build.

Por isso, quando algo der errado, o usuário precisará entender a implementação da macro para resolver problemas de build. Além disso, os resultados de bazel query podem ser difíceis de interpretar porque os destinos mostrados nos resultados vêm da expansão de macro. Por fim, os aspectos não reconhecem as macros. Portanto, as ferramentas dependendo de aspectos (IDEs e outros) podem falhar.

Um uso seguro para macros é definir destinos adicionais que serão referenciados diretamente na CLI do Bazel ou em arquivos BUILD. Nesse caso, somente os usuários finais desses destinos precisam saber sobre eles, e os problemas de compilação introduzidos pelas macros nunca estão longe de serem usados.

Para macros que definem destinos gerados (detalhes de implementação da macro que não devem ser referenciados na CLI nem dependem de destinos não instanciados por essa macro), siga estas práticas recomendadas:

  • Uma macro precisa usar um argumento name e definir um destino com esse nome. Esse destino se torna o principal destino dessa macro.
  • Os destinos gerados, ou seja, todos os outros definidos por uma macro, precisam:
    • Ter os nomes prefixados por <name> ou _<name>. Por exemplo, usando name = '%s_bar' % (name).
    • Têm visibilidade restrita (//visibility:private) e
    • Use uma tag manual para evitar a expansão em destinos de caracteres curinga (:all, ..., :* etc.).
  • O name só é usado para derivar nomes de destinos definidos pela macro, não para nada mais. Por exemplo, não use esse nome para derivar uma dependência ou um arquivo de entrada que não seja gerado pela própria macro.
  • Todos os destinos criados na macro precisam ser acoplados de alguma forma ao destino principal.
  • Mantenha os nomes dos parâmetros na macro consistentes. Se um parâmetro for transmitido como um valor de atributo para o destino principal, mantenha o mesmo nome. Se um parâmetro de macro tiver a mesma finalidade que um atributo de regra comum, como deps, nomeie o atributo (veja abaixo).
  • Ao chamar uma macro, use apenas argumentos de palavra-chave. Isso é consistente com as regras e melhora muito a legibilidade.

Os engenheiros costumam gravar macros quando a API Starlark de regras relevantes é insuficiente para o caso de uso específico, independentemente de a regra ser definida no Bazel em código nativo ou no Starlark. Se você estiver com esse problema, pergunte ao autor da regra se ele pode estender a API para atingir suas metas.

Como regra geral, quanto mais macros se parecerem com as regras, melhor.

Consulte também macros.

Regras

  • Regras, aspectos e atributos precisam usar nomes em letras minúsculas ("Snakecase").
  • Nomes de regras são substantivos que descrevem o principal tipo de artefato produzido pela regra, do ponto de vista das dependências (ou, para regras de folha, o usuário). Não é necessariamente um sufixo de arquivo. Por exemplo, uma regra que produz artefatos C++ destinados a serem usados como extensões Python pode ser chamada py_extension. Para a maioria das linguagens, as regras típicas incluem:
    • *_library: uma unidade de compilação ou "módulo".
    • *_binary: um destino que produz um executável ou uma unidade de implantação.
    • *_test: um destino de teste. Isso pode incluir vários testes. Espera-se que todos os testes em um destino *_test sejam variações do mesmo tema, por exemplo, testando uma única biblioteca.
    • *_import: um destino que encapsula um artefato pré-compilado, como um .jar ou um .dll usado durante a compilação.
  • Use nomes e tipos consistentes para atributos. Alguns atributos geralmente aplicáveis incluem:
    • srcs: label_list, permitindo arquivos: arquivos de origem, normalmente criados por humanos.
    • deps: label_list, normalmente não permite arquivos (dependências de compilação).
    • data: label_list, permite arquivos (arquivos de dados, por exemplo, dados de teste etc.).
    • runtime_deps: label_list: dependências do ambiente de execução que não são necessárias para compilação.
  • Para qualquer atributo com comportamento não óbvio (por exemplo, modelos de string com substituições especiais ou ferramentas invocadas com requisitos específicos), forneça a documentação usando o argumento de palavra-chave doc na declaração do atributo (attr.label_list() ou semelhante).
  • As funções de implementação de regras quase sempre devem ser privadas (nomeadas com um sublinhado no início). Um estilo comum é atribuir o nome _myrule_impl à função de implementação de myrule.
  • Transmita informações entre suas regras usando uma interface do provedor bem definida. Declarar e documentar campos do provedor.
  • Crie a regra pensando na extensibilidade. Considere que outras regras podem interagir com sua regra, acessar seus provedores e reutilizar as ações criadas.
  • Siga as diretrizes de desempenho nas suas regras.