Source Generators

This page provides a high-level view of how generated source is supported and how it can be used in the build system.

All source generators provide similar build-system functionality. The three build-system supported source-generation use cases are generating C bindings using bindgen, AIDL interfaces, and protobuf interfaces.

Crates from generated source

Every Rust module that generates source code can be used as a crate, exactly as if it were defined as a rust_library. (This means it can be defined as a dependency in the rustlibs, rlibs, and dylibs properties.) The best usage pattern for platform code is to employ generated source as a crate. Although the include! macro is supported for generated source, its primary purpose is to support third-party code that resides in external/.

There are cases where platform code might still use generated source through the include!() macro, such as when you use a genrule module to generate source in a unique fashion.

Using include!() to include generated source

Using generated source as a crate is covered by the examples in each specific (respective) module page. This section shows how to reference generated source through the include!() macro. Note that this process is similar for all source generators.

Prerequisite

This example is based on the assumption that you have defined a rust_bindgen module (libbuzz_bindgen) and can proceed to the Steps for including generated source for using theinclude!() macro. If you haven't, please go to Defining a rust bindgen module, create libbuzz_bindgen, then return here.

Note that the build-file portions of this are applicable for all source generators.

Steps for including generated source

Create external/rust/hello_bindgen/Android.bp with the following contents:

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

Create external/rust/hello_bindgen/src/bindings.rs with the following contents:

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

Create external/rust/hello_bindgen/src/lib.rs with the following contents:

mod bindings;

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

Why crates for generated source

Unlike C/C++ compilers, rustc only accepts a single source file representing an entry point to a binary or library. It expects that the source tree is structured such that all required source files can be automatically discovered. This means that generated source must either be placed in the source tree, or provided through an include directive in source:

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

The Rust community depends on build.rs scripts, and assumptions about the Cargo build environment, to work with this difference. When it builds, the cargo command sets an OUT_DIR environment variable into which build.rs scripts are expected to place generated source code. Use the following command to include source code:

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

This presents a challenge for Soong as outputs for each module are placed in their own out/ directory1. There isn't a single OUT_DIR where dependencies output their generated source.

For platform code, AOSP prefers packaging generated source into a crate that can be imported, for several reasons:

  • Prevent generated source file names from colliding.
  • Reduce boilerplate code checked-in throughout the tree that requires maintenance. Any boilerplate that's required to make the generated source compile into a crate can be centrally maintained.
  • Avoid implicit2 interactions between generated code and the surrounding crate.
  • Reduce pressure on memory and disk by dynamically linking commonly used generated sources.

As a result, all of Android’s Rust source generation module types produce code that can be compiled and used as a crate. Soong still supports third-party crates without modification if all the generated source dependencies for a module get copied into a single per-module directory, similar to Cargo. In such cases, Soong sets the OUT_DIR environment variable to that directory when compiling the module, so the generated source can be found. However, for the reasons already described, it's best practice to only use this mechanism in platform code when it's absolutely necessary.


  1. This doesn't present any problems for C/C++ and similar languages, as the path to the generated source is provided directly to the compiler. 

  2. Since include! works by textual inclusion, it might reference values from the enclosing namespace, modify the namespace, or use constructs like #![foo]. These implicit interactions can be difficult to maintain. Always prefer macros when interaction with the rest of the crate is truly required.