笔记:Makefile编写规则
本文是对网上资料的一些整理,用于自学和复习。本文通过一个实例来讲解makefile的基本编写规则。
1、目录和源码
程序文件的目录结构如下:
|--code
|--main.c
|--div.c
|--mul.c
|--sub.c
|--sum.c
|--Makefile
|--inc
|--main.h
本文所用程序源码如下:
===========文件:main.c=============
#include"./main.h"
int main(){
printf("\n--------------\n");
int a = 10;
int b = 3;
printf("a + b = %d\n",sum(a,b));
printf("a - b = %d\n",sub(a,b));
printf("a * b = %d\n",mul(a,b));
printf("a / b = %d\n",div(a,b));
return 0;
}
===========文件:div.c=============
#include"main.h"
int div(int a, int b){
return(a/b);
}
===========文件:mul.c =============
#include"main.h"
int mul(int a, int b){
return(a*b);
}
===========文件:sub.c =============
#include"main.h"
int sub(int a,int b){
return(a-b);
}
===========文件:sum.c =============
#include"main.h"
int sum(int a,int b){
return(a+b);
}
2、1个规则(编写Makefile的基本规则)
目标:依赖条件
(一个tab缩进)命令(该命令可以用于生成目标)
例如:
sum.o:sum.c
gcc -I ./inc/ -c sum.c -o sum.o
其中sum.o是目标,sum.c是依赖条件,gcc –I./inc/… 是命令,用于实现目标。
说明:1、目标的时间必须要晚于依赖条件的时间,否则,更新目标。如上,如果第二编译前sum.c进行了修改(目标的时间早于依赖条件的时间),则第二次编译中gcc -I ./inc/ -c sum.c -o sum.o会被再次执行;如果第二编译前sum.c没有进行修改,则第二次编译中gcc -I ./inc/ -c sum.c -o sum.o不会被再次执行。
2、依赖条件若不存在,则找寻新的规则去产生依赖条件。
3、ALL(也可以小写all):用于指定makefile的最终目标,如果没写ALL,一般系统会默认文件最前面的目标是最终目标。
4、clean:没有依赖,主要用于清理编译过程中产生的.o文件和.out(可执行文件),例如
clean:
(一个tab缩进) -rm –rf ./*.o *.out #(rm前面的“-”作用是在删除不存在的文件时,不报错,顺序执行)
根据上面规则,我们可以写出第一版本的makefile了:
#file: Makefile
ALL:main
main:sum.o sub.o mul.o div.o main.o
gcc main.o sum.o sub.o mul.o div.o -o main
sum.o:sum.c
gcc -I ./inc/ -c sum.c -o sum.o
sub.o:sub.c
gcc -I ./inc/ -c sub.c -o sub.o
mul.o:mul.c
gcc -I ./inc/ -c mul.c -o mul.o
div.o:div.c
gcc -I ./inc/ -c div.c -o div.o
main.o:main.c
gcc -I ./inc/ -c main.c -o main.o
clean:
-rm -rf ./*.o main
3、2个函数(wildcard和patsubst)
1、wildcard 函数
src = $(wildcard ./*.c)
:匹配当前工作目录下的所有.c文件,将文件名组成列表,赋给变量src。相当于src= sum.c sub.c mul.c div.c main.c。
2、patsubst 函数
obj = $(patsubst %.c,%.o,$(src))
:将参数3($(src))中包含参数1(%.c)的部分,替换成参数2(%.o)。相当于obj = sum.o sub.o mul.o div.o main.o。
先看一个常见的错误写法:
#file:Makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c,%.o,$(src))
ALL:main
main:$(obj)
gcc $(obj) -o main
$(obj):$(src)
gcc -I ./inc/ -c $(src) -o $(obj) # 这里是错误的,src和obj都表示一组文件,不能用一组.c文件生成一组.o文件。
clean:
-rm -rf ./*.o main
第二版本makefile的正确写法:
#file: Makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c,%.o,$(src))
ALL:main
main:$(obj)
gcc $(obj) -o main
sum.o:sum.c
gcc -I ./inc/ -c sum.c -o sum.o
sub.o:sub.c
gcc -I ./inc/ -c sub.c -o sub.o
mul.o:mul.c
gcc -I ./inc/ -c mul.c -o mul.o
div.o:div.c
gcc -I ./inc/ -c div.c -o div.o
main.o:main.c
gcc -I ./inc/ -c main.c -o main.o
clean:
-rm -rf $(obj) main
4、3个自动变量(注意3个自动变量是在规则中使用)
$@ : 规则命令中的目标
$< : 规则命令中的第一个依赖条件。在模式规则应用中,它可以将依赖条件列表中的依赖依次取出,套用模式规则。
$^ : 规则命令中的所有依赖条件。
根据上面3个自动变量,我们可以写出第三个版本的makefile了:
#file: Makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c,%.o,$(src))
ALL:main
main:$(obj)
gcc $^ -o $@
sum.o:sum.c
gcc -I ./inc/ -c $< -o $@
sub.o:sub.c
gcc -I ./inc/ -c $< -o $@
mul.o:mul.c
gcc -I ./inc/ -c $< -o $@
div.o:div.c
gcc -I ./inc/ -c $< -o $@
main.o:main.c
gcc -I ./inc/ -c $< -o $@
clean:
-rm -rf $(obj) main
5、模式规则(注意模式规则是在目标和依赖条件中使用)
模式规则是在目标和依赖条件中使用%来匹配对应的文件,它可以一次匹配目录下的所有文件。目录下所有文件的编译可以由一条规则来完成。
我们用模式规则完成第四个版本的makefile:
#file: Makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c,%.o,$(src))
ALL:main
main:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -I ./inc/ -c $< -o $@
clean:
-rm -rf $(obj) main
可以和上面一个版本的makefile对比一下,用模式规则的makefile简洁了许多,而且方便扩展(当我们需要编写新的c文件时,可以不用在改动makefile,直接将c文件建在对应的文件夹就好)。
6、静态模式规则
静态模式规则可以指定某个依赖条件套用该模式规则,它可以防止在出现多个模式规则时产生冲突,如下第五个版本的makefile:
#file: Makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c,%.o,$(src))
ALL:main
main:$(obj)
gcc $^ -o $@
$(obj):%.o:%.c
gcc -I ./inc/ -c $< -o $@
clean:
-rm -rf $(obj) main
7、伪目标
所谓的伪目标可以这样来理解,它并不会创建目标文件,只是想去执行这个目标下面的命令。伪目标的存在可以帮助我们找到命令并执行。伪目标可以避免我们的 Makefile 中定义的只执行的命令的目标和工作目录下的实际文件出现名字冲突。例如:
.PHONY:clean ALL
8、一个简单的模板
最后附上一个简单的模板,如果不用交叉编译器,将 CROSS_COMPILE 设置为空即可。
#源文件目录
SRC_DIR = .
#源文件
SRC = $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
#头文件所在目录
INC_DIR = .
#.o文件都放到out目录下
OBJ_DIR = out
$(shell mkdir -p $(OBJ_DIR))
#.o文件
OBJ = $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRC)) )
#指定源文件的搜索路径
VPATH := $(SRC_DIR)
#最终生成的可执行文件
TARGET := main
#交叉编译工具,gcc编译器
CROSS_COMPILE := arm-linux-gnueabihf-
CC := $(CROSS_COMPILE)gcc
ALL:$(TARGET)
$(TARGET):$(OBJ)
$(CC) $^ -o $@
$(OBJ):$(OBJ_DIR)/%.o:%.c
$(CC) -I $(INC_DIR)/ -c $< -o $@
clean:
-rm -rf $(OBJ) $(TARGET)