Guía de estilo de BUILD

Informar un problema Ver código fuente Nightly 8.1 · 8.0 · 7.5 · 7.4 · 7.3 · 7.2

Se prefieren los archivos DAMP BUILD en lugar de DRY

El principio DRY, que significa “no te repitas”, fomenta la singularidad a través de la introducción de abstracciones, como variables y funciones, para evitar la redundancia en el código.

Por el contrario, el principio DAMP (“Frases descriptivas y significativas”) fomenta la legibilidad en lugar de la singularidad para que los archivos sean más fáciles de entender y mantener.

Los archivos BUILD no son código, son configuraciones. No se prueban como el código, pero las personas y las herramientas deben mantenerlos. Eso hace que DAMP sea mejor para ellos que DRY.

Formato del archivo BUILD.bazel

El formato de archivo BUILD sigue el mismo enfoque que Go, en el que una herramienta estandarizada se encarga de la mayoría de los problemas de formato. Buildifier es una herramienta que analiza y emite el código fuente en un estilo estándar. Por lo tanto, cada archivo BUILD tiene el mismo formato de forma automática, lo que hace que el formato no sea un problema durante las revisiones de código. También facilita que las herramientas comprendan, editen y generen archivos BUILD.

El formato del archivo BUILD debe coincidir con el resultado de buildifier.

Ejemplo de formato

# 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",
    ],
)

Estructura de archivos

Recomendación: Usa el siguiente orden (cada elemento es opcional):

  • Descripción del paquete (un comentario)

  • Todas las sentencias load()

  • La función package()

  • Llamadas a reglas y macros

Buildifier hace una distinción entre un comentario independiente y un comentario adjunto a un elemento. Si un comentario no está adjunto a un elemento específico, usa una línea vacía después de él. La distinción es importante cuando se realizan cambios automatizados (por ejemplo, para conservar o quitar un comentario cuando se borra una regla).

# Standalone comment (such as to make a section in a file)

# Comment for the cc_library below
cc_library(name = "cc")

Referencias a destinos en el paquete actual

Se debe hacer referencia a los archivos por sus rutas de acceso relativas al directorio del paquete (sin usar referencias ascendentes, como ..). Los archivos generados deben tener el prefijo ":" para indicar que no son fuentes. Los archivos fuente no deben tener el prefijo :. Las reglas deben tener el prefijo :. Por ejemplo, supongamos que x.cc es un archivo de origen:

cc_library(
    name = "lib",
    srcs = ["x.cc"],
    hdrs = [":gen_header"],
)

genrule(
    name = "gen_header",
    srcs = [],
    outs = ["x.h"],
    cmd = "echo 'int x();' > $@",
)

Nombres de los destinos

Los nombres de los destinos deben ser descriptivos. Si un destino contiene un archivo fuente, por lo general, debe tener un nombre derivado de esa fuente (por ejemplo, un cc_library para chat.cc podría llamarse chat, o un java_library para DirectMessage.java podría llamarse direct_message).

El destino epónimo de un paquete (el destino con el mismo nombre que el directorio que lo contiene) debe proporcionar la funcionalidad que describe el nombre del directorio. Si no existe ese objetivo, no crees uno con el mismo nombre.

