Plataformas

Introdução

O Bazel pode criar e testar códigos 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 build ou produção. As restrições comuns são arquitetura de 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 eles querem criar, quais máquinas precisam executar ações de compilação e teste e quais conjuntos de ferramentas precisam compilar ações de build.

Os desenvolvedores também podem usar restrições para selecionar propriedades personalizadas ou dependências nas regras de build. Por exemplo: "use src_arm.cc quando o build segmenta uma máquina Arm".

Tipos de plataforma

O Bazel reconhece três papéis que uma plataforma pode desempenhar:

  • Host : a plataforma em que o 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 precisa ser executado.

Os 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 de host e 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 de várias plataformas : 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 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 --platforms flag:

$ 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 da máquina de build variam entre as organizações.

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 segmentem a mesma máquina em que o Bazel é executado. As regras de build podem selecionar essas propriedades com as @platforms/os e @platforms/cpu restrições.

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 populares. Todas elas são definidas em https://github.com/bazelbuild/platforms.

O Bazel é fornecido com a seguinte definição de plataforma especial: @platforms//host (com alias @bazel_tools//tools:host_platform). Isso detecta automaticamente as propriedades do SO e da CPU da máquina em que o Bazel é executado.

Como definir restrições

As restrições são modeladas com as constraint_setting e constraint_value regras de build.

constraint_setting declara um tipo de propriedade. Por 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 marcadores ao definir plataformas ou personalizar regras de build nelas. Se os exemplos acima forem definidos em cpus/BUILD, você poderá referenciar a x86 restrição como //cpus:x86.

Se a visibilidade permitir, você poderá estender um constraint_setting existente definindo seu próprio valor para ele.

Como definir plataformas

A regra de build platform define uma plataforma como um conjunto 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 constraint_setting tipo para modelar o segundo valor.

Como ignorar destinos incompatíveis

Ao criar para uma plataforma de destino específica, geralmente é recomendável ignorar destinos que nunca funcionarão nessa plataforma. Por exemplo, é provável que o driver de dispositivo do Windows gere muitos erros de compilador ao criar em uma máquina Linux com //.... Use o target_compatible_with atributo 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 atenda 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",
    ],
)

:win_driver_lib é apenas compatível com a criação com o 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 os destinos são ignorados?

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 todos os destinos incompatíveis encontrados em uma expansão de padrão de destino.

$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all

Os testes incompatíveis em um test_suite também são 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 test_suite destinos com testes incompatíveis também sejam incompatíveis.

A especificação explícita de um destino incompatível na linha de comando resulta em uma mensagem de erro e um build 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 exemplo a seguir 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 exemplo acima pode ser interpretado da seguinte maneira:

  1. Ao segmentar o macOS, o destino não tem restrições.
  2. Ao segmentar o Linux, o destino não tem restrições.
  3. Caso contrário, o destino tem a restrição @platforms//:incompatible. Como @platforms//:incompatible não faz parte de nenhuma plataforma, o destino é considerado incompatível.

Para tornar as restrições mais legíveis, use skylib's selects.with_or().

É possível 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": [],
    }),
)

Como detectar destinos incompatíveis usando bazel cquery

É possível usar o IncompatiblePlatformProvider no formato de saída do bazel cquery's Starlark para distinguir destinos incompatíveis de compatíveis.

Isso pode ser usado para filtrar destinos incompatíveis. O exemplo abaixo só vai imprimir 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.