Makefile编程基础
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。
1.示例代码
add.c
1 #include "add.h" 2 3 int add(int a, int b) 4 { 5 return a + b; 6 }
add.h
1 #ifndef __ADD_H__ 2 #define __ADD_H__ 3 4 int add(int a, int b); 5 6 #endif /* __ADD_H__ */
sub.c
1 #include "sub.h" 2 3 int sub(int a, int b) 4 { 5 return a - b; 6 }
sub.h
1 #ifndef __SUB_H__ 2 #define __SUB_H__ 3 4 int sub(int a, int b); 5 6 #endif /* __SUB_H__ */
main.c
1 #include <stdio.h> 2 #include "add.h" 3 #include "sub.h" 4 5 int main() 6 { 7 int a = 1, b = 2; 8 9 printf("a + b = %d.\n", add(a, b)); 10 printf("a - b = %d.\n", sub(a, b)); 11 12 return 0; 13 }
编译如下:
gcc add.c sub.c main.c -o main
运行如下:
$ ./main a + b = 3. a - b = -1.
2.Makefile基础
简单实例:
1 all: 2 @echo "hello world." 3 echo "nihao world."
加@符和不加@符输出不同,加@只显示结果,不加@会把命令和结果都显示。
输出结果:
hello world. echo "nihao world." nihao world.
目标:依赖
Tab 命令
目标:一般是指要编译的目标,也可以是一个动作;
依赖:指执行当前目标所要依赖的先项,包括其它目标,某个具体文件或库等,一个目标可以有多个依赖;
命令:该目标下要执行的具体命令,可以没有,也可以有多条,多条时,每条命令一行;
make常用选项
make [-f file] [options] [target] # make默认在当前目录中寻找GNU Makefile,makefile的文件作为make的输入文件。 -f 可以指定除上述文件名之外的文件作为输入文件; -v 显示版本号; -d Debug; -n 只输出命令,但并不执行,一般用来测试; -s 只执行命令,但不显示具体命令,此处可在命令中用@符抑制命令输出; -w 显示执行前、执行后的路径; -C dir 指定makefile所在的目录;
没有指定目标时,默认使用第一个目标;如果指定,则执行对应的命令。
1 all:test1 2 echo "hello world." 3 4 test1: 5 echo "nihao world."
all依赖于test1,所以test1先执行,all后执行。执行结果:
nihao world.
hello world.
根据本节知识点,第1节的编译可改为Makefile如下:
1 all: 2 gcc add.c sub.c main.c -o main 3 4 clean: 5 rm -rf main
3.GCC编译流程
直接从源码到编译出目标可执行文件
gcc main.c -o main
上述过程可细分为如下4个步骤:
1).预处理,得到main.ii
gcc -E main.c > main.ii
2).编译,得到main.s
gcc -S main.ii
3).汇编,得到main.o
gcc -c main.s
4).链接,得到可执行文件main
gcc main.o -c main
根据本节知识点,第2节的Makefile,优化如下:
1 main:add.o sub.o main.o 2 gcc add.o sub.o main.o -o main 3 4 add.o:add.c 5 gcc -c add.c -o add.o 6 7 sub.o:sub.c 8 gcc -c sub.c -o sub.o 9 10 main.o:main.c 11 gcc -c main.c -o main.o 12 13 clean: 14 rm -rf *.o main
4.变量
系统变量
$* 不包括扩展名的目标文件名称 $+ 所有的依赖文件,以空格分隔 $< 表示规则中的第一个条件 $? 所有时间戳比目标文件晚的依赖文件,以空格分隔 $@ 目标文件的完整名称 $^ 所有不重复的依赖文件,以空格分隔 $% 如果目标是归档成员,则该变量表示目标的归档成员名称
系统常量(make -p可查看常量)
AS 汇编程序的名称,默认为as CC C编译器名称,默认为cc CPP C预处理器名称,默认为cc -E CXX C++编译器名称,默认为g++ RM 文件删除程序别名,默认rm -f ...
自定义变量
定义:变量名=变量值
使用:$(变量名)/${变量名}
根据本节知识点,第3节的Makefile,优化如下:
1 CC=gcc 2 OBJ=add.o sub.o main.o 3 TARGET=main 4 5 $(TARGET):$(OBJ) 6 $(CC) $^ -o $@ 7 8 add.o:add.c 9 $(CC) -c $^ -o $@ 10 11 sub.o:sub.c 12 $(CC) -c $^ -o $@ 13 14 main.o:main.c 15 $(CC) -c $^ -o $@ 16 17 clean: 18 $(RM) *.o $(TARGET)
5.伪目标和模式匹配
1).伪目标
.PHONY:clean
声明目标为伪目标之后,makefile将不会判断目标是否存在或该目录是否需要更新。
2).模式匹配
// %通配符
%.o:%.cpp ---- .o依赖于对应的.cpp
// 获取当前目录下所有的.cpp文件
wildcard ---- $(wildcard ./*.cpp)
// 将对应的cpp文件名替换成.o文件名
patsubst ---- $(patsubst %.cpp, %.o, ./*.cpp)
根据本节知识点,第4节的Makefile,优化如下:
1 .PHONY:clean 2 3 CC=gcc 4 OBJ=$(patsubst %.c, %.o, $(wildcard ./*.c)) 5 TARGET=main 6 7 $(TARGET):$(OBJ) 8 $(CC) $^ -o $@ 9 10 %.o:%.c 11 $(CC) -c $^ -o $@ 12 13 clean: 14 $(RM) *.o $(TARGET)
通过$(wildcard ./*.c)获取当前目录下的.c文件名,通过patsubst把.c文件名转换为.o文件名,%.o:%.c进行文件"目标:依赖"的通配。
6.运行流程
7.编译动态链接库
动态链接库:不会把代码编译到二进制文件中,而是在运行时才加载,所以只需要维护一个地址。
-fPIC ---- 产生位置无关的代码
-shared ---- 共享
-l(小写L) ---- 指定动态库
-I(大写i) ---- 指定头文件目录,默认当前目录
-L ---- 手动指定库文件搜索目录,默认只链接共享目录
1).编译动态库
gcc -shared -fPIC DynamicTest.c -o libDynamicTest.so
2).链接动态库
gcc -lDynamicTest -L./ main.c -o main
3).发布动态库
复制动态库到/usr/lib或者/usr/local/lib
4).手动指定动态库位置(Linux)
除了发布动态库之外,我们还可以手动指定动态库的位置,而不需要发布。
LD_LIBRARY_PATH=./
export LD_LIBRARY_PATH
5).转换成Makefile
1 CC=gcc 2 TARGET=main 3 4 $(TARGET):libDynamicTest.so 5 $(CC) -lDynamicTest -L./ main.c -o $(TARGET) 6 7 libDynamicTest.so: 8 $(CC) -shared -fPIC DynamicTest.c -o libDynamicTest.so 9 10 clean: 11 $(RM) *.so $(TARGET)
8.编译静态链接库
1).生成静态库
gcc -c StaticTest.cpp -o StaticTest.o ar -r libStaticTest.a StaticTest.o
2).使用静态库
gcc -lStaticTest -L./ main.cpp -o main
3).转换成Makefile
1 CC=gcc 2 TARGET=main 3 4 $(TARGET):libStaticTest 5 6 libStaticTest: 7 $(CC) -c StaticTest.c -o StaticTest.o 8 $(AR) -r libStaticTest.a StaticTest.o 9 10 clean: 11 $(RM) *.o *.a $(TARGET)
9.公共头文件
Makefile中,都是先展开所有的变量,再调用指令
"=" ---- 赋值,但是用终值,就是不管变量调用写在赋值前还是赋值后,调用时都是取终值;
":=" ---- 也是赋值,但只受当前行及之前的代码影响,而不受后面的赋值影响.
文件目录结构
. ├── 001 │ ├── Makefile │ ├── a.c │ ├── b.c │ └── c.c ├── 002 │ ├── Makefile │ ├── x.c │ ├── y.c │ └── z.c └── Makefile
主./Makefile
1 .PHONY:clean 2 3 $(TARGET):$(OBJ) 4 $(CC) $^ -o $@ 5 6 clean: 7 $(RM) $(TARGET) $(OBJ)
001/Makefile
1 TARGET=c 2 OBJ=a.o b.o c.o 3 4 include ../Makefile
002/Makefile
1 TARGET=z 2 OBJ=x.o y.o z.o 3 4 include ../Makefile
执行001/Makefile,会调用主Makefile,并生成"c"可执行文件;执行002/Makefile,会调用主Makefile,并生成"z"可执行文件。
10.调用Shell命令
1 FILE=abc 2 3 A:=$(shell ls ../) 4 B:=$(shell pwd) 5 C:=$(shell if [ ! -f $(FILE) ]; then touch abc; fi) 6 7 all: 8 echo $(A) 9 echo $(B) 10 11 clean: 12 $(RM) $(FILE)
11.嵌套调用
文件目录结构
1 . 2 ├── 001 3 │ ├── Makefile 4 │ ├── a.c 5 │ ├── b.c 6 │ └── c.c 7 ├── 002 8 │ ├── Makefile 9 │ ├── x.c 10 │ ├── y.c 11 │ └── z.c 12 └── Makefile
主./Makefile
1 .PHONY:001 002 clean 2 3 DIR=001 002 4 5 all:$(DIR) 6 echo "hello world." 7 8 $(DIR): 9 make -C $@ 10 11 clean: 12 echo $(shell for dir in $(DIR); do make -C $$dir clean; done)
001/Makefile
1 .PHONY:clean 2 3 TARGET=c 4 OBJ=a.o b.o c.o 5 6 $(TARGET):$(OBJ) 7 $(CC) $^ -o $@ 8 9 clean: 10 $(RM) $(TARGET) $(OBJ)
002/Makefile
1 .PHONY:clean 2 3 TARGET=z 4 OBJ=x.o y.o z.o 5 6 $(TARGET):$(OBJ) 7 $(CC) $^ -o $@ 8 9 clean: 10 $(RM) $(TARGET) $(OBJ)
运行主Makefile,则会自动执行001/Makefile和002/Makefile。
12.条件判断
ifeq ---- 判断是否相等,相等返回true,不相等返回false;
ifneq ---- 判断是否相等,不相等返回true,相等返回false;
ifdef ---- 判断变量是否存在,存在返回true,不存在返回false;
ifndef ---- 判断变量是否存在,不存在返回true,存在返回false;
1 A:=123 2 3 RS1:= 4 RS2:= 5 6 # 判断变量A的值是否等于123 7 ifeq ($(A), 123) 8 RS1:=yes 9 else 10 RS1:=no 11 endif 12 13 # 判断变量A是否存在 14 ifdef A 15 RS2:=yes 16 else 17 RS2:=no 18 endif 19 20 all: 21 echo $(RS1) 22 echo $(RS2)
13.循环
1 TARGET=a b c d 2 3 all: 4 # $(shell for v in $(TARGET); do touch $$v.txt; done) 5 touch $(foreach v, $(TARGET), $v.txt) 6 7 clean: 8 echo $(shell for v in $(TARGET); do $(RM) $$v.txt; done)
14.自定义函数
1).自定义函数,不是真正的函数,本质上是多行命令;
2).自定义函数没有返回值。
1 define FUNC1 2 echo "hello world." 3 echo $(1) $(2) 4 endef 5 6 all: 7 $(call FUNC1, abc, def)
15.make install的实现
1 TARGET=main 2 OBJ=main.c 3 4 $(TARGET):$(OBJ) 5 $(CC) $^ -o $@ 6 7 install: 8 cp $(TARGET) /usr/local/bin 9 10 clean: 11 $(RM) $(TARGET)