Simplifying Make

4 minute read

Published:

This is an attempt at simplifying make, I hope it can help anyone who struggling with the basics.

Simplifying the Makefile

The simplest form for a Makefile is:

TARGET: DEPENDENCIES
	COMMAND(S)

So, if you want to do some command or commands, give it a name (TARGET) and if the commands are dependent upon anything, list those dependencies. For example, if I wanted to compile a simple main.cpp file the command would be g++ main.cpp. I can name that command whatever I want, possibly snuka (my dog’s name). The main file in this example doesn’t require files other than itself, so the dependency list would simply be itself.

snuka: main.cpp
	g++ main.cpp

If that main file used a class I developed, such as Dog, defined in dog.cpp and dog.hpp, then my main program will depend on that class. Classes are most easily dealt with when compiled into object files such as dog.o. Make will create the dog.o file automatically (or at least try to), if I need it. So now my rule might need to be:

snuka: main.cpp dog.o
	g++ main.cpp dog.o

If I wanted to name the executable snuka, rather than a.out (the default), the rule would be:

snuka: main.cpp dog.o
	g++ main.cpp dog.o -o snuka

Thus, to build an executable file named snuka, which has a main function in main.cpp and uses code from the Dog class, I’d just need to run make snuka.

Variables

There are a couple of variables that are very helpful in a Makefile. Firstly, at the top of a Makefile, it’s always useful to define CXX and CXXFLAGS. These are the compiler command (CXX) and the flags (CXXFLAGS) to the compiler that will be used by make for all implicit rules. Above, we didn’t create a rule for the dog.o file. The make program guessed how to create the .o file. It’s generally pretty good at doing that when a .hpp and .cpp file exist with the same name. It will use the CXX compile command and pass the CXXFLAGS when it does that. If you didn’t define those then it will use system defaults.

If you just want to make a rule a bit cleaner and more portable, you can use automatic variables in your rules. The dollar sign ($) is used to de-reference variables, i.e. get their values, and you can think of them as a replacement. If you define CXXFLAGS = -std=c++17 -Wall, then whenever you put $(CXXFLAGS) in your Makefile, it’ll be replaced by -std=c++17 -Wall when you run make.

  • $@ - this at variable refers to the name of the rule. Above, $@ would be snuka.
  • $^ - this can be used to avoid retyping all of the dependencies. It will be replaced by the entire list.

So, with those variables available, our snuka rule could be written as:

snuka: main.cpp dog.o
	g++ $^ -o $@

or if we want to use the specific flags and compiler,

snuka: main.cpp dog.o
	$(CXX) $(CXXFLAGS) $^ -o $@

It might look a bit scary at first, but it’s a very simple syntax to get used to after you play with it a bit.

Questions?

I hope this helps clear some things up. If you have questions, ask Google or StackOverflow. If you are my student, come on by my office hours or message me in Discord and we’ll get it figured out together.