linux开发之Make
简述
代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。
make是最常用的构建工具,他根据makefile来行动。
makefile是一个名字叫Makefile
(或makefile
)的文件,它里面有一堆规则,定义了具体该怎么编译,链接等操作。
当有几十上百份源代码文件时,手动输入命令行编译就很麻烦,你需要自己关注什么文件改变了,需要重新编译,什么保持不变等。
想象一下,当只有一个源文件改变,我们只需要编译这一个,然后重新链接即可,但手动操作的话,也是个细活。
而用make,就只需要输入make
就能够自动构建等待完成。
详细一点的简述
makefile的一般规则如下(注意命令前有tab):
目标:依赖
命令1
命令2
使用make时候只需要在命令行里输入make 目标
就可以进行目标构建。如果不指定目标,那么makefile的第一个目标就是默认目标。当目标文件不存在,或者目标文件比依赖文件要旧,这就说明需要更新了,就执行命令。
最简单的例子
假设我们有main.cpp,a.cpp,b.cpp。head.h中为fun1,fun2函数的声明。
// main.cpp
#include <iostream>
#include "head.h"
int main() {
puts("main fun");
fun1();
fun2();
return 0;
}
// a.cpp
#include <iostream>
void fun1() {
puts("This is fun1");
}
// b.cpp
#include <iostream>
void fun2() {
puts("Here is fun2");
}
最简单的makefile:
app: main.cpp a.cpp b.cpp
g++ main.cpp a.cpp b.cpp -o app
效果如下图,当我们需要编译时候只需要一个make语句就行。
进阶一点的例子
在上面的例子中,其实一点都没有体现出来优势。我们来模拟下,工程中有好多文件时候,有时候只需要重新编译一个文件,其余的保持不变,最后链接一下。
我们把makefile改写一下:
app: main.o a.o b.o
g++ main.cpp a.cpp b.cpp -o app
a.o: a.cpp
g++ -c a.cpp -o a.o
b.o: b.cpp
g++ -c b.cpp -o b.o
main.o: main.cpp
g++ -c main.cpp -o main.o
初次构建成功:
当我们对a.cpp进行修改后,再次构建如下图。可以看到,它只编译了a.cpp,并且进行了链接,其余没有发生改变的源文件就没有进行编译,节省了资源。
具体流程是:
- 检查app目标的依赖,如果遇到目标则递归检测依赖
- 检测到app目标的依赖的依赖,即a.o目标的依赖a.cpp文件要比a.o文件新
- 运行a.o目标命令
- 运行app目标命令
变量
工程中那么多文件,一个个手写名字还是很麻烦的,于是可以使用变量嘿嘿
自定义变量(注意等号左右没有空格): 变量名=变量值。例如:val=hello
预定义变量 | 含义 |
---|---|
AR | 归档维护程序的名称,默认为ar |
CC | c编译器的名称,默认为cc |
CXX | c++编译器的名称,默认为g++ |
$@ | 目标的完整名称 |
$< | 第一个依赖文件 |
$^ | 所有依赖文件 |
% | 目标和依赖字符串通配 |
使用变量格式为$(变量名)
接下来我们对之前的makefile进行修改:
src=a.o b.o main.o
target=app
$(target): $(src)
g++ $(src) -o $(target)
%.o: $.c
$(CXX) -c $< -o $@
成功构建
函数
虽然用了变量简单了很多,但每个目标文件的名字还是需要自己写,还是麻烦,而使用函数就可以轻松获取名字。
常用函数 | 作用 | 示例 | 结果 |
---|---|---|---|
$(wildcard 参数) |
获取参数位置的指定文件 | wildcar .cpp ./src/cpp | main.cpp a.cpp b.cpp |
$(patsubst <pattern>,<replacement>,<text>) |
查找text中的单词是否符合 |
patsubst %.c,%.o,x.c bar.c | x.o bar.o |
把a.cpp,b.cpp放到src文件夹下,然后对我们的makefile再做一点改动:
src=$(wildcard *.cpp ./src/*.cpp)
objs=$(patsubst %.cpp, %.o, $(src))
target=app
$(target): $(objs)
g++ $(objs) -o $(target)
%.o: $.c
$(CXX) -c $< -o $@
构建过程:
清理以及伪目标
当你想把源码发给别人时候,只想有纯净的代码,不想有目标文件,可执行文件等,那么就可以通过make设定一个clean目标来进行清理。
src=$(wildcard *.cpp ./src/*.cpp)
objs=$(patsubst %.cpp, %.o, $(src))
target=app
$(target): $(objs)
g++ $(objs) -o $(target)
%.o: $.c
$(CXX) -c $< -o $@
clean:
rm -f $(objs) $(target)
如图,可以看到成功清理了其他东西。
但是,如果当前文件夹下有一个名字叫clean的文件会发生什么?
clean目标没有依赖,当检测时候发现没有任何依赖比clean文件的时间更新,所以clean文件就是最新的。
解决方法为将clean目标声明为伪目标,指不是真的要生成文件
src=$(wildcard *.cpp ./src/*.cpp)
objs=$(patsubst %.cpp, %.o, $(src))
target=app
$(target): $(objs)
g++ $(objs) -o $(target)
%.o: $.c
$(CXX) -c $< -o $@
.PHONY:clean # 伪目标
clean:
- rm -f $(objs) $(target) # 命令前加 - 表示如果执行遇到问题了继续不用管
-@echo "cleaned!" # 命令前加 @ 表示该命令不在屏幕上显示