Makefile related knowledge (based on the first phase of Weidong mountain course)

Keywords: C Linux Makefile

1. Disadvantages of traditional gcc -o instruction 9.5

Assuming that there are two C files, a.c and b.c, I call the function in b.c in the main function of A.C, then the compiler directive for generating executable program is:

gcc -o test a.c b.c

This instruction will preprocess, compile and assemble a.c and B.C respectively, finally generate a.o and b.o, and finally become test in the link. The problem with this is that if I modify a.c and need to re execute the gcc instruction, this instruction will perform the above processing on a.c and b.c. but I don't modify B.C at all and don't need to re process it, resulting in duplication.
We generally call preprocessing, compilation and assembly as compilation, that is, the formation process of the program is divided into two steps, compilation and linking. The method to solve duplication is to separate compilation and linking, for example:

gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o

If I modify a.c, I just need to recompile a.c into a.o, and then link a.o with the unchanged b.o.
So the problem is, if there are many files, how can we know that the file has been modified? The processing method is to look at the file time. If the time of a.o is earlier than that of a.c, it indicates that a.c has been modified and needs to be recompiled. The so-called makefile is doing such work

2. makefile rule introduction 9.5

A "rule" is used as the basic unit in Makefile. The rules are written as follows:

Destination file name: dependent file name
tab Key command

When the dependent file is "new" than the target file or the dependent file does not exist, execute the corresponding command on the next line. Write the above program generation process as Makefile as follows. There are three rules in total:

test: a.o b.o
	gcc -o test a.o b.o
a.o: a.c
	gcc -c -o a.o a.c
b.o: b.c
	gcc -c -o b.o b.c

Write the above statement into a file and put it in the same directory as a.c and b.c, and the file is named Makefile without suffix.

When we execute the make command for the first time, the machine realizes that the test file needs to be generated, which depends on the a.o and b.o files. If we find that the two files do not exist, we first generate a.o. looking down, we find that a.o depends on a.c. now that I have a.c but no a.o, we think a.c is more "new" than a.o. then execute the following command: gcc -c -o a.o a.c generates a.o, b. The same is true for the generation of O. finally, a.o and b.o are available, but test does not, so it is considered that both a.o and b.o are "new" than test, so execute the following command: gcc -o test a.o b.o, and finally generate test.

Suppose I modify a.c and execute make again. The machine recognizes that the test file needs to be generated, which depends on the a.o b.o file. First, it generates a.o. if it finds that A.C is newer than the existing a.o, it regenerates a.o with gcc -c -o a.o a.c; When generating b.o, it is found that there is already b.o and b.c is not newer than b.o, so the command is not executed. Finally, link.

3. makefile syntax introduction 9.6

3.1 wildcards and some common symbols

Background: according to the writing method in point 2 above, if there are 10000 c files, you need to write 10000 statements similar to the following. It's too troublesome.

a.o: a.c
	gcc -c -o a.o a.c

Solution: use wildcards
Overwrite the Makefile above

test: a.o b.o
	gcc -o test a.o b.o
a.o: a.c
	gcc -c -o a.o a.c
b.o: b.c
	gcc -c -o b.o b.c

After Rewriting:

test: a.o b.o
	gcc -o test $^
%.o: %.c
	gcc -c -o $@ $<

notes:
%. o:%. c:% is a wildcard. When looking for the rules that generate a.o and b.o, the machine finds the corresponding rules through%. O.
$@: indicates the target file
$<: indicates the first dependent file
$^: indicates all dependent files

3.2 hypothetical objectives

Background: suppose I want to add a function in Makefile to clear all. o files and test files. The writing method is as follows:

test: a.o b.o
	gcc -o test $^
%.o: %.c
	gcc -c -o $@ $<
clean: 
	rm *.o test

Type make clean to complete the cleanup operation, that is, you can bring the target name after make, and the rules corresponding to the target name will be executed. If there is no target name, that is, simple make starts from the first by default.

Problem: if a file named "clean" already exists in the current directory, because there is no dependency behind the clean corresponding rule in the Makefile, that is, the dependency is always "older" than the target clean. In this case, if you execute make clean again, there will be no response.

Solution: define clean as an imaginary target

test: a.o b.o
	gcc -o test $^
%.o: %.c
	gcc -c -o $@ $<
clean: 
	rm *.o test
.PHONY:  clean

3.3 variables

There are two variables in Makefile, immediate variable and delay variable.
Immediate variable: the value of A: = xxx #A is determined immediately, which is determined at the time of definition
Delay variable: the value of B=xxx #B is determined only when it is used

Use of variables: $(variable name, such as A or B)

example:

A: =$(C)
B=$(C)
all:
	@echo A=$(A)
	@echo B=$(B)
C=abc

After running make
Output A is equal to null and B is equal to abc, because when A is defined, C has not been defined, and B is A delay variable. Note that the reason why C can still take effect after B is because the whole Makefile is analyzed first during the make operation.

Summarize the common variable definitions:
: = # immediate variable
=# delay variable
?= # The delay variable is valid only if it is defined for the first time. If the variable has been defined before, this sentence will be ignored
+=# additional, similar to C language, whether it is immediate or delayed depends on the previous definition

4. Some functions of makefile 9.7

