En esta página, se explican los dos sistemas de visibilidad de Bazel: la visibilidad del destino y la visibilidad de la carga.
Ambos tipos de visibilidad ayudan a otros desarrolladores a distinguir entre la API pública de tu biblioteca y los detalles de su implementación, y ayudan a aplicar la estructura a medida que crece tu espacio de trabajo. También puedes usar la visibilidad cuando dejes de usar una API pública para permitir el acceso a los usuarios actuales y rechazar a los nuevos.
Visibilidad del objetivo
La visibilidad del destino controla quién puede depender de tu destino, es decir, quién puede usar la etiqueta de tu destino dentro de un atributo como deps
.
Un destino A
es visible para un destino B
si ambos están en el mismo paquete o si A
otorga visibilidad al paquete de B
. Por lo tanto, los paquetes son la unidad de granularidad para decidir si se permite o no el acceso. Si B
depende de A
, pero A
no es visible para B
, cualquier intento de compilar B
falla durante el análisis.
Ten en cuenta que otorgar visibilidad a un paquete no otorga visibilidad a sus subpaquetes. Para obtener más detalles sobre los paquetes y los subpaquetes, consulta Conceptos y terminología.
Para la creación de prototipos, puedes inhabilitar la aplicación de la visibilidad del destino configurando la marca --check_visibility=false
. Esto no se debe hacer para el uso en producción en el código enviado.
La forma principal de controlar la visibilidad es con el atributo visibility
en los destinos de reglas. En esta sección, se describe el formato de este atributo y cómo determinar la visibilidad de un objetivo.
Especificaciones de visibilidad
Todos los destinos de reglas tienen un atributo visibility
que toma una lista de etiquetas. Cada etiqueta tiene una de las siguientes formas. Con la excepción del último formulario, estos son solo marcadores de posición sintácticos que no corresponden a ningún objetivo real.
"//visibility:public"
: Otorga acceso a todos los paquetes. (No se puede combinar con ninguna otra especificación)."//visibility:private"
: No otorga ningún acceso adicional; solo los destinos de este paquete pueden usar este destino. (No se puede combinar con ninguna otra especificación)."//foo/bar:__pkg__"
: Otorga acceso a//foo/bar
(pero no a sus subpaquetes)."//foo/bar:__subpackages__"
: Otorga acceso a//foo/bar
y a todos sus subpaquetes directos e indirectos."//some_pkg:my_package_group"
: Otorga acceso a todos los paquetes que forman parte delpackage_group
determinado.- Los grupos de paquetes usan una sintaxis diferente para especificar paquetes. Dentro de un grupo de paquetes, las formas
"//foo/bar:__pkg__"
y"//foo/bar:__subpackages__"
se reemplazan por"//foo/bar"
y"//foo/bar/..."
, respectivamente. Del mismo modo,"//visibility:public"
y"//visibility:private"
son solo"public"
y"private"
.
- Los grupos de paquetes usan una sintaxis diferente para especificar paquetes. Dentro de un grupo de paquetes, las formas
Por ejemplo, si //some/package:mytarget
tiene su visibility
establecido en [":__subpackages__", "//tests:__pkg__"]
, cualquier destino que forme parte del árbol de origen //some/package/...
y los destinos definidos en //tests/BUILD
podrán usarlo, pero no los destinos definidos en //tests/integration/BUILD
.
Práctica recomendada: Para que varios destinos sean visibles para el mismo conjunto de paquetes, usa un package_group
en lugar de repetir la lista en el atributo visibility
de cada destino. Esto aumenta la legibilidad y evita que las listas se desincronicen.
Visibilidad del destino de la regla
La visibilidad de un destino de regla es la siguiente:
El valor de su atributo
visibility
, si está configurado; de lo contrario,El valor del argumento
default_visibility
de la instrucciónpackage
en el archivoBUILD
del destino, si existe tal declaración; de lo contrario,//visibility:private
.
Práctica recomendada: Evita establecer default_visibility
como público. Puede ser conveniente para la creación de prototipos o en bases de código pequeñas, pero el riesgo de crear inadvertidamente destinos públicos aumenta a medida que crece la base de código. Es mejor ser explícito sobre qué destinos forman parte de la interfaz pública de un paquete.
Ejemplo
Archivo //frobber/bin/BUILD
:
# This target is visible to everyone
cc_binary(
name = "executable",
visibility = ["//visibility:public"],
deps = [":library"],
)
# This target is visible only to targets declared in the same package
cc_library(
name = "library",
# No visibility -- defaults to private since no
# package(default_visibility = ...) was used.
)
# This target is visible to targets in package //object and //noun
cc_library(
name = "subject",
visibility = [
"//noun:__pkg__",
"//object:__pkg__",
],
)
# See package group "//frobber:friends" (below) for who can
# access this target.
cc_library(
name = "thingy",
visibility = ["//frobber:friends"],
)
Archivo //frobber/BUILD
:
# This is the package group declaration to which target
# //frobber/bin:thingy refers.
#
# Our friends are packages //frobber, //fribber and any
# subpackage of //fribber.
package_group(
name = "friends",
packages = [
"//fribber/...",
"//frobber",
],
)
Visibilidad del destino del archivo generado
Un destino de archivo generado tiene la misma visibilidad que el destino de la regla que lo genera.
Visibilidad del destino del archivo fuente
Puedes establecer de forma explícita la visibilidad de un destino de archivo fuente llamando a exports_files
. Cuando no se pasa ningún argumento visibility
a exports_files
, se establece la visibilidad como pública.
exports_files
no se puede usar para anular la visibilidad de un archivo generado.
Para los destinos de archivos fuente que no aparecen en una llamada a exports_files
, la visibilidad depende del valor de la marca --incompatible_no_implicit_file_export
:
Si se establece la marca, la visibilidad es privada.
De lo contrario, se aplica el comportamiento heredado: la visibilidad es la misma que la de
default_visibility
del archivoBUILD
o privada si no se especifica una visibilidad predeterminada.
Evita depender del comportamiento heredado. Siempre escribe una declaración exports_files
cuando un destino de archivo fuente necesite visibilidad no privada.
Práctica recomendada: Cuando sea posible, prefiere exponer un destino de regla en lugar de un archivo fuente. Por ejemplo, en lugar de llamar a exports_files
en un archivo .java
, envuelve el archivo en un destino java_library
no privado. En general, los destinos de las reglas solo deben hacer referencia directamente a los archivos fuente que se encuentran en el mismo paquete.
Ejemplo
Archivo //frobber/data/BUILD
:
exports_files(["readme.txt"])
Archivo //frobber/bin/BUILD
:
cc_binary(
name = "my-program",
data = ["//frobber/data:readme.txt"],
)
Visibilidad del parámetro de configuración
Históricamente, Bazel no aplicó la visibilidad para los destinos config_setting
a los que se hace referencia en las claves de un select()
. Existen dos marcas para quitar este comportamiento heredado:
--incompatible_enforce_config_setting_visibility
habilita la verificación de visibilidad para estos objetivos. Para ayudar con la migración, también hace que cualquierconfig_setting
que no especifique unvisibility
se considere público (independientemente deldefault_visibility
a nivel del paquete).--incompatible_config_setting_private_default_visibility
hace que losconfig_setting
s que no especifican unvisibility
respeten eldefault_visibility
del paquete y recurran a la visibilidad privada, al igual que cualquier otro destino de regla. No hace nada si no se establece--incompatible_enforce_config_setting_visibility
.
Evita depender del comportamiento heredado. Cualquier config_setting
que se pretenda usar fuera del paquete actual debe tener un visibility
explícito, si el paquete aún no especifica un default_visibility
adecuado.
Visibilidad del objetivo del grupo de paquetes
Los destinos package_group
no tienen un atributo visibility
. Siempre son visibles públicamente.
Visibilidad de las dependencias implícitas
Algunas reglas tienen dependencias implícitas, es decir, dependencias que no se especifican en un archivo BUILD
, pero son inherentes a cada instancia de esa regla. Por ejemplo, una regla cc_library
podría crear una dependencia implícita de cada uno de sus destinos de regla a un destino ejecutable que represente un compilador de C++.
La visibilidad de una dependencia implícita de este tipo se verifica con respecto al paquete que contiene el archivo .bzl
en el que se define la regla (o el aspecto). En nuestro ejemplo, el compilador de C++ podría ser privado siempre y cuando se encuentre en el mismo paquete que la definición de la regla cc_library
. Como alternativa, si la dependencia implícita no es visible desde la definición, se verifica con respecto al destino cc_library
.
Puedes cambiar este comportamiento inhabilitando --incompatible_visibility_private_attributes_at_definition
.
Cuando se inhabilita, las dependencias implícitas se tratan como cualquier otra dependencia.
Esto significa que el destino del que se depende (como nuestro compilador de C++) debe ser visible para cada instancia de la regla. En la práctica, esto suele significar que el objetivo debe tener visibilidad pública.
Si deseas restringir el uso de una regla a ciertos paquetes, usa la visibilidad de carga.
Visibilidad de la carga
La visibilidad de carga controla si se puede cargar un archivo .bzl
desde otros archivos BUILD
o .bzl
fuera del paquete actual.
Del mismo modo en que la visibilidad del destino protege el código fuente encapsulado por los destinos, la visibilidad de la carga protege la lógica de compilación encapsulada por los archivos .bzl
. Por ejemplo, el autor de un archivo BUILD
podría querer factorizar algunas definiciones de destino repetitivas en una macro en un archivo .bzl
. Sin la protección de la visibilidad de la carga, es posible que otros colaboradores reutilicen su macro en el mismo espacio de trabajo, por lo que modificarla interrumpiría las compilaciones de otros equipos.
Ten en cuenta que un archivo .bzl
puede tener o no un destino de archivo fuente correspondiente.
Si es así, no hay garantía de que la visibilidad de la carga y la visibilidad del objetivo coincidan. Es decir, el mismo archivo BUILD
podría cargar el archivo .bzl
, pero no incluirlo en el srcs
de un filegroup
, o viceversa. A veces, esto puede causar problemas en las reglas que desean consumir archivos .bzl
como código fuente, por ejemplo, para la generación de documentación o pruebas.
Para la creación de prototipos, puedes inhabilitar la aplicación de la visibilidad de la carga configurando --check_bzl_visibility=false
. Al igual que con --check_visibility=false
, esto no se debe hacer con el código enviado.
La visibilidad de carga está disponible a partir de Bazel 6.0.
Cómo declarar la visibilidad de la carga
Para establecer la visibilidad de carga de un archivo .bzl
, llama a la función visibility()
desde el archivo.
El argumento para visibility()
es una lista de especificaciones de paquetes, al igual que el atributo packages
de package_group
. Sin embargo, visibility()
no acepta especificaciones de paquetes negativas.
La llamada a visibility()
solo debe ocurrir una vez por archivo, en el nivel superior (no dentro de una función) y, de manera ideal, inmediatamente después de las instrucciones load()
.
A diferencia de la visibilidad del objetivo, la visibilidad de carga predeterminada siempre es pública. Los archivos que no llaman a visibility()
siempre se pueden cargar desde cualquier lugar del espacio de trabajo. Es una buena idea agregar visibility("private")
en la parte superior de cualquier archivo .bzl
nuevo que no esté específicamente destinado a usarse fuera del paquete.
Ejemplo
# //mylib/internal_defs.bzl
# Available to subpackages and to mylib's tests.
visibility(["//mylib/...", "//tests/mylib/..."])
def helper(...):
...
# //mylib/rules.bzl
load(":internal_defs.bzl", "helper")
# Set visibility explicitly, even though public is the default.
# Note the [] can be omitted when there's only one entry.
visibility("public")
myrule = rule(
...
)
# //someclient/BUILD
load("//mylib:rules.bzl", "myrule") # ok
load("//mylib:internal_defs.bzl", "helper") # error
...
Prácticas de visibilidad de la carga
En esta sección, se describen sugerencias para administrar las declaraciones de visibilidad de la carga.
Factorización de visibilidades
Cuando varios archivos .bzl
deben tener la misma visibilidad, puede ser útil factorizar sus especificaciones de paquetes en una lista común. Por ejemplo:
# //mylib/internal_defs.bzl
visibility("private")
clients = [
"//foo",
"//bar/baz/...",
...
]
# //mylib/feature_A.bzl
load(":internal_defs.bzl", "clients")
visibility(clients)
...
# //mylib/feature_B.bzl
load(":internal_defs.bzl", "clients")
visibility(clients)
...
Esto ayuda a evitar la asimetría accidental entre las visibilidades de los distintos archivos de .bzl
. También es más legible cuando la lista clients
es grande.
Composición de visibilidades
A veces, es posible que un archivo .bzl
deba ser visible para una lista de entidades permitidas compuesta por varias listas de entidades permitidas más pequeñas. Esto es análogo a cómo un package_group
puede incorporar otros package_group
a través de su atributo includes
.
Supongamos que dejas de usar una macro muy utilizada. Quieres que solo sea visible para los usuarios existentes y para los paquetes que son propiedad de tu equipo. Podrías escribir lo siguiente:
# //mylib/macros.bzl
load(":internal_defs.bzl", "our_packages")
load("//some_big_client:defs.bzl", "their_remaining_uses")
# List concatenation. Duplicates are fine.
visibility(our_packages + their_remaining_uses)
Cómo quitar duplicados con grupos de paquetes
A diferencia de la visibilidad del objetivo, no puedes definir la visibilidad de la carga en términos de un package_group
. Si deseas reutilizar la misma lista de entidades permitidas para la visibilidad del destino y la visibilidad de la carga, lo mejor es mover la lista de especificaciones de paquetes a un archivo .bzl, al que pueden hacer referencia ambos tipos de declaraciones. Basándote en el ejemplo de Factorización de visibilidades anterior, podrías escribir lo siguiente:
# //mylib/BUILD
load(":internal_defs", "clients")
package_group(
name = "my_pkg_grp",
packages = clients,
)
Esto solo funciona si la lista no contiene ninguna especificación de paquete negativa.
Cómo proteger símbolos individuales
No se puede cargar desde otro archivo ningún símbolo de Starlark cuyo nombre comience con un guion bajo. Esto facilita la creación de símbolos privados, pero no permite compartir estos símbolos con un conjunto limitado de archivos de confianza. Por otro lado, la visibilidad de carga te permite controlar qué otros paquetes pueden ver tu .bzl file
, pero no te permite evitar que se cargue ningún símbolo que no esté subrayado.
Afortunadamente, puedes combinar estas dos funciones para obtener un control detallado.
# //mylib/internal_defs.bzl
# Can't be public, because internal_helper shouldn't be exposed to the world.
visibility("private")
# Can't be underscore-prefixed, because this is
# needed by other .bzl files in mylib.
def internal_helper(...):
...
def public_util(...):
...
# //mylib/defs.bzl
load(":internal_defs", "internal_helper", _public_util="public_util")
visibility("public")
# internal_helper, as a loaded symbol, is available for use in this file but
# can't be imported by clients who load this file.
...
# Re-export public_util from this file by assigning it to a global variable.
# We needed to import it under a different name ("_public_util") in order for
# this assignment to be legal.
public_util = _public_util
Linter de Buildifier bzl-visibility
Existe un lint de Buildifier que proporciona una advertencia si los usuarios cargan un archivo desde un directorio llamado internal
o private
, cuando el archivo del usuario no se encuentra debajo del directorio principal. Este análisis precede a la función de visibilidad de carga y es innecesario en los espacios de trabajo en los que los archivos .bzl
declaran visibilidades.