Visibilidad

Informar un problema Ver fuente

En esta página, se describen los dos sistemas de visibilidad de Bazel: visibilidad objetivo y visibilidad de 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 lugar de trabajo. También puedes usar la visibilidad cuando se da de baja una API pública para permitir los usuarios actuales y denegar 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.

Un A de destino es visible para un B de destino si están en el mismo paquete o si A otorga visibilidad al paquete de B. Por lo tanto, los paquetes son la unidad de nivel de detalle 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 por sí solo. Para obtener más detalles sobre los paquetes y subpaquetes, consulta Conceptos y terminología.

Para el prototipado, puedes inhabilitar la aplicación forzosa de la visibilidad objetivo configurando la marca --check_visibility=false. Esto no debe hacerse para el uso de producción en el código enviado.

La forma principal de controlar la visibilidad es con el atributo visibility en los objetivos de la regla. En esta sección, se describe el formato de este atributo y cómo determinar la visibilidad de un objetivo.

Especificaciones de visibilidad

Todas las orientaciones de reglas tienen un atributo visibility que toma una lista de etiquetas. Cada etiqueta tiene uno de los siguientes formatos: A 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. (puede no combinarse con ninguna otra especificación).

  • "//visibility:private": No otorga ningún acceso adicional. Solo los objetivos de este paquete pueden usarlo. (puede no combinarse con ninguna otra especificación).

  • "//foo/bar:__pkg__": Otorga acceso a //foo/bar (pero no a sus subpaquetes).

  • "//foo/bar:__subpackages__": Otorga acceso de //foo/bar y todos sus subpaquetes indirectos y directos.

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

Por ejemplo, si visibility tiene su visibility establecido en [":__subpackages__", "//tests:__pkg__"], podría usarlo cualquier destino que forme parte del árbol de fuentes de //some/package/..., así como los objetivos definidos en //tests/BUILD, pero no por objetivos definidos en //tests/integration/BUILD.//some/package:mytarget

Práctica recomendada: Si deseas que varios destinos sean visibles para el mismo conjunto de paquetes, usa 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 desincronizan.

Práctica recomendada: Cuando otorgues visibilidad al proyecto de otro equipo, prioriza __subpackages__ en lugar de __pkg__ para evitar la deserción innecesaria de la visibilidad a medida que el proyecto evoluciona y agrega subpaquetes nuevos.

Visibilidad del objetivo de la regla

La visibilidad de un objetivo de regla es:

  1. El valor de su atributo visibility, si se configuró.

  2. El valor del argumento default_visibility de la declaración package en el archivo BUILD del destino, si existe esa declaración

  3. //visibility:private.

Práctica recomendada: Evita configurar default_visibility como público. Puede ser conveniente para el prototipado o en bases de código pequeñas, pero el riesgo de crear objetivos públicos de forma involuntaria aumenta a medida que crece la base de código. Es mejor indicar de forma explícita 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 objetivo del archivo generado

Un destino de archivo generado tiene la misma visibilidad que el destino de la regla que lo genera.

Visibilidad objetivo del archivo fuente

Puedes establecer de forma explícita la visibilidad de un destino de archivo de origen llamando a exports_files. Cuando no se pasa ningún argumento visibility a exports_files, la visibilidad es pública. No se puede usar exports_files para anular la visibilidad de un archivo generado.

En el caso de los destinos del archivo de origen 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 está configurada, la visibilidad es privada.

  • De lo contrario, se aplica el comportamiento heredado: la visibilidad es la misma que la del archivo 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 cada vez que el destino de un archivo de origen necesite visibilidad que no sea privada.

Práctica recomendada: Cuando sea posible, es preferible exponer el destino de una regla en lugar de un archivo de origen. 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 objetivos 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 los parámetros de configuración

