Compiling & Makefiles

Libraries

Programming languages such as C & C++ provide only very basic tools by themselves. Most advanced features, like OpenGL graphics commands, are not part of the language, but are libraries.

A library is simply a collection of code that someone else has written and packaged for others to use. It typically consists of two parts - header files (e.g. GL/gl.h) and the actual library file (libGL.so).

The header files define the interface for the library (the functions and their arguments, new data types, etc.), they usually don't contain the actual code. This is all that's needed to compile your own code into a .o (object) file - the machine code version what you've written.

To create the final complete program that can run, your object files are linked with the library files that you use, so that the executable contains all the necessary code.

Header files are normally found in the directory /usr/include. They can also be in other places, such as /usr/local/include, in which case you have to tell the compiler where to look for them, with the -I flag.

Library files are normally found in /usr/lib. If they're elsewhere, tell the compiler where to look for them with the -L flag.

Example: OpenGL

To use OpenGL commands in a program, first include the header files in your source code, so that the compiler will see the interface definition:

#include <GL/glut.h>
#include <GL/gl.h>

Then compile it into an object file:

cc -c myprogram.c

This produces the file myprogram.o.

Finally, link the object file with the OpenGL libraries to produce the executable program. Note that the OpenGL and Glut code make use of X Windows and math functions, and so you must also link with the X Windows libraries, which are in /usr/X11R6/lib, and the math library:

cc -o myprogram myprogram.o -lglut -lGLU -lGL -L/usr/X11R6/lib -lX11 -lm

(Note: in this simple case, you could combine the compiling & linking steps into one by doing:

cc -o myprogram myprogram.c -lglut -lGLU -lGL -L/usr/X11R6/lib -lX11 -lm

)


Makefiles

Having to type the complete cc command every time you want to compile a program is a bit much - it's better to have some automated way to do it.

For a simply program, you could just put the commands into a shell script. e.g., put:

cc -o myprogram myprogram.c -lglut -lGLU -lGL \
      -L/usr/X11R6/lib -lX11 -lm

into a file named "build", make the file executable (chmod +x build), and then just type build whenever you need to recompile.

make is a more powerful tool, which is useful for larger programs that are split into multiple files, or for defining (and redefining) standard procedures for compiling several similar programs.

When you have a program that comprises multiple source code files, make is able to recompile just those files that have changed since the last time you built the program.

make reads a makefile to define the rules for what it should compile. A simple example makefile is the following:

CFLAGS = -O
LFLAGS = -O
LIBS = -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -lm


example0: example0.o
        cc $(LFLAGS) -o example0 example0.o $(LIBS)

The file consists of a set of symbol definitions and rules; they can occur in any order, though the symbols usually come first, since they're used in the rules.

A symbol definition is something like:

CFLAGS = -O

This is like a variable assignment, and tells make to replace any subsequent occurrence of $(CFLAGS) by -O. Symbol definitions can make use of other symbols that have previously been defined. e.g.:

CFLAGS = -O
C++FLAGS = $(CFLAGS) -g

will define the symbol C++FLAGS to have the value -O -g.

A rule is something like:

example0: example0.o
        cc $(LFLAGS) -o example0 example0.o $(LIBS)

This particular rule tells make how to build the program example0. It consists of two parts. The first line, which should start in the first column of the file (i.e. have no leading spaces or tabs), consists of the name of the program, a colon, and a list of other files that the program depends on (dependencies). The dependencies are the files that are needed in order to compile the program - this should be just your files that can change, and not any system files like libraries.

The rest of the rule is a series of commands (one per line) that are to be run in order to build the program. These lines must all begin with tabs (not a bunch of spaces, but an actual tab character).

When you run make, you tell it which target (program) to build. If you don't tell it a target, it will select the first one that it finds in the makefile.

To build a target, make looks at the files that are listed as dependencies for that target. If any of the dependency files don't exist, it will first try to build them (using other rules in the makefile, or its own built-in rules). Then, if any of the dependency files are newer than the target program, or the target program doesn't exist, it will run the commands listed for that target.

make has several built-in rules for building .o files, which it will use if no explicit rule exists in your makefile for a desired .o file. These rules make use of some standard symbols, such as CC to define the compiler to use, and CFLAGS to define flags to pass to the compiler. The rules assume that C source code files use the extension .c, and that C++ files use the extension .cpp (this is for GNU make under Linux; other Unix versions can have different defaults).

Example

Suppose we create an OpenGL program that uses two source code files - main.c & support.c - and we want the executable to be called project1. The makefile could look like this:

CFLAGS = -O
LFLAGS = -O
LIBS = -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -lm

project1: main.o support.o
        $(CC) $(LFLAGS) -o project1 main.o support.o $(LIBS)

The first time we type make, neither of the .o files exist, so make will try to build them. Since there are no rules in the makefile for them, make will use its built-in rules and compile the .c files, and then run the command to compile project1:

% make
cc -O   -c -o main.o main.c
cc -O   -c -o support.o support.c
cc -O -o project1 main.o support.o -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -lm

If we immediately type make again, it will determine that nothing needs to be done:

% make
make: `project1' is up to date.

If we modify main.c and then run make, it will rebuild main.o and project1:

% make
cc -O   -c -o main.o main.c
cc -O -o project1 main.o support.o -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -lm

If any command produces an error, make will stop at that point, and not attempt to build anything further.



Further example: Makefile for this class's examples


next