A Noob’s guide to using make and writing Makefile

Anirudh Swarnakar
6 min readSep 19, 2019

Hey, Hope you doing great ! so let’s dig in what Makefile and make is all about but first lemme tell you how I got to know about this and then How I implemented it in my project.

During my 1st internship at AST Imaging Pvt. Ltd., I saw my mentor typing makecommand, then in the next few seconds, I could see the files in the project getting compiled but not all. This blew my mind because 15 minutes before I was using a long tedious command to compile my files so this made me curious about make that without even writing a compile command like g++ or GCC how the files are being compiled and the best part only some files. I went through some Makefiles I cloned from Github to get an idea what it is all about, and believe me it was way off my level of understanding and I couldn’t even figure out how and what is happening in it. Makefile at first glance is a bit arcane but they can be readily understood with just a little bit of information.

It was during the 2nd internship I have to compile dependent CPP files with some local libraries and compiler flags and there I used make and Makefile extensively. After some time it becomes very tedious to remember and write the compile command for multiple files every time here Makefiles turns out to be a game-changer for me.

GNU make is a tool that simplifies building programs when they are composed of many files, or building many programs simultaneously. The make utility automatically determines which pieces of a large program need to be recompiled, and issues command to recompile them. Makefile describes the relationships among files and provides commands. Executable(s) are updated from object files, which are compiled from sources files.

To verify make is installed in your system run make in the terminal. If you see anything other than this means make is there in your system.

No make file found.

To install make in Linux/Unix with other build tools use

sudo apt-get install build-essential

Makefile consists of “rules” that has a definite structure.
let’s see what a rule looks like

target : prerequisites
<TAB>recipe

A target is usually the name of executable or object files. It can also be the name of an action such as clean.
A prerequisite is a file name that is used as an input for target. Target could depend on multiple files which are separated by space. Some rule may not require prerequisites.
A recipe is a command that make executes. It may have more than one command either in the same line or each on its line.

Remember a rule is preceded by a <TAB> character. I f you prefer any other character other than tab, you can set the .RECIPEPREFIX variable to an alternate character.

  • Lines beginning with a hash (#) are comments and are ignored.
  • Variables are in ALL_CAPS followed by a = sign. Its best practice to place all variables at the top of the file.
  • To use variable later in the file use $(VARIABLE_NAME) or ${VARIABLE NAME}. The parens/brackets are mandatory.
  • Starting the recipe with @ will not print the command on terminal.

Let’s see a basic Makefile which prints a variable

#sample Makefile #1
NAME = arthav
test :
<TAB>@echo $(NAME)
<TAB>echo $(CXX)

Save the file with no extension and name as Makefile. Open the terminal and navigate to the destination of Makefile and run make.

Let’s see how to compile a program from 2 files and one shared header

#program from 2 files and one shared header
CFLAGS = -g
all : programsource1.o: source1.c
<TAB>$(CXX) $(CFLAGS) -c source1.c
source2.o: source2.c
<TAB>$(CXX) $(CFLAGS) -c source2.c
program: source1.o source2.o
<TAB>$(CXX) $(CFLAGS) -o program source1.o source2.o

If you are using the proper variables (e.g., CXX and CFLAGS), then you can take advantage of make’s builtin rules for creating object files. For example, it knows how to properly make a .o file from a .c file.
Run make — print-data-base to see all the builtin rules.

Above program can be shortened with just one build rule, with the rest being implicit.

#program from 2 files and one shared header
CFLAGS = -Wall

program: source1.o source2.o
<TAB>$(CXX) $(CFLAGS) -o program source1.o source2.o

There are also a variety of automatic variables that make uses. Two that I extensively use are:

  • $@ is the target for the rule
  • $^ is the full list of dependencies

So, we can simplify things a little bit more

#program from 2 files and one shared header
CFLAGS = -Wall
program: source1.o source2.o
<TAB>$(CXX) $(CFLAGS) -o $@ $^

by default make will run the first rule. so running make will by default run program rule.
Apart from these, there are some rules which do not generate and file or executable such as clean, to know about them refer to Phony Targets.

Compiling a program is not the only thing we might want to write rules for. Makefiles commonly tell how to do a few other things besides compiling a program: for example, how to delete all the object files and executables so that the directory is `clean’.

clean :
<TAB>rm edit $(objects)

In practice, we might want to write the rule in a somewhat more complicated manner to handle unanticipated situations. We would do this:

.PHONY : clean
clean :
-rm edit $(objects)

This prevents make from getting confused by an actual file called `clean’ and causes it to continue in spite of errors from rm. A rule such as this should not be placed at the beginning of the makefile, because we do not want it to run by default.
Since clean is not a prerequisite of any rule, this rule will not run at all if we give the command `make’ with no arguments. To make the rule run, we have to type `make clean’. If its prerequisite in all then mention it in the last.

#program from 2 files and one shared header
CFLAGS = -Wall
program: source1.o source2.o
<TAB>$(CXX) $(CFLAGS) -o $@ $^
clean :
<TAB>rm -f *.o

Make
the command

When make recompiles the editor, each changed C source file must be recompiled. If a header file has changed, each C source file that includes the header file must be recompiled to be safe. Each compilation produces an object file corresponding to the source file. Finally, if any source file has been recompiled, all the object files, whether newly made or saved from previous compilations, must be linked together to produce the new executable editor.

make command accepts arguments such as -f or — file to specify the name of the Makefile. Also, variable values can be passed as an argument which can be used in the Makefile.
Exit status of make is always one of three values :
0: if successful
1: if you use the ‘-q’ flag and make determines that some target is not already up to date
2: if make encounters any errors. It will print messages describing particular errors.

By default make will use a single core for all compilation but using the -j flag you can utilize multiple cores available.

nproc

nproc print the number of processing units(CPUs) available.

make -j4 var=arthav -f Makefile2

Conclusion
The reason we need make is because it enables the end-user to build and install a package without knowing the details of how it’s done. Every project comes with its own rules and nuances, and it can get quite painful every time for a new collaborator. That’s the reason we have this makefile. The details of the build process are actually recorded in the makefile that you supply. It automatically determines the proper order for updating the files, in case one non-source file depends on another non-source file. You can also use make to control installing or uninstalling a package, generate tags tables for it, or anything else you want to do often enough to make worth while writing down how to do it.

For more detailed info and references please refer to :

--

--

Anirudh Swarnakar

I am a Technophile with leisure pursuit in Robotics, Embedded Linux, IoT. Here on medium, you will find me sharing some of my experiences & acquired knowledge.