Prefiere los archivos BUILD de DAMP a los de DRY
El principio DRY (“No te repitas”) fomenta la singularidad introduciendo abstracciones, como variables y funciones, para evitar la redundancia en el código.
En contraste, el principio DAMP ("Frases descriptivas y significativas") fomenta la legibilidad por sobre la singularidad para que los archivos sean más fáciles de comprender y mantener.
Los archivos BUILD
no son código, sino configuraciones. No se prueban como el código, pero las personas y las herramientas deben mantenerlas. Esto hace que DAMP sea mejor para ellos que DRY.
Formato del archivo BUILD.bazel
El formato de archivos 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, todos los archivos BUILD
se formatean de la misma manera 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 (todos los elementos son opcionales):
Descripción del paquete (un comentario)
Todas las sentencias
load()
La función
package()
Llamadas a reglas y macros
Buildifier distingue 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. Esta 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 nunca referencias superiores, 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 fuente:
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 objetivos deben ser descriptivos. Si un destino contiene un archivo fuente, generalmente 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 destino, no crees un destino epónimo.
Prefiere usar 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__
, nombres que tienen una semántica especial y pueden causar confusión y comportamientos inesperados cuando se usan.
En ausencia de una convención de equipo predominante, estas son algunas recomendaciones no vinculantes que se usan ampliamente en Google:
- En general, usa "snake_case".
- En el caso de un
java_library
con unsrc
, esto significa usar un nombre que no sea el mismo que el nombre de archivo sin la extensión. - Para las reglas de Java
*_binary
y*_test
, usa "Upper CamelCase". Esto permite que el nombre del destino coincida con uno de lossrc
. En el caso dejava_test
, esto permite que el atributotest_class
se infiera a partir del nombre del destino.
- En el caso de un
- Si hay varias variantes de un objetivo en particular, agrega un sufijo para diferenciarlas (por ejemplo,
:foo_dev
,:foo_prod
o:bar_x86
,:bar_x64
) - Sufijo
_test
segmentado con_test
,_unittest
,Test
oTests
- Evita los sufijos sin significado, como
_lib
o_library
(a menos que sea necesario para evitar conflictos entre un destino_library
y su_binary
correspondiente). - Para los destinos relacionados con .proto, haz lo siguiente:
- Los destinos
proto_library
deben tener nombres que terminen en_proto
- Las reglas específicas de idiomas
*_proto_library
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
- Los destinos
Visibilidad
La visibilidad debe ser lo más limitada posible, pero debe permitir el acceso de las pruebas y las dependencias inversas. Usa __pkg__
y __subpackages__
según corresponda.
Evita establecer el paquete default_visibility
en //visibility:public
.
//visibility:public
solo se debe configurar de forma individual para los destinos de la API pública del proyecto. Podrían ser bibliotecas diseñadas para que dependan de ellas proyectos externos o archivos binarios que podría usar el proceso de compilación de un proyecto externo.
Dependencias
Las dependencias deben restringirse a las dependencias directas (las que necesitan las fuentes que se indican en la regla). No se deben incluir las dependencias transitivas.
Las dependencias locales del paquete deben aparecer primero y hacer referencia a ellas 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. Colocar las dependencias "comunes" de varios destinos en una variable reduce la capacidad de mantenimiento, imposibilita que las herramientas cambien las dependencias de un destino y puede generar dependencias no utilizadas.
Globs
Indica "sin objetivos" con []
. No uses un comodín que no coincida con nada, ya que es más propenso a errores y menos obvio que una lista vacía.
Recursivo
No uses globs recursivos para hacer coincidir archivos fuente (por ejemplo, glob(["**/*.java"])
).
Los globs recursivos dificultan el razonamiento sobre los archivos BUILD
porque omiten los subdirectorios que contienen archivos BUILD
.
En 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.
Es una buena práctica 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 destino con nombre con una regla de nivel superior o una llamada a macro independientes. Asigna a cada uno un parámetro name
corto para mayor claridad.
La comprensión de listas reduce lo siguiente:
- Capacidad de mantenimiento Es difícil o imposible que los mantenedores 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 su 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 sencillas. 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 deps
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",
...
],
)
Deja que Gazelle y otras herramientas se encarguen de mantenerlos. Habrá repetición, pero no tendrás que pensar en cómo administrar las dependencias.
Prefiere las cadenas literales
Si bien Starlark proporciona operadores de cadenas para la concatenación (+
) y el formato (%
), úsalos con precaución. Es tentador factorizar partes comunes de cadenas para que las expresiones sean más concisas o dividir líneas largas. Sin embargo,
Es más difícil leer los valores de cadena divididos de un vistazo.
Las herramientas automatizadas, como buildozer y Code Search, tienen problemas para encontrar valores y actualizarlos correctamente cuando los valores se dividen.
En los archivos
BUILD
, la legibilidad es más importante que evitar la repetición (consulta DAMP versus DRY).Esta guía de estilo advierte sobre la división de cadenas con valores de etiquetas y permite explícitamente las líneas largas.
Buildifier fusiona automáticamente las cadenas concatenadas cuando detecta que son etiquetas.
Por lo tanto, prefiere las cadenas literales explícitas a las cadenas concatenadas o con formato, en especial en los atributos de tipo etiqueta, como name
y deps
. Por ejemplo, este fragmento de 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 reescribir 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, funciones) que exporta cada archivo .bzl
(Starlark) público. Recomendamos que un archivo exporte varios símbolos solo si es seguro que se usarán juntos. De lo contrario, divídelo en varios archivos .bzl
, cada uno con su propia bzl_library.
El uso excesivo de símbolos puede hacer que los archivos .bzl
se conviertan en amplias "bibliotecas" de símbolos, lo que provoca que los cambios en archivos individuales obliguen a Bazel a volver a compilar muchos destinos.
Otras convenciones
Usa mayúsculas y guiones bajos para declarar constantes (como
GLOBAL_CONSTANT
) y minúsculas y guiones bajos para declarar variables (comomy_variable
).Las etiquetas nunca deben dividirse, incluso si tienen más de 79 caracteres. Siempre que sea posible, las etiquetas deben ser literales de cadena. Justificación: Facilita la búsqueda y el reemplazo. También mejora la legibilidad.
El valor del atributo name debe ser una cadena literal constante (excepto en las macros). Justificación: Las herramientas externas usan el atributo name para hacer referencia a una regla. Necesitan encontrar reglas sin tener que interpretar 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 booleanos según sea necesario, pero esto no se recomienda. Justificación:
flaky = 1
podría interpretarse de forma errónea como "corrige este destino volviéndolo a ejecutar una vez".flaky = True
indica sin ambigüedades que "esta prueba es inestable".
Diferencias con la guía de estilo de Python
Si bien la compatibilidad con la guía de estilo de Python es un objetivo, existen algunas diferencias:
No hay un límite estricto para la longitud de la línea. Los comentarios y las cadenas largos 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 a la confirmación. Justificación: Las etiquetas pueden ser largas y exceder este límite. Es común que las herramientas generen o editen archivos
BUILD
, lo que no se adapta bien a un límite de longitud de línea.No se admite la concatenación implícita de cadenas. Usa el operador
+
. Justificación: Los archivosBUILD
contienen muchas listas de cadenas. Es fácil olvidarse de una coma, lo que lleva a un resultado completamente diferente. Esto ha generado muchos errores en el pasado. Consulta también este debate.Usa espacios alrededor del signo
=
para los argumentos de palabras clave en las reglas. Justificación: Los argumentos con nombre 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 desde hace mucho tiempo, y no vale la pena modificar todos los archivosBUILD
existentes.De forma predeterminada, usa comillas dobles para las cadenas. Justificación: Esto no se especifica en la guía de estilo de Python, pero se recomienda la coherencia. Por lo tanto, decidimos usar solo cadenas entre 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. Justificación: La estructura de un archivo
BUILD
no es como la de un archivo de Python típico. Solo tiene instrucciones de nivel superior. Usar una sola línea en blanco hace que los archivosBUILD
sean más cortos.