Plataformas

Informar um problema Acessar fonte

Ele pode compilar e testar código em vários hardwares, sistemas operacionais e configurações de sistema, usando muitas versões diferentes de ferramentas de compilação, como vinculadores 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 ser diferentes, como arquitetura da CPU, presença ou ausência de GPU ou versão de um compilador instalado pelo sistema. Uma plataforma é uma coleção nomeada de opções para essas restrições, representando os recursos específicos disponíveis em algum ambiente.

Modelar o ambiente como uma plataforma ajuda o Bazel a selecionar automaticamente os conjuntos de ferramentas adequados para ações de compilação. As plataformas também podem ser usadas em combinação com a regra 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.
  • Execution: 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 relacionados 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 em uma CPU Intel x64.

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

  • Compilações de várias plataformas: as plataformas de host, execução e destino são diferentes.

Definição de restrições e plataformas

O espaço de opções possíveis para plataformas é definido usando as regras constraint_setting e constraint_value nos 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 uma enumeração e os valores possíveis dela. Por exemplo, o snippet a seguir de um arquivo BUILD introduz 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 existente definindo seu próprio valor para ela.

A regra platform introduz uma nova plataforma com certas opções de valores de restrição. O seguinte cria uma plataforma chamada linux_x86 e informa que ela descreve qualquer ambiente que executa um sistema operacional Linux em uma arquitetura x86_64 com uma versão glibc de 2.25. Veja abaixo 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 os sistemas operacionais mais conhecidos. Todos eles estão localizados em https://github.com/bazelbuild/platforms (link em inglês).

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

Como especificar uma plataforma para um build

Você pode 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.
    • Existe 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 sinalização estiver definida, @platforms//host será a plataforma de destino.
    • Se --host_platform estiver definido e não for --platforms, o valor de --host_platform será a plataforma de host e de destino.

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 do dispositivo Windows gere muitos erros do compilador ao criar em uma máquina Linux com //.... Use o atributo target_compatible_with para informar ao Bazel quais restrições da 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 uma 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",
    ],
)

O :win_driver_lib é compatível apenas para criação com o Windows de 64 bits e incompatível com todo o restante. A incompatibilidade é transitiva. Todos os destinos que dependem transitivamente de um destino incompatível são considerados incompatíveis.

Quando as segmentações 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 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 serão ignorados da mesma forma 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.

A especificação explícita de um destino incompatível na linha de comando resulta em uma mensagem de erro e em uma versã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

Destinos explícitos incompatíveis são ignorados silenciosamente se --skip_incompatible_explicit_targets estiver ativado.

Restrições mais expressivas

Para ter 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 código a seguir marca uma biblioteca compatível com macOS e Linux, mas nenhuma outra plataforma.

cc_library(
    name = "unixish_lib",
    srcs = ["unixish_lib.cc"],
    target_compatible_with = select({
        "@platforms//os:osx": [],
        "@platforms//os:linux": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
)

Isso pode ser interpretado da seguinte maneira:

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

Para tornar suas restrições mais legíveis, use selects.with_or() do skylib.

Você pode expressar compatibilidade inversa de forma semelhante. O exemplo a seguir descreve uma biblioteca compatível com tudo, exceto o 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.

Você pode usar o IncompatiblePlatformProvider no formato de saída do Starlark (link em inglês) do bazel cquery para distinguir destinos incompatíveis e compatíveis.

Isso pode ser usado para filtrar segmentações incompatíveis. O exemplo abaixo imprimirá apenas os rótulos de destinos compatíveis. 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

Destinos incompatíveis ignoram as restrições de visibilidade.