本文参考文章:
Makefile由浅入深–教程、干货
Makefile文件是什么?
1. Makefile文件是什么?
Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,我们在 Makefile 中都可以进行描述。换句话说,有了 Makefile 可以使得我们的项目工程的编译变得自动化,不用每次都手动输入一堆源文件和参数。
2. 一个简单的Makefile demo
需要说明一下,文档目录结构是用户目录HOME下有src incl bin lib。
src:源码
incl:头文件
bin:执行码
lib:静态/动态库
这是大家最常见的Linux编程目录结构,以下代码编译都是依据这个结构。
Makefile代码:
hello:hello.c
gcc -I${HOME}/incl -c hello.c
gcc -o hello hello.o
rm -f hello.o
mv hello ${HOME}/bin
没接触过Makefile的同学肯定能看出,这段代码不就是把编译、链接、删除、移动写成shell脚本执行吗?没错的,把第一行去掉,其他代码粘贴到shell脚本里同样可以编译成功,一点问题都没有。
3. Makefile结构说明
Makefile里主要包含了五个东西:变量定义、显式规则、隐晦规则、文件指示和注释。
1、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
2、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。 刚才写的疑似shell脚本的Makefile全部都是显示规则。
3、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符。
4. 复杂一些的Makefile
根据上面的结构说明,我们对Makefile一层一层的改写,首先是隐晦规则,告诉大家其中一种用法:
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
这个隐晦规则其实就是告诉大家,后缀为cpp的文件怎么编译成.o,后缀为c的文件怎么编译成.o。
到目前为止的Makefile已经有模有样了,Makefile代码如下。
#隐含规则
INCL=-I${HOME}/incl
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
#C++编译
hellocpp:hellocpp.o
echo "开始编译"
g++ -o hellocpp hellocpp.o
rm -f hellocpp.o
mv hellocpp ${HOME}/bin
echo "编译结束"
#C编译
hello:hello.o
echo "开始编译"
gcc -o hello hello.o
rm -f hello.o
mv hello ${HOME}/bin
echo "编译结束"
细心的同学会发现刚才有个“$<”,如果看的有些蒙圈,那一定要了解预定义变量,下面这些是常用的预定义变量。
$* 不包含扩展名的目标文件名称。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$< 第一个依赖文件的名称。
$? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
$@ 目标的完整名称。
$^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。
Makefile的最终展现
实际编译结果
看着挺乱的吧,稍微改一改
#最后形成的Makefile
INCL=-I${HOME}/incl
BIN=$(HOME)/bin
OBJ1=hellocpp.o
OBJ2=hello.o
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
all: hellocpp hello
#C++编译
hellocpp:${OBJ1}
@echo "============开始编译============"
g++ -o $@ $?
@rm -f ${OBJ1}
@mv $@ ${BIN}
@echo "============编译结束============"
@echo ""
#C编译
hello:${OBJ2}
@echo "============开始编译============"
gcc -o $@ $?
@rm -f ${OBJ2}
@mv $@ ${BIN}
@echo "============编译结束============"
@echo ""
命令前加@,表示当前命令不显示,最后编译结果是这样。