Como criar com plataformas

Informar um problema Ver origem

O Bazel tem suporte sofisticado à modelagem de plataformas e conjuntos de ferramentas. A integração desse recurso com projetos reais requer uma cooperação cuidadosa entre proprietários de código, mantenedores de regras e desenvolvedores principais do Bazel.

Esta página resume o propósito das plataformas e mostra como criar com elas.

tl;dr: as APIs de plataforma e de conjunto de ferramentas do Bazel estão disponíveis, mas não funcionam em todos os lugares até que todas as regras de linguagem, select()s e outras referências legadas sejam atualizadas. Esse trabalho é constante. Todos os builds serão baseados em plataforma. Leia abaixo para ver onde seus builds se encaixam.

Para documentação mais formal, consulte:

Contexto

As plataformas e os conjuntos de ferramentas foram introduzidos para padronizar a forma como os projetos de software visam máquinas diferentes e criam com as ferramentas de linguagem corretas.

Essa é uma adição relativamente recente ao Bazel. Ele foi inspirado pela observação de que os mantenedores de linguagem faziam isso de maneiras ad hoc e incompatíveis. Por exemplo, as regras de C++ usam --cpu e --crosstool_top para definir a CPU de destino e o conjunto de ferramentas do C++ de uma versão. Nenhuma das opções modela corretamente uma "plataforma". As tentativas históricas de fazer isso causaram builds estranhos e imprecisos. Essas sinalizações também não controlam a compilação Java, que desenvolveu a própria interface independente com --java_toolchain.

O Bazel é destinado a projetos grandes, com várias linguagens e várias plataformas. Isso exige suporte mais fundamentado para esses conceitos, incluindo APIs claras que incentivam a interoperabilidade da linguagem e do projeto. É para isso que essas novas APIs servem.

Migração

As APIs de plataforma e de conjunto de ferramentas só funcionam quando os projetos as utilizam. Isso não é trivial porque a lógica da regra, os conjuntos de ferramentas, as dependências e select()s de um projeto precisam ser compatíveis com eles. Isso requer uma sequência de migração cuidadosa para manter todos os projetos e as dependências funcionando corretamente.

Por exemplo, as Regras C++ do Bazel oferecem suporte a plataformas. Mas as Regras da Apple, não. Seu projeto em C++ pode não se importar com a Apple. Outras podem talvez. Por isso, ainda não é seguro ativar plataformas globalmente para todos os builds C++.

O restante desta página descreve essa sequência de migração e como e quando seus projetos podem se encaixar.

Meta

A migração da plataforma do Bazel é concluída quando todos os projetos são compilados com o formulário:

bazel build //:myproject --platforms=//:myplatform

Isso implica:

  1. As regras usadas pelo projeto podem inferir os conjuntos de ferramentas corretos de //:myplatform.
  2. As regras que as dependências do projeto usam podem inferir os conjuntos de ferramentas corretos de //:myplatform.
  3. Ou os projetos que dependem do seu oferecem suporte a //:myplatform ou seu projeto oferece suporte às APIs legadas (como --crosstool_top).
  4. //:myplatform faz referência a [declarações comuns][Common Platform Declaration]{: .external} de CPU, OS e outros conceitos genéricos que são compatíveis com a compatibilidade automática entre projetos.
  5. Todos os select()s dos projetos relevantes entendem as propriedades de máquina implícitas por //:myplatform.
  6. O //:myplatform é definido em um local claro e reutilizável: no repositório do seu projeto se a plataforma for exclusiva dele. Caso contrário, em algum lugar, todos os projetos que podem ser usados por essa plataforma podem ser encontrados.

As APIs antigas serão removidas assim que a meta for alcançada. Essa será a maneira padrão que os projetos usam para selecionar plataformas e conjuntos de ferramentas.

Devo usar plataformas?

Se você quiser apenas criar ou fazer uma compilação cruzada de um projeto, siga a documentação oficial dele.

Se você mantiver um projeto, uma linguagem ou um conjunto de ferramentas, talvez queira oferecer suporte às novas APIs. A espera até a conclusão da migração global ou a ativação antecipada depende das suas necessidades específicas de valor / custo:

