Makefile理论到实践

1.什么是Makefile

工程按功能、模块把不同的文件放在不同的目录下面,当我们生成一个target的时候,需要知道生成这个target需要哪些文件,以及用什么方法生成,Makefile就是一个这样的文件,定义了一系列的规则,定义了这个target需要哪些依赖文件、生成的规则,Make命令工具还提供了变量、关键字、函数、支持调用shell命令,使得Makefile异常强大

2.Makefile的好处

一旦Makefile写好,只需要make,就可以一键编译、链接生成target文件,极大提高了工作的效率。Makefile的实现过程中,会让你了解整个工程,而且对编译和链接的一些知识有一定的理解,不像使用IDE那样,不深入理解,只会让自己的认知停留在表面,这样是不会成为一个真正的工程师

3.编译和链接相关知识

以c语言为例,使用gcc -c可以生成文件.o文件,这个可以说是中间文件,再用形如gcc all_.o -o main 把所有的.o链接生成main

编译只会检查语法错误,链接会重定位一些变量、函数,例如a.c调用了b.c中定义的A函数,那么链接的时候,a需要重定位A函数的地址,A函数的地址必须在合并所有.o才能真正的确定,一旦链接的时候找不到A函数的地址,就会出现链接错误

4.Makefile的规则

Makefile的规则

 target...: prerequisites ...(预备知识,先决条件)

         command(指令)

扫描二维码关注公众号,回复: 2819355 查看本文章

         ...

         ...
        -------------------------------------------------------------------------------

       target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性

      prerequisites就是,要生成那个target所需要的文件或是目标。

      command也就是make需要执行的命令。(任意的Shell命令)

       这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新或者不存在的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容

5.Makefile实验

假设我目录下面有如下这样的文件

yxw@Linux:/mnt/hgfs/share$ tree
.
├── main.c
├── makefile
├── make_test
│   ├── inc
│   │   ├── mul.h
│   │   └── sum.h
│   ├── main
│   │   └── main.c
│   └── util
│       ├── mul.c
│       └── sum.c
├── mul.c
├── mul.h
├── sum.c
└── sum.h

我做了2个实验,一个是所有的文件(main.c mul.c mul.h sum.c sum.h)放在一个目录,另一个实验是把所有文件分类存放在不同的目录下

6.Makefile基础

以第一个实验为例,所有的文件都在一个目录,实际不用Makefile,我们也可以用一些命令实现

第一步 :

 gcc -o main.o -c main.c
 gcc -o mul.o -c mul.c
 gcc -o sum.o -c sum.c

第二步:

gcc  main.o sum.o mul.o -o main

我们可以利用makefile的规则把这个再抽象一下,生成一个简单的makefile

认真看,会发现没有.h,实际对于所有makefile和所有依赖文件都在同一目录情况,会有一个隐含的搜索目录./(当前目录)。在第二个实验的时候就必须指出哪些头文件的所在目录

可以看到target a依赖于b,b==target c ,target c依赖于d,为了生成target c,会执行command

上面的makefile的工作过程

在默认的方式下,也就是我们只输入make命令。那么,
1.make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2.如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“main”这个文件,并把这个文件作为最终的目标文件。
3.如果main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,那么,他就会执行后面所定义的命令来生成main这个文件。
4.如果main所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)

上面的makefile完全没有利用上makefile的优势,makefile还提供变量、关键字、函数、调用shell命令这些,我们可以利用这些把makefile写得更简单

ALL_OBJ := $(patsubst %.c, %.o, $(wildcard *.c)) #利用wildcard函数获取当前目录下所有.c,再用patsubst函数,把得到的所有.c后缀变成.o

main:$(ALL_OBJ) #还定义了一个变量ALL_OBJ, $(变量名),引用变量 := 类似赋值语句 :=等于覆盖以前的值 +=在以前的值前面追加
	gcc $(ALL_OBJ) -o $@ 	#$@代表目标
%.o:%.c					#%是代表一个替代符号,假设前面a.o,那么就是a.c
	gcc -c $< -o $@		#$<代表第一个依赖对象
.PHONY:clean			#.PHONY代表这是一个伪目标,不生成相应文件,只执行相应command
clean:
	rm -f $(ALL_OBJ) main	#rm -f前面加上-,也许某些文件出现问题,但不要管,继续做后面的事

上面利用了makefile的变量、函数、伪目标的清楚规则、注释

总结

赋值
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

函数

SOURCES= $(wildcard *.c)   
这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。

