源代码生成器

本页概述了构建系统如何支持生成的源代码以及如何在构建系统中使用它。

所有源代码生成器都提供类似的构建系统功能。构建系统支持的三种源代码生成用例是:使用 bindgen 生成 C 绑定、AIDL 接口和 protobuf 接口。

来自生成的源代码的包

每个生成源代码的 Rust 模块都可以用作包,就像它被定义为 rust_library 一样。(这意味着它可以定义为 rustlibsrlibsdylibs 属性中的依赖项。)平台代码的最佳使用模式是将生成的源代码用作包。虽然 include! 宏支持生成的源代码,但其主要目的是支持位于 external/ 中的第三方代码。

在某些情况下,平台代码可能仍通过 include!() 宏使用生成的源代码,例如当您使用 genrule 模块以独特的方式生成源代码时。

使用 include!() 包含生成的源代码

在每个特定(各自)模块页面中的示例中介绍了如何将生成的源代码用作包。本节介绍如何通过 include!() 宏引用生成的源代码。请注意,此过程对于所有源代码生成器都类似。

前提条件

本示例基于以下假设:您已定义 rust_bindgen 模块 (libbuzz_bindgen),并且可以继续执行包含生成的源代码的步骤,以使用 include!() 宏。如果您尚未执行此操作,请转到定义 rust bindgen 模块,创建 libbuzz_bindgen,然后返回此处。

请注意,此构建文件的部分适用于所有源代码生成器。

包含生成的源代码的步骤

使用以下内容创建 external/rust/hello_bindgen/Android.bp

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

使用以下内容创建 external/rust/hello_bindgen/src/bindings.rs

#![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"));

使用以下内容创建 external/rust/hello_bindgen/src/lib.rs

mod bindings;

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

为什么为生成的源代码使用包

与 C/C++ 编译器不同,rustc 仅接受表示二进制文件或库入口点的单个源文件。它期望源树的结构使得可以自动发现所有必需的源文件。这意味着生成的源代码必须放置在源树中,或通过源文件中的 include 指令提供

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

Rust 社区依赖于 build.rs 脚本以及关于 Cargo 构建环境的假设,以解决此差异。构建时,cargo 命令会设置一个 OUT_DIR 环境变量build.rs 脚本应将生成的源代码放置在该变量中。使用以下命令包含源代码

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

这对 Soong 提出了挑战,因为每个模块的输出都放置在它们自己的 out/ 目录中1。没有一个 OUT_DIR,依赖项在其中输出它们生成的源代码。

对于平台代码,AOSP 更倾向于将生成的源代码打包到可以导入的包中,原因如下:

  • 防止生成的源代码文件名冲突。
  • 减少在整个树中检入的样板代码,这些代码需要维护。使生成的源代码编译成包所需的任何样板代码都可以在中心位置维护。
  • 避免生成的代码与周围的包之间存在隐式2交互。
  • 通过动态链接常用的生成的源代码,减少内存和磁盘压力。

因此,Android 的所有 Rust 源代码生成模块类型都会生成可以编译和使用的代码作为包。如果模块的所有生成的源代码依赖项都复制到单个按模块目录中(类似于 Cargo),则 Soong 仍然支持第三方包而无需修改。在这种情况下,Soong 会在编译模块时将 OUT_DIR 环境变量设置为该目录,以便可以找到生成的源代码。但是,由于已描述的原因,最佳实践是仅在绝对必要时才在平台代码中使用此机制。


  1. 这对于 C/C++ 和类似语言来说不会造成任何问题,因为生成的源代码的路径是直接提供给编译器的。 

  2. 由于 include! 通过文本包含工作,因此它可能会引用封闭命名空间中的值、修改命名空间或使用 #![foo] 等构造。这些隐式交互可能难以维护。当确实需要与包的其余部分交互时,始终首选宏。