Valor

  • Você pode usar select() ou escolher conjuntos de ferramentas com as propriedades exatas que quiser, em vez de sinalizações codificadas, como --cpu. Por exemplo, várias CPUs podem ser compatíveis com o mesmo conjunto de instruções.
  • Builds mais corretos. Se você select() com --cpu no exemplo acima e adicionar uma nova CPU compatível com o mesmo conjunto de instruções, o select() não reconhecerá a nova CPU. No entanto, um select() nas plataformas permanece preciso.
  • Experiência do usuário mais simples. Todos os projetos entendem: --platforms=//:myplatform. Não é necessário ter vários sinalizadores específicos de linguagem na linha de comando.
  • Design de linguagem mais simples. Todas as linguagens compartilham uma API comum para definir, usar e selecionar o conjunto de ferramentas correto para uma plataforma.
  • Os destinos podem ser ignorados na fase de criação e teste se forem incompatíveis com a plataforma de destino.

Custos

  • Projetos dependentes que ainda não são compatíveis com plataformas podem não funcionar automaticamente com o seu.
  • Para fazê-los funcionar, você pode precisar de manutenção temporária adicional.
  • A coexistência de APIs novas e legadas requer uma orientação do usuário mais cuidadosa para evitar confusão.
  • As definições canônicas de propriedades comuns, como OS e CPU, ainda estão em evolução e podem exigir mais contribuições iniciais.
  • As definições canônicas de conjuntos de ferramentas específicos da linguagem ainda estão em evolução e podem exigir contribuições iniciais extras.

Revisão da API

Um platform é uma coleção de destinos constraint_value:

platform(
    name = "myplatform",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:arm",
    ],
)

Uma constraint_value é uma propriedade de máquina. Valores do mesmo "tipo" são agrupados em um constraint_setting comum:

constraint_setting(name = "os")
constraint_value(
    name = "linux",
    constraint_setting = ":os",
)
constraint_value(
    name = "mac",
    constraint_setting = ":os",
)

Uma toolchain é uma regra Starlark. Os atributos dele declaram as ferramentas de uma linguagem (como compiler = "//mytoolchain:custom_gcc"). Os provedores transmitem essas informações para regras que precisam ser criadas com essas ferramentas.

Os conjuntos de ferramentas declaram os constraint_values de máquinas em que podem segmentar (target_compatible_with = ["@platforms//os:linux"]) e as máquinas em que as ferramentas podem ser executadas (exec_compatible_with = ["@platforms//os:mac"]).

Ao criar $ bazel build //:myproject --platforms=//:myplatform, o Bazel seleciona automaticamente um conjunto de ferramentas que pode ser executado na máquina de build e binários de build para //:myplatform. Isso é conhecido como resolução do conjunto de ferramentas.

O conjunto de conjuntos de ferramentas disponíveis pode ser registrado no WORKSPACE com register_toolchains ou na linha de comando com --extra_toolchains.

Veja mais detalhes aqui.

Status

A compatibilidade atual com a plataforma varia de acordo com o idioma. Todas as principais regras do Bazel são migradas para plataformas. Mas esse processo levará algum tempo. Isso se deve a três motivos principais:

  1. A lógica da regra precisa ser atualizada para receber informações da ferramenta da nova API do conjunto de ferramentas (ctx.toolchains) e interromper a leitura das configurações legadas, como --cpu e --crosstool_top. Isso é relativamente simples.

  2. Os mantenedores do conjunto de ferramentas precisam definir conjuntos de ferramentas e torná-los acessíveis aos usuários (em repositórios do GitHub e entradas WORKSPACE). Isso é tecnicamente simples, mas precisa ser organizado de maneira inteligente para manter uma experiência fácil do usuário.

    As definições de plataforma também são necessárias (a menos que você crie para a mesma máquina em que o Bazel é executado). Geralmente, os projetos precisam definir as próprias plataformas.

  3. É necessário migrar projetos existentes. select()s e transições também precisam ser migrados. Esse é o maior desafio. Isso é particularmente desafiador para projetos com várias linguagens (que podem falhar se todos os idiomas não puderem ler --platforms).