4.1,$(foreach var,list,text)

Function: for each variable in the list, execute the operation in text
For example, add. o suffix to all variables in A

A= a b c
B= $(foreach f,$(A), $(f).o)

all:
        @echo B= $(B)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
B= a.o b.o c.o

4.2 $(filter pattern..., text) and (filter out pattern..., text)

Function: take out the value conforming to the pattern format in text or the value not conforming to the pattern format in text
For example, filter the variables with / and without / from variable C.

C=a b c d/
D=$(filter %/,$(C))
E=$(filter-out %/,$(C))

all:
        @echo D= $(D)
        @echo E= $(E)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
D= d/
E= a b c

4.3,$(wildcard pattern)

Function: defines the format of the file name in the pattern, and the wildcard function takes out the files in the current directory that match the format
For example, suppose there are three files A.C, B.C, C.C in the current directory

files=$(wildcard *.c)#Assign the name of the. c file in the current directory to files
  
files2=a.c b.c c.c d.c e.c
flies3=$(wildcard $(files2))#Check which files in the variable files2 actually exist in the current directory
all:
        @echo files = $(files)
        @echo files3= $(files3)

4.4,$(patsubst pattern,replacement,text)

Function: replace all values in text that conform to pattern format with replacement format
For example, change all. c in files2 to. d

files2 = a.c b.c c.c e.c d.c abc
dep_files=$(patsubst %.c,%.d,$(files2))
all:
        @echo dep_files = $(dep_files)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
dep_files = a.d b.d c.d e.d d.d abc

5. About header file dependency in Makefile 9.8

Some files in the current directory:

root@zwg-virtual-machine:/home/zwg/weidongshan# ls
a.c  b.c  c.c  c.h  Makefile

The latest Makefile is as follows:

test: a.o b.o c.o
	gcc -o test $^
%.o: %.c
	gcc -c -o $@ $<
clean: 
	rm *.o test
.PHONY:  clean

Background: suppose I add a new file c.h, which does some operations, such as defining a macro, and then using the macro in c.c. Then my c.o depends on both c.c and c.h files. I need to add c.h to the dependency of Makefile as follows:

test: a.o b.o c.o
	gcc -o test $^
c.o: c.c c.h         #This rule has no commands, just tells the machine the dependencies of c.o
%.o: %.c
	gcc -c -o $@ $<
clean: 
	rm *.o test
.PHONY:  clean

This is troublesome because I know that c.h is used in c.c, but in fact, how to determine which. h files are included in a. c file is troublesome.

Solution: reference blog https://blog.csdn.net/qq1452008/article/details/50855810
Directive 1: use gcc -M c.c to view all dependencies of c.c:

root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M c.c
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/long-double.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h \
 /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
 /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
 /usr/include/x86_64-linux-gnu/bits/libio.h \
 /usr/include/x86_64-linux-gnu/bits/_G_config.h \
 /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h

Instruction 2: use gcc -M -MF c.d c.c to store all dependencies of the c.c file in the file c.d

root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M -MF c.d c.c
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001  a.c  b.c  c.c  c.d  c.h  Makefile
root@zwg-virtual-machine:/home/zwg/weidongshan# cat c.d
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/long-double.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h \
 /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
 /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
 /usr/include/x86_64-linux-gnu/bits/libio.h \
 /usr/include/x86_64-linux-gnu/bits/_G_config.h \
 /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h

Instruction 3: use the instruction gcc -c -o c.o c.c -MD -MF c.d to generate c.d while compiling

root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -c -o c.o c.c -MD -MF c.d
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001  a.c  b.c  c.c  c.d  c.h  c.o  Makefile

Modify Makefile:

Step 1:

test: a.o b.o c.o
	gcc -o test $^
	
# c.o: c.c c.h  
# %.o: %.c
	# gcc -c -o $@ $<
%.o : %.c
        gcc -c -o $@ $< -MD -MF .$@.d	
clean: 
	rm *.o test
.PHONY:  clean

After modifying make, three new files will appear:. A.o.d. b.o.d. c.O.D. these three files store the dependent items in the corresponding. c file
So you don't have to write statements like c.o: C.C, C.H
The final product is as follows. A condition judgment is added to determine whether the dependent file is included. See the 9.8 video for details

objs=a.o b.o c.o
  
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))

test:$(objs)
        gcc -o test $^

ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
        gcc -c -o $@ $< -MD -MF .$@.d

clean:
        rm *.o test

distclean:
        rm $(dep_files)

.PHONY:clean

6. CFLAGS 9.8 in Makefile

objs=a.o b.o c.o
  
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))

CFLAGS= -Werror -I  
#Explain the content of CFLAGS. Werror treats all warn ings as errors during compilation. This is recommended to avoid many errors
#Where - I is the default header file directory for the specified compiler search. For example, - I. indicates that the default header file directory is in the current directory
#Generally, we are used to putting all. h header files into an include folder, and we can specify this folder with - Iinclude

test:$(objs)
        gcc -o test $^

ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
        gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #Add CFLAGS to the compilation process

clean:
        rm *.o test

distclean:
        rm $(dep_files)

.PHONY:clean

Posted by jfourman on Fri, 08 Oct 2021 01:38:46 -0700