Históricamente, Bazel no aplicó la visibilidad a los destinos de 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 (sin importar el default_visibility a nivel de paquete).

  • --incompatible_config_setting_private_default_visibility hace que los config_setting que no especifiquen un visibility respeten el default_visibility del paquete y recurran a la visibilidad privada, al igual que cualquier otro objetivo de regla. Es una no-op si --incompatible_enforce_config_setting_visibility no está configurado.

Evita depender del comportamiento heredado. Cualquier config_setting que se vaya a usar fuera del paquete actual debe tener un visibility explícito si el paquete no especifica un default_visibility adecuado.

Visibilidad del objetivo del grupo de paquetes

Los destinos de package_group no tienen un atributo de visibility. Siempre son visibles de forma pública.

Visibilidad de las dependencias implícitas

Algunas reglas tienen dependencias implícitas: dependencias que no están detalladas 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 los objetivos de la regla en un destino ejecutable que represente un compilador de C++.

Se verifica la visibilidad de esa dependencia implícita con respecto al paquete que contiene el archivo .bzl en el que se define la regla (o 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 regla cc_library. Como resguardo, si la dependencia implícita no es visible en 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 en su lugar.

Visibilidad de carga

La visibilidad de la carga controla si un archivo .bzl se puede cargar desde otros archivos BUILD o .bzl fuera del paquete actual.

De la misma manera que la visibilidad del objetivo protege el código fuente que está encapsulado por los objetivos, la visibilidad de carga protege la lógica de compilación que está encapsulada por archivos .bzl. Por ejemplo, es posible que el autor de un archivo BUILD quiera incluir algunas definiciones de destino repetitivas en una macro en un archivo .bzl. Sin la protección de la visibilidad de carga, es posible que otros colaboradores del mismo lugar de trabajo vuelvan a usar la macro, por lo que la modificación de la macro interrumpe las compilaciones de otros equipos.

Ten en cuenta que un archivo .bzl puede tener o no un destino de archivo de origen correspondiente. Si es así, no hay garantía de que la visibilidad de la carga y la visibilidad de destino coincidan. Es decir, el mismo archivo BUILD podría cargar el archivo .bzl, pero no incluirlo en la lista de srcs de un filegroup, o viceversa. A veces, esto puede causar problemas para las reglas que desean consumir archivos .bzl como código fuente, por ejemplo, para la generación o las pruebas de documentación.

Para el prototipado, puedes inhabilitar la aplicación de visibilidad de la 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 la carga de un archivo .bzl, llama a la función visibility() desde el archivo. El argumento de 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, idealmente, inmediatamente después de las sentencias load().

A diferencia de la visibilidad objetivo, la visibilidad de carga predeterminada siempre es pública. Los archivos que no llaman a visibility() siempre se pueden cargar desde cualquier parte del lugar de trabajo. Es una buena idea 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 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 las especificaciones de sus 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 el sesgo 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 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 darás de baja una macro muy usada. Quieres que solo sea visible para los usuarios existentes y 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)

Anula la duplicación con grupos de paquetes

A diferencia de la visibilidad objetivo, no puedes definir la visibilidad de una carga en términos de un package_group. Si deseas volver a usar la misma lista de entidades permitidas para la visibilidad objetivo y la visibilidad de carga, es mejor mover la lista de especificaciones de paquetes a un archivo .bzl, en el que ambos tipos de declaraciones puedan hacer referencia a ella. Para continuar con el ejemplo anterior sobre Factorización de visibilidades, puedes 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 paquete negativas.

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 compartirlos con un conjunto limitado de archivos de confianza. Por otro lado, la visibilidad de la carga te permite controlar qué otros paquetes pueden ver tu .bzl file, pero no te permite evitar que se cargue ningún símbolo no guionado.

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

Lint del compilador de bzl-visibility

Existe una Linta del compilador 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 elemento superior de ese directorio. Esta lint es anterior a la función de visibilidad de carga y no es necesaria en lugares de trabajo en los que los archivos .bzl declaran visibilidades.