What is a Makefile ? (applied to a Boost project)
The root recursive idea behind Makefile is:
To build a target we need prerequisites (other targets!) and instructions to build
Prerequisites
They are either files, folders or fake targets (usually in .PHONY
). Files/folders are tested for existence and modification date.
The target needs to be rebuilt if it has no prerequisite or if older that any of the prerequisites.
Instruction
An Instruction is shell commands, starting with one tab. Each instruction line is one shell instance. The shell command can be continued on next line when the current one ends with backslash \
.
Target definition
A target is either a dependency or a rule.
Dependency:
target : prerequisite1 prerequisite2 prerequisiteN
Rule:
target : prerequisite1 prerequisite2 prerequisiteN
instructions1
@hidden_batch1 ; \
hidden_batch2
With tabs in front of instruction start.
Debug
Debugging a Makefile can become a real headache.
Try the following in your Makefile to show traces (with file and line location for warning
):
$(info Shell: $(SHELL))
$(warning CXX: $(CXX))
This is helpful when your Makefile contains lots of nested if/else/endif
and you're not sure
anymore what is the current path.
Makefile Structure
The ideal makefile structure is:
- variable setup
- target/dependency declarations
The real target-instructions processing starts once the whole Makefile and its include files
has been understood (stored in make
internal database).
Example
Finally, apply theory to this specific example using Boost and create fake source files
to illustrate.
rawr.cpp
#include "rawr.h"
simple_ls.cpp
#include "rawr.h"
converter.cpp
#include <iostream>
#include "rawr.h"
#include "simple_ls.h"
#include "2dquicksort.h"
#include <boost/array.hpp> // Boost!
int main(int argc, char **argv)
{
boost::array<int,4> a = { { 1, 2, 3, 4} };
std::cout << a[1] << std::endl;
return 0;
}
Makefile
Don't forget to replace spaces with real Tabs if you copy Makefile
source from stackoverflow :
sed -i~ -e 's/^ /\t/' Makefile
Makefile source:
## Makefile for C++ project using Boost
#
# @author Cedric "levif" Le Dillau
#
# Some notes:
# - Using ':=' instead of '=' assign the value at Makefile parsing time,
# others are evaluated at usage time. This discards
# - Use ':set list' in Vi/Vim to show tabs (Ctrl-v-i force tab insertion)
#
# List to '.PHONY' all fake targets, those that are neither files nor folders.
# "all" and "clean" are good candidates.
.PHONY: all, clean
# Define the final program name
PROGNAME := converter
# Pre-processor flags to be used for includes (-I) and defines (-D)
CPPFLAGS := -DUSE_BOOST
# CFLAGS is used for C compilation options.
CFLAGS := -Wall -O0
# CXXFLAGS is used for C++ compilation options.
CXXFLAGS += -Wall -O0
# LDFLAGS is used for linker (-g enables debug symbols)
LDFLAGS += -g
# Which Boost modules to use (all)
BOOST_MODULES = \
date_time \
filesystem \
graph \
iostreams \
math_c99 \
system \
serialization \
regex
# Boost libraries' type (a suffix)
BOOST_MODULES_TYPE := -mt
# Define library names with their type
BOOST_MODULES_LIBS := $(addsuffix $(BOOT_MODULES_TYPE),$(BOOST_MODULES))
# Define the linker argument to use the Boost libraries.
BOOST_LDFLAGS := $(addprefix -lboost_,$(BOOST_MODULES_LIBS))
# Feed compiler/linker flags with Boost's
CPPFLAGS += $(BOOST_CPPFLAGS)
LDFLAGS += $(BOOST_LDFLAGS)
# List the project' sources to compile or let the Makefile recognize
# them for you using 'wildcard' function.
#
#SOURCES = simple_ls.cpp rawr.cpp converter.cpp
SOURCES = $(wildcard *.cpp)
# List the project' headers or let the Makefile recognize
# them for you using 'wildcard' function.
#
#HEADERS = simple_ls.h 2dquicksort.h rawr.h
HEADERS = $(wildcard %.h)
# Construct the list of object files based on source files using
# simple extension substitution.
OBJECTS = $(SOURCES:%.cpp=%.o)
#
# Now declare the dependencies rules and targets
#
# Starting with 'all' make it becomes the default target when none
# is specified on 'make' command line.
all : $(PROGNAME)
# Declare that the final program depends on all objects and the Makfile
$(PROGNAME) : $(OBJECTS) Makefile
$(CXX) -o $@ $(LDFLAGS) $(OBJECTS)
# Now the choice of using implicit rules or not (my choice)...
#
# Choice 1: use implicit rules and then we only need to add some dependencies
# to each object.
#
## Tells make that each object file depends on all headers and this Makefile.
#$(OBJECTS) : $(HEADERS) Makefile
#
# Choice 2: don't use implicit rules and specify our will
%.o: %.cpp $(HEADERS) Makefile
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(OUTPUT_OPTION) $<
# Simple clean-up target
# notes:
# - the '@' before 'echo' informs make to hide command invocation.
# - the '-' before 'rm' command to informs make to ignore errors.
clean :
@echo "Clean."
-rm -f *.o $(PROGNAME)
File list
2dquicksort.h
converter.cpp
Makefile
rawr.cpp
rawr.h
simple_ls.cpp
simple_ls.h
Compilation
make clean all
Clean.
rm -f *.o converter
g++ -Wall -O0 -DUSE_BOOST -c -o converter.o converter.cpp
g++ -Wall -O0 -DUSE_BOOST -c -o rawr.o rawr.cpp
g++ -Wall -O0 -DUSE_BOOST -c -o simple_ls.o simple_ls.cpp
g++ -o converter -g -lboost_date_time -lboost_filesystem -lboost_graph -lboost_iostreams -lboost_math_c99 -lboost_system -lboost_serialization -lboost_regex converter.o rawr.o simple_ls.o
Result
And now, the result of nearly the tiniest Boost program:
./converter
2
No excuse not to use it ! Boost is really a featured C++ toolbox :)