源代码生成器

本文将简要介绍如何支持生成的源代码,以及如何在构建系统中使用生成的源代码。

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

使用生成的源代码作为 crate

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

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

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

每个具体模块各自的页面中都举例说明了如何使用生成的源代码作为 crate。本部分介绍如何通过 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) }
}

为什么要使用生成的源代码作为 crate

与 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 倾向于将生成的源代码打包成可以导入的 crate:

  • 防止生成的源代码的文件名发生冲突。
  • 减少整个树中签入的需要维护的样板代码。可以集中维护将生成的源代码编译成 crate 所需的所有样板代码。
  • 避免生成的代码与周围的 crate 之间的隐式2 交互。
  • 动态关联生成的常用源代码,减轻内存和磁盘的压力。

因此,Android 的所有 Rust 源代码生成模块类型都会生成可以编译和用作 crate 的代码。 如果将某个模块生成的源代码的所有依赖项都复制到单个模块级目录中(像 Cargo 一样),那么 Soong 仍能支持第三方 crate,而无需进行修改。在这种情况下,Soong 会在编译此模块时将 OUT_DIR 环境变量设置为该目录,以便找到生成的源代码。但是,出于已经介绍过的原因,最佳做法是仅在绝对必要时才在平台代码中使用此机制。


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

  2. 由于 include! 是以文本包含的方式工作,因此它可能会引用来自外围命名空间的值、修改命名空间或使用 #![foo] 等构造。这些隐式交互可能难以维护。如果确实需要与 crate 的其余部分交互,始终应首选宏。