Once a while some colleague of mine is in deep need of Makefiles for compiling some C/C++ code. Although I normally suggest to use Autoconf/Automake or CMake, not everybody is interested in learning how to use those tools. Furthermore, sometimes is just more handy to start writing the code immediately, instead of writing/copying/modifying configuration files.
So, since I believe I dedicated several hours of my life in explaining the mysteries of GNU Make, I decided to spend some time and showing how Make can be use to automagically compile all you need with no extra time spent in writing the Makefile itself.
How it works
The main idea is that you might want to code a small library in C/C++ and write a few executables that rely on the library. I will describe how a Makefile can be used to automatically compile the library without any need of adding the files one by one to the Makefile itself. Once you have downloaded and uncompressed the archive, you will see the following file layout:
bintemplate/ |-- both.cpp |-- chicken.cpp |-- eagle.cpp |-- lib/ |-- Makefile `-- src/ |-- Bird.cpp |-- Bird.hpp |-- Chicken.cpp |-- Chicken.hpp |-- Eagle.cpp `-- Eagle.hpp
The src/ directory contains all the files that are needed to compile the library, so both the included ones (.hpp) and the non-included ones (.cpp). The lib/ directory will contain the library once it will be compiled.
The small capitals .cpp files are the executables that will be linked against the library.
In this example, the project contains tree C++ classes (Bird.*pp, Chicken.*pp and Eagle.*pp) and tree executables (both.cpp, chicken.cpp, eagle.cpp).
As explained later on, the Makefile will compile automatically in the library all the src/*.*pp files and will compile and link against the library all the *.cpp in the root folder. This means that, if you want to add a new class or a new include, or a new executable, you simply create the file at his place, and you call make. Let’s now see how it is possible.
Makefile
Let’s now take a look at the Makefile itself, chunk-by-chunk.
1 2 3 4 5 6 7 8 9 10 | # 2010-07-27 Michele Tavella <tavella.michele@gmail.com> PROJECT=bintemplate VERSION=v0.0.1 AUTHOR=Michele Tavella <tavella.michele@gmail.com> CC=g++ CFLAGS=-Wall -O2 LDFLAGS=-lm CTAGS=ctags -R --c++-kinds=+p --fields=+iaS --extra=+q |
The first few lines define the project name and version, the compiler, the compiler options, some libraries we normally want to link against and the parameters to build the tags.
12 13 14 15 | LOCAL_PATH=~/Paths/playback CONFIG_CFLAGS=-I$(LOCAL_PATH)/include CONFIG_LDPATH=-L$(LOCAL_PATH)/lib CONFIG_LDFLAGS= -lpthread -lstdc++ -Wl,--rpath -Wl,$(LOCAL_PATH)/lib/ |
The second block is a little more tricky. I always install libraries, headers etc. in a local folder (LOCAL_PATH). This allows me to have different directories with different releases of one or a set of libraries (this becomes very handy when you work on several projects at the same time). BTW, if you have some libraries installed in some weird place, modify lines 13-14 accordingly, otherwise simply remove whatever is on the right of the “=” character. Line 15 specifies the LDFLAGS needed to link against the correct libraries, in this case libpthread and libstrc++.
17 18 19 20 21 22 | SRC=src LIB=lib LIB_INC=$(wildcard $(SRC)/*.hpp) LIB_SRC=$(wildcard $(SRC)/*.cpp) BIN_SRC=$(wildcard *.cpp) TARGET_LIB=$(LIB)/$(PROJECT).a |
Lines 17-18 define the location of the src/ and the lib/ directories, while lines 19-21 use the Make wildcard command to generate a list of the files required to build the library (src/*.*pp) and the files required to build the executables (*.cpp), as it was discusses in the previous section.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | .PHONY: library libraryo binaries binarieso default: library binaries binaries: binarieso $(BIN_SRC:.cpp=) binarieso: $(BIN_SRC:.cpp=.o) library: libraryo ar r $(TARGET_LIB) $(LIB_SRC:.cpp=.o) libraryo: $(LIB_SRC:.cpp=.o) %.o : %.cpp $(CC) -c $(CFLAGS) $(CONFIG_CFLAGS) -I$(SRC) \ $(CONFIG_LDPATH) $< -o $@ $(CONFIG_LDFLAGS) $(LDFLAGS) .o: $(CC) $(CONFIG_CFLAGS) $(CONFIG_LDPATH) \ $*.o -o $* $(TARGET_LIB) $(LDFLAGS) $(CONFIG_LDFLAGS) |
To understand this chunk, you need to know a little about Make. The main idea is that we define the targets (phony or not) and the rules needed to first compile the library and then to compile the binaries.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | .PHONY: tags install clean info edit clean: @echo "[Makefile] Cleaning..." @rm -fv $(TARGET_LIB) @rm -fv $(BIN_SRC:.cpp=) @rm -fv $(BIN_SRC:.cpp=.o) @rm -fv $(LIB_SRC:.cpp=.o) @rm -fv core* @rm -fv vgcore* @rm -fv tags install: @echo "[Makefile] Running installation" @cp -v $(BIN_SRC:.cpp=) $(LOCAL_PATH)/bin tags: @echo "[Makefile] Building tags" @$(CTAGS) $(LOCAL_PATH)/include/ $(SRC)/ . info: @echo "[Makefile] Info:" @echo " Project: $(PROJECT)" @echo " Version: $(VERSION)" @echo " Author: $(AUTHOR)" @echo " Lib: $(TARGET_LIB)" @echo " Bin: $(TARGET_BIN)" @echo " Src: $(LIB_SRC)" @echo " Inc: $(LIB_INC)" edit: @echo "[Makefile] Editing:" @gvim -geom 175x60 -O2 $(LIB_INC) $(LIB_SRC) $(BIN_SRC) |
Lines 45-73 define all the phony targets (command, in Make jargon) that I use to make my life easier.
Example
Once you extracted the archive, you can run
make
to compile the libraries and the executables, or similarly you can run:
make clean
to remove all the .o and .a files. Assuming you have GVim and exuberant-ctags installed, you can try to play around with all the other phony targets I briefly discussed above.
Download
bintemplate.tar.bz2 – 2010-07-27
To-do
1. Make phony rules not-phony
2. Improve overall, many important details are missing