Using bindgen
with System Frameworks on macOS
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 bindgen
.
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 wrapper.h
to bindgen
results in an error.
The 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 /usr/include
and /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 bindgen
and 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 /Library/Developer/CommandLineTools
directory.
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.
Using 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.
To conclude, 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
. Using xcrun
will work regardless if the OS has a full Xcode.app
installation or just the Command Line Tools for XCode installed.