linux-Makefile
参考学习视频:[linux从零到精通] gcc和Makefile,多文件编译神器
通配符
$@ 所有的目标文件
$^ 所有的依赖文件
$< 所有的依赖文件的第一个文件
make -j 参数加快编译效率
对于大型项目,在使用cmake控制编译时,仅仅执行make指令效率较低,使用make -j后面跟一个数字,比如make -j4 make -j6 make -j14等。
含义是 让make最多允许n个编译命令同时执行,这样可以更有效的利用CPU资源。
假设我们的系统是cpu是12核,在不影响其他工作的情况下,我们可以make -j12
将cpu资源充分利用起来,一般来说,最大并行任务数为cpu_num * 2
1. 查看物理CPU的个数
#cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l
2. 查看逻辑CPU的个数
#cat /proc/cpuinfo |grep "processor"|wc -l
3. 查看CPU是几核
#cat /proc/cpuinfo |grep "cores"|uniq
4. 查看CPU的主频
#cat /proc/cpuinfo |grep MHz|uniq
#以'#'开头的行表示注释
#定义变量VAR,强制赋值为app
VAR=test
#在VAR之前定义的值后面再追加app这个值,这时该变量值扩展为testapp
VAR+=app
#如果之前VAR没有被定义,则定义并使用testapp;否则使用之前的值。
VAR?=testapp
# 第一条目标为总的目标,
# 依赖可以是文件(目录)或为其他目标,依赖不是必须
# 动作可以是Linux命令,动作的那一行必须以TAB键开头
target: depend1 depend2 depend3 ...
[TAB] action1
[TAB ] action2
target1:
[TAB] action1
[TAB] action2
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到后就解释并执行该文件,如果找不到就提示错误并退出。一般Makeifle文件名我们会用Makefile或makefile,而不会使用GNUmakefile。接下来我们以之前的静态库和动态库为例讲解makefile的编写和使用。
之前我们在src路径下需要敲好几条命令才能编译生成动态库文件,而如果要拷贝安装文件或删除不用的文件时也要添加额外的命令。这样如果每次都要编译、安装、删除就比较麻烦。而如果我们写了一个makefile之后,接下来的工作只需要敲一条命令即可。下面是lib路径下用来同时生产静态库和动态库的makefile文件:
#定义变量指定生成的静态库和动态库的名字
LIBNAME=mycrypto
#定义一个变量指定库文件和头文件的安装路径
INSTPATH=`pwd`/../lib/
#定义编译器,如果今后交叉编译的话,只需要在这里改成交叉编译器即可。
CC=gcc
AR=ar
# 这里all是整个makefile文件的第一个目标,也就是总的目标,当我们输入make命令时就是要完成这个目标;
#该目标有两个依赖dynamic_lib和 static_lib 和 两个动作 make clear和 make install
# 其中两个依赖 dynamic_lib 和 static_lib 也是makefile文件的目标,
#所以整个makefile要先执行完dynamic_lib和static_lib这两个目标后才能执行后面的动作;
# 在动作make clear和make install前面有个 @ 符,这个符号会让执行make名时不打印这两天命令本身,而只是输出命令执行的结果;
#另外make命令在执行过程中,也会多次载入makefile文件;
all: dynamic_lib static_lib
@make clear
@make install
# dynamic_lib 目标用来编译生成动态库,它是all目标的一个依赖;
dynamic_lib:
${CC} -shared -fPIC *.c -o lib${LIBNAME}.so
# static_lib 目标用来编译生成静态库,它是all目标的一个依赖;
static_lib:
${CC} -c *.c
${AR} -rcs lib${LIBNAME}.a *.o
# install是一个单独的目标,他用来将编译生成的库文件和头文件拷贝到相应的安装路径下。
# 在总目标all下有个动作@make install 会执行该目标;
install:
cp -rf lib${LIBNAME}.* ${INSTPATH}
cp -rf *.h ${INSTPATH}
# uninstall是一个单独的目标,他用来在安装路径下删除之前安装的库文件和头文件
# 该目标没有被总的目标all依赖或执行,所以默认该目标不会被执行,
#如果想执行该目标,则可以在Linux命令行下输入make uninstall来执行
uninstall:
rm -f ${INSTPATH}/lib${LIBNAME}.*
rm -f ${INSTPATH}/*.h
# clear是一个单独的目标,他用来将编译生成的object临时文件删除。
# 在总目标all下有个动作@make clear会执行该目标;
clear:
rm -f *.o
# clean是一个单独的目标,它依赖clear目标,所以先通过clear目标删除所有的object临时文件,之后再删除编译产生的库文件;
# 该目标没有被总的目标all依赖或执行,所以默认该目标不会被执行,如果想执行该目标,则可以在Linux命令行下输入make clean 来执行
clean: clear
rm -f lib${LIBNAME}.*
Makefile中.PHONY的作用
单词phony (即phoney)的意思是:伪造的,假的。
来自collins的解释是:
If you describe something as phoney, you disapprove of it because it is false rather than genuine.
举个例子:
$ cat -n Makefile1
1 clean:
2 rm -f foo
$ cat -n Makefile2
1 .PHONY: clean
2 clean:
3 rm -f foo
Makefile1和Makefile2的差别就是在Makefile2中定义了:
.PHONY: clean
直接Make看看
$ make -f Makefile1 clean
rm -f foo
$ make -f Makefile2 clean
rm -f foo
从上述来看,Makefile1和Makefile2的行为没有啥子区别。
$ touch clean
$ ls -l
total 8
-rw-r--r-- 1 lzy lzy 0 Jul 13 18:06 clean
-rw-r--r-- 1 lzy lzy18 Jul 13 17:51 Makefile1
-rw-r--r-- 1 lzy lzy 32 Jul 13 17:51 Makefile2
$ make -f Makefile1 clean
make: 'clean' is up to date.
$ make -f Makefile2 clean
rm -f foo
区别来了,Makefile1拒绝了执行clean, 因为文件clean存在。而Makefile2却不理会文件clean的存在,总是执行clean后面的规则。由此可见,.PHONY clean发挥了作用。
小结:
.PHONY: clean
o means the word "clean" doesn't represent a file name in this Makefile;
o means the Makefile has nothing to do with a file called "clean"
in the same directory.
1 makefile的基本规则
makefile由一组规则组成,规则如下:
目标: 依赖
(tab)命令
makefile基本规则三要素:
- 目标: 要生成的目标文件
- 依赖: 目标文件由哪些文件生成
- 命令: 通过执行该命令由依赖文件生成目标
下面以具体的例子来讲解:
当前目录下有main.c fun1.c fun2.c sum.c, 根据这个基本规则编写一个简单的makefile文件, 生成可执行文件main.
第一个版本的makefile:
缺点: 效率低, 修改一个文件, 所有的文件会全部重新编译.
2 makefile工作原理
基本原则:
-
若想生成目标, 检查规则中的所有的依赖文件是否都存在:
- 如果有的依赖文件不存在, 则向下搜索规则, 看是否有生成该依赖文件的规则:
如果有规则用来生成该依赖文件, 则执行规则中的命令生成依赖文件;
如果没有规则用来生成该依赖文件, 则报错.
- 如果有的依赖文件不存在, 则向下搜索规则, 看是否有生成该依赖文件的规则:
-
如果所有依赖都存在, 检查规则中的目标是否需要更新, 必须先检查它的所有依赖,依赖中有任何一个被更新, 则目标必须更新.(检查的规则是哪个时间大哪个最新)
- 若目标的时间 > 依赖的时间, 不更新
- 若目标的时间 < 依赖的时间, 则更新
总结:
-
分析各个目标和依赖之间的关系
-
根据依赖关系自底向上执行命令
-
根据依赖文件的时间和目标文件的时间确定是否需要更新
-
如果目标不依赖任何条件, 则执行对应命令, 以示更新(如:伪目标)
第二个版本:
缺点: 冗余, 若.c文件数量很多, 编写起来比较麻烦.
3 makefile中的变量
在makefile中使用变量有点类似于C语言中的宏定义, 使用该变量相当于内容替换, 使用变量可以使makefile易于维护, 修改起来变得简单。
makefile有三种类型的变量:
-
普通变量
-
自带变量
-
自动变量
3.1 普通变量
-
变量定义直接用 =
-
使用变量值用 $(变量名)
如:下面是变量的定义和使用
foo = abc // 定义变量并赋值
bar = $(foo) // 使用变量, $(变量名)
定义了两个变量: foo、bar, 其中bar的值是foo变量值的引用。
除了使用用户自定义变量, makefile中也提供了一些变量(变量名大写)供用户直接使用, 我们可以直接对其进行赋值:
CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 -I
CFLAGS: C编译器的选项 -Wall -g -c
LDFLAGS : 链接器选项 -L -l
3.2 自动变量
-
$@: 表示规则中的目标
-
$<: 表示规则中的第一个条件
-
$^: 表示规则中的所有条件, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项。
特别注意:自动变量只能在规则的命令中使用.
模式规则
至少在规则的目标定义中要包含’%’, ‘%’表示一个或多个, 在依赖条件中同样可以使用’%’, 依赖条件中的’%’的取值取决于其目标:
比如: main.o:main.c fun1.o: fun1.c fun2.o:fun2.c, 说的简单点就是: xxx.o:xxx.c
makefile的第三个版本:
4 makefile函数
makefile中的函数有很多, 在这里给大家介绍两个最常用的。
- wildcard – 查找指定目录下的指定类型的文件
src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src- patsubst – 匹配替换
obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o
在makefile中所有的函数都是有返回值的。
当前目录下有main.c fun1.c fun2.c sum.c
src=$(wildcard *.c) 等价于src=main.c fun1.c fun2.c sum.c
obj=$(patsubst %.c,%.o, $(src))等价于obj=main.o fun1.o fun2.o sum.o
makefile的第四个版本:
缺点: 每次重新编译都需要手工清理中间.o文件和最终目标文件
5 makefile的清理操作
用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令, 解决方案:
-
伪目标声明:
.PHONY:clean- 声明目标为伪目标之后,
makefile将不会检查该目标是否存在或者该目标是否需要更新
- 声明目标为伪目标之后,
-
clean命令中的特殊符号:
-
“-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
rm -f: 强制执行, 比如若要删除的文件不存在使用-f不会报错 -
“@”不显示命令本身, 只显示结果。如:“@echo clean done”
-
其它
– make 默认执行第一个出现的目标, 可通过make dest指定要执行的目标
make -f : -f执行一个makefile文件名称, 使用make执行指定的makefile: make -f mainmak
makefile的第5个版本:
在makefile的第5个版本中, 综合使用了变量, 函数, 模式规则和清理命令, 是一个比较完善的版本.