En esta página, se abarcan los dos sistemas de visibilidad de Bazel: la visibilidad de destino y la visibilidad de carga.
Ambos tipos de visibilidad ayudan a otros desarrolladores a distinguir entre la API pública de tu biblioteca y sus detalles de implementación, y ayudan a aplicar la estructura a medida que crece tu espacio de trabajo. También puedes usar la visibilidad cuando das de baja una API pública para permitir a los usuarios actuales y denegar a los nuevos.
Visibilidad de destino
La visibilidad de 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 no se compilará
durante la fase de análisis si infringe la visibilidad de una de sus dependencias.
Por lo general, un destino A es visible para un destino B si están en la misma
ubicación o si A otorga visibilidad a la ubicación de B. En ausencia de
macros simbólicas, el término "ubicación" se puede simplificar
a "paquete". Consulta a continuación para obtener más información sobre las macros simbólicas.
La visibilidad se especifica enumerando los paquetes permitidos. Permitir un paquete no significa necesariamente que también se permitan sus subpaquetes. Para obtener más detalles sobre los paquetes y subpaquetes, consulta Conceptos y terminología.
Para la creación de prototipos, puedes inhabilitar la aplicación de la visibilidad de destino si configuras la
marca --check_visibility=false. Esto no se debe hacer para el uso de producción en el código enviado.
La forma principal de controlar la visibilidad es con el atributo
visibility de una regla.
En las siguientes subsecciones, se describe el formato del atributo, cómo aplicarlo a
varios tipos de destinos y la interacción entre el sistema de visibilidad y
las macros simbólicas.
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 de la última forma, estos
son solo marcadores de posición sintácticos que no corresponden a ningún destino real.
"//visibility:public": Otorga acceso a todos los paquetes."//visibility:private": No otorga ningún acceso adicional; solo los destinos en el paquete de esta ubicación pueden usar este destino."//foo/bar:__pkg__": Otorga acceso a//foo/bar(pero no a sus subpaquetes)."//foo/bar:__subpackages__": Otorga acceso a//foo/bary a todos sus subpaquetes directos e indirectos."//some_pkg:my_package_group": Otorga acceso a todos los paquetes que forman parte delpackage_group.- 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 configurado como
[":__subpackages__", "//tests:__pkg__"], cualquier destino
que forme parte del árbol de origen //some/package/... podría usarlo, así como los destinos
declarados en //tests/BUILD, 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 desincronizen.
Práctica recomendada: Cuando otorgues visibilidad al proyecto de otro equipo, prefiere
__subpackages__ en lugar de __pkg__ para evitar cambios innecesarios en la visibilidad a medida que ese
proyecto evoluciona y agrega subpaquetes nuevos.
Visibilidad de destino de la regla
La visibilidad de destino de una regla se determina tomando su atributo visibility
(o un valor predeterminado adecuado si no se proporciona) y agregando la ubicación en la que se declaró el
destino. Para los destinos que no se declaran en una macro simbólica, si el
paquete especifica un default_visibility,
se usa este valor predeterminado. Para todos los demás paquetes y para los destinos declarados en una
macro simbólica, el valor predeterminado es solo ["//visibility:private"].
# //mypkg/BUILD
package(default_visibility = ["//friend:__pkg__"])
cc_library(
name = "t1",
...
# No visibility explicitly specified.
# Effective visibility is ["//friend:__pkg__", "//mypkg:__pkg__"].
# If no default_visibility were given in package(...), the visibility would
# instead default to ["//visibility:private"], and the effective visibility
# would be ["//mypkg:__pkg__"].
)
cc_library(
name = "t2",
...
visibility = [":clients"],
# Effective visibility is ["//mypkg:clients, "//mypkg:__pkg__"], which will
# expand to ["//another_friend:__subpackages__", "//mypkg:__pkg__"].
)
cc_library(
name = "t3",
...
visibility = ["//visibility:private"],
# Effective visibility is ["//mypkg:__pkg__"]
)
package_group(
name = "clients",
packages = ["//another_friend/..."],
)
Práctica recomendada: Evita configurar 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 destinos públicos de forma involuntaria
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.
Visibilidad de destino del archivo generado
Un destino de archivo generado tiene la misma visibilidad que el destino de la regla que lo genera.
# //mypkg/BUILD
java_binary(
name = "foo",
...
visibility = ["//friend:__pkg__"],
)
# //friend/BUILD
some_rule(
name = "bar",
deps = [
# Allowed directly by visibility of foo.
"//mypkg:foo",
# Also allowed. The java_binary's "_deploy.jar" implicit output file
# target the same visibility as the rule target itself.
"//mypkg:foo_deploy.jar",
]
...
)
Visibilidad de destino del archivo fuente
Los destinos de archivos fuente se pueden declarar de forma explícita con
exports_fileso se pueden crear de forma implícita
haciendo referencia a su nombre de archivo en un atributo de etiqueta de una regla (fuera de una
macro simbólica). Al igual que con los destinos de reglas, la ubicación de la llamada a
exports_files, o el archivo BUILD que hizo referencia al archivo de entrada, siempre se agrega automáticamente a la visibilidad del archivo.
Los archivos declarados por exports_files pueden tener su visibilidad establecida por el
visibility parámetro de esa función. Si no se proporciona este parámetro, la visibilidad es pública.
Para los archivos que no aparecen en una llamada a exports_files, la visibilidad
depende del valor de la marca
--incompatible_no_implicit_file_export:
Si la marca es verdadera, la visibilidad es privada.
De lo contrario, se aplica el comportamiento heredado: la visibilidad es la misma que la
BUILDdel archivodefault_visibility, o privada si no se especifica una visibilidad predeterminada.
Evita depender del comportamiento heredado. Siempre escribe una exports_files
declaración 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. Por lo general, los destinos de reglas
solo deben hacer referencia directa 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 de la configuración
Históricamente, Bazel no aplicó la visibilidad para
config_setting destinos a los que se
hace referencia en las claves de un select(). Existen dos marcas para quitar este comportamiento heredado:
--incompatible_enforce_config_setting_visibilityhabilita la verificación de visibilidad para estos destinos. Para ayudar con la migración, también hace que cualquierconfig_settingque no especifique unvisibilityse considere público (independientemente deldefault_visibilitya nivel del paquete).--incompatible_config_setting_private_default_visibilityhace que losconfig_settingque no especifican unvisibilityrespeten eldefault_visibilitydel paquete y recurran a la visibilidad privada, al igual que cualquier otro destino de regla. Es una operación no operativa si--incompatible_enforce_config_setting_visibilityno se configura.
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 de destino del grupo de paquetes
Los destinos package_group no tienen un atributo visibility. Siempre son
visibles de forma pública.
Visibilidad de las dependencias implícitas
Algunas reglas tienen dependencias implícitas, es decir,
dependencias que no se especifican en un archivo BUILD, pero que 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 reglas a un destino ejecutable
que represente un compilador de C++.
La visibilidad de esa dependencia implícita se verifica con respecto al
paquete que contiene el .bzl archivo en el que se define la regla (o el aspecto). En
nuestro ejemplo, el compilador de C++ podría ser privado siempre que se encuentre en el mismo
paquete que la definición de la cc_library regla. Como alternativa, si la
dependencia implícita no es visible desde la definición, se verifica con
respecto al cc_library destino.
Si deseas restringir el uso de una regla a ciertos paquetes, usa la visibilidad de carga en su lugar.
Visibilidad y macros simbólicas
En esta sección, se describe cómo interactúa el sistema de visibilidad con las macros simbólicas.
Ubicaciones dentro de macros simbólicas
Un detalle clave del sistema de visibilidad es cómo determinamos la ubicación de una
declaración. Para los destinos que no se declaran en una macro simbólica, la ubicación
es solo el paquete en el que reside el destino, el paquete del archivo BUILD.
Sin embargo, para los destinos creados en una macro simbólica, la ubicación es el paquete
que contiene el archivo .bzl en el que aparece la definición de la macro (la instrucción
my_macro = macro(...) ). Cuando se crea un destino dentro de
varios destinos anidados, siempre se usa la definición de la macro simbólica más interna
que se utiliza.
Se usa el mismo sistema para determinar qué ubicación verificar con respecto a la visibilidad de una dependencia determinada. Si el destino de consumo se creó dentro de una macro, observamos la definición de la macro más interna en lugar del paquete en el que reside el destino de consumo.
Esto significa que todas las macros cuyo código se define en el mismo paquete son
automáticamente "amigas" entre sí. Cualquier destino creado directamente por una macro
definida en //lib:defs.bzl se puede ver desde cualquier otra macro definida en //lib,
independientemente de los paquetes en los que se instancien las macros. Del mismo modo,
pueden ver los destinos declarados directamente en //lib/BUILD y
sus macros heredadas, y estos pueden verlos. Por el contrario, los destinos que residen en el mismo paquete no pueden
necesariamente verse entre sí si al menos uno de ellos se crea con una macro
simbólica.
Dentro de la función de implementación de una macro simbólica, el parámetro visibility
tiene el valor efectivo del atributo visibility de la macro después de agregar
la ubicación en la que se llamó a la macro. La forma estándar para que una macro exporte
uno de sus destinos a su llamador es reenviar este valor a la declaración del destino, como en some_rule(..., visibility = visibility). Los destinos que omiten
este atributo no serán visibles para el llamador de la macro, a menos que el llamador
se encuentre en el mismo paquete que la definición de la macro. Este comportamiento
se compone, en el sentido de que una cadena de llamadas anidadas a submacros puede pasar
visibility = visibility, volver a exportar los destinos exportados de la macro interna a
la persona que llama en cada nivel, sin exponer ninguno de los detalles de implementación de las macros.
Delegar privilegios a una submacro
El modelo de visibilidad tiene una función especial para permitir que una macro delegue sus permisos a una submacro. Esto es importante para factorizar y componer macros.
Supongamos que tienes una macro my_macro que crea un borde de dependencia con una regla
some_library de otro paquete:
# //macro/defs.bzl
load("//lib:defs.bzl", "some_library")
def _impl(name, visibility, ...):
...
native.genrule(
name = name + "_dependency"
...
)
some_library(
name = name + "_consumer",
deps = [name + "_dependency"],
...
)
my_macro = macro(implementation = _impl, ...)
# //pkg/BUILD
load("//macro:defs.bzl", "my_macro")
my_macro(name = "foo", ...)
El destino //pkg:foo_dependency no tiene un visibility especificado, por lo que solo es
visible dentro de //macro, lo que funciona bien para el destino de consumo. Ahora, ¿qué
sucede si el autor de //lib refactoriza some_library para que se
implemente con una macro?
# //lib:defs.bzl
def _impl(name, visibility, deps, ...):
some_rule(
# Main target, exported.
name = name,
visibility = visibility,
deps = deps,
...)
some_library = macro(implementation = _impl, ...)
Con este cambio, la ubicación de //pkg:foo_consumer ahora es //lib en lugar de
//macro, por lo que su uso de //pkg:foo_dependency infringe la
visibilidad de la dependencia. No se puede esperar que el autor de my_macro pase
visibility = ["//lib"] a la declaración de la dependencia solo para evitar
este detalle de implementación.
Por este motivo, cuando una dependencia de un destino también es un valor de atributo de la macro que declaró el destino, verificamos la visibilidad de la dependencia con respecto a la ubicación de la macro en lugar de la ubicación del destino de consumo.
En este ejemplo, para validar si //pkg:foo_consumer puede ver
//pkg:foo_dependency, vemos que //pkg:foo_dependency también se pasó como una
entrada a la llamada a some_library dentro de my_macro y, en su lugar, verificamos la
visibilidad de la dependencia con respecto a la ubicación de esta llamada, //macro.
Este proceso se puede repetir de forma recursiva, siempre que un destino o una declaración de macro se encuentre dentro de otra macro simbólica que tome la etiqueta de la dependencia en uno de sus atributos de tipo de etiqueta.
Finalizadores
Los destinos declarados en un finalizador de reglas (una macro simbólica con finalizer = True),
además de ver los destinos que siguen las reglas de visibilidad de macros simbólicas habituales, pueden también ver todos los destinos que son visibles para el paquete de destino del finalizador.
En otras palabras, si migras una macro heredada basada en native.existing_rules() a
un finalizador, los destinos declarados por el finalizador aún podrán ver
sus dependencias anteriores.
Es posible definir destinos que un finalizador pueda inspeccionar con
native.existing_rules(), pero que no pueda usar como dependencias en el
sistema de visibilidad. Por ejemplo, si un destino definido por macro no es visible para su
propio paquete o para la definición de la macro del finalizador, y no se delega al
finalizador, este no puede ver ese destino. Sin embargo, ten en cuenta que una macro heredada basada en
native.existing_rules()tampoco podrá ver ese
destino.
Visibilidad de carga
La visibilidad de carga controla si se puede cargar un archivo .bzl desde otros
BUILD o .bzl archivos fuera del paquete actual.
De la misma manera que la visibilidad de destino protege el código fuente que está encapsulado
por los destinos, la visibilidad de carga protege la lógica de compilación que está encapsulada por los archivos .bzl. Por ejemplo, un autor de archivos BUILD podría querer factorizar algunas declaraciones de destino repetitivas
en una macro en un archivo .bzl. Sin la protección de
la visibilidad de carga, es posible que otros colaboradores reutilicen su macro en
el mismo espacio de trabajo, de modo que la modificación de la macro interrumpa las compilaciones de otros equipos.
Ten en cuenta que un archivo .bzl puede tener o no un destino de archivo fuente correspondiente.
Si lo hace, no hay garantía de que la visibilidad de carga y la visibilidad de destino
coincidan. Es decir, es posible que el mismo archivo BUILD pueda cargar el
.bzl pero no lo enumere en el srcs de un filegroup,
o viceversa. A veces, esto puede causar problemas para las reglas que desean consumir
.bzl archivos como código fuente, como para la generación de documentación o las pruebas.
Para la creación de prototipos, puedes inhabilitar la aplicación de la visibilidad de carga si configuras
--check_bzl_visibility=false. Al igual que con --check_visibility=false, esto no se debe hacer para el código enviado.
La visibilidad de carga está disponible a partir de Bazel 6.0.
Declarar la visibilidad de carga
Para configurar la visibilidad de carga de un .bzl archivo, llama a la
visibility() función desde el archivo.
El argumento para visibility() es una lista de especificaciones de paquetes, al igual que
el packages atributo 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 de destino, 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") a la parte superior de cualquier
archivo .bzl nuevo que no esté destinado específicamente para 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 carga
En esta sección, se describen sugerencias para administrar las declaraciones de visibilidad de carga.
Factorizar 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 distorsión accidental entre las visibilidades de los distintos archivos .bzl'
. También es más legible cuando la lista clients es grande.
Componer visibilidades
A veces, es posible que un archivo .bzl deba ser visible para una lista de entidades permitidas que esté
compuesta por varias listas de entidades permitidas más pequeñas. Esto es análogo a la forma en que un
package_group puede incorporar otros package_group a través de su
includes atributo.
Supongamos que das de baja una macro de uso generalizado. Quieres que solo sea visible solo 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)
Deduplicar con grupos de paquetes
A diferencia de la visibilidad de destino, no puedes definir una visibilidad de carga en términos de un
package_group. Si deseas reutilizar la misma lista de entidades permitidas para la visibilidad de destino
y la visibilidad de carga, es mejor mover la lista de especificaciones de paquetes
a un archivo .bzl, donde ambos tipos de declaraciones pueden hacer referencia a
ella. A partir del ejemplo de Factorizar 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 especificaciones de paquetes negativas.
Proteger símbolos individuales
No se puede cargar ningún símbolo de Starlark cuyo nombre comience con un guion bajo desde
otro archivo. Esto facilita la creación de símbolos privados, pero no te 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 sin guion bajo.
Por suerte, 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
bzl-visibility Buildifier lint
Hay 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 está debajo del superior de ese
directorio. Este lint es anterior a la función de visibilidad de carga y no es necesario en
los espacios de trabajo en los que .bzl archivos declaran visibilidades.