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.

Leave a Reply

How to post code in comments?