Visibilidade

Informar um problema Acessar fonte

Nesta página, abordamos os dois sistemas de visibilidade do Bazel: visibilidade do destino e visibilidade de carga.

Os dois tipos de visibilidade ajudam outros desenvolvedores a distinguir entre a API pública da biblioteca e os detalhes de implementação dela, além de ajudar a aplicar a estrutura à medida que o espaço de trabalho cresce. Também é possível usar a visibilidade ao suspender o uso de uma API pública para permitir usuários atuais e negar novos.

Visibilidade desejada

A visibilidade do destino controla quem pode depender do seu destino, ou seja, quem pode usar o rótulo do destino dentro de um atributo como deps.

Um A de destino ficará visível para um B de destino se estiverem no mesmo pacote ou se A conceder visibilidade ao pacote da B. Assim, os pacotes são a unidade de granularidade para decidir se o acesso será permitido ou não. Se B depender de A, mas A não estiver visível para B, qualquer tentativa de criar B falhará durante a análise.

Observe que conceder visibilidade a um pacote não garante, por si só, visibilidade aos subpacotes dele. Para mais detalhes sobre pacotes e subpacotes, consulte Conceitos e terminologia.

Para prototipagem, é possível desativar a aplicação da visibilidade do destino definindo a flag --check_visibility=false. Isso não deve ser feito para uso de produção no código enviado.

A principal maneira de controlar a visibilidade é com o atributo visibility nas metas de regras. Esta seção descreve o formato desse atributo e como determinar a visibilidade de um destino.

Especificações de visibilidade

Todas as segmentações de regras têm um atributo visibility que usa uma lista de rótulos. Cada rótulo tem um dos formatos a seguir. Com exceção da última forma, esses são apenas marcadores de posição sintáticos que não correspondem a nenhum destino real.

  • "//visibility:public": concede acesso a todos os pacotes. Não pode ser combinada com nenhuma outra especificação.

  • "//visibility:private": não concede nenhum acesso extra. Somente os destinos neste pacote podem usar esse destino. Não pode ser combinada com nenhuma outra especificação.

  • "//foo/bar:__pkg__": concede acesso a //foo/bar, mas não aos subpacotes dele.

  • "//foo/bar:__subpackages__": concede acesso a //foo/bar e a todos os subpacotes diretos e indiretos dela.

  • "//some_pkg:my_package_group": concede acesso a todos os pacotes que fazem parte do package_group especificado.

    • Os grupos de pacotes usam uma sintaxe diferente para especificar pacotes. Dentro de um grupo de pacotes, os formulários "//foo/bar:__pkg__" e "//foo/bar:__subpackages__" são substituídos, respectivamente, por "//foo/bar" e "//foo/bar/...". Da mesma forma, "//visibility:public" e "//visibility:private" são apenas "public" e "private".

Por exemplo, se //some/package:mytarget tiver o visibility definido como [":__subpackages__", "//tests:__pkg__"], ele poderá ser usado por qualquer destino que faça parte da árvore de origem //some/package/..., bem como destinos definidos em //tests/BUILD, mas não por destinos definidos em //tests/integration/BUILD.

Prática recomendada:para tornar vários destinos visíveis para o mesmo conjunto de pacotes, use um package_group em vez de repetir a lista em cada atributo visibility de cada destino. Isso aumenta a legibilidade e evita que as listas fiquem fora de sincronia.

Prática recomendada:ao conceder visibilidade ao projeto de outra equipe, prefira __subpackages__ em vez de __pkg__ para evitar desistências desnecessárias de visibilidade à medida que o projeto evolui e adiciona novos subpacotes.

Visibilidade do destino da regra

A visibilidade de uma segmentação de regra é:

  1. O valor do atributo visibility, se definido. Caso contrário,

  2. O valor do argumento default_visibility da instrução package no arquivo BUILD do destino, se essa declaração existir; ou outros

  3. //visibility:private.

Prática recomendada:evite definir default_visibility como público. Isso pode ser conveniente para prototipagem ou em pequenas bases de código, mas o risco de criar inadvertidamente alvos públicos aumenta à medida que a base do código cresce. É melhor ser explícito sobre quais destinos fazem parte da interface pública de um pacote.

