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语句就行。
makefile1使用

进阶一点的例子

在上面的例子中,其实一点都没有体现出来优势。我们来模拟下,工程中有好多文件时候,有时候只需要重新编译一个文件,其余的保持不变,最后链接一下。

我们把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,并且进行了链接,其余没有发生改变的源文件就没有进行编译,节省了资源。
具体流程是:

  1. 检查app目标的依赖,如果遇到目标则递归检测依赖
  2. 检测到app目标的依赖的依赖,即a.o目标的依赖a.cpp文件要比a.o文件新
  3. 运行a.o目标命令
  4. 运行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)

如图,可以看到成功清理了其他东西。
清理1

但是,如果当前文件夹下有一个名字叫clean的文件会发生什么?
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!"          # 命令前加 @ 表示该命令不在屏幕上显示

clean成功

posted @ 2023-04-13 17:18  1v7w  阅读(70)  评论(1编辑  收藏  举报