Prefiere los archivos BUILD DAMP en lugar de los DRY
El principio DRY ("No te repitas") fomenta la singularidad mediante 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 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 mantenerlos. Eso hace que DAMP sea mejor
para ellos que DRY.
Formato de 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 con un estilo estándar. Por lo tanto, cada archivo BUILD se formatea de la misma manera automatizada, 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 BUILD archivos.
El formato de 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 instrucciones
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. 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 nunca 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, si 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 destino
Los nombres de destino deben ser descriptivos. Si un destino contiene un archivo fuente,
el destino generalmente debe tener un nombre derivado de esa fuente (por ejemplo, una
cc_library para chat.cc podría llamarse chat, o una java_library para
DirectMessage.java podría llamarse direct_message).
El destino homónimo de un paquete (el destino con el mismo nombre que el directorio contenedor) debe proporcionar la funcionalidad que describe el nombre del directorio. Si no existe ese destino, no crees un destino homónimo destino.
Prefiere usar el nombre corto cuando hagas referencia a un destino homó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__. Estos nombres 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"
- Para un
java_librarycon unsrc, esto significa usar un nombre que no sea el mismo que el nombre de archivo sin la extensión. - Para las reglas Java
*_binaryy*_test, usa "Upper CamelCase". Esto permite que el nombre del destino coincida con uno de lossrcs. Parajava_test, esto permite que el atributotest_classse infiera del nombre del destino.
- Para un
- Si hay varias variantes de un destino en particular, agrega un sufijo para
desambiguar (como.
:foo_dev,:foo_prodo:bar_x86,:bar_x64) - Agrega el sufijo
_testa los destinos con_test,_unittest,TestoTests. - Evita los sufijos sin sentido como
_libo_library(a menos que sea necesario para evitar conflictos entre un destino_libraryy su_binarycorrespondiente). - Para los destinos relacionados con proto:
- Los destinos
proto_librarydeben tener nombres que terminen en_proto - Las reglas específicas del lenguaje
*_proto_librarydeben coincidir con el proto subyacente , pero reemplazar_protopor un sufijo específico del lenguaje, como:cc_proto_library:_cc_protojava_proto_library:_java_protojava_lite_proto_library:_java_proto_lite
- Los destinos
Visibilidad
La visibilidad debe tener un alcance lo más ajustado posible, sin dejar de permitir el acceso
a las pruebas y las dependencias inversas. Usa __pkg__ y __subpackages__ según
corresponda.
Evita configurar el paquete default_visibility en //visibility:public.
//visibility:public solo debe configurarse de forma individual para los destinos en la
API pública del proyecto. Podrían ser bibliotecas diseñadas para que dependan
de proyectos externos o archivos binarios que pueda 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 enumeres las dependencias transitivas.
Las dependencias locales del paquete deben aparecer primero y se debe hacer referencia a ellas de una manera compatible con la Referencias a destinos en el paquete actual sección que se encuentra más arriba (no por su nombre de paquete absoluto).
Prefiere enumerar las dependencias directamente, como una sola lista. Colocar las dependencias "comunes" de varios destinos en una variable reduce la capacidad de mantenimiento, impide que las herramientas cambien las dependencias de un destino y puede generar dependencias sin usar.
Globs
Indica "sin destinos" con []. No uses un glob que no coincida con nada: es
más propenso a errores y menos obvio que una lista vacía.
Recurrente
No uses globs recursivos para que coincidan con los archivos fuente (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.
Se recomienda crear un archivo BUILD en cada directorio y definir un
gráfico de dependencias entre ellos.
No recurrente
Por lo general, los globs no recursivos son aceptables.
Evita las comprensiones de lista
Evita usar comprensiones de lista 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 de macro independiente. Para mayor claridad, asigna a cada uno un parámetro name corto.
La comprensión de lista reduce lo siguiente:
- Capacidad de mantenimiento. Es difícil o imposible que los encargados del mantenimiento humano y los cambios automatizados a gran escala actualicen las comprensiones de lista correctamente.
- Visibilidad Como el patrón no tiene
nameparámetros, es difícil encontrar la regla por nombre.
Una aplicación común del patrón de comprensión de lista 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 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 las mantengan. Habrá repetición, pero no tendrás que pensar en cómo administrar las dependencias.
Prefiere 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 que las expresiones sean más concisas o para 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 en comparación con DRY).En esta Guía de estilo se advierte sobre la división de cadenas con valores de etiqueta y se permiten 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 en lugar de las cadenas concatenadas o con formato
cadenas, en especial en los atributos de tipo de etiqueta, como name y deps. Por
ejemplo, este BUILD fragmento:
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",
)
se reescribiría mejor 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
público .bzl (Starlark). Te 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 exceso de símbolos puede hacer que los archivos .bzl se conviertan en "bibliotecas" amplias de
símbolos, lo que hace 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), usa minúsculas y guiones bajos para declarar variables (comomy_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. Motivo: 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 las macros). Motivo: 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 booleanos según sea necesario, pero esto no se recomienda. Motivo:
flaky = 1podría interpretarse de forma incorrecta como "quitar la marca de este destino volviéndolo a ejecutar una vez".flaky = Truedice sin ambigüedades "esta prueba es inestable".
Diferencias con la guía de estilo de Python
Aunque la compatibilidad con la 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 largos 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 de envío previo. Motivo: Las etiquetas pueden ser largas y superar 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 implícita de cadenas. Usa el operador
+. Motivo: Los archivosBUILDcontienen muchas listas de cadenas. Es fácil olvidar una coma, lo que genera un resultado completamente diferente. Esto creó muchos errores en el pasado. Consulta también este debate.Usa espacios alrededor del signo
=para los argumentos de palabras clave en las reglas. Motivo: Los argumentos con nombre son mucho más frecuentes que en Python y siempre están 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 archivosBUILDexistentes.De forma predeterminada, usa comillas dobles para las cadenas. Motivo: Esto no está especificado en la guía de estilo de Python, pero 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. Motivo: La estructura de un archivo
BUILDno es como un archivo típico de Python. Solo tiene instrucciones de nivel superior. El uso de una sola línea en blanco hace que los archivosBUILDsean más cortos.