make
provides some convenient tools for us to compile our code. All it need is a instruction file, called Makefile. Writing a makefile, I believe, is an art by itself, which is why it can get really complicated (and messy) :D. But before boring you with the details, I suggest you just create a file, named "Makefile" with the following text inside. You will see that it resembles almost exactly what we did in previous tutorial.
1) Makefile 1 - A Makefile that ... make
# Makefile
all:func_1.c func_2.c hello_make.c
gcc -c func_1.c -o func_1.o -I .
gcc -c func_2.c -o func_2.o -I .
gcc -c hello_make.c -o hello_make.o -I .
gcc func_1.o func_2.o hello_make.o -o hello_make
.PHONY: clean
clean:
rm func_1.o func_2.o hello_make.o hello_make
After you have the Makefile, save it and try, in the same directory, issuing this command into the terminal
make clean
You will notice that all the machine code files (.o) and the executable has been removed, and we are only left with the source code and header files, as when we started compiling. Now try this
make
And you will notice that everything has been built for you, including the executable. This is because when you call make
by default, it will go to the first rule, which refers to rule all
in our case, and execute the commands that belongs to this rule. On the other hand, when you command make clean
, it went to the rule clean
, and execute the command rm ...
to remove the object files accordingly.
Now I will generalize the Makefile step by step.
2) Makefile 2 - First generalization attempt
Firstly, I don't want to write the command gcc -c ... -o ... -I ...
too many times, I will create a rule just for this:
# Makefile
all:func_1.o func_2.o hello_make.o
gcc func_1.o func_2.o hello_make.o -o hello_make
%.o:%.c
gcc -c $< -o $@ -I .
.PHONY: clean
clean:
rm func_1.o func_2.o hello_make.o hello_make
Note that my target all
depends on .o
files, which then in turn depends on the target %.o
, which then depends on the presence of corresponding %.c
files (%
implies the same name requirement for .o
and .c
files). So before getting into target all
, the .o
files must have already been built.
You might have already guessed the purpose of this gcc -c $< -o $@ -I .
$<
means whatever on the right of:
of the rule line, which is whatever named.c
file (%.c
)$@
means whatever on the left of:
of the rule line, which is whatever named.o
file (%.o
)- If % is func_1, it would then becomes
gcc -c func_1.c -o func_1.o -I .
This is why I said it could look quite messy. However, after your eyes get used to these symbols, you will find them very efficient.
3) Makefile 3 - Here come the acronyms
If you have some prior experience in coding, you know that sometimes headers and source code can be placed in different folders. I will now put them into src
and include
folders, and also create obj
folder for .o
files, by
cd ~/make_tut/tut_2/
mkdir src
mkdir include
mkdir obj
mv hello_make.c src/
mv func_1.c src/
mv func_2.c src/
mv func_1.h include/
mv func_2.h include/
Immediately, we will have problems running Makefile because of changed directory. Hence, there is a need to make directory related parameters flexible, so that changes can be implemented quickly. One good way is to make them into variables
# Makefile
CC = gcc
EXC_NAME = hello_world
SRC_DIR = ./src
OBJ_DIR = ./obj
INC_DIR = ./include
CFLAGS = -I $(INC_DIR)/
DEP_FILES = func_1.h func_2.h
SRC_FILES = func_1.c func_2.c hello_make.c
OBJ_FILES = func_1.o func_2.o hello_make.o
SRC = $(patsubst %, $(SRC_DIR)/%, $(SRC_FILES))
OBJ = $(patsubst %, $(OBJ_DIR)/%, $(OBJ_FILES))
DEP = $(patsubst %, $(INC_DIR)/%, $(DEP_FILES))
all:$(OBJ)
$(CC) $(OBJ) -o $(EXC_NAME)
$(OBJ_DIR)/%.o:$(SRC_DIR)/%.c $(DEP)
$(CC) -c $< -o $@ $(CFLAGS)
.PHONY: clean
clean:
rm -f $(OBJ) $(EXC_NAME)
Firstly, I have put the directories into variables SRC_DIR
, OBJ_DIR
and INC_DIR
for source codes, object files and headers respectively. Secondly, I defined a variable called CFLAGS
to keep track of all necessary flags, such as -I
, -Wall
, -v
, etc.. Thirdly, I put all the file names into variables DEP_FILES
, SRC_FILES
and OBJ_FILES
and then forms a set of directories to these files by this trick $(patsbst ...)
. The definition according to GNU make docs is basically
$(patsubst pattern,replacement,text)
which means: it finds whitespace-separated words in text
that match pattern
and replaces them with replacement
In our case SRC = $(patsubst %, $(SRC_DIR)/%, $(SRC_FILES))
will be interpreted as: take whatever word (%
) in SRC_FILES
, then put SRC_DIR/
in front of it. In plain text, it will produce:
./src/func_1.c ./src/func_2.c ./src/hello_make.c
Thanks to this trick, now dependencies for my target all
can be as short as $(OBJ)
and most importantly, it automatically changes when I add more file names. If you want to be lazier (more efficient), you can do this to make OBJ_FILES
even dependent on SRC_FILES
. and changes to SRC_FILES
will automatically influence OBJ_FILES
.
OBJ_FILES = $(patsubst %.c, %.o, $(SRC_FILES))
or even lazier ;)
OBJ_FILES = $(SRC_FILES:.c=.0)