Exemplo

Arquivo //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"],
)

Arquivo //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",
    ],
)

Visibilidade do destino de arquivo gerada

Um destino de arquivo gerado tem a mesma visibilidade que o destino da regra que o gera.

Visibilidade do destino do arquivo de origem

É possível definir explicitamente a visibilidade de um destino de arquivo de origem chamando exports_files. Quando nenhum argumento visibility é transmitido para exports_files, a visibilidade fica pública. Não é possível usar exports_files para substituir a visibilidade de um arquivo gerado.

Para destinos de arquivos de origem que não aparecem em uma chamada para exports_files, a visibilidade depende do valor da flag --incompatible_no_implicit_file_export:

  • Se a flag estiver definida, a visibilidade será particular.

  • Caso contrário, o comportamento legado será aplicado: a visibilidade será igual à default_visibility do arquivo BUILD ou será particular se uma visibilidade padrão não for especificada.

Evite depender do comportamento legado. Sempre escreva uma declaração exports_files sempre que um destino de arquivo de origem precisar de visibilidade não particular.

Prática recomendada:quando possível, prefira expor um destino de regra em vez de um arquivo de origem. Por exemplo, em vez de chamar exports_files em um arquivo .java, una o arquivo em um destino java_library não particular. Geralmente, os destinos de regra precisam referenciar diretamente apenas arquivos de origem que residem no mesmo pacote.

Exemplo

Arquivo //frobber/data/BUILD:

exports_files(["readme.txt"])

Arquivo //frobber/bin/BUILD:

cc_binary(
  name = "my-program",
  data = ["//frobber/data:readme.txt"],
)

Visibilidade da configuração de configuração

Historicamente, o Bazel não aplicou a visibilidade aos destinos config_setting que são referenciados nas chaves de uma select(). Há duas sinalizações para remover esse comportamento legado:

  • --incompatible_enforce_config_setting_visibility ativa a verificação de visibilidade para esses destinos. Para ajudar na migração, ela também faz com que todos os config_setting que não especifiquem um visibility sejam considerados públicos, independente do default_visibility no nível do pacote.

  • --incompatible_config_setting_private_default_visibility faz com que config_settings que não especificam um visibility respeitem a default_visibility do pacote e façam a substituição na visibilidade particular, assim como qualquer outro destino de regra. Será um ambiente autônomo se --incompatible_enforce_config_setting_visibility não estiver definido.

Evite depender do comportamento legado. Qualquer config_setting destinado ao uso fora do pacote atual precisará ter um visibility explícito, se o pacote ainda não especificar um default_visibility adequado.

Visibilidade do destino do grupo de pacotes

Os destinos package_group não têm um atributo visibility. Eles sempre são visíveis publicamente.

Visibilidade de dependências implícitas

Algumas regras têm dependências implícitas, dependências que não são escritas em um arquivo BUILD, mas são inerentes a todas as instâncias dessa regra. Por exemplo, uma regra cc_library pode criar uma dependência implícita de cada um dos destinos de regra para um destino executável que representa um compilador C++.

A visibilidade dessa dependência implícita é verificada em relação ao pacote que contém o arquivo .bzl em que a regra (ou aspecto) é definido. No nosso exemplo, o compilador C++ pode ser particular, desde que esteja no mesmo pacote que a definição da regra cc_library. Como substituto, se a dependência implícita não estiver visível na definição, ela será verificada em relação ao destino cc_library.

Para restringir o uso de uma regra a determinados pacotes, use a visibilidade de carregamento.

Carregar visibilidade

A visibilidade de carga controla se um arquivo .bzl pode ser carregado de outros arquivos BUILD ou .bzl fora do pacote atual.

Da mesma forma que a visibilidade de destino protege o código-fonte encapsulado por destinos, a visibilidade de carregamento protege a lógica do build encapsulada por arquivos .bzl. Por exemplo, o autor de um arquivo BUILD pode querer fatorar algumas definições de destinos repetitivos em uma macro em um arquivo .bzl. Sem a proteção da visibilidade de carga, eles podem encontrar a macro reutilizada por outros colaboradores no mesmo espaço de trabalho, de modo que a modificação da macro corrompa os builds de outras equipes.

