前言:对,我就是标题党,我也不太会写makefile,本篇只是入个门,只能教你写一些简单的makefile。
为什么要学makefile
make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的,所以在你写项目是,只需要编写一次,之后你编译的时候只用make一下就行了。
makefile命名规则:
- makefile
- Makefile
当然如果你不喜欢这个命名,偏要自己写名字,那也可以,但是就需要你在每次make时指定文件,用法如下
make -f 你写的makefile文件名
makefile三要素:
- 目标
- 依赖
- 规则命令
当然,这三要素也不一定全都要写出来,依赖和规则在特定情况下可以不写,而目标必须写。
写法:
在将写法之前,我先来说一下我的这个例子的目录结构,源代码请看我这篇帖子,除了头文件和目录格式稍微改了一下,内容没改,可以预先看一下头文件包含,其实不看也可以,主要是学写的方法。
include目录里面
下面来正式学习makefile的写法
标准格式:
目标:依赖
[tab键]规则命令
有了格式,所以我们就照着写法写出了一个很简单makefile,其实makefile的语法和写bash的比较像,学了bash在学makefile相对来说更好学一点。
第一版:
myString:myString.cpp Dbug.cpp
g++ -o myString -I./include myString.cpp Dbug.cpp
这时候在命令行输make执行,就会生成一个可执行文件myString,如果你的源码没有改变执行make会提示当前已经是最新的,不需要编译。
ps:make只关心.cpp和.o的时间差,不关心.h的时间差,也就是说如果只修改.h文件,make不会去重新编译这个.c文件。
这一版的makefile有弊端,如果更改其中一个文件,所有的源码就得重新编译,所以,为了更高效率,我们可以考虑将整个编译过程分解,先生成.o文件,然后链接.o文件变成可执行文件,这样的话,如果只改了一个.cpp文件,则就更改相应的.o文件,在链接,就不需要将整个文件编译。
第二版:
我们写makefile的规则是递推的,之前一版的依赖文件直接是.c文件
改进版我们可以先写一个标准格式,这里的依赖文件是.o文件,而因为本来预先没有.o文件,而在单写每个.o文件生成的标准格式,这里.o文件生成的依赖文件是.cpp文件,如果依赖文件比目标文件新,则重新生成目标文件,说这么多可能有点迷糊,我们来看一下代码理解一下。
myString:myString.o Dbug.o
g++ -o myString -I./include myString.o Dbug.o
myString.o:myString.cpp
g++ -c -I./include myString.cpp
Dbug.o:Dbug.cpp
g++ -c -I./include Dbug.cpp
这下大概理解意思了吧,虽然效率增高了,但是这里写的还是比较麻烦,因为makefile可以使用变量,我们用变量表示的方式可以稍微减轻一点代码量,这里的变量写法和bash的语法比较像,定义使用变量是要加$(变量名)。
Objfiles = myString.o Dbug.o
#目标变量的使用$(Objfiles)
myString:$(Objfiles)
g++ -o myString -I./include $(Objfiles)
myString.o:myString.cpp
g++ -c -I./include myString.cpp
Dbug.o:Dbug.cpp
g++ -c -I./include Dbug.cpp
大家看这样写时,有很多目标,为什么最后生成的是myString呢?这里就牵扯了makefile的一条隐含规则,默认处理第一个目标。
写这一版的makefile,大家发现虽然有的变量,但是还是要进行很多的重复性工作,比如要一个一个的把所有的.o文件写出来,还有就是,每一个.o文件都要给他写一下他是怎么生成的,但是写的这几个代码相似度很高,当.cpp文件比较多的时候,写起来很烦人,所以,我们接下来就用makefile的函数和变量来简化这一步。
第三版
函数:
- wildcard 结合通配符,可以进行文件匹配
- pastsubst 结合通配符,将内容进行替换
话不多说,看一下用了这个函数之后的makefile怎么写,用法在代码中也写了
#获取所有的.cpp文件,wildcard函数的使用
SrcFiles=$(wildcard *.cpp) #这里的SrcFiles变量就是所有的.cpp文件了
#使所有的.cpp文件变成.o文件,pastsubst函数的使用
Objfiles=$(patsubst %.cpp,%.o,$(SrcFiles)) #这里通过函数的三个参数,将第三个参数里的SrcFiles变量的所有.cpp变成了.o
#目标变量的使用$(Objfiles)
myString:$(Objfiles)
g++ -o myString -I./include $(Objfiles)
myString.o:myString.cpp
g++ -c -I./include myString.cpp
Dbug.o:Dbug.cpp
g++ -c -I./include Dbug.cpp
使用了函数,我们简化了写.o文件的步骤,下来,我们就使用makefile的变量解决需要重复写生成.o文件的这个过程。
变量:
- $@ 表示目标
- $^ 表示全部依赖
- $< 表示第一个依赖
- $? 表示第一个变化的依赖
注意:模式匹配规则, < 这样的变量,只能在规则中出现,不能出现在目标和依赖中
根据上面说的几个变量,加上通配符,就根据相应的变化和依赖来一次性写完下面所以的.cpp文件的生成,下面就是代码,由于这个依赖只有一个,所以有多个通配符都可以使用,我下面是三种实现。
#获取所有的.cpp文件,wildcard函数的使用
SrcFiles=$(wildcard *.cpp) #这里的SrcFiles变量就是所有的.cpp文件了
#使所有的.cpp文件变成.o文件,pastsubst函数的使用
Objfiles=$(patsubst %.cpp,%.o,$(SrcFiles)) #这里通过函数的三个参数,将第三个参数里的SrcFiles变量的所有.cpp变成了.o
#目标变量的使用$(Objfiles)
myString:$(Objfiles)
g++ -o myString -I./include $(Objfiles)
#模式匹配规则,$@,$< 这样的变量,只能在规则中出现
%.o:%.cpp
# g++ -o $@ -c -I./include $^
# g++ -c -I./include $^
g++ -c -I./include $<
写到这里,这个make已经写的差不多了,接下来就是进行一些小修小补完善一下。
第四版
增加clean功能,在make的要写一个clean功能,一键删除已生成的可执行文件和.o文件,这里就比较简单了,大家一看就懂,还有四个小技巧需要大家掌握一下
- 在使用make的时候,如果想要你的命令不回显,你可以在命令前加上@符号
- 在使用make的时候,如果想要单行命令如果有错而不退出继续执行,你可以在命令前加上-
- 在写makefile的时候,在开始写一个all:[想要生成的文件名],这样写可以同时生成多个目标
- 在写clean时,需要将clean定义成伪目标,防止有歧义,定义方法是在clean目标上加一行代码 .PHONY:clean
下面是完全版
#获取所有的.cpp文件,wildcard函数的使用
SrcFiles=$(wildcard *.cpp) #这里的SrcFiles变量就是所有的.cpp文件了
#使所有的.cpp文件变成.o文件,pastsubst函数的使用
Objfiles=$(patsubst %.cpp,%.o,$(SrcFiles)) #这里通过函数的三个参数,将第三个参数里的SrcFiles变量的所有.cpp变成了.o
#目标变量的使用$(Objfiles)
all:myString
myString:$(Objfiles)
g++ -o myString -I./include $(Objfiles)
#模式匹配规则,$@,$< 这样的变量,只能在规则中出现
%.o:%.cpp
# g++ -o $@ -c -I./include $^
# g++ -c -I./include $^
g++ -c -I./include $<
.PHONY:clean
clean:
-@rm -f *.o
-@rm -f myString
总结:makefile在一些小的工程完全可以人工手写,但是当工程非常大的时候,手写makefile也是非常麻烦的,而且,如果换了个平台makefile又要重新修改。这时候为了减轻负担,就需要Cmake这个工具,cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他功能,就是可以跨平台生成对应平台能用的makefile,你不用再自己去修改了。
cmake根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。但是CMakeLists.txt文件需要你自己手写的。