Bind library code generation tutorial

Bind libraries can auto-generate code to help driver authors refer to items in bind libraries from driver code. Currently the build will produce code for C++ and Rust. This tutorial will go into detail on using these auto-generated code targets.

This guide assumes familiarity with the following concepts:

An example bind library

First let's define an example bind library with some different definitions. We will also make it depend on another existing bind library. This is optional but for completeness we'll include it.

BUILD.gn

bind_library("my_bindlib_target") {  # target_name
  source = "mybindlib.bind"
  name = "fuchsia.example.library"  # library_name (optional, defaults to
                                    # target_name)
  public_deps = [ "//src/devices/bind/fuchsia.pci" ]
}

Most of the time the build target name and the library name are the same (it's not necessary to supply the name property, just put the library name as the build target name). But here we define them separately to help distinguish where each one is used later on.

mybindlib.bind

library fuchsia.example.library;

using fuchsia.pci;

string Name;
string DeviceCategory {
  KB = "keyboard",
  Mouse = "mouse",
};

uint I2C_ADDRESS;
enum I2C_PROTOCOL {
  Device,
  Impl,
};

bool Flag {
  ENABLE = true,
  DISABLE = false,
};

extend uint fuchsia.BIND_PCI_VID {
  GIZMOTRONICS = 0x314159,
};

Auto-generated libraries

The generated build target

C++

The build will automatically give us a new target based on the target_name of the bind library, :{target_name}_cpp

In our example from earlier this was my_bindlib_target. So the target will be :my_bindlib_target_cpp.

Rust

The build will automatically give us a new target based on the target_name of the bind library, :{target_name}_rust

In our example from earlier this was my_bindlib_target. So the target will be :my_bindlib_target_rust.

Using the generated library

C++

This is a source_set target that contains a header file with constants generated from the bind library. Driver authors can use this when they are creating node properties. They just need to create a dependency from their executable or other C++ based target on this.

The include path and namespace will be based on the library_name of the bind library (with some replacements of the .s in the library name), #include <bind/{library_name with slashes}/cpp/bind.h> and namespace bind_{library_name with underscores}.

In our example from earlier the library_name was fuchsia.example.library. So we will have #include <bind/fuchsia/example/library/cpp/bind.h> and namespace bind_fuchsia_example_library.

Rust

This is a rustc_library target, which is a Rust crate with a root module with constants generated from the bind library. Driver authors can use this when they are creating node properties. They just need to create a dependency from their Rust based targets on this.

The crate name to reference in use statements will be based on the library_name of the bind library (with some replacements of the .s in the library name), use bind_{library_name with underscores};.

In our example from earlier the library_name was fuchsia.example.library so we will have use bind_fuchsia_example_library;.

The generated header file

C++


// WARNING: This file is machine generated by bindc.

#ifndef BIND_FUCHSIA_EXAMPLE_LIBRARY_BINDLIB_
#define BIND_FUCHSIA_EXAMPLE_LIBRARY_BINDLIB_

#include <string>

#include <bind/fuchsia/pci/cpp/bind.h>

namespace bind_fuchsia_example_library {

static const std::string NAME = "fuchsia.example.library.Name";

static const std::string DEVICECATEGORY = "fuchsia.example.library.DeviceCategory";
static const std::string DEVICECATEGORY_KB = "keyboard";
static const std::string DEVICECATEGORY_MOUSE = "mouse";

static const std::string I2C_ADDRESS = "fuchsia.example.library.I2C_ADDRESS";

static const std::string I2C_PROTOCOL = "fuchsia.example.library.I2C_PROTOCOL";
static const std::string I2C_PROTOCOL_DEVICE = "fuchsia.example.library.I2C_PROTOCOL.Device";
static const std::string I2C_PROTOCOL_IMPL = "fuchsia.example.library.I2C_PROTOCOL.Impl";

static const std::string FLAG = "fuchsia.example.library.Flag";
static constexpr bool FLAG_ENABLE = true;
static constexpr bool FLAG_DISABLE = false;

static constexpr uint32_t BIND_PCI_VID_GIZMOTRONICS = 3227993;

}  // namespace bind_fuchsia_example_library

#endif  // BIND_FUCHSIA_EXAMPLE_LIBRARY_BINDLIB_

Rust


// WARNING: This file is machine generated by bindc.

pub use bind_fuchsia_pci;

pub const NAME: &str = "fuchsia.example.library.Name";

pub const DEVICECATEGORY: &str = "fuchsia.example.library.DeviceCategory";
pub const DEVICECATEGORY_KB: &str = "keyboard";
pub const DEVICECATEGORY_MOUSE: &str = "mouse";

pub const I2C_ADDRESS: &str = "fuchsia.example.library.I2C_ADDRESS";

pub const I2C_PROTOCOL: &str = "fuchsia.example.library.I2C_PROTOCOL";
pub const I2C_PROTOCOL_DEVICE: &str = "fuchsia.example.library.I2C_PROTOCOL.Device";
pub const I2C_PROTOCOL_IMPL: &str = "fuchsia.example.library.I2C_PROTOCOL.Impl";

pub const FLAG: &str = "fuchsia.example.library.Flag";
pub const FLAG_ENABLE: bool = true;
pub const FLAG_DISABLE: bool = false;

pub const BIND_PCI_VID_GIZMOTRONICS: u32 = 3227993;

The generated constants

Keys

For every key in the bind library, there is a string constant identifier of the same name, but in all uppercase. The value of the string will be the full name of the key. The full name contains the library_name of the bind library and the key name, with no casing adjustments, separated by a ..

C++

If we have a key called keyName in a library called fuchsia.example.library, this will be the generated constant: std::string KEYNAME = "fuchsia.example.library.keyName";.

Rust

If we have a key called keyName in a library called fuchsia.example.library, this will be the generated constant: pub const KEYNAME: &str = "fuchsia.example.library.keyName";.

Values

For every value defined for a key in the bind library there is a constant based on the type of the key (see table below). The identifier of the constant is the all uppercase version of the key and value name separated by an underscore.

For example if there is a key foo with two values defined for it bar and baz, then there are two identifiers for the values, FOO_BAR and FOO_BAZ.

C++

Key type C++ constant type C++ constant value
uint uint32_t Integer value from entry
string std::string String value from entry
bool bool Boolean value from entry
enum std::string Full name of the enum (see below)

Rust

Key type Rust constant type Rust constant value
uint u32 Integer value from entry
string &str String value from entry
bool bool Boolean value from entry
enum &str Full name of the enum (see below)

The full name of an enum contains the library_name, key name, and value name, all separated by .s.

C++

If we have a value called someValue under a enum based key enumeratedKey in our library called fuchsia.example.library, then the generated constant will be std::string ENUMERATEDKEY_SOMEVALUE = "fuchsia.example.library.enumeratedKey.someValue";.

Rust

If we have a value called someValue under a enum based key enumeratedKey in our library called fuchsia.example.library, then the generated constant will be pub const ENUMERATEDKEY_SOMEVALUE: &str = "fuchsia.example.library.enumeratedKey.someValue";.

Dependencies

C++

Because our bind library example depended on //src/devices/bind/fuchsia.pci, another bind library, our generated code has automatically included the header generated from that as well. So by including this header, the code can refer to values from the base bind library as well.

Rust

Because our bind library depended on //src/devices/bind/fuchsia.pci, another bind library, our generated code has automatically imported the generated crate from that as well. The difference with the C++ header is that in Rust the user will have to nest the crate names when referring to a dependency crate. See example below for what this looks like.

Example usage

BUILD.gn

C++

source_set("parent_cpp_code") {
  sources = [ "parent-driver.cc" ]
  deps = [ ":my_bindlib_target_cpp" ]
}

Rust

rustc_binary("parent_rust_code") {
  edition = "2021"
  source_root = "parent-driver.rs"
  sources = [ "parent-driver.rs" ]
  deps = [ ":my_bindlib_target_rust" ]
}

parent-driver

C++

#include <bind/fuchsia/example/library/cpp/bind.h>

std::string a = bind_fuchsia_example_library::NAME;
uint32_t b = bind_fuchsia_example_library::BIND_PCI_VID_GIZMOTRONICS;
uint32_t c = bind_fuchsia_pci::BIND_PROTOCOL_DEVICE;

Rust

use bind_fuchsia_example_library;
use bind_fuchsia_example_library::bind_fuchsia_pci;

fn main() {
    let _a: &str = bind_fuchsia_example_library::NAME;
    let _b: u32 = bind_fuchsia_example_library::BIND_PCI_VID_GIZMOTRONICS;
    let _c: u32 = bind_fuchsia_pci::BIND_PROTOCOL_DEVICE;
}

Using Out-Of-Tree with the SDK

To see how to use this feature Out-Of-Tree with the SDK see this page: