Makefile的介绍
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,当工程中的文件逐渐增多,甚至变得十分庞大的时候,使用 GCC 命令编译就会变得力不从心。Linux 中的 make 工具提供了一种管理工程的功能,可以方便的进行程序的编译,对更新的文件进行重编译。
Makefile的基本格式为:
TARGET... : DEPENDEDS...
COMMAND
...
...
- TARGET:规则所定义的目标,通常是最后生成的文件,也可以是一个“动作”,称之为“伪目标”。
- DEPENDEDS:执行此规则所必须的依赖条件。
- COMMAND:规则所执行的命令。命令可以是多个,每个命令占一行,以 Tab 开头。
使用make进行项目管理,需要编写Makefile。在编译时,make程序按照顺序从Makefile文件中读取指令,依次执行!
当需要编译工程时,直接在工程目录中执行make
即可。如果想清除编译过程中生成的目标文件,执行make clean
即可。
makefile两种执行方式:直接make与make -f 指定文件
如果在本地工程目录下有文件名为makefile的makefile文件,使用make执行编译,使用make clean执行清除目标文件。
如果在本地工程目录下有指定的makefile文件,比如:demo.makefile文件,使用make -f demo.makefile执行编译,使用make clean -f demo.makefile执行清除目标文件。
使用预定义变量的Makefile
在Makefile中还有一些变量是系统预定义的,用户可以直接使用。
Makefile中经常使用的变量及含义
变量名 | 含 义 | 默 认 值 |
---|---|---|
AR | 生成静态库库文件的程序名称 | ar |
AS | 汇编编译器的名称 | as |
CC | C语言编译器的名称 | cc |
CPP | C语言预编译器的名称 | $(CC) -E |
CXX | C++语言编译器的名称 | g++ |
FC | FORTRAN语言编译器的名称 | f77 |
RM | 删除文件程序的名称 | rm -f |
ARFLAGS | 生成静态库库文件程序的选项 | 无默认值 |
ASFLAGS | 汇编语言编译器的编译选项 | 无默认值 |
CFLAGS | C语言编译器的编译选项 | 无默认值 |
CPPFLAGS | C语言预编译器的编译选项 | 无默认值 |
CXXFLAGS | C++语言编译器的编译选项 | 无默认值 |
FFLAGS | FORTRAN语言编译器的编译选项 | 无默认值 |
因此,前面的Makefile文件可以改写成:
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
clean:
-$(RM) $(TARGET) $(OBJS)
其中变量$(CC) $(RM)
可以直接使用,默认值分别是cc
和rm -f
。另外CFLAGS
等变量是调用编译器时的默认选项配置,在生成main.o
时没有指定编译选项,make程序自动调用了文件中定义的CFLAGS
选项来增加头文件的搜索路径。
使用自动变量的Makefile
还记得上面出现的 <和<和@ 吗?它们是Makefile中的自动变量,分别代表依赖项和目标项。下面是一些常见的自动变量及其含义:
Makefile 中常见的自动变量和含义
变量 | 含义 |
---|---|
* | 表示目标文件的名称,不包含目标文件的扩展名 |
+ | 表示所有的依赖文件,这些依赖文件之间以空格分开,按照出现的先后为顺序,其中可能包含重复的依赖文件 |
< | 表示依赖项中第一个依赖文件的名称 |
? | 依赖项中,所有目标文件时间戳晚的依赖文件,依赖文件之间以空格分开 |
@ | 目标项中目标文件的名称 |
^ | 依赖项中,所有不重复的依赖文件,这些文件之间以空格分开 |
由此,对上面的Makefile文件进行重写,代码如下:
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
$(CC) $^ -o $@ $(CFLAGS)
$(OBJS):%o:%c
$(CC) -c $< -o $@ $(CFLAGS)
clean:
-$(RM) $(TARGET) $(OBJS)
Makefile的实例
实例
add.h
#include <stdio.h>
int add(int a,int b);
add.cpp
#include "add.h"
int add(int a,int b)
{
int r = 0;
r = a+b;
return r;
}
main.cpp
#include "add.h"
int main(int argc, char *argv[])
{
printf("simple demo \n");
printf("add(91,8)=%d\n",add(91,8));
return 0;
}
main.makefile文件或makefile文件
test:main.o add.o
g++ main.o add.o -o test
main.o:main.cpp add.h
g++ -c main.cpp -o main.o
add.o:add.cpp add.h
g++ -c add.cpp -o add.o
.PHONY:clean
clean:
rm -rf *.o test
文件名为makefile的makefile文件,使用make执行编译,使用make clean执行清除目标文件。
文件名为main.makefile文件,使用make -f main.makefile执行编译,使用make clean -f main.makefile执行清除目标文件。
简化后的makefile文件
#这是简化后的makefile
CC=g++
OBJS=main.o add.o
test:$(OBJS)
${CC} $^ -o $@
%.o:%.c
${CC} -c $< -o $@
clean:
rm -rf *.o test
执行makefile并编译,生成test文件。
最后执行文件,输入命令./test
结果输出:
simple demo
add(91,8)=99
makefile中的$@, $^, $< , $?, $%, $+, $*
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
$% 表示仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是“foo.a(bar.o)”,那么,“$%”就是“bar.o”,“$@”就是“foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$+ 这个变量很像“$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*”就是空值。
Makefile中的条件判断
Makefile 中有多个条件判断语句,可以根据不同条件执行不同的命令。
ifeq:if equal的缩写,判断是否相等,相等返回true,不相等返回false
ifneq:if not equal的缩写,判断是否不相等,不相等返回true,相等返回false
ifdef:if definite的缩写,判断变量是否存在,存在返回true,不存在返回false
ifndef:if not definite的缩写,判断变量是否不存在,不存在返回true,存在返回false
else:否则(可有可无)
endif:结束if判断(一定要写)
以下是常见的 Makefile 条件判断语句:
1.ifeq 和 ifneq
ifeq 和 ifneq 分别表示等于和不等于的条件判断语句,用法如下:
ifeq ($(VARIABLE),value)
...
else
...
endif
ifneq ($(VARIABLE),value)
...
else
...
endif
其中,$(VARIABLE) 是需要被判断的变量名,value是需要和变量比较的值。如果判断是正确的,则执行第一组命令;否则执行第二组命令。
2.ifdef 和 ifndef
ifdef 和 ifndef 用于判断变量是否被定义,用法如下:
ifdef VARIABLE
...
else
...
endif
ifndef VARIABLE
...
else
...
endif
如果变量 VARIABLE 被定义,则执行第一组命令;否则执行第二组命令。
3.ifeq 的比较操作符
ifeq 还支持比较操作符,如 >、<、>=、<=、!= 等。例如:
ifeq ($(NUM1), $(NUM2))
...
endif
ifeq ($(NUM1), 10)
...
else ifeq ($(NUM1), 20)
...
else
...
endif
ifeq ($(strip $(SOME_VAR)),)
...
endif
这里用到了比较两个变量的值,根据不同条件执行不同命令。第三个例子中,使用了 strip 函数,去掉变量值中的所有空格,并且判断是否为空。
Makefile中的循环
makefile中支持循环的,并且循环有两种:
GNU平台下Makefile有默认的foreach循环
其他平台可以调用shell脚本的循环
调用shell指令可以通过$(shell 指令)或者指令的形式都可以
示例:循环创建文件和文件夹
target:=a b c d
filename=$(foreach v, $(target), $v.txt)
show:
@echo $(target)
@echo $(foreach v, $(target), $v)
for v in $(target);\
do touch $$v.txt;\
done
$(shell for v in $(target); do mkdir $$v-txt;done)
clean:
${RM} -rf $(target) $(filename) *txt
Makefile中定义命令包
命令包有点像是个函数, 将连续的相同的命令合成一条, 减少 Makefile 中的代码量, 便于以后维护。
语法:
define <command-name>
command
...
endef
示例:
define run_demo_makefile
@echo -n "Hello"
@echo " Makefile!"
@echo "这里可以执行多条 Shell 命令!"
endef
all:
$(run_demo_makefile)
Makefile中使用shell 命令
在 Makefile 中调用shell 命令有两种形式。
1.1 第一种是为了获取命令在shell环境中的执行结果。
利用 $(shell commmand) 作为基本结构,不需要放在基本规则格式以制表符Tab开始的 command 位置处,我们提到的基本规则格式及文章开头展示的 target-prerequisted-command 的形式。如下示例
CUR_DIR := $(shell pwd)
CUR_TIME := $(shell date)
FILE_LIST := $(shell ls *.c)
all:
@echo $(CUR_DIR)
@echo $(CUR_TIME)
@echo $(FILE_LIST)
#@echo回显内容
1.2 第二种是在单纯执行指定的命令以进行特定的操作。
用在基本规则格式的 command 处,例如打印信息、创建文件夹、删除中间文件等等。
all:
mkdir build
clean:
rm hello_test build/*.o
Makefile常用函数
Makefile常用字符串函数
把符合某一格式的字符串进行替换 patsubst
字符串替换函数 subst
整理空格函数 strip
查找字符串函数 findstring
过滤函数filter 和 反过滤函数filter-out
排序函数 sort
取单词函数 word
Makefile常用文件操作函数
取目录路径函数 dir
取文件名 函数 notdir
取后缀名 函数 suffix
取前缀 函数 notdir
添加后缀名函数 addsuffix
添加前缀名函数 addperfix
链接函数 join:拼接
获取匹配模式文件名函数 wildcard
Makefile中的其它常用函数
foreach
if
call 创建新的参数化的函数
origin 寻找变量的来源
详细参考:https://blog.csdn.net/akuaner/article/details/119534310
查看目标的依赖关系
写 Makefile 的时候, 需要确定每个目标的依赖关系。GNU提供一个机制可以查看C代码文件依赖那些文件。
查看C文件的依赖关系
gcc -MM main.c
结果输出
main.o: main.cpp add.h
查看C++文件的依赖关系
g++ -MM main.cpp
结果输出
main.o: main.cpp add.h
通用Makefile编写,参考下面文章
通用Makefile的编写和在项目工程中使用Makefile(包括动态库、静态库的链接、整个工程联合编译)
通用Makefile的编写和在项目工程中使用Makefile(包括动态库、静态库的链接、整个工程联合编译)_makefile 同时编译二进制和库-CSDN博客
通用Makefile文件
https://www.cnblogs.com/sysu-blackbear/p/4034394.html