How to make a Makefile?
Most software compiled on BLU (BSD/Linux/Unix) operating systems is done using make
.
The simplest Makefile
The simplest Makefile compiles one single executable. Think of your simplest “Hello, World!” project.
HelloWorld.cpp
#include <iostream> using namespace std; int main() { cout << "Hello, World!"; return(0); }
Of course, for one file you don’t need a Makefile. You could simply run this command that will compile hw.cpp
g++ -o HelloWorld HelloWorld.cpp
So even though a Makefile seems useless for a single file, here is how you would do it.
all: g++ -o HelloWorld HelloWorld.cpp
Notice that you have a label and the same compile command one line below the label.
Important! The syntax requires the second line to start with a tab.
Adding objects to your Makefile
Lets assume instead of one file, you have the three file HelloWord.
- Main.cpp
- HelloWorld.h
- HelloWord.cpp
Main.cpp
#include <iostream> #include "HelloWorld.h" using namespace std; int main() { HelloWorld hw = HelloWorld(); cout << hw.Text << endl; }
HelloWorld.h
#include <iostream> using namespace std; class HelloWorld { public: HelloWorld(); ~HelloWorld(); string Text; };
HelloWorld.cpp
#include "HelloWorld.h" HelloWorld::HelloWorld() { Text = string("Hello, World!"); } HelloWorld::~HelloWorld() { }
This simple project can also easily be compiled without a Makefile using this command line.
g++ -o HelloWorld Main.cpp HelloWorld.cpp
However, even with only three files you can start to see how it is much easier to type make than the lengthening command above.
Makefile
all: g++ -o HelloWorld Main.cpp HelloWorld.cpp
This is not perfect however, as this compiles both files every time make is run. If changes are made only to Main.cpp there is no reason to recompile HelloWorld.cpp. We can accomplish this by compiling HelloWorld.cpp to a HelloWorld.o module.
all: HelloWorld.o g++ -o HelloWorld Main.cpp HelloWorld.o
Similarly if you make changes to HelloWorld.h or HelloWorld.cpp, why do you need to recompile Main.cpp? So you can make it a module too.
all: Main.o HelloWorld.o g++ -o HelloWorld Main.o HelloWorld.o
Now only the libraries that have been modified will be recompiled when you run make. This can save significant build time when the project size increases.
Using variables in your Makefile
Mistakes are annoying. Having to type the same thing in multiple places often leads to mistakes and typos. If you look at the above, there is duplication that is unnecessary.
Makefile with duplication
all: Main.o HelloWorld.o g++ -o HelloWorld Main.o HelloWorld.o
Makefile using a variable to avoid duplication
objs = Main.o HelloWorld.o all: ${objs} g++ -o HelloWorld ${objs}
We can even add more variables which may not seem useful now, but are useful later.
CXX = g++ CXXFLAGS = objs = Main.o HelloWorld.o Outfile = HelloWorld all: ${objs} ${CXX} ${CXXFLAGS} -o ${Outfile} ${objs}
Think about it. Right now you only have one build command, but someday on a huge project you may have dozens and possibly hundreds. Could you imaging changing the CXXFLAGS everywhere? We don’t even have one listed yet, but of course, with the variable you only have to change it once in one place and it will work everywhere you used it.
Adding make clean to your Makefile
It is very common to want to delete all build files and build again. This is often done with the make clean command. But to get make clean to work you have to create a section or label in the make file called clean.
Because we already have variables, it is easy to configure the Makefile to support make clean.
Makefile with clean
CC = g++ CXXFLAGS = -W objs = Main.o HelloWorld.o Outfile = HelloWorld all: ${objs} ${CC} ${CXXFLAGS} -o ${Outfile} ${objs} clean: rm ${objs} ${outfile}
So simple, we just use rm to delete the files we created, which are all in variables so we had a nice clean short command.
Adding debugging to your make file
There are two schools of thought for debugging.
- All builds should be release builds unless you run make debug.
- All builds should be debug builds unless you run make release.
I am not going to tell you which school of thought you should have. What matters is that you can configure the Makefile to perform how you want it to.
This make file will always build without debugging (release) unless yous specify make debug.
CXX = g++ CXXFlags = -W objs = Main.o HelloWorld.o Outfile = HelloWorld all: objects build objects: ${objs} debug: clean CXXFLAGS += -g LDFLAGS += -g debug: objects build build: ${CXX} ${CXXFLAGS} -o ${Outfile} ${objs} clean: rm -f ${objs} ${Outfile}
Notice we set LDFLAGS but we never actually call it. It is a special variable that is called automatically by the linker when creating the objects. Yes it must be capitalized.