-
Notifications
You must be signed in to change notification settings - Fork 6
05 Automatically Generating the Binding Files
if you've been following along with our examples so far, you're probably thinking that writing these binding files would be a lot of work for large libraries like OpenEXR, let alone a monster like USD. You'd be right. I managed to write a complete set of binding files for OpenEXR in a couple of evenings' couch work, but it is a significant amount of typing. Most of it copy/paste and cleanup and mind-numbingly boring. Surely large parts of this could be automated?
To help out with that, there's genbind
and its companion wrapper script, genbind.py
. genbind
works on single headers, while genbind.py
can work on an entire header directory (recursively if necessary). You will almost always want to use genbind.py
.
NOTE: for now, you must copy src/genbind.py
to build/genbind/
in order for it to work
Running genbind.py -h
gives you the documentation
Usage:
genbind.py <header-path>... [--namespace <ns>] [--namespace-internal <nsi>] [--namespace-public <nsp>] [-v <verbosity>] [--clang-arg <arg>...] [--output-path <path>] [--format]
Options:
-v <level>, --verbosity <level> Verbosity level of output. 0=Errors, 1=Warnings, 2=Info, 3=Debug, 4=Trace
-n <ns>, --namespace <ns> Target namespace from which to scrape bindable items (e.g. Imf_2_5)
-i <nsi>, --namespace-internal <nsi> The target library's internal #define for the namespace (e.g. OPENEXR_IMF_INTERNAL_NAMESPACE)
-p <nsp>, --namespace-public <nsp> The target library's public #define for the namespace (e.g. Imf)
-a <arg>..., --clang-arg <arg>... Arguments to pass to Clang, e.g. the include path to the library. Can be repeated
-o <path>, --output-path <path> Directory under which to write the output binding files. Will be created if it does not exist
-f, --format Run clang-format on the output binding file
As an example, let's try generating a binding file for ImfArray.h from OpenEXR. We're using OpenEXR 2.5.5 for this example.
So, the first positional argument is the path to the header(s) we want to bind, or a directory containing headers, which will be glob-searched recursively. On my system, that looks like this, where I'm running from the build
directory
./genbind/genbind.py \
/home/anders/packages/openexr/2.5.5/include/OpenEXR/ImfArray.h
The next argument, -v
specifies the verbosity of the console output. The default value of 1 specifies warnings and errors. Higher levels are mostly interesting for debugging.
./genbind/genbind.py \
/home/anders/packages/openexr/2.5.5/include/OpenEXR/ImfArray.h \
-v 1 \
The next three arguments tell genbind about the namespace structure of the target library. --namespace
is the expanded namespace name. For OpenEXR 2.5.5 this would be Imf_2_5
. --namespace-internal
is the name of the library's internal #define that expands to that namespace name. So for OpenEXR this is OPENEXR_IMF_INTERNAL_NAMESPACE
. The final option, --namespace-public
is the name of the public-facing namespace. For OpenEXR this is Imf
.
./genbind/genbind.py \
/home/anders/packages/openexr/2.5.5/include/OpenEXR/ImfArray.h \
-v 1 \
--namespace Imf_2_5 \
--namespace-internal OPENEXR_IMF_INTERNAL_NAMESPACE \
--namespace-public Imf
Next we need to specify arguments to clang such as the include paths to the target library, and to any dependencies of the target library. These are passed with the -a
flag, and the flags themselves must be quoted:
./genbind/genbind.py \
/home/anders/packages/openexr/2.5.5/include/OpenEXR/ImfArray.h \
-v 1 \
--namespace Imf_2_5 \
--namespace-internal OPENEXR_IMF_INTERNAL_NAMESPACE \
--namespace-public Imf \
-a "-I/home/anders/packages/openexr/2.5.5/include/" \
-a "-I/home/anders/packages/openexr/2.5.5/include/OpenEXR"
Note that the "double up" in the include paths for OpenEXR is an issue with OpenEXR that's fixed in 3.0
Finally, we need to tell genbind where to put the generated binding files with the -o
flag. This is just the path to a directory that will be created if it doesn't exist. The filenames for the binding files will be generated automatically from the header filenames. With all that, we have the complete command for genbind:
./genbind/genbind.py \
/home/anders/packages/openexr/2.5.5/include/OpenEXR/ImfArray.h \
-v 1 \
--namespace Imf_2_5 \
--namespace-internal OPENEXR_IMF_INTERNAL_NAMESPACE \
--namespace-public Imf \
-a "-I/home/anders/packages/openexr/2.5.5/include/" \
-a "-I/home/anders/packages/openexr/2.5.5/include/OpenEXR" \
-o gb_openexr
Running this should give you no output in the shell and create a gb_openexr
directory with a single file called imf_array.cpp
that looks like this:
#include <OpenEXR/ImfArray.h>
#include <cppmm_bind.hpp>
namespace cppmm_bind {
namespace OPENEXR_IMF_INTERNAL_NAMESPACE {
namespace Imf = ::OPENEXR_IMF_INTERNAL_NAMESPACE;
template <class T>
struct Array {
using BoundType = Imf::Array<T>;
Array<T>();
Array<T>(long size);
~Array<T>();
auto operator type-parameter-0-0 *() -> T*;
auto operator const type-parameter-0-0 *() const -> const T*;
auto resizeErase(long size) -> void;
auto resizeEraseUnsafe(long size) -> void;
auto size() const -> long;
} CPPMM_OPAQUEBYTES; // struct Array
// TODO: fill in explicit instantiations, e.g.:
// template class Array<int>;
// using ArrayInt = Imf::Array<int>;
template <class T>
struct Array2D {
using BoundType = Imf::Array2D<T>;
Array2D<T>();
Array2D<T>(long sizeX, long sizeY);
~Array2D<T>();
auto operator[](long x) -> T*;
auto operator[](long x) const -> const T*;
auto resizeErase(long sizeX, long sizeY) -> void;
auto resizeEraseUnsafe(long sizeX, long sizeY) -> void;
auto height() const -> long;
auto width() const -> long;
} CPPMM_OPAQUEBYTES; // struct Array2D
// TODO: fill in explicit instantiations, e.g.:
// template class Array2D<int>;
// using Array2DInt = Imf::Array2D<int>;
} // namespace OPENEXR_IMF_INTERNAL_NAMESPACE
} // namespace cppmm_bind
// TODO: fill in explicit instantiations
// template class Imf::Array<int>;
// template class Imf::Array2D<int>;
It's still a work in progress so it's not quite perfect (operator type-parameter-0-0
), but it's pretty close. The main thing you need to watch out for is the // TODO:
comments. These are there to help with the explicit instantiations you need to add for any templated types. Just replace the int
in each place with whatever type you want to instantiate, and copy all of the lines to add more types.
That's it! If you're feeling brave try generating the bindings for the rest of OpenEXR by just pointing genbind.py at the directory instead of just one file, and try binding other libraries!