All of the game code I write these days uses a custom-built framework that I've named Stem. I've been developing and extending it for about 11 years now, and my constant investment in it has paid off hugely - what I have now is a stable API that lets me write code I in the way I find expressive and intuitive, in a high performance language, building for 5 different platforms with minimal effort, and I understand it at every level so I can fix any problem that comes up. It's pretty great.
This weekend, I decided I wanted to fix all of the things that had been bothering me about my build system. Stem is a granular set of libraries - right now there are
32 separate modules that I can opt into linking for each project, depending on its needs. For example, a command-line tool that manipulates JSON data would link to jsonio and maybe jsonserialization, but has no need for audioplayer, glgraphics, collision, etc. An actual game would probably link to all of them at once.
The way I've made this work is that each individual project (whether it's a Stem library itself, a supporting tool, or an application that uses Stem) has a minimalistic makefile that lists source files, supported platforms, and build instructions that are unique to that project (
example). Then, I have a big global makefile that each project imports at the end of its own makefile, which contains all of the plumbing to turn those minimalistic project-specific definitions into compiler invocations and everything else necessary for building something.
It's a bit of a monster, but it does the job extremely well. It turns out that Make is actually a reasonably capable macro/scripting language if you really get deep into it.
I try to keep my third-party dependencies to an absolute minimum, but by necessity, I do have to use at least libpng and libogg/libvorbis (I know of stb, but it only does input, and I have cases where I need output). Until this weekend, I was building these in a kind of janky way that wasn't incorporated into my main build system. I managed to fix this, so what I have now is a container project for each third party library, which has the unique build instructions in its makefile for building that library on each platform, and pretty much nothing else other than an archive of the library itself. For example, here's what the libvorbis makefile looks like:
http://ludobloom.com/svn/thirdpartymanaged/libvorbis/trunk/MakefileThe one thing Make isn't able to do for me on its own is to understand complex interdependencies between libraries. Some Stem libraries build directly on others; for example, if I want to use jsonserialization, I also need to link to the jsonio and serialization libraries. In addition to my global makefile, I have
a Ruby script that knows how to parse the dependency variables in each project's makefile, and write a database of them to a central location. My global makefile then invokes the same script to have it report all dependencies to make in the appropriate order based on its database, so they can be passed as linker flags during the build process.
Another major thing I had wanted to fix this weekend: Previously, building the dependency database involved making a bunch of HTTP requests to my SVN server to get the makefile data to parse. This meant I had to be online to do it, and it was really slow! Having to rebuild the database every time I made a significant change got on my nerves, so I came up with a way to have each project create or update its own individual entry in the dependency database whenever I would build and install it. This keeps everything local, doesn't require me to have committed my changes before being able to test their effect on the things depending on each library, and just feels a whole lot better.
It's all put back together and working the way I want it now. By my count, this was about 14 solid hours of intense work. Totally worth it.