Makefile学习 之前只是略有了解,感觉十分方便,所以想深入了解一下。
概念 Makefile是一个规则文件,make是一个程序,在命令行键入make后,make自动找见Makefile来解析其中的规则来完成编译工作。
hello-world
1 2 3 a: @echo "hello world" @ls ./
1 2 3 os@os-virtual-machine:~/makefilestudy/1$ make hello world Makefile
1 2 3 4 5 6 7 8 9 a: @echo "hello world" @ls ./ gcc main.c clean: @rm -rf a.out @echo "a.out del success"
1 2 3 4 5 6 os@os-virtual-machine:~/makefilestudy/1$ make hello world main.c Makefile gcc main.c os@os-virtual-machine:~/makefilestudy/1$ make clean a.out del success
编译流程 首先是把各个文件先编译成目标文件,然后再统一把目标文件进行链接,这样做的好处就是当修改了一个文件之后,在执行make
并不会把所有文件全部再编译一次,而是只把改动的和改动相关的代码再编译一遍。
1 2 3 4 5 6 7 8 9 10 11 calc:add.o sub.o multi.o gcc add.o sub.o multi.o calc.cpp -o calc add.o:add.cpp gcc -c add.cpp -o add.o sub.o:sub.cpp gcc -c sub.cpp -o sub.o multi.o:multi.cpp gcc -c multi.cpp -o multi.o
变量使用
可以自定义变量,然后用$(变量名)来使用这个变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 OBJ=add.o sub.o multi.o calc.o TARGET=calc $(TARGET) :$(OBJ) gcc $(OBJ) -o $(TARGET) add.o:add.cpp gcc -c add.cpp -o add.o sub.o:sub.cpp gcc -c sub.cpp -o sub.o multi.o:multi.cpp gcc -c multi.cpp -o multi.o calc.o:calc.cpp gcc -c calc.cpp -o calc.o clean: rm -rf *.o calc
可以使用系统变量更加简化
$^就是所有不重复的依赖文件也就是:
后面跟着的内容,$(TARGET)冒号后面是变量OBJ
,所以是add.o sub.o multi.o calc.o
,$@是目标文件的完整名称,也就是$(TARGET)
,最后gcc $^ -o $@
=gcc add.o sub.o multi.o calc.o -o calc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 OBJ=add.o sub.o multi.o calc.o TARGET=calc $(TARGET) :$(OBJ) gcc $^ -o $@ add.o:add.cpp gcc -c $^ -o $@ sub.o:sub.cpp gcc -c $^ -o $@ multi.o:multi.cpp gcc -c $^ -o $@ calc.o:calc.cpp gcc -c $^ -o $@ clean: rm -rf *.o $(TARGET)
然后还可以使用系统常量来完成跨平台的一些效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 OBJ=add.o sub.o multi.o calc.o TARGET=calc $(TARGET) :$(OBJ) $(CXX) $^ -o $@ add.o:add.cpp $(CXX) -c $^ -o $@ sub.o:sub.cpp $(CXX) -c $^ -o $@ multi.o:multi.cpp $(CXX) -c $^ -o $@ calc.o:calc.cpp $(CXX) -c $^ -o $@ clean: $(RM) *.o $(TARGET)
伪目标和模式匹配 伪目标 按照我的理解,可能广义上来说目标就是指这个目标的命令执行后最后产生的文件名,一般make clean
就是执行clean目标的命令,如果在当前目录下touch了一个clean文件,然后再执行make clean
会发现没有执行命令,原因就是make认为clean文件就是目标,已经存在而且没有更新所以不需要执行命令了。
对于这种情况就得声明clean
为伪目标了,所以可以看出当当前目标不生成对应文件,而是一种功能目标时就应该把他声明成伪目标。
模式匹配
感觉这个规则还是很方便的,就拿上面的makefile来说,可以修改成下面的样子,makefile就缩短成几行的样子了,至于这条规则的原理,大概就是他会生成第一个目标也就是$(TARGET):$(OBJ)
,然后发现add.o sub.o multi.o calc.o
这些依赖文件都没有生成,然后就会去找生成这些文件的目标,首先是第一个add.o
,%.o:%.cpp
目标就能匹配,然后就会执行这个目标的命令,进而生成add.o
,其他所有的目标文件也是这样生成,最后生成calc
。
通过这个模式匹配,感觉makefile就是一层套一层,然后从最底层往上找,直到最底层的依赖全程都有了,再执行最底层的命令。有点递归那味了。
1 2 3 4 5 6 7 8 9 10 11 OBJ=add.o sub.o multi.o calc. TARGET=calc $(TARGET) :$(OBJ) $(CXX) $^ -o $@ %.o:%.cpp $(CXX) -c $^ -o $@ clean: $(RM) *.o $(TARGET)
这个makefile还可以利用wildcard
和patsubst
来增加makefile的泛用性
1 2 3 4 5 6 7 8 9 10 11 12 13 OBJ=$(patsubst %.cpp,%.o,$(wildcard ./*.cpp) ) TARGET=calc $(TARGET) :$(OBJ) $(CXX) $^ -o $@ %.o:%.cpp $(CXX) -c $^ -o $@ clean: $(RM) *.o $(TARGET)
编译动态链接库 感觉。。。没啥。。。。,和makefile关系不大
通用部分做公共头文件 makefile还有自动推导能力,上面的makefile还可以简短到以下几行
1 2 3 4 5 6 7 8 9 10 OBJ=$(patsubst %.cpp,%.o,$(wildcard ./*.cpp) ) TARGET=calc $(TARGET) :$(OBJ) $(CXX) $^ -o $@ clean: $(RM) *.o $(TARGET)
makefile还能嵌套makefile,那就可以把通用的内容写在一个makefile中,然后在其他makefile中使用,如下例
1 2 3 4 TARGET=c include ../makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SOURCE=$(wildcard ./*.cpp ./*.c) OBJ=$(patsubst %.cpp,%.o,$(SOURCE) ) OBJ:=$(patsubst %.c,%.o,$(OBJ) ) .PHONY :clean$(TARGET) :$(OBJ) $(CXX) $^ -o $@ clean: $(RM) $(TARGET) $(OBJ) show: echo $(SOURCE) echo $(OBJ)
在makefile中都是先解析所有变量然后再执行命令的,所以变量的声明放在哪里都是没有所谓的。
=
,赋值操作,把终值赋值给变量,注意不能字节赋值给字节,比如Y=$(Y)
:=
也是赋值,但是与=
不同的是他不会受到它下面的代码以及变量的影响。
调用shell 在makefile中可以使用ifndef endif
来添加默认值
在makefile中可以在目标以外执行shell命令,如下
1 2 3 A=$(shell ls) a: echo $(A)
在makefile中还可以调用其他makefile,不是像上面那样的包含,而是调用,因为很有时候不同模块有不同的makefile,要想直接全部就一起编译,可以再写个makefile调用他们,调用规则其实就是shell指令
条件判断&循环
自定义函数 变量,if,循环和函数都有了,感觉和一门语言都差不多了。
install
例子,把二进制程序放置在一个目录下,然后在/bin目录中放一个二进制程序的软链接,然后就可以在任何目录中调用这个程序了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 TARGET:=006_main OBJ:=$(TARGET) .o CC:=g++ PATH:=/tmp/006_main/ BIN:=/usr/local/bin/ $(TARGET) :$(OBJ) install:$(TARGET) if [ -d $(PATH) ];\ then echo $(PATH) exist; \ else \ /bin/mkdir $(PATH) ;\ /bin/cp $(TARGET) $(PATH) ;\ /bin/ln -sv $(PATH) $(TARGET) $(BIN) ;\ fi; clean: $(RM) $(TARGET) $(OBJ) $(RM) -rf $(PATH) .PHONY :clean install