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)

results matching ""

    No results matching ""