Se você estiver criando um novo conjunto de regras, precisará oferecer suporte às plataformas desde o início. Isso torna automaticamente suas regras compatíveis com outras regras e projetos, aumentando o valor à medida que a API da plataforma se torna mais onipresente.

Propriedades comuns da plataforma

Propriedades de plataforma, como OS e CPU, comuns em projetos, precisam ser declaradas em um local padrão e centralizado. Isso incentiva a compatibilidade entre projetos e linguagens.

Por exemplo, se MyApp tiver um select() em constraint_value @myapp//cpus:arm e SomeCommonLib tiver um select() em @commonlib//constraints:arm, os modos "arm" serão acionados com critérios incompatíveis.

Propriedades globalmente comuns são declaradas no repositório @platforms. Portanto, o rótulo canônico do exemplo acima é @platforms//cpu:arm. As propriedades linguísticas comuns precisam ser declaradas nos repositórios das respectivas linguagens.

Plataformas padrão

Geralmente, os proprietários de projetos precisam definir plataformas explícitas para descrever os tipos de máquinas para as quais querem criar. Em seguida, eles são acionados com --platforms.

Quando --platforms não está definido, o padrão do Bazel é definido como um platform que representa a máquina de build local. Como ele é gerado automaticamente em @local_config_platform//:host, não é necessário defini-lo explicitamente. Ele mapeia o OS e o CPU da máquina local com constraint_values declarados em @platforms.

C++

