As beginners, most of the C/C++ programmers compile their programs on Linux command line by running the gcc or g++ compiler commands. But, as their projects start to grow, compilation through a single command does not remain easy and effective.
As a solution to all the compilation related problems, you need to understand the concept of makefiles. This topic would be covered in a series of articles and in this first part, we will discuss the very basics of makefiles in Linux.
Problems with single command method
Suppose your project consists of the following three files kept in a directory:
test.c anotherTest.c test.h
As a beginner, you'll run the following command again and again to compile your project :
gcc -Wall test.c anotherTest.c -o test -I.
This is fine as long as your project contains just a few files . Now suppose, your project grows and now contains 10 more source and header files. What will you do then?
Many would argue that they will extend the existing command by adding the names of those new files. But, what if you somehow loose this command or switch to another system? Will you again type the long command?
Also, suppose your project grows into a big project that contains hundreds of files, and takes 5-10 minutes to compile. Now, suppose you add a simple printf debug line in one of the .c files but use a single command to recompile. Wouldn't it be inefficient for a program to take 10 minutes to compile, even if a single line is changed/added in one of the source files?
Well, the solution is to use Makefiles.
A makefile is a special file (named as 'Makefile' only) that consists of targets, dependencies and commands, structured in a way that makes it easy for a programmer to compile the program.
In a layman's terms, here is a very basic syntax of a makefile :
target: dependencies [tab] command
So, if we want to create a very basic makefile for our example project (listed in last section), it would be something like :
all: test test: test.o anotherTest.o gcc -Wall test.c anotherTest.c -o test -I. test.o: test.c gcc -Wall -c test.c -I. anotherTest.o: anothertest.c gcc -Wall -c anotherTest.c -I.
This may look a bit complicated to a beginner but if you observe closely, it contains nothing but groups of targets, dependencies and commands.
- The target 'all' is nothing but a default makefile target.
- The target 'test' depends on test.o and anotherTest.o, and can be produced through the command mentioned below it.
- Similarly, the targets 'test.o' and 'anotherTest.o' both depend on their corresponding .c files, and can be produced through the respective commands mentioned below them.
Now, one would ask, how to use these targets? Well, these are used through the 'make' command. This command accepts a target as an argument and looks for a file named 'Makefile' to understand how to build that target. For example, if you specify the following command :
Then this command would scan the Makefile, build (if required) the dependencies of test and then execute the command to build test itself.
Similarly, If you want to remove all the non-required object or executable files before every cycle of compilation, you can add a target clean to your Makefile.
Here is an example :
all: test test: test.o anotherTest.o gcc -Wall test.c anotherTest.c -o test -I. test.o: test.c gcc -Wall -c test.c -I. anotherTest.o: anothertest.c gcc -Wall -c anotherTest.c -I. clean: rm -rf *o test
So you can just issue the following command :
to clean all the extra object files and the test executable file, before you start a compilation cycle.
The beauty of this command is that it knows which files have changed since the last time it was executed, and builds only those dependencies that are required. So, the combination of make command and Makefile not only makes it easy for the programmer to compile the project but also minimizes the overall compilation time.