Visibilidad

Informar un problema Ver código fuente Nocturno · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

El control de visibilidad del objetivo determina quién puede depender de tu objetivo, es decir, quién puede usar la etiqueta de tu objetivo dentro de un atributo como deps. Un destino no se compilará durante la fase de análisis si incumple la visibilidad de una de sus dependencias.

En general, un objetivo A es visible para un objetivo B si ambos 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 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 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 de una regla. En las siguientes subsecciones, se describen el formato del atributo, cómo aplicarlo a varios tipos de objetivos 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 objetivo real.

  • "//visibility:public": Otorga acceso a todos los paquetes.

  • "//visibility:private": No otorga ningún acceso adicional; solo los destinos del 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/bar y a todos sus subpaquetes directos e indirectos.

  • "//some_pkg:my_package_group": Otorga acceso a todos los paquetes que forman parte del package_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".

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/... 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 desincronicen.

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 nuevos subpaquetes.

Visibilidad del destino de la regla

La visibilidad de un destino de 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 que se declaran 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 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.

Visibilidad del 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 del destino del archivo fuente

Los destinos de archivos fuente se pueden declarar de forma explícita con exports_files o 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 parámetro visibility de esa función. Si no se proporciona este parámetro, la visibilidad será 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 de default_visibility del archivo BUILD 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 cualquier config_setting que no especifique un visibility se considere público (independientemente del default_visibility a nivel del paquete).

  • --incompatible_config_setting_private_default_visibility hace que los config_settings que no especifican un visibility respeten el default_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.

Si deseas restringir el uso de una regla a ciertos paquetes, usa la visibilidad de carga.

Macros simbólicas y de visibilidad

En esta sección, se describe cómo el sistema de visibilidad interactúa 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, es decir, 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.

El mismo sistema se usa para determinar qué ubicación se debe verificar en relación con 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 realmente las macros. Del mismo modo, pueden ver los objetivos declarados directamente en //lib/BUILD y sus macros heredadas, y los objetivos pueden verlos. Por el contrario, los destinos que se encuentran en el mismo paquete no necesariamente pueden 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 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 omitan 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, lo que vuelve 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.

Cómo delegar privilegios a una submacro

El modelo de visibilidad tiene una función especial que permite 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 especificado ningún visibility, 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, en su lugar, 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 incumple 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 solucionar 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 que la consume.

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 cambio, verificamos la visibilidad de la dependencia en la ubicación de esta llamada, //macro.

Este proceso se puede repetir de forma recursiva, siempre y cuando una declaración de destino o macro se encuentre dentro de otra macro simbólica que tome la etiqueta de la dependencia en uno de sus atributos con tipo de etiqueta.

Finalizadores

Los destinos declarados en un finalizador de regla (una macro simbólica con finalizer = True), además de ver los destinos según las reglas habituales de visibilidad de la macro simbólica, también pueden ver todos los destinos que son visibles para el paquete del destino del finalizador.

En otras palabras, si migras una macro heredada basada en native.existing_rules() a un finalizador, los objetivos declarados por el finalizador aún podrán ver sus dependencias anteriores.

Es posible definir destinos que un finalizador puede introspeccionar con native.existing_rules(), pero que no puede usar como dependencias en el sistema de visibilidad. Por ejemplo, si un destino definido por una macro no es visible para su propio paquete o para la definición de la macro del finalizador, y no se delega en el finalizador, este no podrá ver ese destino. Sin embargo, ten en cuenta que una macro heredada basada en native.existing_rules() tampoco podrá ver ese objetivo.

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 declaraciones 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.