ModDB Wiki
Advertisement

Makefiles and make are a system for automating compilation and dependancy management in a project (although it can be used for other things). Basically you create a file (usually called Makefile) which contains a number of rules that tell the program make how to build your program.

octt07-r19-id109-z-oct21.txt;20;30

Rules[]

The most important part of a makefile are rules. These rules indicate how a certain file (or collection of files) should be built, and what it depends on. If file X depends on file Y, X will be rebuilt if the current X is older than the current Y. Here is an example:

program: main.c library.c library.h
	gcc main.c library.c -o program
# ^^ that is a TAB

First comes the name of the file, followed by a colon. Then comes a list of files that this file depends on (the list may be empty). On the next line the command that builds this file is given. This line must start with a TAB character, and you can use more than one of these lines if more than one command is needed to build the file. As shown in the example, # can be used to start a comment.

To use this makefile you put the above text into a file called Makefile, go to it's directory and run make. Of course this only works if the source code files are also in that directory.

The above example is not very useful, you could just have made a shell script or batch file with the command in it and run that every time you wanted to build the program. Makefiles make it easy to compile a program in pieces though, which prevents you from having to recompile everything every time you change something. Look at this:

program: main.o library.o
	gcc main.o library.o -o program

main.o: main.c library.h
	gcc -c main.c -o main.o

library.o: library.c library.h
	gcc -c library.c -o library.o

The program executable file depends on two object files, main.o and library.o. make will always try to create the file indicated in the first rule of the Makefile (unless given extra arguments), in this case that is program. program depends on two object files: main.o and library.o. First thing make will do is check whether these are up to date. If they are not, or they do not exist, they are built using their own rules. If the resulting files are newer than the current program file (which is usually the case when files get rebuilt), program is rebuilt as well.

It is possible to manage a project by writing rules for every file in it, but this is a lot of work and quite annoying - when you change something in the project you have to manually change the Makefile as well. Fortunately, there are some other features.

Variables[]

A makefile can make use of variables. These are specified like this:

VARIABLE1 = hello world

world.txt:
	echo $(VARIABLE1) > world.txt

This rather useless example will create a file world.txt containing the text "hello world". As you can see a variable can be created with the '=' sign. All variables are strings of text. The syntax $(variable name) is replaced by the value of that variable (or by nothing if the variable does not exist). This allows some conveniences.

# specify a compiler
CC = gcc
# and compiler flags (optimize, output all warnings)
CFLAGS = -O2 -Wall

program: main.c
	$(CC) $(CFLAGS) main.c -o program

You can specify some parameters at the top of your makefile, and easily change them there. Another thing you can do with variables is add text to them:

VAR = a b
VAR += c
# VAR is now "a b c"

Suffix rules[]

It is possible to add a general rule to a makefile describing how a certain type of file is created. For example:

%.o: %.c	
	$(CC) $(CFLAGS) -c $< -o $@

This is getting rather cryptic, isn't it? This tells make how to create a .o file from a .c file. The $@ in the command is replaced by the target (.o) file, and the $< by the (first) dependancy (.c file).

This brings writing easy-to-use makefiles a step closer but there is still a major headache: Object files usually also depend on header files, and if a header file is changed the objects that depend on it have to be recompiled. Of course you could make all objects depend on all header files, but this would cause a full recompile everytime you change a teeny little thing in a header. I am not aware of any solutions to this with 'standard' makefiles, but if you are using the GNU make system together with GCC, there is a solution. Since most of the use of makefiles that I am aware of takes place in an environment that uses GCC and supports GNU make, this is no big problem.

GNU extensions[]

GNU make supports a variety of 'extra' features that allow you to do more complex stuff in a makefile, and GCC has an option that automatically determines dependencies for a C or C++ source file. Together, this can be used to create a makefile that adapts to your program without interference.

The switch -MMD filename causes GCC (or G++) to create a 'dependancy' file. This file contains a single makefile-style rule that contains the dependancies for the source file that was compiled. These files can be 'included' into a bigger makefile so that make knows about these dependancies. To include another file in a makefile, use -include $(DFILES). The '-' character in front causes make to ignore it if the file is not found.

Of course, if you are using dependancy files you'll have to keep them up to date. When do they need to be updated? That is easy - if the corresponding object file needs to be updated, the dependancy file for that object must be updated too. GCC allows you to do the compiling and the creating of the dependancy file at the same time, so that works out fine.

To have GNU make easily recognize new source files you create you can use the wildcard function:

