Recently I was using IOKit in Rust and I found that existing FFI bindings on crates.io were not sufficient for my use case. I had two options:
- Create FFI bindings by hand.
- Use bindgen to generate FFI bindings from header files.
Since IOKit has a large and complex API surface, I felt that creating them by hand would be too cumbersome, so I ended up looking at
The premise of
bindgen is pretty simple, given a C header file, it will parse the header file and output Rust functions and structures that allow for FFI. Underneath the hood,
bingen relies on
libclang to parse the headers and then given the parsed results, it produces Rust code for FFI.
However, on macOS
bindgen fails out of the box to generate bindings for headers that are located in the macOS SDK and this is because Apple’s provided
libclang does not have the same logic as Apple’s provided
clang on where to find Framework headers.
A simple example can show this failure. A
wrapper.h file which references a macOS SDK provided header like
CoreFoundation.h will fail with
bindgen even though
clang can process it successfully.
However passing the same
bindgen results in an error.
libclang used by
bindgen is searching in
/System/Library/Frameworks which does not contain headers. Ever since macOS 10.14 Apple stopped placing headers in
/System/Library/Frameworks. Instead the headers are located in the SDK directories. Apple’s
clang knows to check in these directories.
The fix is to direct
libclang to the SDK directory which has the headers. It might be temping to hard-code the
Xcode.app directory however headers can also be installed by the Command Line Tools for Xcode which would be located in the
The fix would be not to hard code these directories but instead query the SDK path with the
xcrun tool. From the man page
xcrun provides a means to locate or invoke developer tools from the command-line, without requiring users to modify Makefiles or otherwise take inconvenient measures to support multiple Xcode tool chains.
xcrun the SDK path can be obtained not only for the macOS SDK but for the iOS SDK and other Apple platforms.
The path can be passed to
bindgen to ensure it can find the headers.
bindgen on macOS does not work out of the box for System Frameworks. The only way for
bindgen to process the framework headers is to be told where the macOS SDK path is and a way to do that is to use
xcrun will work regardless if the OS has a full
Xcode.app installation or just the Command Line Tools for XCode installed.