Prerequisites
- Should know how to compile program in Linux by using terminal
- Basic knowledge of compiler stages like compiler, assembler, linker, etc
Introduction
If you ever come across the manual installation of any software/Library in Linux than you will definitely heard about make and Makefile. The general procedure to install any software/Library in Linux is
Step 1: Run Configure or config script
Step 2: Do make
Step 3: and in last make install
Here, we only discuss about make and Makefile with theory and hands-on.
What is Makefile?
Makefile is a script written in a certain prescribed syntax which helps to build the target output (normally, one or more executables) from source files by compilation and linking. In simple words, makefile will compile your source code in simple & fast way.
Why we need Makefile?
=> Large projects can contain multiple source files which are dependent in one another or arranged in hierarchical manner for example, in order to compile file A, you have to first compile B; in order to compile B, you have to first compile C; and so on.
=> Make is a solution to these problems. It can be used to compile whole project in well arranged manner and generate your target according to your make rule(which we will discuss later) by entering single command that is make.
=> An important feature is that when a project is recompiled after a few changes, it will recompile only those files which are changed, and any other files that are dependent on it. This saves a lot of time.
=> For a large project, when a few changes are made to the source, manually recompiling the entire project each time is tedious, error-prone and time-consuming.
How to write Makefile ?
Now writing Makefiles, But before that i want to tell you that i have divided this course in different levels so you can start slow with level 1 and go on. And after the end of this small course you can write and understand Makefile easily.
Level 1 : Manual Compilation Process
Level 2 : Basics of Makefile
Level 3 : How Makefile Works ?
Level 4 : Advanced Makefile
For example if you have three files .i.e. function.c function.h and main.c to compile and generate binary, this is the simple way to compile your code rather than write compilation line on terminal again and again. function.h: function.c: main.c: The following are the manual steps to compile the project and produce the target binary: Now all these steps of compiling & linking are cumbersome if source and header files are increased in number, so we make a single Makefile to compile and generate binary by entering single command i.e. make. The general syntax of a Makefile rule is as follows: Note: here tab is must otherwise make will present error makefile:<line_number>: *** missing separator. Stop. => When the make command is executed on terminal, it looks for a file named makefile or Makefile in the current directory and constructs a dependency tree. If you have several Makefiles, then you can execute specific with the command: make -f MyMakefile => Based on make target specified in makefile, make checks if the dependency files of that target exist. And if they exist, whether they are newer than the target itself, by comparing file timestamps. Here our first and default target is “all” which looks for main.o and function.o file dependencies. Second and third target is main.o and function.o respectively which have dependencies of main.c and function.c respectively. => Before executing the commands of corresponding target, its dependencies must be met, when they are not met, the targets of those dependencies are executed before the given make target, to supply the missing dependencies. => When a target is a file-name, make compares the time-stamps of the target file and its dependency files. If the dependency file is newer than target file, target execute otherwise not execute. In our case, when first target “all” start executing it looks for main.o file dependency, if its not met. Then it goes to second target main.o which check for its dependency main.c and compare time-stamp with it. If target found main.c dependency is updated, then target execute else not. Same process is follow for next target function.o. => It thus winds up recursively checking all the way down the dependency tree, to the source code files. By this process, make saves time, by executing only commands that need to be executed, based on which of the source files (listed as dependencies) have been updated, and have a newer time-stamp than their target. => Now, when a target is not a file-name(which we called “special targets”), make obviously cannot compare time-stamps to check whether the target’s dependencies are newer. Therefore, such a target is always executed. In our Makefile, special targets are “all” and “clean”. As we discussed target “all” earlier, but we not discuss target clean. Target clean removes the all object files created during compilation and binary executable files according to command. For the execution of each target, make prints the actions while executing them. Note that each of the command are executed in a separate sub-shell environment because of secure execution so that they can not change current shell environment which may affect other target execution. For example, if one command contains cd newdir, the current directory will be changed only for that line command, for the next line command, the current directory will be unchanged. Makefile Macros/Variables The makefile allows you to use macros, which are similar to variables and used as same as C macros. For example: Here, CC is variable or macro which assign value gcc and same as other variables too. There are many different techniques to assign value to variables like Note: In Makefile everything is treated as string. There are mainly two types of macros, user defined like variables and standard macros.There are lots of standard macros (type “make -p” to print out the standard macros), some of those are CC, CFLAGS, AR, LDFLAGS, CPP, etc. The exapansion of macros is done with $ sign, for example $(CC) or ${CC}. You can also define macros at the command line such as Patterns/Directives of Makefile Executing shell command in Makefile Sometimes we need to use the output of shell command from the Makefile. For example, checking versions/locations of installed libraries, or other files required for compilation. We can obtain the shell output using the shell command. For example: Advanced Makefile With this brief introduction to Makefiles, you can create some very sophisticated mechanisms for compiling your projects efficiently. However, this is just the drop of the river. I don’t expect anyone to fully understand the example presented above without having consulted some Make documentation Manual Compilation Process
#include <stdio.h>
void sample_func();
#include "function.h"
void func()
{
printf("Hello world! \n");
}
#include "function.h"
void func();
int main()
{
func();
return 0;
}
vishal@firmcodes:~$ gcc -c -I ./ main.c
vishal@firmcodes:~$ gcc -c -I ./ function.c
vishal@firmcodes:~$ gcc -o Binary main.o function.o
Basics of Makefile
target: dependency1 dependency2 ...
[TAB] command1
[TAB] command2
...
# My First Makefile
all: main.o function.o
gcc -o Binary main.o function.o
main.o: main.c
gcc -c main.c -I ./
function.o: function.c
gcc -c function.c -I ./
clean:
rm -rf *.o
rm -rf Binary
How Makefile works ?
Advanced Makefile
CC = gcc
CFLAGS = -O -Wall
LDFLAGS =
LIBS = -lmcpp -lm -lopenssl
OBJECTS = main.c function.c
make CC=arm-none-linux-gnueabi-gcc
%.o: %.c //When % appears in the dependency list, it is replaced with the same string that was used to perform substitution in the target.
[TAB] command
Binary: main.o function.o
gcc -o $@ main.o function.o //$@ will replace with target name Binary
Binary: main.o function.o
gcc -o $@ $? $(OBJECTS) //$? will replaced with newer dependencies if required
%.o: %.c
gcc -c $*.c -I ./ //$* will replaced with target name
@echo "Building.."
@echo $(shell ls) # To print output of command 'ls'
//Note: We can also store output of this command in variable like this LIST=$(shell ls)
CC = gcc # Compiler to use
INCLUDES = -I ./ # Directory for header file
OBJS = main.o function.o # List of objects to be build
all: ${OBJS}
@echo "Building..." # To print "Building.." message
${CC} ${OPTIONS} ${INCLUDES} ${OBJS} -o Binary
%.o: %.c # % pattern wildcard matching
${CC} ${OPTIONS} -c $*.c ${INCLUDES}
list:
@echo $(shell ls) # To print output of command 'ls'
clean:
@echo "Cleaning..."
rm -rf *.o
rm Binary