Esta página aborda os dois sistemas de visibilidade do Bazel: visibilidade de destino e visibilidade de carregamento.
Ambos os tipos de visibilidade ajudam outros desenvolvedores a distinguir entre a API pública da sua 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, mas negar novos.
Visibilidade de destino
A visibilidade de destino controla quem pode depender do seu destino, ou seja, quem pode
usar o rótulo do destino em um atributo, como deps. Um destino não será criado
durante a fase de análise se
violar a visibilidade de uma das dependências.
Geralmente, um destino A fica visível para um destino B se eles estiverem no mesmo
local ou se A conceder visibilidade ao local de B. Na ausência de
macros simbólicas, o termo "local" pode ser simplificado
para apenas "pacote". Consulte abaixo mais informações sobre macros simbólicas.
A visibilidade é especificada listando os pacotes permitidos. Permitir um pacote não significa necessariamente que os subpacotes dele também sejam permitidos. Para mais detalhes sobre pacotes e subpacotes, consulte Conceitos e terminologia.
Para prototipagem, é possível desativar a aplicação da visibilidade de 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 de uma regra.
As subseções a seguir descrevem o formato do atributo, como aplicá-lo a
vários tipos de destinos e a interação entre o sistema de visibilidade e
macros simbólicas.
Especificações de visibilidade
Todos os destinos de regra têm um atributo visibility que recebe uma lista de rótulos. Cada
rótulo tem uma das seguintes formas. Com exceção da última forma, esses
são apenas marcadores sintáticos que não correspondem a nenhum destino real.
"//visibility:public": concede acesso a todos os pacotes."//visibility:private": não concede nenhum acesso adicional. Somente os destinos no pacote desse local podem usar esse destino."//foo/bar:__pkg__": concede acesso a//foo/bar, mas não aos subpacotes dele."//foo/bar:__subpackages__": concede acesso a//foo/bare a todos os subpacotes diretos e indiretos dele."//some_pkg:my_package_group": concede acesso a todos os pacotes que fazem parte dopackage_groupespecificado.- Os grupos de pacotes usam uma
sintaxe diferente para
especificar pacotes. Em um grupo de pacotes, as formas
"//foo/bar:__pkg__"e"//foo/bar:__subpackages__"são substituídas por"//foo/bar"e"//foo/bar/...", respectivamente. Da mesma forma,"//visibility:public"e"//visibility:private"são apenas"public"e"private".
- Os grupos de pacotes usam uma
sintaxe diferente para
especificar pacotes. Em um grupo de pacotes, as formas
Por exemplo, se //some/package:mytarget tiver a visibility definida como
[":__subpackages__", "//tests:__pkg__"], ela poderá ser usada por qualquer destino
que faça parte da árvore de origem //some/package/..., bem como por destinos
declarados 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 no atributo visibility de cada destino. Isso aumenta a legibilidade e evita que as
listas fiquem dessincronizadas.
Prática recomendada: ao conceder visibilidade ao projeto de outra equipe, prefira
__subpackages__ em vez de __pkg__ para evitar mudanças desnecessárias de visibilidade à medida que o
projeto evolui e adiciona novos subpacotes.
Visibilidade do destino da regra
A visibilidade de um destino de regra é determinada usando o atributo visibility ou um padrão adequado, se não for fornecido, e anexando o local em que o
destino foi declarado. Para destinos não declarados em uma macro simbólica, se o
pacote especificar um default_visibility,
esse padrão será usado. Para todos os outros pacotes e para destinos declarados em uma
macro simbólica, o padrão é apenas ["//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ática recomendada:evite definir default_visibility como pública. Isso pode ser
conveniente para prototipagem ou em bases de código pequenas, mas o risco de criar destinos públicos inadvertidamente
aumenta à medida que a base de código cresce. É melhor ser
explícito sobre quais destinos fazem parte da interface pública de um pacote.
Visibilidade do destino do arquivo gerado
Um destino de arquivo gerado tem a mesma visibilidade do destino da regra que o gera.
# //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",
]
...
)
Visibilidade do destino do arquivo de origem
Os destinos de arquivo de origem podem ser declarados explicitamente usando
exports_files ou criados implicitamente
referindo-se ao nome do arquivo em um atributo de rótulo de uma regra (fora de uma
macro simbólica). Assim como com os destinos de regra, o local da chamada para
exports_files, ou o arquivo BUILD que se referiu ao arquivo de entrada, é sempre
anexado automaticamente à visibilidade do arquivo.
Os arquivos declarados por exports_files podem ter a visibilidade definida pelo
visibility parâmetro para essa função. Se esse parâmetro não for fornecido, a visibilidade será pública.
Para arquivos 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 for verdadeira, a visibilidade será privada.
Caso contrário, o comportamento legado será aplicado: a visibilidade será a mesma que a
BUILDdo arquivodefault_visibility, ou privada se uma visibilidade padrão não for especificada.
Evite depender do comportamento legado. Sempre escreva uma exports_files
declaração quando um destino de arquivo de origem precisar de visibilidade não privada.
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,
encapsule o arquivo em um destino java_library não privado. Geralmente, os destinos de regra
só devem referenciar diretamente arquivos de origem que estejam 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
Historicamente, o Bazel não aplicou a visibilidade para
config_setting destinos que são
referenciados nas chaves de um select(). Há duas flags para remover esse comportamento legado:
--incompatible_enforce_config_setting_visibilityativa a verificação de visibilidade para esses destinos. Para ajudar na migração, ela também faz com que qualquerconfig_settingque não especifique umavisibilityseja considerada pública (independentemente dadefault_visibilityno nível do pacote).--incompatible_config_setting_private_default_visibilityfaz com queconfig_settings que não especificam umavisibilityrespeitem adefault_visibilitydo pacote e voltem à visibilidade privada, assim como qualquer outro destino de regra. Ela não faz nada se--incompatible_enforce_config_setting_visibilitynão estiver definida.
Evite depender do comportamento legado. Qualquer config_setting que seja usado fora do pacote atual precisa ter uma visibility explícita, se o pacote ainda não especificar uma default_visibility adequada.
Visibilidade do destino do grupo de pacotes
Os destinos package_group não têm um atributo visibility. Eles são sempre
visíveis publicamente.
Visibilidade de dependências implícitas
Algumas regras têm dependências implícitas —
dependências que não são especificadas em um arquivo BUILD mas são inerentes a
cada instância 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 de uma dependência implícita é verificada em relação ao
pacote que contém o .bzl arquivo em que a regra (ou aspecto) está definida. No
nosso exemplo, o compilador C++ pode ser privado, desde que esteja no mesmo
pacote que a definição da regra cc_library. Como alternativa, se a
dependência implícita não estiver visível na definição, ela será verificada em
relação ao destino cc_library.
Se você quiser restringir o uso de uma regra a determinados pacotes, use a visibilidade de carregamento em vez disso.
Visibilidade e macros simbólicas
Esta seção descreve como o sistema de visibilidade interage com macros simbólicas.
Locais em macros simbólicas
Um detalhe importante do sistema de visibilidade é como determinamos o local de uma
declaração. Para destinos que não são declarados em uma macro simbólica, o local
é apenas o pacote em que o destino está, ou seja, o pacote do arquivo BUILD.
No entanto, para destinos criados em uma macro simbólica, o local é o pacote
que contém o .bzl arquivo em que a definição da macro (a
my_macro = macro(...) instrução) aparece. Quando um destino é criado dentro
de vários destinos aninhados, é sempre a definição da macro simbólica mais interna
que é usada.
O mesmo sistema é usado para determinar qual local verificar em relação à visibilidade de uma determinada dependência. Se o destino de consumo foi criado dentro de uma macro, nós analisamos a definição da macro mais interna em vez do pacote em que o destino de consumo está.
Isso significa que todas as macros cujo código é definido no mesmo pacote são
automaticamente "amigas" umas das outras. Qualquer destino criado diretamente por uma macro
definida em //lib:defs.bzl pode ser visto em qualquer outra macro definida em //lib,
independentemente dos pacotes em que as macros são instanciadas. Da mesma forma,
eles podem ver e ser vistos por destinos declarados diretamente em //lib/BUILD e
nas macros legadas. Por outro lado, os destinos que estão no mesmo pacote não podem
necessariamente se ver se pelo menos um deles for criado por uma macro
simbólica.
Na função de implementação de uma macro simbólica, o parâmetro visibility
tem o valor efetivo do atributo visibility da macro após anexar
o local em que a macro foi chamada. A maneira padrão de uma macro exportar
um de seus destinos para o autor da chamada é encaminhar esse valor para a declaração do destino, como em some_rule(..., visibility = visibility). Os destinos que omitem
esse atributo não ficam visíveis para o autor da chamada da macro, a menos que o autor da chamada
esteja no mesmo pacote que a definição da macro. Esse comportamento
é composto, no sentido de que uma cadeia de chamadas aninhadas para submacros pode transmitir
visibility = visibility, reexportando os destinos exportados da macro interna para
o autor da chamada em cada nível, sem expor nenhum dos detalhes de implementação das macros.
Como delegar privilégios a uma submacro
O modelo de visibilidade tem um recurso especial para permitir que uma macro delegue as permissões a uma submacro. Isso é importante para fatorar e compor macros.
Suponha que você tenha uma macro my_macro que cria uma aresta de dependência usando uma regra
some_library de outro pacote:
# //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", ...)
O destino //pkg:foo_dependency não tem visibility especificada, então ele só fica
visível em //macro, o que funciona bem para o destino de consumo. Agora, o que
acontece se o autor de //lib refatorar some_library para ser
implementada usando uma 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, ...)
Com essa mudança, o local de //pkg:foo_consumer agora é //lib em vez de
//macro, então o uso de //pkg:foo_dependency viola a
visibilidade da dependência. Não é possível esperar que o autor de my_macro transmita
visibility = ["//lib"] para a declaração da dependência apenas para contornar esse detalhe de implementação.
Por esse motivo, quando uma dependência de um destino também é um valor de atributo da macro que declarou o destino, verificamos a visibilidade da dependência em relação ao local da macro em vez do local do destino de consumo.
Neste exemplo, para validar se //pkg:foo_consumer pode ver
//pkg:foo_dependency, vemos que //pkg:foo_dependency também foi transmitido como uma
entrada para a chamada para some_library dentro de my_macro e, em vez disso, verificamos a
visibilidade da dependência em relação ao local dessa chamada, //macro.
Esse processo pode se repetir recursivamente, desde que um destino ou uma declaração de macro esteja dentro de outra macro simbólica que receba o rótulo da dependência em um dos seus atributos do tipo de rótulo.
Finalizadores
Os destinos declarados em um finalizador de regra (uma macro simbólica com finalizer = True),
além de ver os destinos seguindo as regras de visibilidade de macro simbólica usuais, podem também ver todos os destinos visíveis para o pacote de destino do finalizador.
Em outras palavras, se você migrar uma macro legada baseada em native.existing_rules() para
um finalizador, os destinos declarados pelo finalizador ainda poderão ver
as dependências antigas.
É possível definir destinos que um finalizador pode inspecionar usando
native.existing_rules(), mas que não podem ser usados como dependências no
sistema de visibilidade. Por exemplo, se um destino definido por macro não estiver visível para o
próprio pacote ou para a definição da macro finalizadora e não for delegado ao
finalizador, o finalizador não poderá ver esse destino. No entanto, uma macro legada baseada em
native.existing_rules()- também não poderá ver esse
destino.
Visibilidade de carregamento
A visibilidade de carregamento controla se um arquivo .bzl pode ser carregado de outros
BUILD ou arquivos .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 de build encapsulada por .bzl
arquivos. Por exemplo, um autor de arquivo BUILD pode querer fatorar algumas declarações de destino repetitivas
em uma macro em um arquivo .bzl. Sem a proteção da
visibilidade de carregamento, a macro pode ser reutilizada por outros colaboradores no
mesmo espaço de trabalho, de modo que a modificação da macro interrompa os builds de outras equipes.
Observe que um arquivo .bzl pode ou não ter um destino de arquivo de origem correspondente.
Se tiver, não há garantia de que a visibilidade de carregamento e a visibilidade de destino
coincidam. Ou seja, o mesmo arquivo BUILD pode carregar o
.bzl arquivo, mas não listá-lo nos srcs de um filegroup,
ou vice-versa. Isso às vezes pode causar problemas para regras que querem consumir
.bzl arquivos como código-fonte, como para geração de documentação ou testes.
Para prototipagem, é possível desativar a aplicação da visibilidade de carregamento definindo
--check_bzl_visibility=false. Assim como --check_visibility=false, isso não deve ser feito para o código enviado.
A visibilidade de carregamento está disponível no Bazel 6.0.
Como declarar a visibilidade de carregamento
Para definir a visibilidade de carregamento de um .bzl arquivo, chame a
visibility() função de dentro do arquivo.
O argumento para visibility() é uma lista de especificações de pacote, assim como
o packages atributo de
package_group. No entanto, visibility() não aceita especificações de pacote negativas.
A chamada para visibility() só pode ocorrer uma vez por arquivo, no nível superior (não
dentro de uma função) e, idealmente, imediatamente após as instruções load().
Ao contrário da visibilidade de destino, a visibilidade de carregamento padrão é sempre pública. Os arquivos
que não chamam visibility() são sempre carregáveis de qualquer lugar no
espaço de trabalho. É recomendável adicionar visibility("private") à parte de cima de qualquer
novo .bzl arquivo que não seja especificamente destinado ao 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
Esta seção descreve dicas para gerenciar declarações de visibilidade de carregamento.
Fatorar visibilidades
Quando vários arquivos .bzl precisam ter a mesma visibilidade, pode ser útil
fatorar as especificações de pacote em uma lista comum. Por 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 distorções acidentais entre as visibilidades dos vários arquivos .bzl'
. Também é mais legível quando a lista clients é grande.
Compor visibilidades
Às vezes, um arquivo .bzl precisa ficar visível para uma lista de permissões que é
composta por várias listas de permissões menores. Isso é análogo a como um
package_group pode incorporar outros package_groups pelo atributo
includes.
Suponha que você esteja suspendendo o uso de uma macro amplamente usada. Você quer que ela fique visível apenas para usuários atuais e para os pacotes pertencentes à sua 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 remover duplicações com grupos de pacotes
Ao contrário da visibilidade de destino, não é possível definir uma visibilidade de carregamento em termos de um
package_group. Se você quiser reutilizar a mesma lista de permissões para visibilidade de destino
e visibilidade de carregamento, é melhor mover a lista de especificações de pacote
para um arquivo .bzl, em que os dois tipos de declarações podem se referir a
ela. Com base no exemplo em Fatorar visibilidades
acima, você pode escrever:
# //mylib/BUILD
load(":internal_defs", "clients")
package_group(
name = "my_pkg_grp",
packages = clients,
)
Isso só funciona se a lista não contiver especificações de pacote negativas.
Como proteger símbolos individuais
Qualquer símbolo do Starlark cujo nome comece com um sublinhado não pode ser carregado de
outro arquivo. Isso facilita a criação de símbolos particulares, mas não permite
compartilhar esses símbolos com um conjunto limitado de arquivos confiáveis. Por outro
lado, a visibilidade de carregamento oferece controle sobre quais outros pacotes podem ver seu
.bzl file, mas não permite impedir que qualquer símbolo não sublinhado seja
carregado.
Felizmente, é possível 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
Lint do Buildifier bzl-visibility
Há um lint do Buildifier
que fornece 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 é anterior ao recurso de visibilidade de carregamento e é desnecessário em
espaços de trabalho em que .bzl arquivos declaram visibilidades.