Here at Coherent Labs, we have a large and cross-platform project. The build system is crucial for a project like ours – it pretty much defines the programmer workflow and iteration time. How we chose our build system deserves a blog post of its own. But at the end – we chose premake – it is very high level, generates almost perfect Visual Studio project files and you have a complete programming language for describing how to build your project.
The idea for this post was to show how to convert a classic C library using
autotools (./configure && make) to a premake-based project. But half of the post would be a repetition of
Converting a C library to gyp, because the most complex part is generating all the config files for the current platform. So I decided to take
node-lame, a node-gyp based project and convert it to premake.
Premake projects are written in plain Lua, typically in a file called premake4.lua. The “Hello World” project file looks like:
Next step is to include the right config.h file. To make our lives simpler we are going to use
“token replacement”, a relatively new feature, available in the
premake-dev branch. Token replacement allows
the declarative syntax of premake to fallback to imperative functions. So we write a global function and place a call to it inside %{} in the include directory specification. This function will be called for each concrete configuration of the project and the %{} is replaced with the result.
libmpg123 has several functions written in assembly for x64 platforms, but they cannot be used on Windows. Here again we use Lua to avoid duplicating the lists of defines and files:
The next step is to define the dependencies between the projects. We include the projects in the solution and for each project declare that it links with its dependencies. This will ensure that all projects will be built in the correct order and the correct outputs will be used for linking. Unfortunately the include paths dependencies are not solved so easily. bindings depends on mpg123 and includes mpg123.h, so it needs deps/mpg123/src/libmpg123 as include path. But mpg123.h needs config.h, so bindings needs mpg123‘s config directory as include path, too. So each project has to know not only the directories of its dependencies, but their include directories also.
This is a point where gyp shines – ‘direct_dependent_settings’ allows you to add include and library paths to dependent projects. Premake has a planned feature –
“usage projects” that will provide similar functionality.
As I said node-lame uses node-gyp, which takes care of installing the correct headers and libraries of node.js for your platform. With enough scripting in Lua we can achieve the same effect, but, for simplicity, we are going to add an option to premake4, specifying the location of the node.js source tree with built libraries.
At last lets see where the high abstraction of premake makes it so good:
This section sets linking against the static run-time and enables debug symbols on Windows and the architecture for Xcode.
And the same for premake (but on all platforms)
You may ask why bother converting third party libraries to your own build system. Well – to be sure they are build with the correct (same) settings as your own project and to avoid building all variants and committing them under source control – here we already have 18 combinations of configurations and platforms – and no one wants to maintain that many binaries.
Follow Dimitar on Twitter: @DimitarNT