Geradores de origem

Confira nesta página uma explicação de alto nível do suporte à origem gerada e como ela pode ser usada no sistema de build.

Todos os geradores de origem fornecem funcionalidades parecidas com as do sistema de compilação. Os três casos de uso de geração de origem com suporte ao sistema de compilação geram vinculações C usando bindgen, interfaces AIDL e interfaces protobuf.

Caixas das origens geradas

Cada módulo Rust que gera código-fonte pode ser usado como uma caixa, exatamente como se fosse definido como uma rust_library. Isso significa que eles podem ser definidos como uma dependência nas propriedades rustlibs, rlibs e dylibs. O melhor padrão de uso do código da plataforma é empregar a origem gerada como uma caixa. Embora a macro include! ofereça suporte a origens geradas, a finalidade principal dela é oferecer suporte ao código de terceiros que reside em external/.

Em alguns casos, o código da plataforma ainda pode usar a origem gerada com a macro include!(). Por exemplo, quando você usa um módulo genrule para gerar a origem de maneira exclusiva.

Como usar include!() para incluir a origem gerada

O uso da origem gerada como uma caixa é abordado nos exemplos na página específica de cada módulo respectivo. Nesta seção, mostramos como referenciar a origem gerada pela macro include!(). Esse processo é parecido para todos os geradores de origem.

Pré-requisito

Neste exemplo, pressupomos que você tenha definido um módulo rust_bindgen (libbuzz_bindgen) e pode seguir as Etapas para incluir a origem gerada a fim de usar a macro include!(). Caso contrário, acesse Como definir um módulo rust bindinggen, crie libbuzz_bindgen e volte até esta parte do guia.

As partes do arquivo do build do módulo são aplicáveis a todos os geradores de origem.

Etapas para incluir a origem gerada

Crie external/rust/hello_bindgen/Android.bp com o conteúdo abaixo:

rust_binary {
   name: "hello_bzip_bindgen_include",
   srcs: [
         // The primary rust source file must come first in this list.
         "src/lib.rs",

         // The module providing the bindgen bindings is
         // included in srcs prepended by ":".
         ":libbuzz_bindgen",
    ],

    // Dependencies need to be redeclared when generated source is used via srcs.
    shared_libs: [
        "libbuzz",
    ],
}

Crie external/rust/hello_bindgen/src/bindings.rs com o conteúdo abaixo:

#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
#![allow(missing_docs)]

// Note that "bzip_bindings.rs" here must match the source_stem property from
// the rust_bindgen module.
include!(concat!(env!("OUT_DIR"), "/bzip_bindings.rs"));

Crie external/rust/hello_bindgen/src/lib.rs com o conteúdo abaixo:

mod bindings;

fn main() {
    let mut x = bindings::foo { x: 2 };
    unsafe { bindings::fizz(1, &mut x as *mut bindings::foo) }
}

Por que usar caixas para origens geradas

Ao contrário dos compiladores C/C++, o rustc aceita apenas um único arquivo de origem que representa um ponto de entrada para um binário ou biblioteca. Ele espera que a árvore de origem seja estruturada de modo que todos os arquivos de origem necessários sejam descobertos automaticamente. Isso significa que a origem gerada precisa ser colocada na árvore de origem ou fornecida por uma diretiva de inclusão na origem:

include!("/path/to/hello.rs");

A comunidade do Rust depende dos scripts build.rs e das suposições sobre o ambiente de build do Cargo para trabalhar com essa diferença (link em inglês). Ao fazer a criação, o comando cargo define uma variável de ambiente OUT_DIR (link em inglês) em que os scripts build.rs precisam colocar o código-fonte gerado. Use o comando abaixo para incluir o código-fonte:

include!(concat!(env!("OUT_DIR"), "/hello.rs"));

Isso apresenta um desafio para o Soong, já que as saídas para cada módulo são colocadas no próprio diretório out/1. As dependências não armazenam a origem gerada em apenas um OUT_DIR.

Para o código da plataforma, o AOSP prefere o empacotamento da origem gerada em uma caixa que pode ser importada, por estes motivos:

  • Evitar a colisão de nomes de arquivos de origem gerados.
  • Reduzir o código boilerplate (link em inglês) registrado em toda a árvore que exige manutenção. Qualquer código boilerplate que seja necessário para fazer a compilação de origem gerada em uma caixa pode ser mantido de forma centralizada.
  • Evitar interações implícitas2 entre o código gerado e a caixa ao redor.
  • Reduzir a pressão sobre a memória e o disco vinculando dinamicamente as origens geradas usadas com frequência.

Como resultado, todos os tipos de módulo de geração de origem do Rust no Android produzem código que pode ser compilado e usado como uma caixa. O Soong ainda oferecerá suporte às caixas de terceiros sem modificação se todas as dependências de origem geradas para um módulo forem copiadas em um único diretório por módulo, de forma semelhante ao Cargo. Nesses casos, o Soong define a variável de ambiente OUT_DIR para esse diretório ao compilar o módulo. Assim, a origem gerada poderá ser encontrada. No entanto, pelos motivos já descritos, a prática recomendada é usar esse mecanismo no código da plataforma apenas quando for absolutamente necessário.


  1. Isso não apresenta problemas para o C/C++ e linguagens parecidas, porque o caminho da origem gerada é fornecido diretamente ao compilador. 

  2. Como include! funciona por inclusão textual, ele pode referenciar valores do namespace delimitador, modificar o namespace ou usar construções como #![foo]. Essas interações implícitas podem ser difíceis de manter. Sempre dê preferência às macros quando a interação com o restante da caixa for realmente necessária.