在linux中我们使用gcc来编译单个文件,然而我们在工作中可不单单只有一个文件,因此面对大工程我们可以使用makefile来对整个项目工程进行编译。
那什么是makefile,其实就是定义了一整套编译规则的一个文件,然后使用make才启动这项规则,下面先来介绍一下make 这个命令
执行make命令的时候,首先CPU会去搜索这三个文件
(资料图片仅供参考)
“GNU-makefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行
还要记住make的环境变量是MAKEFILES
如果你的当前环境中定义了环境变量 MAKEFILES ,那么,make 会把这个变量中的值做一个类似于include 的动作,把这个MAKEFILES的变量指包含进去;
make的工作步骤
读入所有的 Makefile。
读入被 include 的其它 Makefile。
初始化文件中的变量。
推导隐晦规则,并分析所有规则。
为所有的目标文件创建依赖关系链。
根据依赖关系,决定哪些目标要重新生成。
执行生成命令
make的参数
make命令的参数有许多种,然而我们也没必要全部记住,重要的2个先记住
主要记住参数-f和-C
-f: 指定需要执行的 makefile文件
-C: 指定读取 makefile 的目录。如果有多个“-C”参数,make 的解释是
后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test
-C prog”等价于“make -C ~hchen/test/prog”。
合并使用实例:
make -C a/ -f Makefile.build
意思是到a目录里面找到Makefile.build,并执行
makefile基础
介绍完make之后,接下来,来看一下makefile的规则,其实整个规则是围绕着这三个概念
目标:依赖
规则
当依赖时间 > 目标,说明有需要更改,makefile才会执行规则(规则有些地方也叫命令)
目标就是我们需要生成的文件,比如可执行文件;目标也有伪目标
伪目标用.PHONY来定义
.PHONY XXX文件 定义为假想目标,就不会去判断目标是否存在
依赖就是生成目标需要的文件
规则也叫命令,就是执行这项规则去生成这个目标,其实大部分的规则其实也就是采用上一篇GCC编译规则去生成目标
实例:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
规则有分显示规则和隐示规则
显示规则:代码中有写出来的,可直接根据代码推导;
隐示规则:代码中没写,是make指令自己的规则,隐示规则是重点,在介绍隐示规则之前
先读一下这张表,名为“自动化变量表”
隐含规则:
重点记住这几条即可
1、只要 make 看到一个 .o 文件,它就会自动的把 .c (或者.cpp,根据其设置的环境变量而定)文件加在依赖关系中,并根据环境变量自动推导其相关规则
2、%的作用,比如当sub.o找到不到依赖的时候,回来这一条做匹配;
例如:
上面找不到.o的规则的会去第6行执行
3、同一个目标,一条规则有命令,另一个没有,他们的依赖会合并在一起
例如:
第5行和第8行合并
重点记住这三条,其他的有遇到在临时去查
重点记住:makefile会想尽办法生成最终目标,然后选择是否删除中间件(这个用户可以定义)
重要的知识点汇总
当然作为编程脚本,不仅仅这些,把它和C语言作对比,也有以下这些同样的功能,
引用其它的文件文件的功能
include filename
make 命令开始时,会找寻 include 所指出的其它的文件,并把其内容安置在当前的位置
make的寻找路径顺序
1、当前路径
2、如果 make 执行时,有 -I 或 --include-dir 参数,在这个参数所指定的目录下去寻找
3、/usr/local/bin 或 /usr/include
如果有文件没有找到的话,make 会生成一条警告信息,继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件make 才会出现一条致命信息;
如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在include 前加一个减号“-”。如:-include
文件搜寻功能:
大写的VPATH
VPATH:
如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。
如果定义了这个变量,那么,make 就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了
小写的vpath
vpath:
主要目的是搜索,搜索什么东西,在哪里搜索?
例如vpath %.h ../headers
搜索.h文件,在headers目录下搜错
如遇到连续的vpath
例如
vpath %.c foo
vpath % blish
vpath %.c bar
搜索.c文件,在foo目录下搜索,然后这blish,最后在bar目录下搜索
多目标的时候
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
等同于
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
@ 字符的作用
@ 字符在命令行前,那么,这个命令将不被 make 显示出来
例如 @echo 1234
如果没有@的话,会把整条命令都打印出来,有的话只会打印1234
分号的作用
如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令
例如
示例一
exec:
cd /home/hchen
pwd
示例二:
exec:
cd /home/hchen; pwd
当我们执行 make exec 时,第一个例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,而
第二个例子中,cd 就起作用了,pwd 会打印出“/home/hchen”
错误命令忽略:
有些时候,命令的出错并不表示就是错误的,为了忽略错误,我们可以在 Makefile 的命令行前加一个减号 - (在 Tab 键之
后),标记为不管命令出不出错都认为是成功的
还有一个全局的办法是,给 make 加上 -i 或是 --ignore-errors 参数,那么,Makefile 中所有命
令都会忽略错误
make 的参数的是 -k 或是 --keep-going ,这个参数的意思是,如果某规则中
的命令出错了,那么就终止该规则的执行,但继续执行其它规则。
export 的作用
传递上级变量给子级的makefile用
“嵌套执行”中比较有用的参数,-w 或是 --print-directory 会在 make 的过程中输出
一些信息,让你看到目前的工作目录
有两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管你是否 export,
其总是要传递到下层 Makefile 中,特别是 MAKEFLAGS 变量,其中包含了 make 的参数信息
定义命令包
定义一系列命令的集合
定义这种命令序列的语法以 define 开始,以 endef 结束
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
使用:
foo.c : foo.y
$(run-yacc)
要使用这个命令包,我们就好像使用变量一样,make在执行命令包时,命令包中的每个命令会被依次独立执行
override指示符
我们可能会有这样的需求;可以通过命令行来指定一些附加的编译参数,
对一些通用的参数或者必需的编译参数在Makefile中指定,而在命令行中指定一些特殊的参数。
对于这种需求,我们就需要使用指示符“override”来实现
EXEF = foo
override CFLAGS += -Wall –g
.PHONY : all debug test
all : $(EXEF)
foo : foo.c
………..
………..
$(EXEF) : debug.h
$(CC) $(CFLAGS) $(addsuffix .c,$@) –o $@
debug :
@echo ”CFLAGS = $(CFLAGS)”
执行:make CFLAGS=-O2 将显式编译“foo”的过程是“cc –O2 –Wall –g foo.c –o foo”。
执行“make CFLAGS=-O2 debug”可以查看到变量“CFLAGS”的值为“–O2 –Wall –g”。
另外,这个例子中,如果把变量“CFLAGS”之前的指示符“override”去掉,
使用相同的命令将得到不同的结果
如果make 指定了“-e”参数,那么,系统环境变量将覆盖 Makefile 中定义的变量
变量
变量的使用
$(变量名)
变量的赋值
•“=”延时赋值,该变量只有在调用的时候,才会被赋值
•“:=”直接赋值,与延时赋值相反,使用直接赋值的话,变量的值定义时就已经确定了。
•“?=”若变量的值为空,则进行赋值,通常用于设置默认值。
•“+=”追加赋值,可以往变量后面增加新的内容。
变量高级用法
$(var:a=b) 或是 ${var:a=b}
把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”
实例
foo := a.o b.o c.o
bar := $(foo:.o=.c)
在foo这个变量的值中,把.o结尾的换成.c
$(bar) 的值就是“a.c b.c c.c”
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
目标变量
首先在makefile中定义的变量都是全局变量
prog : CFLAGS = -g
定义在目标之后的变量,只在目标作用域范围内有效
模式变量
我们可以给定一种“模式”,可以把变量定义
在符合这种模式的所有目标上
%.o : CFLAGS = -O
同样,模式变量的语法和“目标变量”一样
意思是说只有目标为.o的文件才能使用这变量
条件判断
有变量,当然也有类似于C语言的 if .....else......条件判断
条件判断的关键字有四个
第一个ifeq
比较参数 arg1 和 arg2 的值是否相同
第二个ifneq
ifneq和ifeq相反
第三个条件关键字是 ifdef
ifdef
如果变量的值非空,那到表达式为真。否则,表达式为假
第四个条件关键字是 ifndef
ifndef
和ifdef意思相反
后缀规则:
后缀规则不允许任何的依赖文件,如果有依赖文件的话,那就不是后缀规则,那些后缀统统被认为是文件名
单后缀:
例如:.c 相当于 % : %.c
双后缀:
.c.o:(相当于%.o : %.c)
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
.c.o 那么其就是双后缀规则,意义就是 .c 是源文件的后缀,.o 是目标文件的后缀
函数库文件的后缀规则
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $.o$(AR) r $@ $.o
$(RM) $.o
其等效于:
(%.o) : %.c$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $.o
$(AR) r $@ $.o$(RM) $.o
函数
最后介绍一下函数:
函数包括函数名和参数
语法:
$()或者${}
函数和变量的括号最好一样,
如使用 $(subst a,b,$(x)) 这样的形式,而不是 $(subst a,b, ${x}) 的形式
记住格式语法就行,不需要去背,用到的时候这临时去查
总结: