When I took my first programming course at Polytech, I used an integrated development environment (IDE) called Visual Studio. It magically complied all my files together and formed an executable. Now a days, I've moved on from using IDEs and switch to using a text editor, Sublime Text. As the C++ code I wrote started to get more complicated and span multiple files, I needed a way to systematically complied the files together. Thus, I created a Makefile to build and organize my C++ files.
CXX = g++ CXXFLAGS=-g -O2 -Wall -Wconversion
The first two lines of my Makefile defines the complier and its flags. I especially like the
-Wconversion flag, since I prefer code that have explicit type conversions. This flag issues a warning when a type conversion is implicit.
SOURCES=$(wildcard src/**/*.cpp src/*.cpp) OBJECTS=$(patsubst src/%.cpp,bin/%.o,$(SOURCES))
wildcard function uses a pattern to find the sources for compilation. In this case, all the filenames in src folder and its subfolders are stored into
patsubst function changes the extension of
.o extension and defines a destination folder,
bin, for the complied objects.
This defines the location of the
main() function and the output executable. I have gotten into the habit of creating an executable for quick testing and having a single file,
main.cpp, defining the main function of my program.
LIB_SOURCES=$(filter-out $(MAIN), $(SOURCES)) LIB_OBJ=$(patsubst src/%.cpp,bin/%.o,$(LIB_SOURCES)) LIB_TARGET=build/libYOURLIBRARY.a SO_TARGET=$(patsubst %.a,%.so,$(LIB_TARGET))
If you wanted to build libraries, these variables give the sources and destination for the libraries.
filter-out removes the main.cpp from the library compilation. There are two types of libraries, static and dynamic. The former uses a
.a extension while the latter uses a
With all the variables defined, the Makefile is used to do various actions. My most used action is the to build an executable from all the source files:
exec: build $(OBJECTS) $(CXX) $(CXXFLAGS) -o $(EXECUT) $(OBJECTS) @./$(EXECUT)
This code is called by running
make exec in the same directory as the Makefile. The components
$(OBJECTS) on the same line of
exec must run or compile before the body can be executed. The body of
exec compiles the executable and then runs it. On the last line,
@ is used to issue a command on the shell.
build: @mkdir -p build @mkdir -p bin
The first component of
build, simply makes two directories, build and bin. The second component,
$(OBJECTS), is syntactic sugar to reference the variable,
OBJECTS. The make utility knows to take these files and compile them when it is a component to an action. In this case, it is a component to
OBJECTS were not redefined to be in the new folder, bin, the make utility would compile the
.cpp files into the same location as the source files, doubling the number of files in the src folder. Since the
OBJECTS were given a new home in bin, the following is needed:
bin/%.o: src/%.cpp $(CXX) -c $(CXXFLAGS) -o $@ $<
This tells the make utility to compile all the
.cpp files into the bin folder. There is some more syntactic sugar here:
$@ refers to the action name,
$< refers to component,
clean: rm -rf build bin $(OBJECTS)
The clean action removes all the output files created by the Makefile. This gives you a clean slate, with only the source code. You can run this action with
The rest of my Makefile defines actions for building static and dynamic libraries. I put the
exec action first, which makes it the default action. In other words, I can just run
make to run
make exec. It is quite exhilarating using Makefile for compilation rather than an IDE.
The Makefile in its Entirety
CXX = g++ CXXFLAGS=-g -O2 -Wall -Wconversion SOURCES=$(wildcard src/**/*.cpp src/*.cpp) OBJECTS=$(patsubst src/%.cpp,bin/%.o,$(SOURCES)) EXECUT=build/test.exe MAIN=src/main.cpp LIB_SOURCES=$(filter-out $(MAIN), $(SOURCES)) LIB_OBJ=$(patsubst src/%.cpp,bin/%.o,$(LIB_SOURCES)) LIB_TARGET=build/libYOURLIBRARY.a SO_TARGET=$(patsubst %.a,%.so,$(LIB_TARGET)) exec: build $(OBJECTS) $(CXX) $(CXXFLAGS) -o $(EXECUT) $(OBJECTS) @./$(EXECUT) all: build $(OBJECTS) lib dyn exec libexec: build $(OBJECTS) lib $(CXX) $(CXXFLAGS) -o $(EXECUT) $(MAIN) $(LIB_TARGET) test: gdb $(EXECUT) bin/%.o: src/%.cpp $(CXX) -c $(CXXFLAGS) -o $@ $< run: @./$(EXECUT) lib: build $(LIB_OBJ) ar rcs $(LIB_TARGET) $(LIB_OBJ) ranlib $(LIB_TARGET) dyn: build $(LIB_OBJ) $(CXX) -shared -o $(SO_TARGET) $(LIB_OBJ) build: @mkdir -p build @mkdir -p bin clean: rm -rf build bin $(OBJECTS)