I’ll admit, for most of my hobby projects, I don’t invest much time in putting together a robust build system. My usual solution: just use the IDE’s built in system and forget about it. However, I just recently found myself in a situation where that mindset fails, both at work and at home. If you work at a company that builds its own build systems and has compilation distributed across multiple machines in a build cluster, you probably won’t get much out of this article (but please post your setup in the comments if you like).
At home, I have a couple projects going right now. The first is a cross-platform game library built in c++. In runs on iOS, Mac, and PC. The second is an editor GUI for the game library, built with Python, PyQt, and SWIG. Both are very interesting projects, but building them on every platform has turned into a royal P.I.T.A. So what did I do? I went down to my local coffee shop of course! It’s the developer hang-out in the area. Several of my colleagues suggested I look into Buildr–I don’t think they realize it was built for JVM applications. Others suggested I look into SCons, which is a valid suggestion. And of course, the old-time linux guy said just use GNU make.
So I began research into the perfect build system for my duo of projects. SCons is built in Python, run in Python, and configured in Python. I definitely like the approach SCons takes to dependency management and environment configuration. SCons separates the operating system environment variables from those of the build system. This allows the build configuration to work without hiccups on other OSs, and without the annoying environment variable hell. To start using SCons, you have to create an SConstruct file in your project directory. This file itself is a Python script that invokes all the features of SCons. For more info on the nitty gritty, just check out the SCons Crash Course. I like the easy configuration for building different targets:
1 2 | t = env.Program(target='bar', source=['foo.c']) Default(t) |
The first line defines a new target called bar. The second sets it as the default target to build if no target is specified (there are a few more options, but those are covered in the SCons docs and wiki). Basically, the whole system is in Python and you write Python to build your project. I like it. But, it is slow. It’s dependency checks took up to 20 seconds longer than make on my game framework project. On my Python editor project, SCons flies through everything. However, building this project mostly involves copying .py files from one directory to the other, invoking pyuic4 a few times, and running swig on altered c++ headers. As awkward as it felt to build a Python project using a Python build system, I did it.
GNU make was the last choice I wanted to use for these projects. Make is used for pretty much every open source project out there, and I always get a bad taste in my mouth when it’s running. GNU make’s syntax is old and standard, but it does not compare to a full-fledge language like Python. SCons on Python can be configured to any project. My 20 second downtime can be spent doing other productive things, I mean, it’s only 20 second. I think the delay is worth it for the flexibility.
My quick foray into makefiles did give me the chance to learn a cool trick:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | CC=gcc CXX=g++ CFLAGS=-I. DEPS= OBJ=main.o LIBS= OBJDIR=../obj DEBUG ?= 1 ifeq ($(DEBUG), 1) CFLAGS+= -g -O0 -DDEBUG EXE=../debug/project else EXE=../bin/project endif OUTOBJS=$(addprefix $(OBJDIR)/, $(OBJ)) all: $(EXE) $(EXE): $(OUTOBJS) $(CXX) -o $@ $(OUTOBJS) $(LIBS) $(OBJDIR)/%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) $(OBJDIR)/%.o: %.cpp $(DEPS) $(CXX) -c -o $@ $< $(CFLAGS) clean: rm -f $(OUTOBJS) rm -f ../bin/kyanite rm -f ../debug/kyanite debug: $(MAKE) $(MAKEFILE) DEBUG=1 release: $(MAKE) $(MAKEFILE) DEBUG=0 |
I always keep a directory configuration with a bin, debug, src, and obj directory. This makefile is in the src directory. Using the tiny bit of conditional makefile syntax (the ‘ifeq’ bit), I can use the same makefile for both a debug and release build. The two targets at the bottom are the ones that should be invoked: debug or release. Both call the makefile recursively with the correct DEBUG parameter.
So overall, you might tryout SCons for your next hobby project. It works well, and it can only get better–and most of all, it’s more fun than writing makefiles. You can use a debugger on your build script!
A Quick Note:
Choosing to use Python and PyQt for the editor in my project turned out to be the best decision ever. I use SWIG to automatically generate interface code for my c++ game framework. Qt Designer takes the heavy lifting out of designing the GUI (no one in his right mind would use a GUI framework without a good WYSIWYG designer, as some certain open source fanatics were trying to convince me). PyQt is an awesome binding library, and makes using the framework easier than in the language it was written in! Also, anytime I need to add a plugin or new feature, there is almost no effect involved. After all, the editor is in an interpreted language! I can design a new GUI element, pyuic it (pyuic is a program that converts the Qt XML ui format to .py python classes), and drop in a new python file. Iteration times are in minutes, not hours. My next post will likely be about building game editor GUIs with Python, PyQt, and SWIG. I love it.