Makefile
1、基本知识
语法:
目标:依赖 //依赖可以是0个或多个,依赖之间用空格分隔
命令 //命令前面必须有tab键
- 目标:最终要生成的文件
- 依赖:生成目标所需要的文件
- 命令:怎么样通过依赖来生成目标的
make访问makefile的方式:
- make后不加目标:访问第一个目标
- make后加目标:访问指定目标
- 当有makefile和Makefile两个文件时,优先访问makefile
不让makefile打印执行的命令:
在命令前面加上" @ "即可
2、使用makefile编译文件
题目
现在有一个文件test.c,它需要用到add.c、sub.c文件中的内容。要求使用makefile进行编译。
add.h、sub.h的文件路径在xxx/linux_test/inc中
add.c、sub.c的文件路径在xxx/linux_test/src中
test.c、add.c、sub.c的内容如下:
编写步骤
1、构思,分析如何一步步生成所需目标文件
想要使得makefile实现只去编译修改过的文件,而不编译未改动的文件,就必须进行分步编译。第一步先将.c文件转为.o文件,第二步将.o文件转为可执行文件。
2、 创建makefile文件
在.c文件目录下创建makefile文件
3、编写makefile文件
makefile只识别第一个目标,之后去查找第一个目标后面的依赖。如果依赖存在,那么执行完毕。如果依赖不存在,就会去寻找下面是否有生成这个依赖文件的目标,从而执行第二个、第三个以及之后的目标。
备注:这里makefile里面写了rm *.o,这个应该去掉不写。这样就能实现只编译修改的文件了。
4、执行makefile,运行可执行文件
3、伪目标.PHONY
伪目标可以解决目标与文件同名问题。
问题:假设我们的目标为clean,并且当前目录下也有一个clean文件,这时make clean就会有问题
解决方法:使用伪目标 ".PHONY:目标名"
4、变量
4.1 自定义变量
取出变量值:
$(变量名)、${变量名}这两种方式的含义是一样的
如果想输出$符号,应该输入$$
变量赋值:
变量赋值有4个符号:=、+=、?=、:=、
1、" = ":是最基本的赋值方式,与在文中位置无关,系统自动推导将最终的赋值作为该变量的值。
2、" += ":追加赋值,旧值保持不变,将新值黏贴到旧值后面
3、" ?= ":当某变量前面已经定义赋值过,则不执行本次定义赋值,否则执行本次赋值
4、" := ":是覆盖式赋值,假如某变量在前面已经定义赋值过,则将本次赋值作为最新的变量值
4.2 自动变量
自动变量的作用是更方便的操作目标和依赖,具体的变量如下:
操作目标:
- $*:不包含扩展名的目标文件名称。例如目标为a.out,则$*=a
- $@:目标文件的完整名称。例如目标为a.out,则$*=a.out
- $%:如果目标是归档成员,则该变量表示目标的归档成员名称
操作依赖:
- $+:所有依赖文件,以空格分开,可能包含重复的依赖文件
- $^:所有依赖文件,以空格分开,依赖文件不重复
- $<:”第一个依赖文件的名称
- $?:所有时间戳比目标文件晚的的依赖文件,并以空格分开
操作函数传入的参数:
- $(n):获取第n个变量。$(0)为函数名,$(1)为第一个参数
4.3 隐含变量
隐含变量的作用是更方便的操作常用的命令。比如gcc命令的隐含变量是CC
隐含变量也是一个变量,只是约定一个名字,在代码中容易理解含义,其使用方法与普通变量一样
下面是隐含变量的简单使用,以gcc为例:
命令的隐含变量:
- CC:C编译器的名称,默认值为cc。
- CPP:C预编译器的名称,默认值为$(CC) –E。
- RM:文件删除程序的名称,默认值为rm -f
- CXX:C++编译器的名称,默认值为g++。
- AR:库文件维护程序的名称,默认值为ar。
- AS:汇编程序的名称,默认值为as。
- FC:FORTRAN编译器的名称,默认值为f77
命令选项的隐含变量:
隐含变量格式:xxxFLAGS
- CFLAGS:C编译器的选项,无默认值。
- CPPFLAGS:C预编译的选项,无默认值。
- CXXFLAGS:C++编译器的选项,无默认值。
- ARFLAGS:库文件维护程序的选项,无默认值。
- ASFLAGS:汇编程序的选项,无默认值。
- FFLAGS:FORTRAN编译器的选项,无默认值
- LDFLAGS:链接器的选项,无默认值
5、条件判断
判断是否相等:
判断符号:ifeq、ifneq、else ifeq 、else ifeq 、else(最终要以endif结尾)
格式:<判断符号> ($(变量名),判断值) 变量名常用ARCH命名
判断是否定义:
判断符号:ifdef、ifndef、else ifdef、else ifndef、else
格式:<判断符号> <变量名>
对于makefile,变量定义了代表有赋值。没有赋值或者没有写过这个变量都认为是没有定义。
6、函数
使用函数:$(函数名 参数) 或者 ${函数名 参数}
6.1 常用函数
6.1.1 wildcard
wildcard可以列出当前文件下符合条件的文件名。
格式:
$(wildcard <文件类型>) 该函数的参数可以使用通配符进行操作。
例如:列出当前目录下全部.c文件$(wildcard *.c) 。注意这里使用的是*去匹配全部内容
6.1.2 patsubst
patsubst可以将在指定的范围中,将一种后缀替换成另一种后缀
格式:
$(patsubst <替换后的后缀>,<要替换的后缀>,<指定范围>)
例如:把全部的.c文件换成.o文件进行输出,注意这里使用的是%去匹配全部内容
6.1.3 shell
shell可以实现在makefile中调用一个shell命令
格式:
$(shell <命令>)
6.1.4 notdir
notdir可以实现将传入的参数去除路径,只留下文件名
格式:
$(notdir <参数>)
6.2 调用自己实现的函数
定义格式:
define 函数名
函数体
endef
调用格式:
$(call <函数名>,<参数>) 多个参数之间用" , "隔开
获取传入的参数:
$(n):获取第n个变量。$(0)为函数名,$(1)为第一个参数
7、make的使用
7.1 make指定目标
- make:执行第一个目标
- make <目标>:执行指定的目标
7.2 make时定义变量
格式:make <变量名>=<值>
7.3 常用的选项
- -f:当makefile文件不叫makefile时,可以指定作为makefile的文件名
- -i:忽略所有的命令执行错误
这代表有错误并不会停止执行,这适用在编译大型工程时,整体排查错误。
不加-i时,遇到错误就停止编译。加上-i后,遇到错误跳过,继续编译其他内容。
- -n:只打印要执行的命令,但不执行这些命令
- -w:如果make在执行过程中改变目录,打印当前目录名
-C:dir读入指定目录下的Makefile。会默认加上-w的选项
-s:在执行命令时不显示命令
8、makefile编写技巧
8.1 更方便的编译:%
在编写用.c文件生成.o文件的makefile时,需要好几个目标。并且目标和依赖去掉扩展名后,名称是一样的。例如:要生成test.o,那就要用到test.c;要生成sub.c,那就要用到sub.o。对于值中情况可以使用%来自动匹配。示例如下:
8.2 使用函数简化链接
wildcard可以直接获取当前目录的.c文件名,使用patsubst可以获取.o文件名。使用这两个函数,可以减少链接步骤时的代码,使得更加通用。
例如:原来链接时写的代码是"test:$SRC",这里的SRC是自己赋值的test.o,add.o,sub.o。如果又多了几个需要的.o文件,还需要自己去添加。而使用函数,不论多少个.o文件,都是一样的代码,这就使得代码更加通用。代码如下:
源码如下:
CC = gcc
CFLAGS = -c -g -Wall -I /home/linux/inc/
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c,%.o,$(SRC))
fun:$(OBJ)
$(CC) $^ -o $@
%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
8.3 分文件处理
8.3.1 需求提出
现在有一个工程,所有的.c文件放到了src目录中,.h文件放到了inc目录中,生成的.o文件放到obj目录中。要求在当前目录下编写makefile,使得能够对整个工程进行编译。在下面示例中,工程就是linux_test,所说的当前目录就是/home/linux/linux_test
需求中的目录结构如下:
8.3.2 准备工作
1、在当前目录下,创建src、inc、obj这三个目录,创建makefile文件
2、进入src编写相应的.c文件,进入inc编写相应的.h文件
8.3.3 编写makefile
在本次需求中,有两个makefile。一个makefile在工程目录,即:/home/linux/linux_test,这一个makefile用于整个工程的编译链接。另一个makefile在src文件下,即:/home/linux/linux_test/src,这一个makefile用于将整个src下的.c编译为.o并存放在/home/linux/linux_test/obj中。
工程目录makefile编写思路:
编译用到的指令为:"gcc -c <.c文件> -o <.o文件>" 。从指令中可以看到,我们需要.c文件的路径,也需要.o文件的输出路径。
1、获取src、inc、obj的绝对路径
比如我们要获得.c文件,首先需要找到它存放的路径,之后去用通配符匹配它。在本题中.c存放的路径应该是/home/linux/linux_test/src/xxx.c。可以发现这个目录分两个部分:
- "/home/linux/linux_test"是工程目录,也就是当前编写的makefile的目录,这个目录可以用$(shell pwd)函数来获取
- "/src"是.c存放的路径,这可以用$(shell pwd)/src来表示。
同理,我们可以获取inc、obj的绝对路径。这个绝对路径我们存入变量SRCDIR、INCDIR、OBJDIR中,方便我们后续调用。
2、获取.c和.o文件路径
.c的文件路径可以用$(wildcard $(SRCDIR)*.c)函数来获取。
这个函数的含义是从所给的$(SRCDIR)路径下找到全部的.c文件并返回$(SRCDIR)/xxx.c这种值。比如找到的是xxx/src/main.c。那么它的返回值就是xxx/src/main.c。
.o文件可以用$(pasubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))来获取。
这个函数的含义是,从所给的$(notdir $(SRC))范围中,找到以.c为后缀的内容。在文件名不变的情况下,把.c替换成.o并存入$(OBJDIR)目录下。
在这个函数中,SRC存放的是全部.c的文件路径,也就是$(wildcard $(SRCDIR)*.c)的返回值。
3、编译器相关参数
这个很简单,与之前一样即可。具体如下:
CC = gcc
CFLAGS = -c -g -Wall -I $(INCDIR)
4、设置全局变量
这个用环境变量export后跟变量即可。需要哪个全局变量在后面加一下就行。
5、编译相关内容
在该makefile中,整个的思路为:调用src中的makefile进行.o文件的生成,之后在当前makefile下用.o文件生成可执行文件。
- 5.1 调用makefile的目标编写
调用src中的makefile的命令是:make -C <makefile路径>。对于这种需求,常常把makefile路径当作目标。在这里,makefile的路径就是SRCDIR的值:/home/linux/linux_test/src,可以看到这是一个目录。
在makefile中目录不能充当目标,想要充当目标首先后面需要跟一个依赖,然后需要其他的目标来调用它。下图为目标编写的代码,调用它的目标没有截图进来。
- 5.2 使用.o文件生成可执行文件的目标编写
因为这个.o文件的生成在其他的makefile,所以我们可以假设调用的makefile是对的,可以生成.o文件。那么在经过5.1的过程中,我们假设这个.o文件已经生成了,在这个基础上编写.o到可执行文件的目标。
.o到可执行文件的指令是:gcc <.o文件路径> -o <可执行文件路径>。.o文件路径就是OBJ保存的路径,即:$(pasubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))的返回值。所以目标编写如下:
- 5.3 总调用目标all
经过5.1、5.2该makefile的功能型的目标全部编写完成,但需要一个"all:依赖"这种总调用目标来调用5.1、5.2中的目标,整体目标的编写如下:
src目录下的makefile编写思路:
src目录下的makefile的功能是将全部的src下的.c文件。如果是编译当前文件下,那么代码如下:
%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
但是%.o这种形式不能充当目标,如果想要当作目标,必须有其他的目标去调用它。所以需要一个"all:依赖"这种形式来调用它。
因为最终输出的.o位置不是当前位置而是xxx/obj这个位置,所以目标应该修改为$(OBJDIR)/%.o。
该makefile的代码如下:
8.3.4 makefile源码
调试中出现的bug:注意OBJDIR后面xxx/obj这后面不能有空格
1、工程目录下的makefile
#1. src、inc、obj的绝对路径
SRCDIR = $(shell pwd)/src#这后面不能有空格
INCDIR = $(shell pwd)/inc#这后面不能有空格
OBJDIR = $(shell pwd)/obj#这后面不能有空格
#2. .c文件路径、.o文件路径
SRC = $(wildcard $(SRCDIR)/*.c)
OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))
#3. 编译相关设置
CC = gcc
CFLAGS = -c -g -Wall -I $(INCDIR)
#4.设置全局变量
export OBJ OBJDIR CC CFLAGS
#5. 开始编译 思路为:调用src中的makefile生成.o文件,再用.o生成a.out
all:debug $(SRCDIR) echo a.out
debug:
@echo "src dir = $(SRCDIR)"
@echo "inc dir = $(INCDIR)"
@echo "obj dir = $(OBJDIR)"
@echo "$(SRCDIR)/*.c"
@echo "src = $(SRC)"
@echo "obj = $(OBJ)"
#5.1 调用src中的makefile
$(SRCDIR):echo
make -C $@
echo:
@echo "start make"
#5.2 用.o生成a.out
a.out:$(OBJ)
$(CC) $^ -o $@
2、src下的makefile
all:$(OBJ)
$(OBJDIR)/%.o:%.c
$(CC) $(CFLAGS) $^ -o $@