Fun With Makefiles
Lately, I’ve been toying with the idea of trying my hand at some graphics programming. After spending the better part of yesterday trying to figure out how to even get started, I think I have a way ahead.
Building hello triangle using OpenGL is a fairly involved task. First, you need to settle on a graphics library. There are two choices here: OpenGL and DirectX. Obviously, I’ll be selecting OpenGL in order to avoid Microsoft vendor lock-in.
Next, you need a library to display a window for you. Sure, you could do it yourself, but then you’d get bogged down in a quagmire of platform specific issues. If you’ve been reading the blog, you know I don’t care for this sort of platform dependent nonsense, so I’ve tenatively settled on SDL 2. SDL is a cross platform multimedia library that handles sound, input, window creation, and the like. I plan to use this, in conjunction with an OpenGL context to do my work.
After you have that in order, you need an OpenGL Function Loader. Apparently the folks at Khronos were inspired by DMP Photobooth’s module system, there isn’t some opengl.h file you can just include and get your functions: you get to call
dlopen and use
dlsym to get function pointers. This wouldn’t be a huge issue if there were just a few functions, but there are thousands of them. In light of this, I’ve elected to go with GL3W for the time being. GL3W is a simple python script that generates a .c file containig the function pointers, and a .h to include.
All of this leads us to the topic of today’s post. How do we build this mess of libraries and random .c files?
We’ll Make it Work
The obvious answer here is that we need to use some sort of build system. Given my past experience with the abominations produced by NetBeans, I’ve elected to roll my own. Let’s take a look:
.DEFAULT_GOAL := all
First, we have the default goal. By default, the default goal is the first one in the file. However, I like to make things explicit. Here, we set the default goal to “all”, which builds the code for all targets.
Next, we define some variables:
CC = gcc COMPILE_FLAGS = -c -g -Wall -Wextra -std=c11 $(OPTIMIZE_LEVEL) LINK_FLAGS = -g -Wall -Wextra -std=c11 $(OPTIMIZE_LEVEL) OPTIMIZE_LEVEL = -Og LINKER_LIBS = -lSDL2 -ldl -lGL RM = rm -f UNIVERSAL = gl3w gl_includes.h
The first variable,
CC is built-in, and defaults to gcc. Again, I’m redefining it here to be explicit. After that, I define
LINK_FLAGS, which are the flags I want to pass when I’m compiling someing to be link at a future time, and when I’m compiling and linking respectively. I define
OPTIMIZE_LEVEL separately, because I want to potentially change it, and I don’t want to have to worry about if the two are in sync.
LINKER_LIBS are the libraries I’m going to be using.
RM is the rm command, with flags, to be used in the clean target.
UNIVERSAL is a list of files and targets that all buid targets depend on.
all : chapter1 chapter2 chapter3 chapter4 chapter5 chapter6 chapter7 chapter8 \ chapter9 chapter10 chapter11 chapter12 chapter13 chapter14 chapter15 \ chapter16 chapter17 chapter1 : $(UNIVERSAL) chapter1.c @echo "Building chapter 1:" $(CC) -o chapter1 $(LINK_FLAGS) chapter1.c gl3w.o $(LINKER_LIBS) ... chapter17 : $(UNIVERSAL) @echo "Building chapter 17:"
Here we have the meat of our makefile. The tutorial I’m following has 17 chapters, and I’ll be building code from each. We have an “all” target that builds each chapter, and we have a target for each chapter that builds an executable. Each chapter target depends on UNIVERSAL and its own files.
gl3w : gl3w.c GL/gl3w.h GL/glcorearb.h $(CC) $(COMPILE_FLAGS) gl3w.c
Here we build the source files that GL3W produces. I’m compiling it into a .o file so that it can be linked into the code for the various chapters.
clean: @echo "Deleting .o files..." $(RM) *.o @echo "Deleting core..." $(RM) core @echo "Deleting chapters..." $(RM) chapter1 $(RM) chapter2 $(RM) chapter3 $(RM) chapter4 $(RM) chapter5 $(RM) chapter6 $(RM) chapter7 $(RM) chapter8 $(RM) chapter9 $(RM) chapter10 $(RM) chapter11 $(RM) chapter12 $(RM) chapter13 $(RM) chapter14 $(RM) chapter15 $(RM) chapter16 $(RM) chapter17
Finally, I have my clean target. Here we delete all the cruft that builds up in the build process.
It’s a simple makefile, but I feel it’ll make this process easier. I can just do the exercises and hopefully spend less time fiddling with gcc.