How to Create And Use Makefiles
If you are a seasoned computer programmer, you understand the need of doing things the easiest way. One of the tasks that people like us face all the time is having to do the same things over and over again. Knowing how to use the right tools can really save the day in that matter.
Chances are that you have never used a Makefile before if you are a windows programmer. This happens because there are powerful compilers like Visual Studio that automate these jobs for you. However, this is not really the case for linux. The vast majority of linux programmers use gcc to create their C programs. Hence, there comes a time when they need to learn more about the make tool and how to create makefiles for their projects. In this post, i will be talking about C programming but whatever i say is easily extended for C++ as well (using g++ compiler for that purpose).
What happens when programming under Linux is that you have to use gcc explicitly. There is no other tool that will do that task for you and that is embedded in that compiler (well, there are some tools, but linux programmers do not really prefer them). If you have even once compiled a program in gcc, say having a single source code file named as file.c, you would have do it like :
gcc -o file file.c
This orders gcc to finally create a binay file named file (-o for output binary). While this is a very simple command that is not much of a problem to constantly type, what happens if there are 10 more c souce files and 10 more header files ? The process would get really messy. This is where makefiles help. The idea is that we just type all these commands once (actually not even once if you’re slick enough, i’ll elaborate below). Then, we just execute a simple “make” command and the work is done for us.
Let’s know see how the process works. Suppose that we are creating a simple chat program in C. For the sake of simplicity, suppose that our project consists of 3 different files, main.c, server.c and client.c and that the name of our program is “chat”. Below, you can see what it would the Makefile be like :
chat: main.o server.o client.o gcc -o chat main.o server.o client.o main.o: main.c gcc -c main.c server.o: server.c gcc -c server.c client.o: client.c gcc -c client.c clean: rm -f chat main.o
The Makefile logic is pretty simple. First, we specify that in order to create the chat binary, we need to create the object files main.o, server.o and client.o. In order to do that we need to execute “gcc -o chat main.o server.o client.o” command that will get the object files and create our final executable. In short, there is always a target, having certain dependencies and executing certain commands. For the first two lines, the target is “chat”, the dependencies are the object files and the command is the gcc -o command mentioned right above. Then, as with apt-get, these dependencies need to be resolved (please notice that you need to have a tab right before the executing command).
Consequently, the Makefile will need to know how to build the dependencies. Thus, we just need to specify how every object file gets created. This is very easy because each object needs a simple “gcc -c source.c” to be created. In the end of the Makefile, we also create a clean directive that would serve so that we can clean our project, removing any make information. This is particularly useful when we need to rebuild the entire project from scratch and is executed like “make clean” from the shell.
The More Advanced Approach to Makefiles
While this is a nice approach to creating a Makefile, the bigger it gets, the more object files need to be created. Check this example (the two examples below are courtesy of this nice Makefile tutorial):
CC = gcc CFLAGS = -g -O2 OBJECTS = main.o foo.o main.exe : $(OBJECTS) $(CC) $(CFLAGS) $(OBJECTS) -o main.exe main.o : main.c $(CC) $(CFLAGS) -c main.c foo.o : foo.c $(CC) $(CFLAGS) -c foo.c
What happens here is that we utilize variables to make the Makefile easier to edit. Instead of directly specifying the objects, for example, we create an objects variable that contains all the object names. The same happens for the gcc string and the flags that we want to use for it as well. The Makefile can be further edited as well and be even better if you use the Make patterns like :
CC = gcc CFLAGS = -g -O2 OBJECTS = main.o foo.o main.exe : $(OBJECTS) $(CC) $(CFLAGS) $(OBJECTS) -o main.exe %.o : %.c $(CC) $(CFLAGS) -c $<
The % is used as a pattern that when used with $< instructs Make to use all the objects we specify and make them all automatically, once we add them in the OBJECTS variable alone. If you want to learn more inside stuff about how Makefiles work, you may want to check this great tutorial about advanced Makefile uses.