Bundlex — compiling C with Elixir made easy
Note: Bundlex has grown since this article was firstly published
In particular, it now supports C nodes besides NIFs. See Bundlex GitHub for more details.
When integrating Elixir with native code (e.g., written in C) one of the important aspects is adapting new tooling responsible for compiling that code into our workflow. Of course, a native compiler can be invoked manually, but this approach is far from perfect, mainly for the following reasons:
- you have to take care of compiling the code each time it changes,
- if someone uses your project as a dependency (no matter whether directly or indirectly), they still have to execute the native compiler manually,
- if you wish to support different operating systems, you have to handle this yourself,
- if you have several projects and dependencies in the native code between them, you need to resolve these dependencies somehow.
All these are problems we have encountered while developing Membrane. As we intended this framework to be as modular as possible, we have split it into multiple Elixir projects, which often rely on native libraries and sometimes depend on a common C code. We quickly realized that managing all the native stuff manually is a huge pain, and that’s why we created Bundlex. It lets you declare and use the native code in a convenient way.
In projects using Bundlex native code is grouped into NIFs. Here is an example of how they are declared:
When mix compiles our app, it runs Bundlex which creates and runs the compilation script for each NIF based on the above configuration. Built binaries (so/dll) are stored in the
priv directory, ready to be loaded. Simple as that!
NIFs may also be specified differently for different platforms:
For more details and all supported options for declaring NIFs, see the documentation of Bundlex.Project module.
Libraries generated by Bundlex can be loaded the same way as any other native library, but our tool provides a more convenient way of doing so:
Bundlex.Loader, needed NIF is identified by the name from the specification, and the boilerplate for loading it is generated. Moreover, native functions can be concisely declared with
defnif macro. A private counterpart,
defnifpis also available, being useful especially when some argument/result parsing is needed:
Let’s try it!
Sounds great? Be sure to take a look at the readme which describes step-by-step how to add Bundlex to your project. You can also check out how we use it in our tools, such as Shmex or Membrane elements.