SOURCES := $(wildcard *.c)

Note the ':=' instead of '=', this causes make to immediately evaluate the expression. With '=', SOURCES would contain "$(wildcard *.c)", and with ':=' it contains (for example) "main.c library.c".

Another useful extension is text replacing in variables. This can be used to create a list of object files from a list of source files:

OBJECTS := $(SOURCE:.c=.o)

The variable name in the parenthesis is followed by a colon, then the text that must be replaced and then the text that replaces it.

For a full manual of GNU make, see this page.

The super-Makefile[]

Below is a big elaborate makefile that can be used to effectively manage a multi-directory C++ project. It allows different 'build-modes' (debug, release, etc), and neatly keeps its object and dependancy files in subdirectories. Note that this file is meant to make developing a project easy, but is not very suitable for distributing it. You'll probably have to adapt it to compile your project on different platforms. Look into autotools if you want to make a portable makefile-system.

# Specify the main target
TARGET = main
# Default build type
TYPE = debug
# Which directories contain source files
DIRS = . util win vid map
# Which libraries are linked
LIBS = glfw GL X11 Xxf86vm c_r
# Dynamic libraries
DLIBS =

# The next blocks change some variables depending on the build type
ifeq ($(TYPE),debug)
LDPARAM = 
CCPARAM = -Wall -g
MACROS =
endif

ifeq ($(TYPE),profile)
LDPARAM = -pg /lib/libc.so.5
CCPARAM = -Wall -pg
MACROS = NDEBUG
endif

ifeq ($(TYPE), release)
LDPARAM = -s
CCPARAM = -Wall -O2
MACROS = NDEBUG
endif

# Add directories to the include and library paths
INCPATH = .
LIBPATH = 

# Which files to add to backups, apart from the source code
EXTRA_FILES = Makefile
# The compiler
C++ = g++

# Where to store object and dependancy files.
STORE = .make-$(TYPE)
# Makes a list of the source (.cpp) files.
SOURCE := $(foreach DIR,$(DIRS),$(wildcard $(DIR)/*.cpp))
# List of header files.
HEADERS := $(foreach DIR,$(DIRS),$(wildcard $(DIR)/*.hpp))
# Makes a list of the object files that will have to be created.
OBJECTS := $(addprefix $(STORE)/, $(SOURCE:.cpp=.o))
# Same for the .d (dependancy) files.
DFILES := $(addprefix $(STORE)/,$(SOURCE:.cpp=.d))

# Specify phony rules. These are rules that are not real files.
.PHONY: clean backup dirs

# Main target. The @ in front of a command prevents make from displaying
# it to the standard output.
$(TARGET): dirs $(OBJECTS)
        @echo Linking $(TARGET).
        @$(C++) -o $(TARGET) $(OBJECTS) $(LDPARAM) $(foreach LIBRARY, \
                $(LIBS),-l$(LIBRARY)) $(foreach LIB,$(LIBPATH),-L$(LIB))

# Rule for creating object file and .d file, the sed magic is to add
# the object path at the start of the file because the files gcc
# outputs assume it will be in the same dir as the source file.
$(STORE)/%.o: %.cpp
        @echo Creating object file for $*...
        @$(C++) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC))\
                $(foreach MACRO,$(MACROS),-D$(MACRO)) -c $< -o $@
        @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d
        @rm -f $(STORE)/$*.dd

# Empty rule to prevent problems when a header is deleted.
%.hpp: ;

# Cleans up the objects, .d files and executables.
clean:
        @echo Making clean.
        @-rm -f $(foreach DIR,$(DIRS),$(STORE)/$(DIR)/*.d $(STORE)/$(DIR)/*.o)
        @-rm -f $(TARGET)

# Backup the source files.
backup:
        @-if [ ! -e .backup ]; then mkdir .backup; fi;
        @zip .backup/backup_`date +%d-%m-%y_%H.%M`.zip $(SOURCE) $(HEADERS) $(EXTRA_FILES)

# Create necessary directories
dirs:
        @-if [ ! -e $(STORE) ]; then mkdir $(STORE); fi;
        @-$(foreach DIR,$(DIRS), if [ ! -e $(STORE)/$(DIR) ]; \
         then mkdir $(STORE)/$(DIR); fi; )

# Includes the .d files so it knows the exact dependencies for every
# source.
-include $(DFILES)

Variables can be overridden by specifying them after the make command. For example make TYPE=release causes the above makefile to build your project in release mode.

Advertisement