What is Makefile and make? How do we use it?

A. Yigit Ogun
5 min readMay 1, 2022

--

If you have previously developed programs with C/C++ programming languages, compiled and run projects from the source code, you must have heard of make and Makefile. Makefiles are interpreted and run with make tools on Unix/Linux and nmake on Windows.

The most common use of Makefiles is to manage the dependencies of the source files of the programs during the compilation and linking (build) phase, that is, to compile only the files that need to be compiled by looking at the dependencies on each other and the last modified dates of the source files while the programs are being compiled.

To get ready to use make, you’ll need to create a makefile that explains the relationships between your program’s files and gives commands with an order. The executable file of a program is typically updated from object files, which are created by compiling source files with the .o file extension.

Once you’ve created a good makefile, run this simple shell command every time you edit some source files:

make

Okay then, we need to have a makefile. Then let us create one.

Create a Makefile

A simple makefile consists of “rules” with the following shape:

target … : prerequisites …
recipe
recipe
recipe
.
.
.

Here I want to point out that you need to put a tab character at the beginning of every recipe line!

A target is can be the name of a file ; executable and object files are examples of targets. A target can also be the name of a task to be completed, such as ‘clean’ or ‘re’ or whichever you want to create.

A prerequisite is a file that is used as input to create the target. A target often depends on several files.

A rule, explains how and when to remake certain files which are the targets of the particular rule. makecarries out the recipe on the prerequisites to create or update the target. A rule can also explain how and when to carry out an action.

Let us create a simple makefile as an example:

all : main.o file1.o file2.o file3 file4.o
cc -o program main.o file1.o file2.o file3.o \
file4.o
main.o : main.c library1.h
cc -c main.c
file1.o : file1.c library1.h library2.h
cc -c file1.c
file2.o : file2.c
cc -c file2.c
file3.o : file3.c
cc -c file3.c
file4.o : file4.c library2.h
cc -c file4.c
clean :
rm program main.o file1.o file2.o file3.o \
file4.o

We can split each long line into two lines using backslash “\”. By this way it will be easier to read. To run this makefile we need to write “make” to our CLI and it will create an executable file called “program”.

When a target is a file, it needs to be recompiled or relinked if any of its prerequisites change. In addition, any prerequisites that are themselves automatically generated should be updated first. In this example, “all”(our target on the top of the makefile) depends on each of the five object files; for example main.o depends on the main.c and the header file library1.h.

The target ‘clean’ is only the name of an action, not a file. Because of you don’t want to make any cleaning operation when you run the make, ‘clean’ isn’t a prerequisite for any other rule. As a result, Make never does anything with it until you specifically tell it to do so. I want to point out that, clean is not prerequisite of any recipe but also it does not have any prerequisite at all. So when you call this rule it simply comes and run the commands in the recipe without being dependent on anything. Targets that do not refer to files but are just actions are called phony targets. Then let’s take a look at what the phony target is.

What is Phony Target?

A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.

If you write a rule whose recipe will not create the target file, the recipe will be executed every time the target comes up for remaking. Here is an example:

clean:
rm *.o

Because the rm command does not create a file named clean, probably no such file will ever exist. Therefore, the rm command will be executed every time you say make clean .

In this example, the clean target will not work properly if a file named clean is ever created in this directory. Since it has no prerequisites, clean would always be considered up to date and its recipe would not be executed. To avoid this problem you can explicitly declare the target to be phony by making it a prerequisite of the special target .PHONY as follows:

.PHONY: clean
clean:
rm *.o

Once this is done,make clean will run the recipe regardless of whether there is a file named clean. So far so good . But still there is an error-prone point in our makefile. Let’s make some improvements in our makefile.

Variables Make Makefiles Simpler

In our example, we had to list all the object files twice in the rule for all (repeated here):

all : main.o file1.o file2.o file3.o file4.o
cc -o program file1.o file2.o file3.o file4.o

Such duplication is error-prone; if a new object file is added to the system, we might add it to one list and forget the other. We can eliminate the risk and simplify the makefile by using a variable. Variables allow a text string to be defined once and substituted in multiple places later.

It is standard practice for every makefile to have a variable named objects, OBJECTS, objs, OBJS, obj, or OBJwhich is a list of all object file names. We would define such a variable objects with a line like this in the Makefile:

objects = main.o file1.o file2.o file3.o file4.o

Then, each place we want to put a list of the object file names, we can substitute the variable’s value by writing ‘ $(objects)

Here is how the complete simple makefile looks when you use a variable for the object files:

objects = main.o file1.o file2.o file3.o file4.oall : $(objects)
cc -o program $(objects)
main.o : main.c library1.h
cc -c main.c
file1.o : file1.c library1.h library2.h
cc -c file1.c
file2.o : file2.c
cc -c file2.c
file3.o : file3.c
cc -c file3.c
file4.o : file4.c library2.h
cc -c file4.c
clean :
rm program $(objects)

Thank you for reading. I hope it helps you guys to have a bit of sense. Make and makefile has 300 pages manual. I think it describes how complicated a makefile can be. Here I just tried to make a simple presentation and explanation of basics. I will write more detailed article about more skills of Makefiles.

--

--

A. Yigit Ogun

42 Heilbronn ✦ AWS Community Builder ✦ DevOps ✦ Cloud ✦ C/C++ ✦ GNU/Linux ✦ https://linktr.ee/ayogun