As regras em C++ do Bazel usam plataformas para selecionar conjuntos de ferramentas quando você define --incompatible_enable_cc_toolchain_resolution (#7260).

Isso significa que é possível configurar um projeto C++ com:

bazel build //:my_cpp_project --platforms=//:myplatform

em vez da legada:

bazel build //:my_cpp_project` --cpu=... --crosstool_top=...  --compiler=...

Se seu projeto for C++ puro e não depender de projetos que não sejam C++, você poderá usar plataformas com segurança, desde que as selects e as transições sejam compatíveis. Consulte No 7260 e Como configurar os conjuntos de ferramentas do C++ para ver mais orientações.

Esse modo não está ativado por padrão. Isso ocorre porque os projetos da Apple ainda configuram dependências C++ com --cpu e --crosstool_top (exemplo). Isso depende das regras da Apple migrando para plataformas.

Java

As regras Java do Bazel usam plataformas.

Isso substitui as sinalizações legadas --java_toolchain, --host_java_toolchain, --javabase e --host_javabase.

Para saber como usar os sinalizadores de configuração, consulte o manual do Bazel e do Java (em inglês). Para mais informações, consulte o documento de design.

Se você ainda estiver usando sinalizações legadas, siga o processo de migração no Problema 7849.

Android

As regras do Bazel para Android usam plataformas para selecionar conjuntos de ferramentas quando você define --incompatible_enable_android_toolchain_resolution.

Essa opção não é ativada por padrão. Mas a migração está a caminho.

Apple

As regras da Apple do Bazel ainda não têm suporte a plataformas para selecionar conjuntos de ferramentas da Apple.

Eles também não são compatíveis com dependências C++ ativadas para plataforma porque usam o --crosstool_top legado para definir o conjunto de ferramentas do C++. Até que isso seja migrado, é possível misturar projetos da Apple com C++ compatível com platorm e mapeamentos de plataforma (exemplo).

Outros idiomas

Se você estiver projetando regras para uma nova linguagem, use plataformas para selecionar os conjuntos de ferramentas da sua linguagem. Consulte a documentação sobre os conjuntos de ferramentas para ver um bom tutorial.

select()

Os projetos podem select() em destinos constraint_value, mas não podem concluir plataformas. Isso é intencional para que select()s ofereçam suporte à maior variedade possível de máquinas. Uma biblioteca com fontes específicas de ARM precisa oferecer suporte a todas as máquinas com tecnologia ARM, a menos que haja uma razão para ser mais específica.

Para selecionar em um ou mais constraint_values, use:

config_setting(
    name = "is_arm",
    constraint_values = [
        "@platforms//cpu:arm",
    ],
)

Isso equivale a selecionar tradicionalmente --cpu:

config_setting(
    name = "is_arm",
    values = {
        "cpu": "arm",
    },
)

Confira mais detalhes neste link.

selects no --cpu, --crosstool_top etc. não entendem --platforms. Ao migrar seu projeto para as plataformas, você precisa convertê-los em constraint_values ou usar mapeamentos de plataforma para oferecer suporte aos dois estilos na janela de migração.

Transições

As transições Starlark mudam as sinalizações para partes do seu gráfico de build. Se o projeto usar uma transição que defina --cpu, --crossstool_top ou outras sinalizações legadas, as regras que leem --platforms não terão essas mudanças.

Ao migrar seu projeto para as plataformas, você precisa converter mudanças como return { "//command_line_option:cpu": "arm" } em return { "//command_line_option:platforms": "//:my_arm_platform" } ou usar mapeamentos de plataforma para oferecer suporte aos dois estilos durante a janela de migração.

Como usar as plataformas atualmente

Se você quiser apenas criar ou fazer uma compilação cruzada de um projeto, siga a documentação oficial dele. Cabe aos mantenedores da linguagem e do projeto determinar como e quando integrar as plataformas e que valor isso oferece.

Se você for um administrador de projetos, linguagens ou conjuntos de ferramentas e sua versão não usar plataformas por padrão, você terá três opções além de aguardar a migração global:

  1. Ative a flag "usar plataformas" para os idiomas do seu projeto (se houver um) e faça os testes necessários para ver se os projetos importantes funcionam.

  2. Se os projetos importantes ainda dependem de sinalizações legadas como --cpu e --crosstool_top, use-as com --platforms:

    bazel build //:my_mixed_project --platforms==//:myplatform --cpu=... --crosstool_top=...
    

    Isso gera algum custo de manutenção (você precisa verificar manualmente se as configurações correspondem). Mas isso deve funcionar na ausência de transições renegadas.

  3. Crie mapeamentos de plataforma para oferecer suporte aos dois estilos, mapeando as configurações do estilo --cpu para as plataformas correspondentes e vice-versa.

Mapeamentos de plataforma

Os mapeamentos de plataforma são uma API temporária que permite a coexistência de lógicas baseadas em plataforma e legadas no mesmo build durante a janela de descontinuação do último.

Um mapeamento de plataforma é um mapa de uma platform() para um conjunto correspondente de sinalizações legadas ou vice-versa. Exemplo:

platforms:
  # Maps "--platforms=//platforms:ios" to "--cpu=ios_x86_64 --apple_platform_type=ios".
  //platforms:ios
    --cpu=ios_x86_64
    --apple_platform_type=ios

flags:
  # Maps "--cpu=ios_x86_64 --apple_platform_type=ios" to "--platforms=//platforms:ios".
  --cpu=ios_x86_64
  --apple_platform_type=ios
    //platforms:ios

  # Maps "--cpu=darwin --apple_platform_type=macos" to "//platform:macos".
  --cpu=darwin
  --apple_platform_type=macos
    //platforms:macos

Ele usa isso para garantir que todas as configurações, sejam baseadas na plataforma ou legadas, sejam aplicadas de maneira consistente em toda a compilação, inclusive por meio de transições.

Por padrão, o Bazel lê os mapeamentos do arquivo platform_mappings na raiz do espaço de trabalho. Também é possível definir --platform_mappings=//:my_custom_mapping.

Clique aqui para ver os detalhes completos.

Perguntas

Para suporte geral e dúvidas sobre o cronograma de migração, entre em contato com bazel-discuss@googlegroups.com ou com os proprietários das regras apropriadas.

Para discussões sobre o design e a evolução das APIs de plataforma/conjunto de ferramentas, entre em contato com bazel-dev@googlegroups.com.

Veja também