Nesta página, descrevemos a estrutura do conjunto de ferramentas, que é uma forma dos autores de regras dissociar a lógica de regra da seleção de ferramentas baseada na plataforma. É recomendamos ler as regras e as plataformas páginas antes de continuar. Nesta página, explicamos por que os conjuntos de ferramentas são necessários, como defini-los e usá-los, além de como o Bazel seleciona um conjunto de ferramentas apropriado com base restrições da plataforma.
Motivação
Primeiro, vamos analisar os problemas que os conjuntos de ferramentas foram projetados para resolver. Suponha que você
estão escrevendo regras para apoiar a "barra" de programação centrada no usuário. Seu bar_binary
regra compilaria arquivos *.bar
usando o compilador barc
, uma ferramenta que por si só
é criado como outro destino no seu espaço de trabalho. Como os usuários que escrevem bar_binary
os destinos não precisam especificar uma dependência no compilador, você o torna
uma dependência implícita adicionando-a à definição da regra como um atributo particular.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
"_compiler": attr.label(
default = "//bar_tools:barc_linux", # the compiler running on linux
providers = [BarcInfo],
),
},
)
//bar_tools:barc_linux
agora é uma dependência de cada destino bar_binary
. Portanto,
ele será criado antes de qualquer destino bar_binary
. Ela pode ser acessada pelo usuário
função de implementação, assim como qualquer outro atributo:
BarcInfo = provider(
doc = "Information about how to invoke the barc compiler.",
# In the real world, compiler_path and system_lib might hold File objects,
# but for simplicity they are strings for this example. arch_flags is a list
# of strings.
fields = ["compiler_path", "system_lib", "arch_flags"],
)
def _bar_binary_impl(ctx):
...
info = ctx.attr._compiler[BarcInfo]
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
O problema aqui é que o rótulo do compilador está fixado no código em bar_binary
, mas
alvos diferentes podem precisar de diferentes compiladores, dependendo da plataforma em que
estão sendo desenvolvidas e em qual plataforma elas estão sendo desenvolvidas, chamada de
plataforma de destino e plataforma de execução, respectivamente. Além disso, a regra
nem necessariamente conhece todas as ferramentas e plataformas disponíveis, então
não é viável codificá-las na definição da regra.
Uma solução não ideal seria transferir a carga para os usuários, tornando
o atributo _compiler
não é particular. Então, os alvos individuais podem ser
fixados no código para criar para uma plataforma ou outra.
bar_binary(
name = "myprog_on_linux",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_linux",
)
bar_binary(
name = "myprog_on_windows",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_windows",
)
É possível melhorar essa solução usando select
para escolher a compiler
com base na plataforma:
config_setting(
name = "on_linux",
constraint_values = [
"@platforms//os:linux",
],
)
config_setting(
name = "on_windows",
constraint_values = [
"@platforms//os:windows",
],
)
bar_binary(
name = "myprog",
srcs = ["mysrc.bar"],
compiler = select({
":on_linux": "//bar_tools:barc_linux",
":on_windows": "//bar_tools:barc_windows",
}),
)
No entanto, isso é tedioso e um pouco difícil de fazer para cada usuário do bar_binary
.
Se esse estilo não for usado de forma consistente em todo o espaço de trabalho, isso resultará em
builds que funcionam bem em uma única plataforma, mas falham quando estendidos para
em vários cenários multiplataforma. Ela também não aborda o problema de adicionar suporte
para novas plataformas e compiladores sem modificar as regras ou os destinos atuais.
A estrutura do conjunto de ferramentas resolve esse problema adicionando um nível extra de ou indireção. Basicamente, você declara que a regra tem uma dependência abstrata. em algum membro de uma família de destinos (um tipo de conjunto de ferramentas), e o Bazel resolve isso automaticamente para um destino específico (um conjunto de ferramentas) com base no restrições de plataforma aplicáveis. Nem o autor da regra nem o autor de destino precisam conhecer o conjunto completo de plataformas e conjuntos de ferramentas disponíveis.
Como escrever regras que usam conjuntos de ferramentas
Na estrutura do conjunto de ferramentas, em vez de as regras dependerem diretamente das ferramentas, em vez disso, eles dependem de tipos de conjunto de ferramentas. Um tipo de conjunto de ferramentas é um destino simples que representa uma classe de ferramentas que têm a mesma função para diferentes plataformas. Por exemplo, é possível declarar um tipo que representa a barra compilador:
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
A definição da regra na seção anterior é modificada para que, em vez de
absorvendo o compilador como um atributo, ele declara que consome um
conjunto de ferramentas //bar_tools:toolchain_type
.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
A função de implementação agora acessa essa dependência em ctx.toolchains
em vez de ctx.attr
, usando o tipo de conjunto de ferramentas como chave.
def _bar_binary_impl(ctx):
...
info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
# The rest is unchanged.
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
ctx.toolchains["//bar_tools:toolchain_type"]
retorna o
ToolchainInfo
provedor
de qualquer destino em que o Bazel resolveu a dependência do conjunto de ferramentas. Os campos do elemento
ToolchainInfo
são definidos pela regra da ferramenta subjacente. no próximo
essa regra é definida de forma que haja um campo barcinfo
que une
um objeto BarcInfo
.
O procedimento do Bazel para resolver conjuntos de ferramentas para destinos é descrito
abaixo. Somente o destino do conjunto de ferramentas resolvido é, na verdade,
uma dependência do destino bar_binary
, não de todo o espaço do candidato
de última geração.
Conjuntos de ferramentas obrigatórios e opcionais
Por padrão, quando uma regra expressa uma dependência de tipo de conjunto de ferramentas usando um rótulo básico (como mostrado acima), o tipo de conjunto de ferramentas é considerado obrigatório. Se o Bazel não consegue encontrar um conjunto de ferramentas correspondente (consulte Resolução do conjunto de ferramentas abaixo) para um conjunto de ferramentas obrigatório. em tipo, isso é um erro, e a análise é interrompida.
Em vez disso, é possível declarar uma dependência de tipo de conjunto de ferramentas opcional, conforme da seguinte forma:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Quando um tipo de conjunto de ferramentas opcional não pode ser resolvido, a análise continua e o
o resultado de ctx.toolchains["//bar_tools:toolchain_type"]
é None
.
O config_common.toolchain_type
o padrão é obrigatório.
É possível usar os seguintes formulários:
- Tipos de conjunto de ferramentas obrigatórios:
toolchains = ["//bar_tools:toolchain_type"]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
- Tipos de conjunto de ferramentas opcionais:
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
...
toolchains = [
"//foo_tools:toolchain_type",
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Também é possível misturar e combinar formulários na mesma regra. No entanto, se o mesmo tipo de conjunto de ferramentas for listado várias vezes, ele usará a versão mais estrita, em que o campo obrigatório é mais restrito do que opcional.
Como escrever aspectos que usam conjuntos de ferramentas
Os aspectos têm acesso à mesma API de conjunto de ferramentas que as regras: é possível definir as regras de tipos de conjunto de ferramentas, acessar os conjuntos por meio do contexto e usá-los para gerar novas ações usando o conjunto de ferramentas.
bar_aspect = aspect(
implementation = _bar_aspect_impl,
attrs = {},
toolchains = ['//bar_tools:toolchain_type'],
)
def _bar_aspect_impl(target, ctx):
toolchain = ctx.toolchains['//bar_tools:toolchain_type']
# Use the toolchain provider like in a rule.
return []
Como definir conjuntos de ferramentas
Para definir alguns conjuntos de ferramentas para um determinado tipo, três coisas são necessárias:
Uma regra específica da linguagem que representa o tipo de ferramenta ou conjunto de ferramentas. De convenção, o nome dessa regra é sufixado com "_conjunto de ferramentas".
- Observação:a regra
\_toolchain
não pode criar ações de build. Em vez disso, ele coleta artefatos de outras regras e os encaminha para o que usa o conjunto de ferramentas. Essa regra é responsável por criar todos ações de build.
- Observação:a regra
Vários destinos desse tipo de regra, representando as versões da ferramenta ou ferramenta para diferentes plataformas.
Para cada uma dessas segmentações, uma segmentação associada do
toolchain
para fornecer metadados usados pela estrutura do conjunto de ferramentas. Estetoolchain
destino também se refere aotoolchain_type
associado a este conjunto de ferramentas. Isso significa que determinada regra_toolchain
pode ser associada a qualquertoolchain_type
e isso apenas em uma instância detoolchain
que usa a regra_toolchain
que a regra está associada a umtoolchain_type
.
Para nosso exemplo em execução, aqui está a definição de uma regra bar_toolchain
. Nossos
tem apenas um compilador, mas outras ferramentas, como um vinculador, também podem ser
agrupadas abaixo dele.
def _bar_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
barcinfo = BarcInfo(
compiler_path = ctx.attr.compiler_path,
system_lib = ctx.attr.system_lib,
arch_flags = ctx.attr.arch_flags,
),
)
return [toolchain_info]
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler_path": attr.string(),
"system_lib": attr.string(),
"arch_flags": attr.string_list(),
},
)
A regra precisa retornar um provedor ToolchainInfo
, que se torna o objeto que
a regra de consumo recupera usando ctx.toolchains
e o rótulo do
tipo de conjunto de ferramentas. ToolchainInfo
, assim como struct
, pode conter valores de campo arbitrários
pares. A especificação de exatamente quais campos são adicionados ao ToolchainInfo
precisam ser claramente documentados
no tipo de conjunto de ferramentas. Neste exemplo, os valores
retornar encapsulados em um objeto BarcInfo
para reutilizar o esquema definido acima; este
pode ser útil para validação e reutilização de código.
Agora você pode definir destinos para compiladores barc
específicos.
bar_toolchain(
name = "barc_linux",
arch_flags = [
"--arch=Linux",
"--debug_everything",
],
compiler_path = "/path/to/barc/on/linux",
system_lib = "/usr/lib/libbarc.so",
)
bar_toolchain(
name = "barc_windows",
arch_flags = [
"--arch=Windows",
# Different flags, no debug support on windows.
],
compiler_path = "C:\\path\\on\\windows\\barc.exe",
system_lib = "C:\\path\\on\\windows\\barclib.dll",
)
Por fim, você vai criar definições de toolchain
para os dois destinos bar_toolchain
.
Essas definições vinculam os destinos específicos da linguagem ao tipo de conjunto de ferramentas e
fornecem as informações de restrição que informam ao Bazel quando o conjunto de ferramentas está
apropriadas para uma determinada plataforma.
toolchain(
name = "barc_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_linux",
toolchain_type = ":toolchain_type",
)
toolchain(
name = "barc_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_windows",
toolchain_type = ":toolchain_type",
)
O uso da sintaxe de caminho relativo acima sugere que essas definições estão todas no
mesmo pacote, mas não há motivo para o tipo de conjunto de ferramentas, configurações
destinos do conjunto de ferramentas, e os destinos de definição toolchain
não podem estar todos em separados
pacotes.
Consulte o go_toolchain
para um exemplo do mundo real.
Conjuntos de ferramentas e configurações
Uma pergunta importante para autores de regras é: quando um destino bar_toolchain
é
analisados, qual configuração ela vê e quais transições
precisa ser usado para dependências? O exemplo acima usa atributos de string, mas
o que aconteceria com um conjunto de ferramentas mais complicado que depende de outros alvos
no repositório do Bazel?
Vamos conferir uma versão mais complexa de bar_toolchain
:
def _bar_toolchain_impl(ctx):
# The implementation is mostly the same as above, so skipping.
pass
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler": attr.label(
executable = True,
mandatory = True,
cfg = "exec",
),
"system_lib": attr.label(
mandatory = True,
cfg = "target",
),
"arch_flags": attr.string_list(),
},
)
O uso de attr.label
é o mesmo que para uma regra padrão,
mas o significado do parâmetro cfg
é um pouco diferente.
A dependência de um destino (chamado de "pai") para um conjunto de ferramentas por meio do conjunto de ferramentas.
usa uma transição de configuração especial chamada de "conjunto de ferramentas
de transição". A transição do conjunto de ferramentas mantém a configuração a mesma, exceto
isso força a plataforma de execução a ser a mesma para o conjunto de ferramentas e para as
o pai. Caso contrário, a resolução do conjunto de ferramentas poderia escolher qualquer
plataforma de execução, e não necessariamente o mesmo que para o pai). Isso
permite que qualquer dependência exec
do conjunto de ferramentas também seja executável para o
das ações de build do pai. Todas as dependências do conjunto de ferramentas que usam cfg =
"target"
(ou que não especificam cfg
, já que "destino" é o padrão) são:
criado para a mesma plataforma de destino que o pai. Isso permite que as regras do conjunto de ferramentas
contribuem com bibliotecas (o atributo system_lib
acima) e ferramentas (o atributo
compiler
) às regras de build que precisam deles. Bibliotecas do sistema
são vinculadas ao artefato final e, portanto, precisam ser criadas para o mesmo
plataforma, enquanto o compilador é uma ferramenta invocada durante o build e precisa
possam ser executados na plataforma de execução.
Como registrar e criar com conjuntos de ferramentas
Neste ponto, todos os elementos básicos estão montados, e você só precisa fazer
os conjuntos de ferramentas disponíveis para o procedimento de resolução do Bazel. Isso é feito pela
registrar o conjunto de ferramentas em um arquivo WORKSPACE
usando
register_toolchains()
ou passando o URL os identificadores do comando
usando a flag --extra_toolchains
.
register_toolchains(
"//bar_tools:barc_linux_toolchain",
"//bar_tools:barc_windows_toolchain",
# Target patterns are also permitted, so you could have also written:
# "//bar_tools:all",
# or even
# "//bar_tools/...",
)
Ao usar padrões de destino para registrar conjuntos de ferramentas, a ordem em que os o registro de conjuntos de ferramentas individuais é determinado pelas seguintes regras:
- Os conjuntos de ferramentas definidos em um subpacote de um pacote são registrados antes que o conjuntos de ferramentas definidos no próprio pacote.
- Dentro de um pacote, os conjuntos de ferramentas são registrados na ordem lexicográfica de os nomes delas.
Agora, quando você criar um destino que dependa de um tipo de conjunto de ferramentas, uma implementação conjunto de ferramentas serão selecionados com base nas plataformas de destino e execução.
# my_pkg/BUILD
platform(
name = "my_target_platform",
constraint_values = [
"@platforms//os:linux",
],
)
bar_binary(
name = "my_bar_binary",
...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
O Bazel vai notar que o //my_pkg:my_bar_binary
está sendo criado com uma plataforma que
tem @platforms//os:linux
e, portanto, resolver
Referência de //bar_tools:toolchain_type
a //bar_tools:barc_linux_toolchain
.
Isso vai criar //bar_tools:barc_linux
, mas não
//bar_tools:barc_windows
.
Resolução do conjunto de ferramentas
Para cada destino que usa conjuntos de ferramentas, é usado o procedimento de resolução de conjunto de ferramentas do Bazel. determina as dependências concretas do conjunto de ferramentas do destino. O procedimento toma como entrada uma de tipos de conjunto de ferramentas obrigatórios, a plataforma de destino, a lista de tipos plataformas de execução e a lista de conjuntos de ferramentas disponíveis. Suas saídas são uma conjunto de ferramentas selecionado para cada tipo de conjunto de ferramentas, bem como uma execução selecionada plataforma para o destino atual.
As plataformas de execução e os conjuntos de ferramentas disponíveis são reunidos nas
Arquivo WORKSPACE
por
register_execution_platforms
e
register_toolchains
Plataformas de execução e conjuntos de ferramentas adicionais também podem ser especificados no
linha de comando via
--extra_execution_platforms
e
--extra_toolchains
A plataforma host é incluída automaticamente como uma plataforma de execução disponível.
As plataformas e os conjuntos de ferramentas disponíveis são rastreados como listas ordenadas para determinismo.
com preferência para os itens anteriores da lista.
O conjunto de conjuntos de ferramentas disponíveis, em ordem de prioridade, é criado
--extra_toolchains
e register_toolchains
:
- Os conjuntos de ferramentas registrados usando
--extra_toolchains
são adicionados primeiro.- Dentro deles, o último conjunto de ferramentas tem a prioridade mais alta.
- Conjuntos de ferramentas registrados usando
register_toolchains
- Nesses casos, o primeiro conjunto de ferramentas mencionado tem a prioridade mais alta.
OBSERVAÇÃO: pseudodestinos, como :all
, :*
e
/...
são ordenados pelo pacote do Bazel
que usa uma ordenação lexicográfica.
As etapas de resolução são as seguintes.
Uma cláusula
target_compatible_with
ouexec_compatible_with
corresponde a uma plataforma se, para cadaconstraint_value
na lista, ela também tiverconstraint_value
(explicitamente ou como padrão).Se a plataforma tiver
constraint_value
s deconstraint_setting
s não referenciados pela cláusula, eles não afetam a correspondência.Se o destino que está sendo criado especificar Atributo
exec_compatible_with
(ou sua definição de regra especificar o argumentoexec_compatible_with
), a lista de plataformas de execução disponíveis é filtrada para remover de qualquer um que não corresponda às restrições de execução.Para cada plataforma de execução disponível, associe cada tipo de conjunto de ferramentas a o primeiro conjunto de ferramentas disponível, se houver, que seja compatível com essa execução entre a plataforma de destino e a de destino.
Qualquer plataforma de execução que não conseguiu encontrar um conjunto de ferramentas obrigatório compatível para um dos tipos de conjunto de ferramentas é descartado. Entre as plataformas restantes, a o primeiro se torna a plataforma de execução do alvo atual e a respectiva os conjuntos de ferramentas (se houver) tornam-se dependências do destino.
A plataforma de execução escolhida é usada para executar todas as ações gera.
Nos casos em que o mesmo destino pode ser criado em várias configurações (como para CPUs diferentes) no mesmo build, o procedimento de resolução é aplicado de maneira independente para cada versão do destino.
Se a regra usar grupos de execução, cada execução executa a resolução do conjunto de ferramentas separadamente, e cada um deles tem a própria execução do Google Cloud e dos conjuntos de ferramentas.
Depuração de conjuntos de ferramentas
Se você está adicionando suporte para o conjunto de ferramentas a uma regra existente, use o
sinalização --toolchain_resolution_debug=regex
. Durante a resolução do conjunto de ferramentas, a sinalização
fornece uma saída detalhada para tipos de conjunto de ferramentas ou nomes de destino que correspondem à variável regex. Você
pode usar .*
para gerar todas as informações. O Bazel mostra os nomes dos conjuntos de ferramentas
verificações e pulos durante o processo de resolução.
Caso você queira ver quais dependências de cquery
são do conjunto de ferramentas
resolução, use a flag --transitions
do cquery
:
# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)
# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
[toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211