Usa el nombre corto cuando te refieras a un destino epónimo (//x en lugar de //x:x). Si estás en el mismo paquete, prefiere la referencia local (:x en lugar de //x).

Evita usar nombres de destino "reservados" que tengan un significado especial. Esto incluye all, __pkg__ y __subpackages__, que tienen semántica especial y pueden causar confusión y comportamientos inesperados cuando se usan.

A falta de una convención de equipo predominante, estas son algunas recomendaciones no vinculantes que se usan ampliamente en Google:

  • En general, usa "snake_case".
    • Para un java_library con un src, esto significa usar un nombre que no sea el mismo que el nombre de archivo sin la extensión.
    • Para las reglas *_binary y *_test de Java, usa "CamelCase mayúscula". Esto permite que el nombre de destino coincida con uno de los src. En el caso de java_test, esto permite que el atributo test_class se infiera a partir del nombre del destino.
  • Si hay varias variantes de un objetivo en particular, agrega un sufijo para aclararlo (como :foo_dev, :foo_prod o :bar_x86, :bar_x64)
  • Sufijos _test de destino con _test, _unittest, Test o Tests
  • Evita sufijos sin sentido, como _lib o _library (a menos que sea necesario para evitar conflictos entre un objetivo _library y su _binary correspondiente).
  • Para los objetivos relacionados con protos, haz lo siguiente:
    • Los destinos de proto_library deben tener nombres que terminen en _proto.
    • Las reglas *_proto_library específicas de los idiomas deben coincidir con el proto subyacente, pero reemplazar _proto por un sufijo específico del idioma, como los siguientes:
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

Visibilidad

La visibilidad debe tener el alcance más estricto posible, a la vez que permite el acceso a través de pruebas y dependencias inversas. Usa __pkg__ y __subpackages__ según corresponda.

Evita configurar el paquete default_visibility en //visibility:public. //visibility:public debe configurarse de forma individual solo para los objetivos en la API pública del proyecto. Estas pueden ser bibliotecas diseñadas para que dependan de proyectos externos o binarios que podría usar el proceso de compilación de un proyecto externo.

Dependencias

Las dependencias deben restringirse a las dependencias directas (las dependencias que necesitan las fuentes que se enumeran en la regla). No incluyas dependencias transitivas.

Las dependencias locales del paquete deben aparecer primero y deben mencionarse de una manera compatible con la sección Referencias a destinos en el paquete actual anterior (no por su nombre de paquete absoluto).

Es preferible enumerar las dependencias directamente, como una sola lista. Poner las dependencias "comunes" de varios destinos en una variable reduce la capacidad de mantenimiento, hace que sea imposible que las herramientas cambien las dependencias de un destino y puede generar dependencias que no se usen.

Globs

Indica "sin objetivos" con []. No uses un glob que no coincida con nada, ya que es más propenso a errores y menos obvio que una lista vacía.

Recurrente

No uses globs recursivos para hacer coincidir archivos de origen (por ejemplo, glob(["**/*.java"])).

Los globs recursivos dificultan el razonamiento sobre los archivos BUILD porque omiten los subdirectorios que contienen archivos BUILD.

Por lo general, los globs recursivos son menos eficientes que tener un archivo BUILD por directorio con un gráfico de dependencias definido entre ellos, ya que esto permite un mejor almacenamiento en caché remoto y paralelismo.

Una práctica recomendada es crear un archivo BUILD en cada directorio y definir un gráfico de dependencias entre ellos.

No recursiva

Por lo general, se aceptan los globs no recursivos.

Evita las comprensiones de listas

Evita usar comprensiones de listas en el nivel superior de un archivo BUILD.bazel. Automatiza las llamadas repetitivas creando cada objetivo nombrado con una regla de nivel superior o una llamada a macro independiente. Asigna a cada uno un parámetro name breve para mayor claridad.

La comprensión de listas reduce lo siguiente:

  • Capacidad de mantenimiento. Es difícil o imposible que los encargados humanos y los cambios automatizados a gran escala actualicen las comprensiones de listas correctamente.
  • Visibilidad Como el patrón no tiene parámetros name, es difícil encontrar la regla por nombre.

Una aplicación común del patrón de comprensión de listas es generar pruebas. Por ejemplo:

[[java_test(
    name = "test_%s_%s" % (backend, count),
    srcs = [ ... ],
    deps = [ ... ],
    ...
) for backend in [
    "fake",
    "mock",
]] for count in [
    1,
    10,
]]

Te recomendamos que uses alternativas más simples. Por ejemplo, define una macro que genere una prueba y la invoque para cada name de nivel superior:

my_java_test(name = "test_fake_1",
    ...)
my_java_test(name = "test_fake_10",
    ...)
...

No uses variables de dependencias

No uses variables de lista para encapsular dependencias comunes:

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 + [ ... ],
)

Del mismo modo, no uses un destino de biblioteca con exports para agrupar dependencias.

En su lugar, enumera las dependencias por separado 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",
      ...
    ],
)

