Visibilidade

Informar um problema Ver código-fonte Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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 biblioteca e os detalhes de implementação dela, além de ajudar a aplicar a estrutura conforme 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.

Segmentação por visibilidade

A visibilidade do alvo controla quem pode depender do seu alvo, ou seja, quem pode usar o rótulo do seu alvo dentro de um atributo, como deps. A criação de um destino vai falhar durante a fase de análise se ele violar a visibilidade de uma das dependências.

Em geral, um alvo A é visível para um alvo 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 saber mais sobre macros simbólicas.

A visibilidade é especificada listando os pacotes permitidos. Permitir um pacote não necessariamente significa que os subpacotes dele também são permitidos. Para mais detalhes sobre pacotes e subpacotes, consulte Conceitos e terminologia.

Para prototipar, é possível desativar a aplicação de 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 forma 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 regras têm um atributo visibility que recebe uma lista de identificadores. Cada rótulo tem um dos seguintes formatos. Com exceção do último formulário, 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. Apenas os destinos no pacote desse local podem usar esse destino.

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

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

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

    • Os grupos de pacotes usam uma sintaxe diferente para especificar pacotes. Em 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 declarados em //tests/BUILD, mas não por destinos definidos em //tests/integration/BUILD.

Prática recomendada:para tornar várias metas visíveis para o mesmo conjunto de pacotes, use um package_group em vez de repetir a lista no atributo visibility de cada meta. 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 a rotatividade desnecessária de visibilidade à medida que o projeto evolui e adiciona novos subpacotes.

Visibilidade da segmentação de regras

A visibilidade de um alvo de regra é determinada usando o atributo visibility ou um padrão adequado, se não fornecido, e anexando o local em que o alvo 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 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 pequenas bases de código, mas o risco de criar acidentalmente 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 do destino do arquivo gerado

Um destino de arquivo gerado tem a mesma visibilidade que o 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 arquivos de origem podem ser declarados explicitamente usando exports_files ou criados implicitamente fazendo referência ao nome de 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 referia ao arquivo de entrada é sempre anexado automaticamente à visibilidade do arquivo.

A visibilidade dos arquivos declarados por exports_files pode ser definida pelo parâmetro visibility 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 do default_visibility do arquivo BUILD ou privada 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 privada.

Prática recomendada:sempre que 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, envolva o arquivo em um destino java_library não particular. Geralmente, os destinos de regras devem referenciar diretamente apenas 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"],
)

Configuração de visibilidade

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 essas metas. Para ajudar na migração, qualquer config_setting que não especifique um visibility também é considerado público (independentemente 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 usem a visibilidade privada como fallback, assim como qualquer outro destino de regra. Não vai funcionar se --incompatible_enforce_config_setting_visibility não estiver definido.

Evite depender do comportamento legado. Qualquer config_setting que tenha a intenção de ser 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 estão sempre visíveis publicamente.

Visibilidade de 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 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 dessa dependência implícita é verificada em relação ao pacote que contém o arquivo .bzl em que a regra (ou aspecto) é 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.

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 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 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 em uma macro, analisamos a definição da macro mais interna, e não o 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" uma da outra. Qualquer destino criado diretamente por uma macro definida em //lib:defs.bzl pode ser visto em qualquer outra macro definida em //lib, independente dos pacotes em que as macros são instanciadas. Da mesma forma, eles podem acessar e serem acessados por destinos declarados diretamente em //lib/BUILD e nas macros legados. Por outro lado, os destinos que estão no mesmo pacote não podem necessariamente se encontrar 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 uma das suas metas para o autor da chamada é encaminhar esse valor para a declaração da meta, como em some_rule(..., visibility = visibility). Os destinos que omitirem esse atributo não vão ficar visíveis para o autor da chamada da macro, a menos que ele esteja no mesmo pacote da definição da macro. Esse comportamento é composto, no sentido de que uma cadeia de chamadas aninhadas para submacros pode transmitir visibility = visibility, exportando novamente 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.

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 borda 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 especificado, 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 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. Portanto, 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 trabalhar com 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, e não ao local do destino consumidor.

Neste exemplo, para validar se //pkg:foo_consumer pode acessar //pkg:foo_dependency, //pkg:foo_dependency também foi transmitido como um input para a chamada de some_library dentro de my_macro. Em vez disso, verifique a visibilidade da dependência em relação ao local dessa chamada, //macro.

Esse processo pode ser repetido de forma recursiva, 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 do tipo rótulo.

Carregar visibilidade

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 do 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, 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 carga, a macro pode ser reutilizada por outros colaboradores no mesmo espaço de trabalho, de modo que a modificação da macro quebra 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 de carregamento e a visibilidade de destino coincidam. Ou seja, o mesmo arquivo BUILD pode carregar o .bzl, mas não listá-lo no srcs de um filegroup, ou vice-versa. Isso pode causar problemas para regras que querem consumir arquivos .bzl como código-fonte, como para geração de documentação ou testes.

Para prototipar, é possível desativar a aplicação da visibilidade de carga 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 no 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 carregamento padrão é sempre pública. Os arquivos que não chamam visibility() podem ser carregados de qualquer lugar no espaço de trabalho. É recomendável adicionar visibility("private") ao início 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.

Visibilidade da fatoração

Quando vários arquivos .bzl precisam ter a mesma visibilidade, pode ser útil considerar as especificações de pacotes 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 várias visibilidades dos arquivos .bzl. Ele também é mais legível quando a lista clients é grande.

Como compor visibilidades

Às vezes, um arquivo .bzl precisa estar visível para uma lista de permissões 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 descontinuando uma macro amplamente usada. Você quer que ele seja visível apenas para os usuários e 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 eliminar a duplicação com grupos de pacotes

Ao contrário da visibilidade do alvo, não é possível definir uma visibilidade de carregamento em termos de package_group. Se você quiser reutilizar a mesma lista de permissões para a 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 podem se referir a ele. Com base no exemplo em Como dividir as 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 que você compartilhe esses símbolos com um conjunto limitado de arquivos confiáveis. Por outro lado, a visibilidade de carregamento permite controlar o que outros pacotes podem ver do .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

Buildifier lint bzl-visibility

Há um Buildifier lint 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 carga e é desnecessário em espaços de trabalho em que os arquivos .bzl declaram visibilidades.