notdir把展开的文件的路径去掉,只显示文件名而不包含其路径信息,例如:
FILES =$(notdir $(SOURCES))
这行的作用是把上面以'.c'结尾的文件的文件列表中附带的路径去掉,只显示符合条件的文件名。

patsubst( patten substitude, 匹配替换的缩写)函数。它需要3个参数:第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要被处理的
OBJS = $(patsubst %.c,%.o,$(SOURCES))
这行将处理所有在 SOURCES列个中的字(一列文件名),如果它的 结尾是 '.c' ,就用'.o' 把 '.c' 取代。注意这里的 % 符号将匹配一个或多个字符

自动化变量
$@ 代表目标
$^ 代表所有的依赖对象
$< 代表第一个依赖对象

还有添加注释和命令显示

#开头添加注释,

@开头隐藏命令显示

 - 开头  命令执行有错的话, 忽略错误, 继续执行

7.调用其他的makefile

    在makefile使用include关键字可以把别的makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include<filename>filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

举例

include $(PROJECT_PATH)/build_make/config/customer_config.mak

8.Makefile进阶

以第二个实验为例,所有的文件按功能、模块放在不同的目录

针对这个情况,实际只需要指定搜索路径和源文件,和所有文件放在同一个目录下是没有任何区别的

CUR_DIR := $(shell pwd)

#===========PATH=============#
INC_PATH := $(CUR_DIR)/inc
MAIN_PATH := $(CUR_DIR)/main
UTIL_PATH := $(CUR_DIR)/util
#===========OBJ==============#
INC_OBJ := $(patsubst %.c, %.o, $(wildcard *.c $(INC_PATH)/*.c))
MAIN_OBJ := $(patsubst %.c, %.o, $(wildcard *.c $(MAIN_PATH)/*.c))
UTIL_OBJ := $(patsubst %.c, %.o, $(wildcard *.c $(UTIL_PATH)/*.c))
#===========ALL PATH=========#
ALL_PATH := $(INC_PATH) 
ALL_PATH += $(MAIN_PATH) 
ALL_PATH += $(UTIL_PATH) 
#===========ALL OBJ=========#
ALL_OBJ := $(INC_OBJ)
ALL_OBJ += $(MAIN_OBJ)
ALL_OBJ += $(UTIL_OBJ)

SERACH_PATH := $(addprefix -I,$(ALL_PATH))

app : $(ALL_OBJ)
	gcc $(ALL_OBJ) -o $@  
%.o : %.c
	gcc -MMD $(SERACH_PATH)  -o $@ -c $<
.PHONY : clean
clean :
	-rm -f $(ALL_OBJ) $(patsubst %.o, %.d, $(ALL_OBJ)) app

上面就是实验二的makefile的实现,实际针对大部分工程都可以这样做

 在makefile中要使用shell 命令必须加shell 例如$( shell pwd)

加前缀函数addprefix,addprefix -I,$(ALL_PATH)在所有的路径前面加上 -I,这样编译的时候可以在指定路径搜索源文件

-MMD生成.d文件,里面包含了相应.o文件的所有依赖不包括标准库头文件,实际还有-M -MM -MMD,当然这个-MMD还有其他的用处,在多级makefile的时候,自动生成依赖,可以利用sources = main.c   include$(sources:.c=.d),这个就自动生成main.o的依赖关系了

实际里面还运用到了一个有用的规则%.o:%.c,这个是很有用,是把.o替换成.c,这个是模式规则,还有隐含规则,这个就比较多和复杂了

makefile还有过滤函数filter

如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令
exec:
 cd/home/hchen; pwd

      每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。
     如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

makefile中的变量,更像C语言的宏替换,而在使用时,需要“$(varname)”符号,函数的使用也是同样的,如果你要使用真实的“$”字符,那么你需要用“$$”来表示

变量还有一些高级用法

obj := main.o sum.o mul.o
src := $(obj:.o=.c)
这个示例中,我们先定义了一个“$(obj)”变量,而第二行的意思是把“$(obj)”中所有以“.o”字串“结尾”全部替换成“.c”

有几个常用的gcc参数,例如

gcc -c:编译

gcc -o:指定生成的目标的名字

gcc -I:指定依赖文件搜索路径

gcc -L:指定库文件搜索路径

gcc -l:指定需要链接的库

makefile的调试,少不了打印函数,用echo命令只能在target后面的command里面,但是很多时候需要在target前面加打印,这个时候可以用$(info "打印"),还可以打印变量的值, $(info macro =$(DEFS)),还有其他打印函数warning

大神有关Makefile的博客https://blog.csdn.net/alpha_love/article/details/62953847

猜你喜欢

转载自blog.csdn.net/follow_blast/article/details/81382483