Esta página aborda os dois sistemas de visibilidade do Bazel: visibilidade de destino e visibilidade de carregamento.
Os dois 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 impor estrutura à medida que seu espaço de trabalho cresce. Você também pode usar a visibilidade ao descontinuar uma API pública para permitir usuários atuais e negar novos.
Visibilidade desejada
O controle de visibilidade do 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.
Em geral, 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 para mais informações sobre macros simbólicas.
A visibilidade é especificada listando os pacotes permitidos. Permitir um pacote não significa que os subpacotes dele também estão permitidos. 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 em 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 as 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 de posição sintáticos que não correspondem a nenhum destino real.
"//visibility:public"
: concede acesso a todos os pacotes."//visibility:private"
: não concede acesso adicional. Somente destinos no pacote deste 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/bar
e a todos os subpacotes diretos e indiretos dele."//some_pkg:my_package_group"
: concede acesso a todos os pacotes que fazem parte dopackage_group
especificado.- 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 respectivamente por"//foo/bar"
e"//foo/bar/..."
. 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 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 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 da segmentação por regra
A visibilidade de um destino de regra é determinada usando o atributo visibility
dele 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úblico. Pode ser conveniente para prototipagem ou em bases de código pequenas, mas o risco de criar inadvertidamente destinos públicos 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 de 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 de destino do arquivo de origem
Os destinos de arquivo de origem podem ser declarados explicitamente usando
exports_files
ou criados implicitamente
ao se referir ao nome do arquivo em um atributo de rótulo de uma regra (fora de uma
macro simbólica). Assim como acontece com os destinos de regra, a localização da chamada para
exports_files
ou o arquivo BUILD que se referia ao arquivo de entrada é sempre
adicionada automaticamente à visibilidade do arquivo.
A visibilidade dos arquivos declarados por exports_files
pode ser definida pelo
parâmetro visibility
dessa 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 do
default_visibility
do arquivoBUILD
ou privada se uma visibilidade padrão não for especificada.
Evite depender do comportamento legado. Sempre escreva uma declaração exports_files
quando um destino de arquivo de origem precisar de visibilidade não privada.
Prática recomendada:quando possível, prefira expor uma meta 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 uma meta java_library
não particular. Em geral, os destinos de regra só devem referenciar diretamente arquivos de origem que estão 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 aplicava a visibilidade para
alvos config_setting
que são
referenciados nas chaves de um select()
. Há
duas flags para remover esse comportamento legado:
--incompatible_enforce_config_setting_visibility
ativa a verificação de visibilidade para esses destinos. Para ajudar na migração, ele também faz com que qualquerconfig_setting
que não especifique umvisibility
seja considerado público (independente dodefault_visibility
no nível do pacote).--incompatible_config_setting_private_default_visibility
faz com queconfig_setting
s que não especificam umvisibility
respeitem odefault_visibility
do pacote e voltem à visibilidade privada, assim como qualquer outro destino de regra. É uma operação nula se--incompatible_enforce_config_setting_visibility
não estiver definido.
Evite depender do comportamento legado. Qualquer config_setting
que seja usado fora do pacote atual precisa 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 são sempre
visíveis publicamente.
Visibilidade das dependências implícitas
Algumas regras têm dependências implícitas, que não são especificadas 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 de uma dependência implícita é verificada em relação ao
pacote que contém o arquivo .bzl
em que a regra (ou o aspecto) é definida. No
exemplo, o compilador C++ pode ser particular desde que esteja no mesmo
pacote da 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.
Macros simbólicas e de visibilidade
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 a localização 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á localizado, 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 arquivo .bzl
em que a definição da macro (a instrução my_macro = macro(...)
) aparece. Quando um destino é criado dentro de vários destinos aninhados, sempre é usada a definição da macro simbólica mais interna.
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, vamos analisar 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 de qualquer outra macro definida em //lib
, independente 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 dele. Por outro lado, segmentações que estão no mesmo pacote não necessariamente se enxergam se pelo menos uma delas for criada 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 depois de anexar o local em que a macro foi chamada. A maneira padrão de uma macro exportar
um dos destinos para o chamador é encaminhar esse valor para a declaração
do destino, como em some_rule(..., visibility = visibility)
. Os destinos que omitirem esse atributo não ficarão visíveis para o autor da chamada da macro, a menos que ele esteja no mesmo pacote da definição da macro. Esse comportamento
compõe, no sentido de que uma cadeia de chamadas aninhadas para submácros pode transmitir
visibility = visibility
, reexportando os destinos exportados da macro interna para
o chamador em cada nível, sem expor nenhum dos detalhes de implementação
das macros.
Delegação de privilégios a uma submacro
O modelo de visibilidade tem um recurso especial que permite que uma macro delegue suas 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 um visibility
especificado. Portanto, 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 implementado 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 é esperado que o autor de my_macro
transmita
visibility = ["//lib"]
à declaração da dependência apenas para contornar
esse detalhe de implementação.
Por isso, 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 consumidor.
Neste exemplo, para validar se //pkg:foo_consumer
pode ver //pkg:foo_dependency
, percebemos que //pkg:foo_dependency
também foi transmitido como uma entrada para a chamada de some_library
em my_macro
. Em vez disso, verificamos a visibilidade da dependência em relação ao local dessa chamada, //macro
.
Esse processo pode ser repetido recursivamente, desde que uma declaração de destino ou macro esteja dentro de outra macro simbólica que use o rótulo da dependência em um dos atributos com tipo de rótulo.
Finalizadores
Além de ver os destinos seguindo as regras normais de visibilidade de macro simbólica, os destinos declarados em um finalizador de regra (uma macro simbólica com finalizer = True
) também podem ver todos os destinos visíveis para o pacote do 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 introspeccionar 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 a ela, a macro 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 carga
A visibilidade de carregamento 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 carga protege a lógica de build encapsulada por arquivos
.bzl
. Por exemplo, o autor de um 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 carga, eles podem descobrir que a macro foi reutilizada por outros colaboradores no mesmo espaço de trabalho, de modo que a modificação da macro interrompe os builds de outras equipes.
Um arquivo .bzl
pode ou não ter um destino de arquivo de origem correspondente.
Se isso acontecer, não há garantia de que a visibilidade da carga e do destino vão coincidir. Ou seja, o mesmo arquivo BUILD
pode carregar o arquivo .bzl
, mas não listá-lo no srcs
de um filegroup
, ou vice-versa. Isso às vezes causa problemas para regras que querem consumir
arquivos .bzl
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 com --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 carregamento
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()
só pode ocorrer uma vez por arquivo, no nível superior (não dentro de uma função) e, de preferência, imediatamente após as instruções load()
.
Ao contrário da visibilidade da meta, a visibilidade de carga padrão é sempre pública. Arquivos
que não chamam visibility()
sempre podem ser carregados de qualquer lugar no
espaço de trabalho. É recomendável adicionar visibility("private")
à parte de cima de qualquer
novo arquivo .bzl
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 carga
Esta seção descreve dicas para gerenciar declarações de visibilidade de carga.
Fatoração de visibilidades
Quando vários arquivos .bzl
precisam ter a mesma visibilidade, é útil
fatorar as especificações de 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 distorções acidentais entre as visibilidades dos vários arquivos .bzl
. Ele também é mais legível quando a lista clients
é grande.
Como criar visibilidades
Às vezes, um arquivo .bzl
precisa ficar visível para uma lista de permissão composta por várias listas menores. Isso é análogo a como um
package_group
pode incorporar outros package_group
s usando o atributo
includes
.
Suponha que você esteja descontinuando uma macro amplamente usada. Você quer que ele fique visível apenas para usuários atuais e para os pacotes da 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 duplicidades com grupos de pacotes
Ao contrário da visibilidade de destino, não é possível definir uma visibilidade de carga em termos de um
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 de pacote para um arquivo .bzl, em que os dois tipos de declarações podem se referir a ela. Com base no exemplo em Fatoração de 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
Nenhum símbolo do Starlark cujo nome comece com um sublinhado 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 carga dá controle sobre o que outros pacotes podem ver do seu
.bzl file
, mas não permite impedir que qualquer símbolo sem 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 Buildifier lint
que mostra 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 carga e é desnecessário em
espaços de trabalho em que os arquivos .bzl
declaram visibilidades.