Plataformas

O Bazel pode criar e testar códigos em vários hardwares, sistemas operacionais e configurações de sistema, usando muitas versões diferentes de ferramentas de build, como linkers e compiladores. Para ajudar a gerenciar essa complexidade, o Bazel tem um conceito de restrições e plataformas. Uma restrição é uma dimensão em que os ambientes de build ou produção podem variar, como arquitetura de CPU, presença ou ausência de uma GPU ou a versão de um compilador instalado no sistema. Uma plataforma é uma coleção nomeada de opções para essas restrições, representando os recursos específicos que estão disponíveis em algum ambiente.

A modelagem do ambiente como uma plataforma ajuda o Bazel a selecionar automaticamente os conjuntos de ferramentas adequados para ações de build. As plataformas também podem ser usadas em combinação com a config_setting para gravar atributos configuráveis.

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

  • Host : a plataforma em que o próprio Bazel é executado.
  • Execução : uma plataforma em que as ferramentas de build executam ações de build para produzir saídas intermediárias e finais.
  • Destino : uma plataforma em que uma saída final reside e é executada.

O Bazel oferece suporte aos seguintes cenários de build em relação a plataformas:

  • Builds de plataforma única (padrão): as plataformas de host, execução e destino são as mesmas. Por exemplo, criar um executável do Linux no Ubuntu executado em uma CPU Intel x64.

  • Builds de compilação cruzada : as plataformas de host e execução são as mesmas, mas a plataforma segmentada é diferente. Por exemplo, criar um app iOS no macOS executado em um MacBook Pro.

  • Builds de várias plataformas : as plataformas de host, execução e destino são todas diferentes.

Definir restrições e plataformas

O espaço de opções possíveis para plataformas é definido usando as constraint_setting e constraint_value regras em arquivos BUILD. constraint_setting cria uma nova dimensão, enquanto constraint_value cria um novo valor para uma determinada dimensão. Juntos, eles definem efetivamente um enum e os valores possíveis. Por exemplo, o snippet a seguir de um arquivo BUILD apresenta uma restrição para a versão glibc do sistema com dois valores possíveis.

constraint_setting(name = "glibc_version")

constraint_value(
    name = "glibc_2_25",
    constraint_setting = ":glibc_version",
)

constraint_value(
    name = "glibc_2_26",
    constraint_setting = ":glibc_version",
)

As restrições e os valores delas podem ser definidos em diferentes pacotes no espaço de trabalho. Eles são referenciados por rótulo e estão sujeitos aos controles de visibilidade usuais. Se a visibilidade permitir, você poderá estender uma configuração de restrição atual definindo seu próprio valor para ela.

A regra platform apresenta uma nova plataforma com determinadas opções de valores de restrição. O exemplo a seguir cria uma plataforma chamada linux_x86 e informa que ela descreve qualquer ambiente que execute um sistema operacional Linux em uma arquitetura x86_64 com uma versão glibc de 2.25. Consulte abaixo para mais informações sobre as restrições integradas do Bazel.

platform(
    name = "linux_x86",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
        ":glibc_2_25",
    ],
)

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. Todos eles estão localizados em https://github.com/bazelbuild/platforms (link em inglês).

O Bazel é fornecido com a seguinte definição de plataforma especial: @platforms//host (com alias @bazel_tools//tools:host_platform). Esse é o valor da plataforma de host detectado automaticamente, que representa a plataforma detectada automaticamente para o sistema em que o Bazel está sendo executado.

Especificar uma plataforma para um build

É possível especificar as plataformas de host e destino para um build usando as seguintes flags de linha de comando:

  • --host_platform : o padrão é @bazel_tools//tools:host_platform
    • Esse destino tem o alias @platforms//host, que é apoiado por uma regra de repositório que detecta o SO e a CPU do host e grava o destino da plataforma.
    • Há também @platforms//host:constraints.bzl, que expõe uma matriz chamada HOST_CONSTRAINTS, que pode ser usada em outros arquivos BUILD e Starlark.
  • --platforms : o padrão é a plataforma de host
    • Isso significa que, quando nenhuma outra flag é definida, @platforms//host é a plataforma segmentada.
    • Se --host_platform estiver definido e não --platforms, o valor de --host_platform será a plataforma de host e a plataforma segmentada.

Ignorar destinos incompatíveis

Ao criar para uma plataforma segmentada 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 segmentada 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 atende.

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": [],
    }),
)

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.