目录
一、Make简介
工程管理器,顾名思义,是指管理较多的文件。
Make工程管理器也就是个"自动编译管理器",这里的"自动 "是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。
Make将只编译改动的代码文件,而不用完全编译。
编译的四大过程;
1、预处理:用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。
如:gcc -E hello.c -o hello.i
2、编译:编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码的过程。
如:gcc -S hello.i -o hello.s
3、汇编:将上一步的汇编代码转换成机器码,这一步产生的文件叫做目标文件,是二进制格式。
如:gcc -c hello.s -o hello.o
4、链接:将多个目标文件以及所需要的库文件(.so等)链接成最终的可执行文件。
如:gcc hello.o -o hello
二、Makefile基本结构
Makefile是Make读入的唯一配置文件
每条规则由三个部分组成分别是目标(target) , 依赖(depend) 和命令(command)。
命令(command):
当前这条规则的动作,一般情况下这个动作就是一个 shell 命令。
---例如:通过某个命令编译文件、生成库文件、进入目录等。
---动作可以是多个,每个命令前必须有一个Tab缩进并且独占占一行。
依赖(depend):
规则所必需的依赖条件,在规则的命令中可以使用这些依赖。
---例如:生成可执行文件的目标文件(*.o)可以作为依赖使用
---如果规则的命令中不需要任何依赖,那么规则的依赖可以为空
---当前规则中的依赖可以是其他规则中的某个目标,这样就形成了规则之间的嵌套
---依赖可以根据要执行的命令的实际需求,指定很多个
目标(target):
规则中的目标,这个目标和规则中的命令是对应的。
---通过执行规则中的命令,可以生成一个和目标同名的文件规则中可以有多个命令,因此可以通过这多条命令来生成多个目标,所有目标也可以有很多个
---通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标。
示例:
Makefile格式
target : dependency_files
<TAB> command
例子
sunq:kang.o yul.o
gcc kang.o yul.o -o sunq
kang.o : kang.c kang.h
gcc --Wall --O -g --c kang.c -o kang.o
yul.o : yul.c
gcc - Wall --O -g --c yul.c -o yul.o
注释:
-Wall:表示允许发出gcc所有有用的报警信息.
-c:只是编译不链接,生成目标文件".o"
-o file:表示把输出文件输出到file里
关于更多的用man工具
Makefile:
源文件:
补充(Makefile):
-
假如我们只负责funtion1部分内容的编写,然后我们就使用命令 make funtion1.o即可,都是可以单独来执行每一个目标的。
-
当我们执行了命令 make funtion1.o 后,我们再去执行 make 我们会发现 makefile 会进行有选择的编译,只执行了funtion2.o、 main.o、test 的目标:
这里也就体现出了概述里面的时间戳和自动的概念(Make工程管理器也就是个"自动编译管理器",这里的"自动 "是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量)
伪目标:
当我们根据以上示例生成了test目标文件后,发现在工程目录中出现了很多的中间文件(*.o),
这时我们如果用 rm *.o 命令当然也可以实现删除,但是我们这里可以在例子中的Makefile文件中增加一个新的目标 clean 来实现删除操作:
增加了clean目标后,在终端输入 make clean 即可实现删除操作。
但是这里我们如果在工程目录中新建一个 clean 的文件的话,在使用 make clean命令 就不好使了,如下图所示:
这是就引入了伪目标的概念:工程中的某些文件和 Makefile 中的目标重名了。伪目标就是肯定会被执行的一些命令。在Makefile中如果我们遇到一些特殊的目标,希望它能够永远执行,那么我们就需要在Makefile中添加一个伪目标(不加条件限制,肯定会去执行的目标):
当我们修改完成后,再次执行 make clean 就正常了:
三、创建和使用变量
创建变量的目的: 用来代替一个文本字符串:
1.系列文件的名字
传递给编译器的参数
需要运行的程序
需要查找源代码的目录
你需要输出信息的目录
你想做的其它事情。
变量定义的方式:
递归展开方式VAR=var
简单方式 VAR:=var
变量使用$(VAR)
用""则用"$"来表示
类似于编程语言中的宏
OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
简单方式:
例:
简单方式 VAR:=var
m := mm
x := $(m)
y := $(x) bar
x := later
echo $(x) $(y)
看看打印什么信息?
用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展。
这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言 。
递归方式:
递归展开方式VAR=var
例子:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
$(foo)的值为?
echo $(foo)来进行查看
优点: 它可以向后引用变量
缺点: 不能对该变量进行任何扩展,例如
CFLAGS = $(CFLAGS) -O 会造成死循环
用?=定义变量
dir := /foo/bar
FOO ?= bar
FOO是?
含义是,如果FOO没有被定义过,那么变量FOO的值就是"bar",如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
为变量添加值
你可以通过+=为已定义的变量添加新的值
Main=hello.o hello-1.o
Main+=hello-2.o
预定义变量
AR 库文件维护程序的名称,默认值为ar。AS汇编程序的名称,默认值为as。
CC C编译器的名称,默认值为cc。CPP C预编译器的名称,默认值为$(CC) --E。
CXX C++编译器的名称,默认值为g++。
FC FORTRAN编译器的名称,默认值为f77
RM 文件删除程序的名称,默认值为rm -f
例子:
Hello: main.c main.h
<tab> $(CC) --o hello main.c
clean:
<tab> $(RM) hello
例
自动变量
$* 不包含扩展名的目标文件名称
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能 包含重复的依赖文件
$< 第一个依赖文件的名称
$? 所有时间戳比目标文件晚的的依赖文件,并以空格分开
$@ 目标文件的完整名称
$^ 所有不重复的目标依赖文件,以空格分开
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
刚才的例子:OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
例
环境变量
make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量
如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量
四、Make使用
直接运行make
选项
-C dir读入指定目录下的Makefile
-f file读入当前目录下的file文件作为Makefile
-i 忽略所有的命令执行错误
-I dir指定被包含的Makefile所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示make变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果make在执行过程中改变目录,打印当前目录名
五、例
工程目录
源文件
Makefile:
六、隐含规则
隐含规则1:编译C程序的隐含规则
"<n>.o"的目标的依赖目标会自动推导为"<n>.c",并且其生成命令是"$(CC) --c $(CPPFLAGS) $(CFLAGS)"
注意:
默认只有 -c ,如果需要其他特殊选项,就需要自己为CFLAGS赋值
例如 五、例 里面需要指定目录的特殊选择: CFLAGS=-I include ,就需要自己为CFLAGS赋值,不然会找不到 include/myinlcude.h文件。
等同于:
还可将Makefile简化到如下(.c到.o的过程也可省略):
隐含规则2:链接Object文件的隐含规则
"<n>" 目标依赖于"<n>.o",通过运行C的编译器来运行链接程序生成(一般是"ld"),其生成命令是:"$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)"。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:
x : x.o y.o z.o
并且"x.c"、"y.c"和"z.c"都存在时,隐含规则将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。
等同于:
七、VPATH的用法
VPATH : 虚路径
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。 Makefile文件中的特殊变量"VPATH"就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src**:**../headers 上面的的定义指定两个目录,"src"和"../headers",make会按照这个顺序进行搜索。目录由"冒号"分隔。(当然,当前目录永远是最高优先搜索的地方)
工程目录结构:
Makefile:
等同于:
八、Makefile的嵌套
例
1、工程目录结构;
2、include目录(只有一个myinclude.h文件):
3、main目录(main.c、Makefile):
4、src1目录(funtion1.c 、Makefile)
5、src2目录(funtion2.c 、Makefile):
6、obj目录(Makefile):
7、工程最顶层目录中的Makefile:
8、在最顶层目录中执行make命令:
补充说明
1、Makefile 中 echo 与 @ 符
在 Makefile 中有时会看到 echo 命令前添加了 "@" 符号:
@echo "hello"
它与不加 @ 符号的区别就是它不会把 "hello" 输出到终端(显示器)。
echo 明明就是用来输出内容的,那么 @ 是不是有点多余?
这是因为有另一种需求存在,就是将 "hello" 输出到文件中(比如需要写一个脚本文件)。
@echo "hello" >> file
此时会看到文件 file 中有追加了一行 "hello"。
如果不需要将 "hello " 显示在终端上的话就可以用 @ 把 echo 的输出内容屏蔽掉。
2、makefile中 rm、@rm 和 -rm的区别
- @告诉make在运行时不要回显要输出的配方。
- -告诉make忽略配方的返回值
3、Makefile中的all
这里需要生成两个可执行文件main1 和main2 (两个目标)。由于makefile只能有一个目标,所以可以构造一个没有规则的终极目标all,并以这两个可执行文件作为依赖。如下:
all:main1 main2
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o: main2.c
@gcc -c main2.c
4、make -C
-C dir读入指定目录下的Makefile(更多选项请参考四、Make使用)。