En esta página, se describen los dos sistemas de visibilidad de Bazel: visibilidad del objetivo y 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 a aplicar la estructura a medida que crece tu espacio de trabajo. También puedes usar la visibilidad cuando descatalogas una API pública para permitir a los usuarios actuales y rechazar a los nuevos.
Visibilidad del objetivo
La visibilidad del objetivo controla quién puede depender de tu objetivo, es decir, quién puede usar la etiqueta de tu objetivo dentro de un atributo, como deps
. No se compilará un destino durante la fase de análisis si incumple la visibilidad de una de sus dependencias.
Por lo general, un objetivo A
es visible para un objetivo B
si se encuentran 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 solo "paquete". Consulta más abajo para obtener más información sobre las macros simbólicas.
Para especificar la visibilidad, se enumeran los paquetes permitidos. Permitir un paquete no significa que sus subpaquetes también estén permitidos. Para obtener más detalles sobre los paquetes y subpaquetes, consulta Conceptos y terminología.
Para crear prototipos, puedes inhabilitar la aplicación forzosa de la visibilidad del objetivo si configuras 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
de una regla.
En las siguientes sub secciones, 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 las 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."//visibility:private"
: No otorga ningún acceso adicional. Solo los objetivos del paquete de esta ubicación pueden usar este objetivo."//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 de lapackage_group
determinada.- Los grupos de paquetes usan una sintaxis diferente para especificar paquetes. Dentro de un grupo de paquetes, los formularios
"//foo/bar:__pkg__"
y"//foo/bar:__subpackages__"
se reemplazan, respectivamente, por"//foo/bar"
y"//foo/bar/..."
. 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, los formularios
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/...
, así como los destinos declarados en //tests/BUILD
, podría 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.
Práctica recomendada: Cuando otorgues visibilidad al proyecto de otro equipo, prefiere __subpackages__
en lugar de __pkg__
para evitar una deserción de visibilidad innecesaria a medida que ese proyecto evoluciona y agrega nuevos subpaquetes.
Visibilidad del objetivo de la regla
La visibilidad de un objetivo de regla se determina tomando su atributo visibility
(o un valor predeterminado adecuado si no se proporciona) y agregando la ubicación donde se declaró el objetivo. 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 establecer default_visibility
como público. Puede ser útil para crear prototipos o en bases de código pequeñas, pero el riesgo de crear objetivos públicos por error aumenta a medida que crece la base de código. Es mejor ser
expresivo sobre qué destinos forman parte de la interfaz pública de un paquete.
Visibilidad del objetivo 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 del objetivo del archivo de origen
Los destinos de archivos de origen se pueden declarar de forma explícita con exports_files
o crearse 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 hace 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 parámetro visibility
para esa función. Si no se proporciona este parámetro, la visibilidad es pública.
En el caso de 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
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 de origen necesite visibilidad no privada.
Práctica recomendada: Cuando sea posible, prefiere exponer un objetivo de regla en lugar de un archivo fuente. Por ejemplo, en lugar de llamar a exports_files
en un archivo .java
, une el archivo en un destino java_library
no privado. Por lo general, los destinos de las reglas solo deben hacer referencia directamente a los archivos de origen 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 forzó la visibilidad de 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
que no especifican unvisibility
respeten eldefault_visibility
del paquete y recurran a la visibilidad privada, al igual que cualquier otro objetivo de regla. No realiza ninguna acción si no se establece--incompatible_enforce_config_setting_visibility
.
Evita depender del comportamiento heredado. Cualquier config_setting
que se use 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 objetivos package_group
no tienen un atributo visibility
. Siempre están visibles para el público.
Visibilidad de las dependencias implícitas
Algunas reglas tienen dependencias implícitas, que son dependencias que no se escriben 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 en relación con el 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 resida en el mismo paquete que la definición de la regla cc_library
. Como resguardo, si la dependencia implícita no es visible desde la definición, se verifica con respecto al objetivo cc_library
.
Si deseas restringir el uso de una regla a ciertos paquetes, usa la visibilidad de carga.
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 se encuentra 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 sentencia 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.
Se usa el mismo sistema para determinar qué ubicación se debe verificar en función de la visibilidad de una dependencia determinada. Si el destino de consumo se creó dentro de una macro, veremos la definición de la macro más interna en lugar del paquete en el que se encuentra el destino de consumo.
Esto significa que todas las macros cuyo código se define en el mismo paquete son
“amigos” automáticamente 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 creen instancias de las macros. Del mismo modo, pueden ver y ser vistos por los objetivos declarados directamente en //lib/BUILD
y sus macros heredadas. Por el contrario, los destinos que se encuentran en el mismo paquete no necesariamente pueden verse entre sí si al menos uno de ellos es creado por 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 a la que se llamó a la macro. La forma estándar en que una macro exporta
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 esté 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 al llamador en cada nivel, sin exponer ninguno de los detalles de implementación de las macros.
Delegación de 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 objetivo //pkg:foo_dependency
no tiene visibility
especificado, por lo que solo es visible dentro de //macro
, lo que funciona bien para el objetivo de consumo. Ahora bien, ¿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 en función de la ubicación de la macro en lugar de la ubicación del destino consumidor.
En este ejemplo, para validar si //pkg:foo_consumer
puede ver //pkg:foo_dependency
, vemos que //pkg:foo_dependency
también se pasó como entrada a la llamada a some_library
dentro de my_macro
y, en su lugar, verificamos la visibilidad de la dependencia en función de la ubicación de esta llamada, //macro
.
Este proceso se puede repetir de forma recursiva, siempre que una declaración de destino o macro esté dentro de otra macro simbólica que tome la etiqueta de la dependencia en uno de sus atributos con el tipo de etiqueta.
Carga la visibilidad
La visibilidad de carga controla si se puede cargar un archivo .bzl
desde otros archivos BUILD
o .bzl
fuera del paquete actual.
De la misma manera que la visibilidad del destino protege el código fuente que encapsulan los destinos, la visibilidad de carga protege la lógica de compilación que encapsulan los archivos .bzl
. Por ejemplo, el autor de un archivo BUILD
podría querer considerar 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 vuelvan a usar su macro en el mismo espacio de trabajo, de modo que modificarla interrumpa 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 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. Esto puede causar problemas en las reglas que desean consumir
archivos .bzl
como código fuente, por ejemplo, para la generación o prueba de documentación.
Para crear prototipos, puedes inhabilitar la aplicación forzosa de la visibilidad de carga configurando --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.
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 dentro del archivo.
El argumento de visibility()
es una lista de especificaciones del paquete, 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 preferencia, inmediatamente después de las sentencias 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 recomendable agregar visibility("private")
a la parte superior de cualquier archivo .bzl
nuevo que no esté diseñado 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 la carga
En esta sección, se describen las sugerencias para administrar las declaraciones de visibilidad de carga.
Factorización de visibilidades
Cuando varios archivos .bzl
deben tener la misma visibilidad, puede ser útil tener en cuenta 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 sesgos accidentales entre las diferentes visibilidades de los archivos .bzl
. También es más legible cuando la lista clients
es grande.
Cómo 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 similar a la forma en que un package_group
puede incorporar otros package_group
a través de su atributo includes
.
Supongamos que darás de baja una macro muy utilizada. Quieres que solo sea visible para los usuarios existentes y para los paquetes que pertenecen a tu propio equipo. Puedes 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 anular la duplicación con grupos de paquetes
A diferencia de la visibilidad del objetivo, no puedes definir una visibilidad de carga en términos de un package_group
. Si deseas volver a usar la misma lista de entidades permitidas para la visibilidad de destino y la visibilidad de carga, lo mejor es mover la lista de especificaciones del paquete a un archivo .bzl, en el que ambos tipos de declaraciones puedan hacer referencia a él. 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
Cualquier símbolo de Starlark cuyo nombre comience con un guion bajo no se puede cargar 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 brinda control sobre lo que otros paquetes pueden ver en tu .bzl file
, pero no te permite evitar que se cargue ningún símbolo que no tenga guiones bajos.
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
bzl-visibility lint de Buildifier
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 elemento 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 los archivos .bzl
declaran visibilidades.