Makefile文件编写规则
文章目录:
1 makefile文件介绍
makefile是一个
项目代码管理工具
,是管理我们程序的源代码
的 ,makefile只是一个文件
,在这个文件中记录了我们程序编译的步骤(和gcc命令编译一样,只是方便管理),
为什么需要makefile呢?
例如我们有一个项目,项目中有100个文件,项目做完之后需要编译成一个可执行文件
,这样在编译程序的时候gcc
就会写的很长
,如果你只修改某个文件中的一个符号,就要gcc
重新编译,这样就很容易出错,而且找错不太好找。因此,我们可以使用代码管理工具makefile,把代码的所有编译命令
都写到makefile
中,然后执行一些makefile中的命令,程序就会编译生成一个可执行文件
2 makefile文件编写
一、makefile文件命名的两种方式:
- 全部小写:makefile
- 首字母大写:Makefile
二、makefile里命令的规则
1、规则中的三要素
- 目标:最终生成可执行程序的名字
- 依赖:生成可执行程序的
条件
(即源代码
) - 命令:使用依赖生成对应的可执行程序
2、第一个版本的makefile
目标:依赖条件
命令 (前面有一个Tab空格)
# 文件的目录结构
├── add.c
├── head.c
├── main.c
├── makefile
├── mul.c
└── sub.c
# 1、先新建一个makefile文件,里面的内容如下
# (make编译的时候,默认是在当前目录下找这些.c文件,如果有不在当前目录下的需要制定路径位置)
myapp:main.c add.c sub.c mul.c
gcc main.c add.c sub.c -o myapp
# 编辑好makefile内容,保存退出,然后用make进行编译
>>>make makefile
3、第二个版本的makefile
上面的这种makefile是最low的,也存在一个问题,如果我们有一个.c
文件发生了修改,那么所有的文件都要重新编译一次,如果文件特别多这样就会浪费很多的时间,效率非常低。想要提高效率:就是只对修改的文件进行重新编译,没有修改的就不编译,就对.c
文件分开来编译
,就需要生成.o
文件
# myapp终极目标:可执行程序,因为依赖是一些 .o文件, 而我们是没有这些文件
# 因此就需要在下面的子目标中生成这些依赖的 .o文件
myapp:main.o add.o sub.o mul.o
gcc main.o add.o sub.o mul.o -o myapp
# main.o是子目标
main.o:main.c
gcc -c main.c
add.o:add.c
gcc-c add.c
mul.o:mul.c
gcc -c mul.c
sub.o:sub.c
gcc -c sub.c
解释:
当生成可执行程序
myapp
时,查找依赖发现没有main.o,add.o等文件,就会向下查找规则,有没有生成这些.o文件
的规则
(编译生成这些.o文件依赖的命令
),找到如果有就会执行下面的规则,全部执行完之后,再执行我们生成终极目标的规则。(下面的子目标规则,都是为了生成终极目标可执行文件服务的
)
注意:
- 1)写在开头的是终极目标
- 2)makefile子目标有没有更新,为什么可以只编译修改的,是根据文件的
修改时间对比
来实现的(这也是makefile的工作原理
) - 3)makefile中的注释用
#
4、makefile文件简化
上面的文件有两个地方可以进行简化:
- main.o add.o sub.o mul.o 上下都出现了写文件,对于这种多次使用的可以用
变量赋值
,makefile不需要变量类型 - 下面的四个子目标的的规则一样,可以用
修改如下:
obj=main.o add.o sub.o mul.o
target=app
$(target):$(obj)
gcc $(obj) -o $(target)
# main.o是子目标
%.o:%.c
gcc -c $< -o $@
在vi底行模式下的替换 : 3,4s/app/$(target)
把第3,4行的app 替换成$(target)
说明,模式规则的三个自动变量
使用:
- %.o:相当于是对上面依赖的一个占位天空,当需要依赖main.o它就会变成main.o,后面与前面匹配就是main.c
- $<:规则中的第一个依赖,如果把其套到第一个规则中,第一个依赖就是main.o,如果套到第一个子规则中,它的第一个依赖就是main.c
- $@:规则中的目标 ,如果套到第一个子规则中,其目标就是main.o
- $^:规则中所有的依赖
上面的三个模式规则
只能在规则的命令中使用,即只能写到第二行中
eg:根据上面的规则:
- gcc main.o add.o sub.o mul.o -o myapp 可以修改如下
- gcc $^ -o $@
5、makefile中有一些系统自己维护的变量
makefile中有一些系统自己维护的变量,一般
大写
,例如:CC=cc, CC的默认值就是cc,cc其实就是gcc,你也可以修改为CC=gcc
常用的一些系统维护变量:
- CC=gcc : 编译命令gcc,用户可以修改这些默认变量的值
- CPPFLAGS=-I :编译命令的预编译参数
-I
- CFLAGS=-WALL -g -c :编译的时候使用的参数
- LDFALGS=-L -l :链接库使用的选项
6、makefile中函数
makefile中函数调用都是有返回值的,我们函数调用的目的就是为了拿到返回值,然后获取一些信息。
上面我们的所有.o
文件的名字都是我们手动指定的obj=main.o add.o sub.o mul.o,如果有很多.o
文件,我们不可能全部手动指定,这样会很繁琐。此时我们就可以通过makefile中的一个函数获取当前目录中的.o
文件,但是这些.o
文件在程序没有执行的时候是不存在的,是我们在编译程序的时候生成的中间文件,但是这个.o
文件是通过.c
文件生成的,因此需要获取指定目录下的.c文件,这样才能够生成对应的.o文件
,因此,这里需要用到两个函数:
- 获取指定目录下的
.c
文件
src=$(wildcard ./*.c)
:wildcard
是函数名
,表示从某目录下查找文件,./*.c
是函数参数
,表示在当前目录下查找所有的.c
文件,然后获取返回值
,再函数前面加一个$
符号即可(加括号就是为了使用$符号取返回值)(返回的结果就是:m ain.c add.c sub.c mul.c)。
- 把对应的
.c
文件换成.o
文件
obj=$(patsubst ./%.o, ./%.c, $(src))
:patsubst
是函数名,该函数是一个匹配替换函数
。把当前目录下的所有.c
文件替换为同名的.o
文件,.c
文件来自$(src)
使用上面的两个函数之后,不管当前目录下有多少文件,它会自动搜索所有的.c
文件,然后做一个字符串替换成对应的.o
文件,返回到obj。
#obj=main.o add.o sub.o mul.o
target=app
#src返回的内容就是我们从指定目录下查找的.c文件
src=$(wildcard ./*.c)
#把所有的.c替换成.o,这样得到的obj和我们手动指定的一样
obj=$(patsubst ./%.o, ./%.c, $(src))
# makefile自己维护的变量
CC = gcc
CPPFLAGS = -I
$(target):$(obj)
$(CC) $(obj) -o $(target)
# main.o是子目标
%.o:%.c
gcc -c $< -o $@
7、清除makefile编译生成的一些文件
当我们需要重新编译
的时候,可以删除之前生成的一些中间文件。
target=app
src=$(wildcard ./*.c)
obj=$(patsubst ./%.o, ./%.c, $(src))
# makefile自己维护的变量
CC = gcc
CPPFLAGS = -I
$(target):$(obj)
$(CC) $(obj) -o $(target)
# main.o是子目标
%.o:%.c
gcc -c $< -o $@
# 删除我们生成的中间.o文件和可执行文件,当我们需要重新编译的时候使用
clean:
rm $(obj) $(target)
hello:
echo "hello, makefile"
然后在命令行中使用:
>>>make clean
:即可清除之前编译生成的中间文件。此时只会执行clean
这个子目标。
当要删除的文件在不存在的时候就会在命令行中提示:无法删除,或没有那个文件/目录,此时可以在后面加一个-f
参数,表示无论存在与否,都会强制删除
,也不会提示不存在信息。
clean:
rm $(obj) $(target) -f
注意:
- clean是要生成的目标
- clean没有依赖
- 在命令行下直接输入
make
编译生成的是终极目标 - 在命令行中输入
make 子目标
,此时只会执行子目标中的命令
。例如:上面我们随便定义一个hello子目标,也没有依赖,此时可以直接在命令行中输入make hello
,此时只会执行(编译)该子目标中的命令
。
8、声明伪目标
我们在执行make clean的时候并不会在当前目录下生成一个clean文件,当我们在当前目录创建一个叫clean名字的文件的时候,此时的clean文件就是最新的,当我们在去执行make clean的时候,就会提示clean是最新的
,因为子目标会的clean会与clean文件的时间做对比。解决方式就是不让其做对比,把clean子目标声明为一个伪目标
。
# 使用.PHONY:clean 把clean声明为一个伪目标
.PHONY:clean
clean:
rm $(obj) $(target)
声明为伪目标之后,再做make clean的时候就不会再做更新比较了。
9、makefile的一些其他细节
命令执行失败,直接忽略
clean:
mkdir /aa
rm $(obj) $(target)
作为普通用户再根目录下创建mkdir /aa
一个文件肯定会失败,要先获取权限才可以。
解决方式,执行的命令前加-
,当命令执行失败之后会忽略,然后继续向下执行,如下:
clean:
-mkdir /aa
rm $(obj) $(target)
♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