A Simple Makefile Tutorial
What is Makefile
Makefile is a simple way to organize code compilation.
Why we need Makefile
Assuming that we have the following three files:
1: hellomake.c
#include <hellomake.h>
int main(){
myPrint("hello");
return 0;
}
2: hellofunc.c
#include <hellomake.h>
#include <stdio.h>
void myPrint(char* str){
myPrint("%s", &str);
return;
}
3: hellomake.h
void myPrint(char* str);
Normally, you would compile this collection of code by executing the following command:
gcc -o hellomake hellomake.c hellofunc.c -I
tips:
- -o hellomake means that the name of compiled executable file is hellomake
- -I is included so that gcc will look in the current directory (.) for the include file hellomake.h
The downfalls of compiling using command line are:
- you have to retype the compile command (without using the up arrow in a terminal), which is inefficient
- if you make change to one .c file, you need to recompiling them every time, which is also inefficient
How to create a Makefile
Let’s start off with a simple example to better understand what is Makefile and how to write your own Makefile.
the simplest makefile that we can create looks like:
Makefile 1
hellomake: hellomake.c hellofunc.c gcc -o hellomake hellomake.c hellofunc.c -I
hellomake: hellomake.c hellofunc.c tells make that the dependency list of hellomake is hellomake.c and hellofunc.c
when you type the command line make, it will execute gcc -o hellomake hellomake.c hellofunc.c -I
(note: there is a tab before gcc command in the makefile)
Makefile 2
CC = gcc CFLAGS = -I. hellomake: hellomake.o hellofunc.o $(CC) -o hellomake hellomake.o hellofunc.o
Macro CC is the C compiler to use
Macro CFLAGS is the list of flags to pass to the compilation command
hellomake: hellomake.o hellofunc.o means that by putting together the .o object files (in this example, they are hellomake.o and hellofunc.o) in the dependency list, make knows it must first compile the .c versions individually, and then build the executable hellomake
(using this form of Make file is sufficient for small projects, but there is one thing missing: dependency on the include files. If you were to make a change to the hellomake.h, make would not recompile the .c files)
Makefile 3
CC = gcc CFLAGS = -I. DEPS = hellomake.h %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: hellomake.o hellofunc.o $(CC) -o hellomake hellomake.o hellofunc.o
Macro CC is the C compiler to use
Macro CFLAGS is the list of flags to pass to the compilation command
Macro DEPS is the set of .h files on which .c files depend
Then we define a rule that applies to all files ending in .o suffix.
First part of the rule %.o: %.c $(DEPS) says that the .o file depends upon the .c version of the file and the .h files included in DEPS. Second part of the rule $(CC) -c -o $@ $< $(CFLAGS) says that to generate the .o files, make needs to compile the .c file using the compiler CC. flag -c says to generate the object file, -o $@ says to put the output of the compilation in the file named on the left side of the :(i.e., %.o). The $< is the first item in the dependencies list.
hellomake: hellomake.o hellofunc.o means that by putting together the .o object files (in this example, they are hellomake.o and hellofunc.o) in the dependency list, make knows it must first compile the .c versions individually, and then build the executable hellomake
(notes: $@, $^ and $< are special macros. $@ is left side of :, $^ is right side of :, $< is the first prerequisite (usually the source file, respectively. )
Makefile 4
CC = gcc CFLAGS = -I. DEPS = hellomake.h OBJ = hellomake.o hellofunc.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) $(CC) -o $@ $^ $(CFLAGS)
In this file, all the files are listed as macro (all include files are listed as part of the macro DEPS, all of the object files are listed as part of the macro OBJ), which makes the overall compilation rule more general.
Makefile 5
IDIR = ../include CC = gcc CFLAGS = -I$(IDIR) ODIR = obj LDIR = ../lib LIBS = -lm _DEPS = hellomake.h DEPS = &(patsubst %, $(IDIR) /%, $(_DEPS)) _OBJ = hellomake.o hellofunc.o OBJ = $(patsubst %, $(ODIR) /%, $(_OBJ)) (ODIR)%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) $(LIBS) .PHONY: clean clean: rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
If we want to put our .h files in an include directory, our source code in a src directory, some local libraries in a lib directory, and hide the annoying .o files that hang around all over the place, the Makefile 5 is an example about how to achieve them.
note:
$(patsubst pattern, replacement, text)
Finds whitespace-separated words in text that match pattern and replaces them with replacement. Here pattern may contain a ‘%’ which acts as a wildcard, matching any number of any characters within a word. If replacement also contains a ‘%’, the ‘%’ is replaced by the text that matched the ‘%’ in pattern. Only the first ‘%’ in the pattern and replacement is treated this way, any subsequent ‘%’ is unchanged.
for example:
$(patsubst %.c, %.o, x.c.c bar.c)
produces: ‘x.c.o bar.o’