I recently had the desire to speed up a build for a medium sized C program which uses a make build system. I had previously used CMake for this project, but had moved back to Make for reasons I may explore in a different post. One takeway I had from using CMake was that generating a Ninja build did result in a significant speedup, so I wanted to see if I could replicate this speed with my make system.

I looked around for ways to convert a make build into a ninja build, and surprisingly I could not find a tool for this. It seems like something people would be interested in, given the large existing programs that use make, and I thought perhaps there is some reason that it couldn't be done like maybe make doesn't support the necessary output to construct the ninja build.

It still might be true that there is a fundemental reason that this is a bad idea, but it does work- you can convert a particular make build into a ninja build, "freezing" the make configuration into a single solution that can be quickly repeated using ninja.

The Concept

I did have a hard time figuring out how to get make to actually print out its dependency graph with the commands that generate the leaves, but the project make2graph showed me the command:

make -Bnd

which generates a text discription of the build with dependency information (at least a topological sort of the dependency graph, which is all I needed), along with the commands.

Using this output, I wrote a terrible little Python script called make2ninja which generates a simple build.ninja file based on the results. It is used as follows:

make -Bnd | python make2ninja.py > build.ninja

The Result

This creates a 10% improvement in full-rebuilds, and a 30% improvement when rebuilding a single file change. I'm perfectly happy with this, but I expect it could even be better- my makefile was designed to keep a very flat dependency graph and build the entire program in a single file (no recursive make)- other, more complex, makefiles might benefit even more.

The limiting factor for me is mostly the building itself, where both make and ninja seem to make good use of parellelism, and the long final linking step which takes a non-trivial amount of the total time, especially for small changes.

I also think that the make2ninja tool could be improved quite a bit, and with not all that much work, but expressing the entire dependency graph instead of make's topological sort of the graph- I expect without proof that this could help ninja with parallelism, and that it could be recovered from the 'make -Bnd' output. If I use this tool on other programs I may one day get around to this, but for now it is good enough.

The Conclusion

I would be interested to know if there are situations that make2ninja does not handle- given the complexity of make I would be surprised if I had not missed anything.

I also think there are some real improvements that are possible to make2ninja, like handling .d files for C/C++.

For now though I'm pretty happy that this experiment worked, and that it does result in an improvement.