Introdução
O Bazel pode criar e testar código em vários hardwares, sistemas operacionais e configurações de sistema. Isso pode envolver diferentes versões de ferramentas de build, como linkers e compiladores. Para ajudar a gerenciar essa complexidade, o Bazel tem os conceitos de restrições e plataformas.
Uma restrição é uma propriedade distintiva de uma máquina de produção ou de build. Restrições comuns são arquitetura da CPU, presença ou ausência de uma GPU ou versão de um compilador instalado localmente. No entanto, as restrições podem ser qualquer coisa que distinga as máquinas de maneira significativa ao orquestrar o trabalho de build.
Uma plataforma é um conjunto de restrições que especifica uma máquina completa. O Bazel usa esse conceito para permitir que os desenvolvedores escolham para quais máquinas querem criar, quais máquinas devem executar ações de compilação e teste e quais conjuntos de ferramentas devem ser usados para compilar ações de build.
Os desenvolvedores também podem usar restrições para selecionar propriedades ou dependências personalizadas nas regras de build. Por exemplo: "use
src_arm.cc when the build targets an Arm machine".
Tipos de plataforma
O Bazel reconhece três funções que uma plataforma pode desempenhar:
- Host: a plataforma em que o próprio Bazel é executado.
- Execução: uma plataforma que executa ações de compilação para produzir saídas de build.
- Destino: uma plataforma em que o código criado será executado.
As builds geralmente têm três tipos de relações com as plataformas:
Builds de plataforma única: as plataformas de host, execução e destino são as mesmas. Por exemplo, criar em uma máquina de desenvolvedor sem execução remota e executar o binário criado na mesma máquina.
Builds de compilação cruzada: as plataformas host e de execução são as mesmas, mas a plataforma de destino é diferente. Por exemplo, criar um app iOS em um Macbook Pro sem execução remota.
Builds multiplataforma: as plataformas de host, execução e destino são todas diferentes. Por exemplo, criar um app iOS em um Macbook Pro e usar máquinas Linux remotas para compilar ações em C++ que não precisam do Xcode.
Como especificar plataformas
A maneira mais comum de os desenvolvedores usarem plataformas é especificar as máquinas de destino desejadas com a flag --platforms:
$ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86
As organizações geralmente mantêm as próprias definições de plataforma porque as configurações de máquina de build variam entre elas.
Quando --platforms não está definido, o padrão é @platforms//host. Isso é definido especialmente para detectar automaticamente as propriedades do SO e da CPU da máquina host, para que os builds sejam direcionados à mesma máquina em que o Bazel é executado. As regras de build podem selecionar essas propriedades com as restrições @platforms/os e @platforms/cpu.
Restrições e plataformas geralmente úteis
Para manter o ecossistema consistente, a equipe do Bazel mantém um repositório com definições de restrição para as arquiteturas de CPU e sistemas operacionais mais usados. Todos eles estão definidos em https://github.com/bazelbuild/platforms.
O Bazel é fornecido com a seguinte definição especial de plataforma: @platforms//host (com alias @bazel_tools//tools:host_platform). Isso detecta automaticamente as propriedades de SO e CPU da máquina em que o Bazel é executado.
Como definir restrições
As restrições são modeladas com as regras de build constraint_setting e constraint_value.
constraint_setting declara um tipo de propriedade. Exemplo:
constraint_setting(name = "cpu")
constraint_value declara um valor possível para essa propriedade:
constraint_value(
name = "x86",
constraint_setting = ":cpu"
)
Eles podem ser referenciados como rótulos ao definir plataformas ou personalizar regras de build
nelas. Se os exemplos acima forem definidos em cpus/BUILD, você poderá referenciar a restrição x86 como //cpus:x86.
Se a visibilidade permitir, você poderá estender um constraint_setting definindo seu próprio valor para ele.
Como definir plataformas
A regra de build platform
define uma plataforma como uma coleção de constraint_values:
platform(
name = "linux_x86",
constraint_values = [
"//oses:linux",
"//cpus:x86",
],
)
Isso modela uma máquina que precisa ter as restrições //oses:linux e //cpus:x86.
As plataformas só podem ter um constraint_value para um determinado constraint_setting.
Isso significa, por exemplo, que uma plataforma não pode ter duas CPUs, a menos que você crie outro tipo constraint_setting para modelar o segundo valor.
Ignorando destinos incompatíveis
Ao criar para uma plataforma de destino específica, muitas vezes é recomendável ignorar
destinos que nunca vão funcionar nessa plataforma. Por exemplo, o driver do dispositivo Windows provavelmente vai gerar muitos erros de compilador ao criar em uma máquina Linux com //.... Use o atributo
target_compatible_with
para informar ao Bazel quais restrições de plataforma de destino seu código tem.
O uso mais simples desse atributo restringe um destino a uma única plataforma.
O destino não será criado para nenhuma plataforma que não satisfaça todas as restrições. O exemplo a seguir restringe win_driver_lib.cc ao Windows de 64 bits.
cc_library(
name = "win_driver_lib",
srcs = ["win_driver_lib.cc"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
],
)
O :win_driver_lib é apenas compatível com a criação no Windows de 64 bits e
incompatível com todo o resto. A incompatibilidade é transitiva. Todos os destinos que dependem transitivamente de um destino incompatível também são considerados incompatíveis.
Quando as metas são ignoradas?
Os destinos são ignorados quando são considerados incompatíveis e incluídos no build como parte de uma expansão de padrão de destino. Por exemplo, as duas invocações a seguir ignoram destinos incompatíveis encontrados em uma expansão de padrão de destino.
$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all
Testes incompatíveis em um test_suite são
igualmente ignorados se o test_suite for especificado na linha de comando com
--expand_test_suites.
Em outras palavras, os destinos test_suite na linha de comando se comportam como :all e .... O uso de --noexpand_test_suites impede a expansão e faz com que
os destinos test_suite com testes incompatíveis também sejam incompatíveis.
Especificar explicitamente um destino incompatível na linha de comando resulta em uma mensagem de erro e uma criação com falha.
$ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform
...
ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested.
...
FAILED: Build did NOT complete successfully
Os destinos explícitos incompatíveis são ignorados silenciosamente se --skip_incompatible_explicit_targets estiver ativado.
Restrições mais expressivas
Para mais flexibilidade na expressão de restrições, use o
@platforms//:incompatible
constraint_value
que nenhuma plataforma satisfaz.
Use select() em combinação com
@platforms//:incompatible para expressar restrições mais complicadas. Por exemplo, use-o para implementar a lógica OR básica. O seguinte marca uma biblioteca
compatível com macOS e Linux, mas não com outras plataformas.
cc_library(
name = "unixish_lib",
srcs = ["unixish_lib.cc"],
target_compatible_with = select({
"@platforms//os:osx": [],
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
O comando acima pode ser interpretado da seguinte forma:
- Ao segmentar o macOS, o destino não tem restrições.
- Ao segmentar o Linux, o destino não tem restrições.
- Caso contrário, o destino terá a restrição
@platforms//:incompatible. Como@platforms//:incompatiblenão faz parte de nenhuma plataforma, o destino é considerado incompatível.
Para tornar suas restrições mais legíveis, use
selects.with_or() da
skylib.
Você pode expressar a compatibilidade inversa de maneira semelhante. O exemplo a seguir descreve uma biblioteca compatível com tudo exceto ARM.
cc_library(
name = "non_arm_lib",
srcs = ["non_arm_lib.cc"],
target_compatible_with = select({
"@platforms//cpu:arm": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
Detectar destinos incompatíveis usando bazel cquery
Use o
IncompatiblePlatformProvider
no formato de saída do Starlark
do bazel cquery para distinguir
destinos incompatíveis de compatíveis.
Isso pode ser usado para filtrar destinos incompatíveis. O exemplo abaixo vai imprimir apenas os rótulos dos destinos compatíveis. Os destinos incompatíveis não são impressos.
$ cat example.cquery
def format(target):
if "IncompatiblePlatformProvider" not in providers(target):
return target.label
return ""
$ bazel cquery //... --output=starlark --starlark:file=example.cquery
Problemas conhecidos
Os destinos incompatíveis ignoram as restrições de visibilidade.