Observe que um arquivo .bzl pode ou não ter um destino de arquivo de origem correspondente. Em caso afirmativo, não há garantia de que a visibilidade da carga e a visibilidade de destino coincidam. Ou seja, o mesmo arquivo BUILD pode carregar o arquivo .bzl, mas não o listar na srcs de uma filegroup ou vice-versa. Isso às vezes pode causar problemas para regras que querem consumir arquivos .bzl como código-fonte, por exemplo, para geração ou teste de documentação.

Para prototipagem, é possível desativar a aplicação da visibilidade de carregamento definindo --check_bzl_visibility=false. Assim como em --check_visibility=false, isso não deve ser feito para o código enviado.

A visibilidade de carga está disponível a partir do Bazel 6.0.

Como declarar a visibilidade de carga

Para definir a visibilidade de carregamento de um arquivo .bzl, chame a função visibility() no arquivo. O argumento para visibility() é uma lista de especificações de pacote, assim como o atributo packages de package_group. No entanto, visibility() não aceita especificações de pacote negativas.

A chamada para visibility() precisa ocorrer apenas uma vez por arquivo, no nível superior (não dentro de uma função) e, de preferência, logo após as instruções load().

Diferente da visibilidade do destino, a visibilidade de carregamento padrão é sempre pública. Arquivos que não chamam visibility() sempre podem ser carregados de qualquer lugar do espaço de trabalho. É recomendável adicionar visibility("private") à parte de cima de qualquer novo arquivo .bzl que não se destine especificamente para uso fora do pacote.

Exemplo

# //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áticas de visibilidade de carregamento

Nesta seção, descrevemos dicas para gerenciar declarações de visibilidade de carga.

Visibilidades de fatoração

Quando vários arquivos .bzl precisam ter a mesma visibilidade, pode ser útil fatorar as especificações do pacote em uma lista comum. Exemplo:

# //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)

...

Isso ajuda a evitar desvios acidentais entre as visibilidades dos vários arquivos .bzl. Ele também fica mais legível quando a lista de clients é grande.

Visibilidades de composição

Às vezes, um arquivo .bzl pode precisar estar visível para uma lista de permissões composta de várias listas menores. Isso é análogo a como um package_group pode incorporar outros package_groups usando o atributo includes.

Suponha que você esteja descontinuando uma macro amplamente usada. Você quer que ele fique visível apenas para usuários existentes e para os pacotes de sua própria equipe. Você pode escrever:

# //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)

Como eliminar a duplicação com grupos de pacotes

Ao contrário da visibilidade do destino, não é possível definir uma visibilidade de carregamento em termos de uma package_group. Se você quiser reutilizar a mesma lista de permissões para visibilidade de destino e de carregamento, é melhor mover a lista de especificações do pacote para um arquivo .bzl, em que ambos os tipos de declarações possam se referir a ela. Com base no exemplo em Visibilidades de fatoração acima, é possível escrever:

# //mylib/BUILD

load(":internal_defs", "clients")

package_group(
    name = "my_pkg_grp",
    packages = clients,
)

Isso só vai funcionar se a lista não tiver especificações de pacote negativas.

Proteção de símbolos individuais

Qualquer símbolo Starlark que tenha um nome iniciado por um sublinhado não pode ser carregado de outro arquivo. Isso facilita a criação de símbolos privados, mas não permite que você compartilhe esses símbolos com um conjunto limitado de arquivos confiáveis. Por outro lado, a visibilidade de carregamento oferece controle sobre o que outros pacotes podem ver seu .bzl file, mas não permite que você evite que qualquer símbolo não sublinhado seja carregado.

Felizmente, você pode combinar esses dois recursos para ter um controle refinado.

# //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-visibilidade do build do lint do build

Há um lint do Buildifier que vai fornecer um aviso se os usuários carregarem um arquivo de um diretório chamado internal ou private, quando o arquivo do usuário não estiver abaixo do pai desse diretório. Esse lint antecede o recurso de visibilidade de carregamento e é desnecessário em espaços de trabalho em que arquivos .bzl declaram visibilidades.