Permite que Gazelle y otras herramientas los mantengan. Habrá repetición, pero no tendrás que pensar en cómo administrar las dependencias.

Se prefieren las cadenas literales

Aunque Starlark proporciona operadores de cadena para la concatenación (+) y el formato (%), úsalos con precaución. Es tentador factorizar partes comunes de cadenas para hacer que las expresiones sean más concisas o dividir líneas largas. Sin embargo,

Por lo tanto, prefiere cadenas literales y explícitas en lugar de cadenas concatenadas o con formato, especialmente en atributos de tipo de etiqueta, como name y deps. Por ejemplo, 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",
)

sería mejor que se reescribiera como

proto_library(
  name = "foo_proto",
  deps = ["//a/b:other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)

Limita los símbolos que exporta cada archivo .bzl

Minimiza la cantidad de símbolos (reglas, macros, constantes y funciones) que exporta cada archivo .bzl (Starlark) público. Recomendamos que un archivo exporte varios símbolos solo si se sabe con certeza que se usarán juntos. De lo contrario, divídelo en varios archivos .bzl, cada uno con su propia bzl_library.

Los símbolos excesivos pueden hacer que los archivos .bzl se conviertan en "bibliotecas" amplias de símbolos, lo que provoca que los cambios en archivos individuales obliguen a Bazel a volver a compilar muchos destinos.

Otros convenios

  • Usa mayúsculas y guiones bajos para declarar constantes (como GLOBAL_CONSTANT) y minúsculas y guiones bajos para declarar variables (como my_variable).

  • Las etiquetas nunca deben dividirse, incluso si tienen más de 79 caracteres. Las etiquetas deben ser literales de cadena siempre que sea posible. Justificación: Facilita la búsqueda y el reemplazo. También mejora la legibilidad.

  • El valor del atributo name debe ser una cadena constante literal (excepto en macros). Motivación: Las herramientas externas usan el atributo name para hacer referencia a una regla. Necesitan encontrar reglas sin tener que interpretar el código.

  • Cuando configures atributos de tipo booleano, usa valores booleanos, no valores enteros. Por motivos heredados, las reglas aún convierten números enteros en valores booleanos según sea necesario, pero no se recomienda. Motivación: flaky = 1 podría malinterpretarse como “quita las astillas de este objetivo reejecutándolo una vez”. flaky = True dice de forma inequívoca que “esta prueba es inestable”.

Diferencias con el manual de estilo de Python

Si bien la compatibilidad con el guía de estilo de Python es un objetivo, existen algunas diferencias:

  • No hay un límite estricto de longitud de línea. Los comentarios y las cadenas largas suelen dividirse en 79 columnas, pero no es obligatorio. No se debe aplicar en las revisiones de código ni en las secuencias de comandos previas al envío. Motivo: Las etiquetas pueden ser largas y exceder este límite. Es común que las herramientas generen o editen archivos BUILD, lo que no funciona bien con un límite de longitud de línea.

  • No se admite la concatenación de cadenas implícita. Usa el operador +. Motivación: Los archivos BUILD contienen muchas listas de cadenas. Es fácil olvidarse de una coma, lo que genera un resultado completamente diferente. Esto generó muchos errores en el pasado. Consulta también esta conversación.

  • Usa espacios alrededor del signo = para los argumentos de palabras clave en las reglas. Motivación: Los argumentos nombrados son mucho más frecuentes que en Python y siempre se encuentran en una línea separada. Los espacios mejoran la legibilidad. Esta convención existe hace mucho tiempo y no vale la pena modificar todos los archivos BUILD existentes.

  • De forma predeterminada, usa comillas dobles para las cadenas. Justificación: Esto no se especifica en el guía de estilo de Python, pero se recomienda la coherencia. Por lo tanto, decidimos usar solo cadenas con comillas dobles. Muchos lenguajes usan comillas dobles para los literales de cadena.

  • Usa una sola línea en blanco entre dos definiciones de nivel superior. Motivación: La estructura de un archivo BUILD no es como la de un archivo Python típico. Solo tiene instrucciones de nivel superior. El uso de una sola línea en blanco acorta los archivos BUILD.