前言
Linux下的开发,一般都是基于开源的编译器,很多时候并没有太好的IDE,此时非常有必要掌握一门通用的编译构建方法。Makefile应运而生,成功了最流行的Linux下的编译构建方法。Makefile主要是面向代码的编译构建,但是其目标依赖执行的基本原理,也可以用到其他有类似的场景。
1. 概念
1.1. Makefile是什么?
Makefile是一个有规则要求的描述文件,然后通过make工具来解析并执行其中描述的动作。Makefile可以调用相应电脑的命令行来执行一些动作,所以除了不同操作系统的命令行有一些差异外,Makefile的基本规则在所有操作系统中都是一样的。
1.2. Makefile的基本内容
Makefile主要包括两部分,一是变量的定义及展开,另外就是目标和依赖及执行。
1.3. Makefile运行的过程
当我们执行make时,make会在当前目录下找Makefile或makefile的文件,找到之后,就开始解析和执行。因为其根据时间戳来判定是否执行,所以可以达到只编译修改过的源文件,而不用重新编译没有修改过的的源文件。
- 解析,就是如果有#include指令,则将相应文件添加进来,然后完成变量的定义及展开。
- 执行,找到终极目标及其依赖,依赖默认是文件,make会检测依赖与目标的时间戳,如果依赖的时间戳比目标的时间戳更新,则开始执行相应的命令,否则不进行执行。此原理可以解决多文件编译时重复编译的问题。
2. 变量
2.1. 变量的定义
> variable=value list
变量命名可是包含字符、数字、下划线,不能含有":“、”#“、”=“或空字符,变量名大小写敏感。变量名,类似C/C++中的宏,所以推荐用全大写加下划线区分单词。
值列表,值可以有多个,以空格分隔。
2.2. 变量的赋值
变量赋值有4种形式:
- 简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
输出:MAIN := main OBJ := $(MAIN).o MAIN := wmain test: @echo $(MAIN) @echo $(OBJ)
wmain
main.o
从输出可以看出,简单赋值是有先后顺序的。
- 递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
输出:MAIN = main OBJ = $(MAIN).o MAIN = wmain test: @echo $(MAIN) @echo $(OBJ)
wmain
wmain.o
从输出可以看出,递归赋值后面的操作会影响前面的关联变量。所以一般情况下,不建议使用递归赋值。
-
条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
MAIN := main OBJ := $(MAIN).o MAIN ?= wmain test: @echo $(MAIN) @echo $(OBJ)
输出:
main
main.o也可以通过make命令参数来指定条件变量。
make MAIN=wmain
-
追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
MAIN := main OBJ := $(MAIN).o MAIN += $(OBJ) test: @echo $(MAIN) @echo $(OBJ)
输出:
main main.o
main.o -
变量赋值的换行
OBJS := main.o \ add.o \ sub.o \ comb.o
2.3. 变量的引用
变量的引用,即使用变量,使用$(variable)或 ${variable},并且变量可以嵌套引用。变量的引用是一个展开的过程,类似宏的展开。
2.3.1. 基本引用及嵌套引用
# file name:Makefile
OBJ = main.o add.o
NEW_OBJ = $(OBJ)
ALIAS_OBJ = NEW_OBJ
ALL_OBJ = $($(ALIAS_OBJ))
# 命令行输入make测试
test:
@echo $(OBJ)
@echo $(NEW_OBJ)
@echo $(ALIAS_OBJ)
@echo $(ALL_OBJ)
```
输出:
main.o add.o
main.o add.o
NEW_OBJ
main.o add.o
2.3.2. 复杂引用
Makefile的变量引用的展开,不仅是单纯的替换,还可以调用一些特殊的函数,使得展开可以完成更复杂的功能。
2.3.2.1. 通配符
通配符 | 使用说明 |
---|---|
* | 匹配0个或者是任意个字符 |
? | 匹配任意一个字符 |
[] | 我们可以指定匹配的字符放在 “[]” 中 |
# 当前目录下有a.c、b.c、ab.c3个源文件
SOURCE1 := *.c
SOURCE2 := ?.c
SOURCE3 := [ab].c
test:
@echo $(SOURCE1)
@echo $(SOURCE2)
@echo $(SOURCE3)
输出
>a.c ab.c b.c
a.c b.c
a.c b.c
2.3.2.2. 特殊通配符
%,在变量展开过程中的模式通配符,表示匹配所有字符。
DIR := A B
OBJS := $(patsubst %, %.o, $(DIR))
test:
@echo $(OBJS)
输出:
A.o
B.o
2.3.2.3. 函数
在变量引用展开的过程中,可以调用特殊函数。
-
patsubst,模式字符串替换函数。$(patsubst %.c, %.o, $(SOURCES)),将 $(SOURCES)中所有.c结构的文件名替换为.o结尾。
-
subst,字符串替换函数,$(subset AA,aa,AAbbcc),将大写AA替换成小写aa。(注意,后面不要空格)
-
strip,去掉空格,$(strip )。
-
findstring,查找字符串函数,找到即返回目标字符串,否则返回空。
-
filter,过滤函数,$(filter pattern…, text),$(filter %.c %.o,1.c 2.o 3.s),返回1.c 2.o。
-
filter-out,反过滤函数,$(filter-out pattern…,text)。
-
sort,去重排序函数,$(sort list)。
-
notdir,去掉目录,保留文件名。
-
dir,取目录函数。
-
suffix,取后缀扩展名函数,$(suffix names…)。
-
basename,去掉后缀函数,$(basename names…)。
-
addsuffix,添加扩展后缀函数,$(addsuffix suffix,names…)。
-
addprefix,添加前缀函数,可以给文件添加目录,$(addprefix prefix,names…)。
-
join,连接函数,$(join list1, list2)。
-
wildcard,通配符函数,在函数中如果使用了通配符,需要用此函数标记,$(wildcard pattern)。
-
if,条件函数,$(if condition,then-part[,else-part])。
-
or,条件或函数,条件中有一个有效则返回,$(or condition1[,condition2[,condition3…]])。
-
and, 条件与函数,条件都有效,则返回最后一个字符串,$(and condition1[,condition2[,condition3…]])。
-
foreach,遍历列表提取指定元素并执行字符串,$(foreach var,list,text)。
-
file,文件操作函数,可以读写文件,$(file op filename[,text])。
-
call,传递参数并调用新的变量展开,$(call variable,param,param,…)。
-
shell,调用shell命令函数,$(shell cmd param)。
ifeq ($(OS),Windows_NT) #获取当前目录 CUR_DIR := $(subst /,\,$(abspath .)) # 提取当前文件夹名 CUR_DIR_NAME := $(notdir $(CUR_DIR)) # 获取当前目录下所有文件夹路径,包括当前文件夹 SOURCE_DIR := $(shell dir . /b /c /s /ad) #遍历获取当前目录下所有.c文件 SOURCES := $(wildcard $(patsubst %, %/*.c, $(SOURCE_DIR))) # 将所有.c文件列表转为.o列表 OBJS := $(SOURCES:.c=.o) else #获取当前目录 CUR_DIR := $(shell pwd) # 提取当前文件夹名 CUR_DIR_NAME := $(notdir $(shell pwd)) # 获取当前目录下所有文件夹路径,包括当前文件夹 SOURCE_DIR := $(shell find $(CUR_DIR) -type d) #遍历获取当前目录下所有.c文件 SOURCES := $(wildcard $(patsubst %, %/*.c, $(SOURCE_DIR))) # 将所有.c文件列表转为.o列表 OBJS := $(SOURCES:.c=.o) endif test: @echo $(CUR_DIR) @echo $(CUR_DIR_NAME) @echo $(SOURCE_DIR) @echo $(OBJS)
2.4. 内置变量
- AR,打包函数,默认命令为"ar"
- AS,汇编编译,默认命令为"as"
- CC,C语言编译,默认命令为"cc"
- CXX,C++语言编译,默认命令为"g++"
- CPP,C语言预处理命令,默认为"$(CC) -E”
- RM,删除文件命令,默认为"rm -f"
- ARFLGS,打包程序AR的参数,默认为"rv"
- ASFLAGS,汇编语言编译器参数
- CFLGS,C语言编译参数
- CXXFLAGS,C++语言编译器参数
- LDFLAGS,链接器参数。
Makefile的隐式规则会调用相应的内置变量,用户如果没有重新赋值,则用默认值。
3. 目标、依赖和命令
3.1. 基础
目标可以1个,也可以多个。依赖可以没有,也可以多个。命令可以没有,也可以多个。目标和依赖即可以是字符量,也可以是变量引用,甚至可以直接使用通配符描述。其规则如下:
注意:command前面必须以真实Tab键(不能是多个空格)隔开,标记后面的内容是命令。
target ... : prerequisites1 ...
command
...
...
```
prerequisites1 ...: prerequisites11 ...
command
...
...
prerequisites11 ...:
command
...
...
一般情况,目标和依赖,都被make程序当作文件处理。make程序会识别出顶层目标,然后一层一层往下链接。
- 当依赖文件的时间戳新于目标文件,则make程序会执行下面的命令,来通过依赖项构建新的目标。
- 当依赖文件的时间戳晚于目标文件,则下面的命令不会执行。
- 当目标文件不存在时,则会调用命令.
- 当依赖文件不存在时,表明依赖文件对应的目标需要构建执行,然后再执行当前目标对应的命令。
当依赖文件被识别到需要构建时,依赖必须作为目标存在,否则会提示相应目标没有规则,报错。
3.2. 多目标与多依赖
3.2.1. 自动化变量
在多目标和多依赖规则中,自动化变量可以自动指代相应变量。
自动化变量 | 说明 |
---|---|
$@ | 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也成为静态的库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名 。 |
$% | 当目标文件是一个静态库文件时,代表静态库的一个成员名。 |
$< | 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。 |
$? | 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。 |
$^ | 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。一个文件可重复的出现在目标的依赖中,变量“ ” 只记录它的第一次引用的情况。就是说变量“ ^”只记录它的第一次引用的情况。就是说变量“ ”只记录它的第一次引用的情况。就是说变量“^”会去掉重复的依赖文件。 |
$+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
$* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录部分)。 |
3.2.2. 普通多目标与多依赖
OBJS := main.o add.o
main: $(OBJS)
@echo main:$@
@echo main:$<
@echo main:$^
$(OBJS): FORCE
@echo OBJS:$@
输出:
OBJS:main.o
OBJS:add.o
main:main
main:main.o
main:main.o add.o
从示例中可以看出,$(OBJS)是遍历执行的,每次提取一个目标$@,并执行命令。$<只提取依赖项中的第1个,$^则提取所有的依赖项。
3.2.3. 静态模式规则
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
<targets …>:可以省略,target-pattern和prereq-patterns需要用到模式通配符%。%表示匹配当前目标集中的目标,并生成相应的依赖。
OBJS := main.o add.o
main: $(OBJS)
@echo main:$@
@echo main:$<
@echo main:$^
$(OBJS): %.o: %.c
@echo OBJS:$@ $<
输出:
OBJS:main.o main.c
OBJS:add.o add.c
main:main
main:main.o
main:main.o add.o
3.3. 伪目标
通常情况下,目标会被识别文件,导致会触发一些隐式编译规则。有时为了避免这种情况,需要标识目标为伪目标,即不对应相应的文件,并且不会被识别为顶层目标。
示例,使用make clean命令来删除所有.o文件。
.PHONE:clean
clean:
rm -rf *.o
3.4. 隐式规则
当依赖项作为的目标不存在时,make会自动根据依赖项执行相应的命令。
OBJS := main.o add.o
main: $(OBJS)
@echo main:$@
@echo main:$<
@echo main:$^
输出
cc -c -o main.o main.c
cc -c -o add.o add.c
main:main
main:main.o
main:main.o add.o
make自动根据当前依赖项,查找当前目标中是否有同名的源文件,然后根据源文件的后缀调用相应的默认编译器变量来执行编译。
3.5. 强制执行
有时想强势执行目标,可以有下面三种方法。
- 当目标文件不存在时,会强制执行
test:
@echo test
- 当依赖文件不存在时,会强制执行
test: FORCE
@echo test
#当FORCE不存在时,会强制执行,习惯用这种方式
FORCE: ;
- 当依赖项是伪目标时,会强制执行
test: FORCE
gcc *.c
# 利用伪目标来强制执行
.PHONE:FORCE
FORCE:;
4. 其他
4.1. 搜索路径
make默认是在当前目录下搜索相关文件,但是有时可能存在相同文件名,或是需要特殊指定搜索目录时,需要有一种方法来指定优先搜索目录及文件。
4.1.1. VPATH
VPATH是Makefile内置的环境变量,默认是空。当VPATH指定目录时,make会优先从VPATH代表地目录去搜索相关文件。
VPATH := src
表示优先从src目录搜索。
VPATH := src dll
表示先从src目录搜索,找不到再从dll目录搜索。
4.1.2. vpath
vpath是Makefile语法的关键字,语法如下:
vpath PATTERN DIRECTORIES
vpath PATTERN
vpath
# 表示从src目录中搜索test.c文件
vpath test.c src
# 表示从src或dll目录中搜索test.c文件
vapath test.c src dll
# 表示从当前目录搜索test.c,如果之前有相关设置,直接清除
vpath test.c
# 恢复默认,相当于清除之前所有设置
vapth
4.2. 条件判断
关键字 | 功能 |
---|---|
ifeq | 判断参数是否不相等,相等为 true,不相等为 false。 |
ifneq | 判断参数是否不相等,不相等为 true,相等为 false。 |
ifdef | 判断是否有值,有值为 true,没有值为 false。 |
ifndef | 判断是否有值,没有值为 true,有值为 false。 |
语法形式: |
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
如Windows下和Linux下的删除命令不同,需要区分。
ifeq ($(OS),Windows_NT)
RM := del /q /f
else
RM := rm -f
endif
4.3. 头文件依赖
头文件修改了,包括头文件的源文件则需要重新编译。但是有时,源文件中包含的头文件较多,且头文件中又引用其他头文件。这样复杂的情况下,手动建立一个源文件和头文件的依赖,不太容易。
gcc专门提供了一个编译选项,供用户提取源文件引用的头文件。
gcc -M main.c
会列出所有引用的头文件,包括系统头文件。系统头文件不会改变,不需要建立依赖关系。
gcc -MM main.c > include.txt
会列出所有引用的非系统头文件,并保存到include.txt文件中。
然后在Makefile中,通过include include.txt来将文件中的内容引入Makefile中。
4.4. make命令参数
参数选项 | 功能 |
---|---|
-b,-m | 忽略,提供其他版本 make 的兼容性 |
-B,–always-make | 强制重建所有的规则目标,不根据规则的依赖描述决定是否重建目标文件。 |
-C DIR,–directory=DIR | 在读取 Makefile 之前,进入到目录 DIR,然后执行 make。当存在多个 “-C” 选项的时候,make 的最终工作目录是第一个目录的相对路径。 |
-d | make 在执行的过程中打印出所有的调试信息,包括 make 认为那些文件需要重建,那些文件需要比较最后的修改时间、比较的结果,重建目标是用的命令,遗憾规则等等。使用 “-d” 选项我们可以看到 make 构造依赖关系链、重建目标过程中的所有的信息。 |
–debug[=OPTIONS] | make 执行时输出调试信息,可以使用 “OPTIONS” 控制调试信息的级别。默认是 “OPTIONS=b” ,“OPTIONS” 的可值为以下这些,首字母有效:all、basic、verbose、implicit、jobs、makefile。 |
-e,–enveronment -overrides | 使用环境变量定义覆盖 Makefile 中的同名变量定义。 |
-f=FILE,–file=FILE,–makefile=FILE | 指定文件 “FILE” 为 make 执行的 Makefile 文件 |
-p,–help | 打印帮助信息。 |
-i,–ignore-errors | 执行过程中忽略规则命令执行的错误。 |
-I DIR,–include-dir=DIR | 指定包含 Makefile 文件的搜索目录,在Makefile中出现另一个 “include” 文件时,将在 “DIR” 目录下搜索。多个 “-i” 指定目录时,搜索目录按照指定的顺序进行。 |
-j [JOBS],–jobs[=JOBS] | 可指定同时执行的命令数目,在没有 “-j” 的情况下,执行的命令数目将是系统允许的最大可能数目,存在多个 “-j” 目标时,最后一个目标指定的 JOBS 数有效。 |
-k,–keep-going | 执行命令错误时不终止 make 的执行,make 尽最大可能执行所有的命令,直至出现知名的错误才终止。 |
-l load,–load-average=[=LOAD],–max-load[=LOAD] | 告诉 make 在存在其他任务执行的时候,如果系统负荷超过 “LOAD”,不在启动新的任务。如果没有指定 “LOAD” 的参数 “-l” 选项将取消之前 “-l” 指定的限制。 |
-n,–just-print,–dry-run | 只打印执行的命令,但是不执行命令。 |
-o FILE,–old-file=FILE, | |
–assume-old=FILE | 指定 "FILE"文件不需要重建,即使是它的依赖已经过期;同时不重建此依赖文件的任何目标。注意:此参数不会通过变量 “MAKEFLAGS” 传递给子目录进程。 |
-p,–print-date-base | 命令执行之前,打印出 make 读取的 Makefile 的所有数据,同时打印出 make 的版本信息。如果只需要打印这些数据信息,可以使用 “make -qp” 命令,查看 make 执行之前预设的规则和变量,可使用命令 “make -p -f /dev/null” |
-q,-question | 称为 “询问模式” ;不运行任何的命令,并且无输出。make 只返回一个查询状态。返回状态 0 表示没有目标表示重建,返回状态 1 表示存在需要重建的目标,返回状态 2 表示有错误发生。 |
-r,–no-builtin-rules | 取消所有的内嵌函数的规则,不过你可以在 Makefile 中使用模式规则来定义规则。同时选项 “-r” 会取消所有后缀规则的隐含后缀列表,同样我们可以在 Makefile 中使用 “.SUFFIXES”,定义我们的后缀名的规则。“-r” 选项不会取消 make 内嵌的隐含变量。 |
-R,–no-builtin-variabes | 取消 make 内嵌的隐含变量,不过我们可以在 Makefile 中明确定义某些变量。注意:“-R” 和 “-r” 选项同时打开,因为没有了隐含变量,所以隐含规则将失去意义。 |
-s,–silent,–quiet | 取消命令执行过程中的打印。 |
-S,–no-keep-going,–stop | 取消 “-k” 的选项在递归的 make 过程中子 make 通过 “MAKEFLAGS” 变量继承了上层的命令行选项那个。我们可以在子 make 中使用“-S”选项取消上层传递的 “-k” 选项,或者取消系统环境变量 “MAKEFLAGS” 中 "-k"选项。 |
-t,–touch | 和 Linux 的 touch 命令实现功能相同,更新所有的目标文件的时间戳到当前系统时间。防止 make 对所有过时目标文件的重建。 |
-v,version | 查看make的版本信息。 |
-w,–print-directory | 在 make 进入一个子目录读取 Makefile 之前打印工作目录,这个选项可以帮助我们调试 Makefile,跟踪定位错误。使用 “-C” 选项时默认打开这个选项。 |
–no-print-directory | 取消 “-w” 选项。可以是 用在递归的 make 调用的过程中 ,取消 “-C” 参数的默认打开 “-w” 的功能。 |
-W FILE,–what-if=FILE,–new-file=FILE,–assume-file=FILE | 设定文件 “FILE” 的时间戳为当前的时间,但不更改文件实际的最后修改时间。此选项主要是为了实现对所有依赖于文件 “FILE” 的目标的强制重建。 |
–warn-undefined-variables | 在发现 Makefile 中存在没有定义的变量进行引用时给出告警信息。此功能可以帮助我们在调试一个存在多级嵌套变量引用的复杂 Makefile。但是建议在书写的时候尽量避免超过三级以上的变量嵌套引用。 |
4.4. 嵌套调用Makefile
ALL_PRJ_OBJ := Public/ZLib \
Public \
Device \
MPF \
Main
MAIN_BIN := Bin/main
all:$(ALL_PRJ_OBJ)
@echo Complete!
cp -r Bin ..
$(ALL_PRJ_OBJ): FORCE
@cd $@ && make
# 强制更新
FORCE:;
.PHONY : clean run
clean:
find . -name "*.o" -o -name "*.so" -o -name ".a" | xargs rm -rf
rm -rf $(MAIN_BIN)
5. 示例
5.1. 编译zlib为libzlib.so动态库
# define the Cpp compiler to use
CXX = gcc
# 动态库需要使用-fPIC
# define any compile-time flags
CXXFLAGS := -std=c99 -Wall -Wextra -g -fPIC
# define library paths in addition to /usr/lib
# if I wanted to include libraries not in /usr/lib I'd specify
# their path using -Lpath, something like:
LFLAGS =
# define output directory
OUTPUT := ../../Bin
# define source directory
SRC := .
# define include directory
INCLUDE := ../include
# define lib directory
LIB := ../../Bin
CUR_DIR_NAME := $(notdir $(shell pwd))
OBJ := ../../Obj/Public/$(CUR_DIR_NAME)
MAIN := lib$(CUR_DIR_NAME).so
SOURCEDIRS := $(shell find $(SRC) -type d)
LIBDIRS := $(shell find $(LIB) -name "*.so")
FIXPATH = $1
RM = rm -f
MD := mkdir -p
# define any directories containing header files other than /usr/include
INCLUDES := $(patsubst %,-I%, $(INCLUDEDIRS:%/=%))
# define the C source files
SOURCES := $(wildcard $(patsubst %, %/*.c, $(SOURCEDIRS)))
# define the C object files
OBJECTS := $(patsubst $(SRC)/%, $(OBJ)/%, $(SOURCES:.c=.o))
# define the C libs
LIBS :=
#
# The following part of the makefile is generic; it can be used to
# build any executable just by changing the definitions above and by
# deleting dependencies appended to the file from 'make depend'
#
OUTPUTMAIN := $(call FIXPATH,$(OUTPUT)/$(MAIN))
# 在make解析规则之前展开执行
OBJ_DIR := $(dir $(OBJECTS))
all: $(OUTPUT) $(OBJ_DIR1) $(MAIN)
@echo Executing 'all' complete!
$(OUTPUT):
$(MD) $(OUTPUT)
# 多目标规则,自动遍历符合规则的所有目标
$(OBJ_DIR):
$(MD) $@
# 动态库需要使用-shared
$(MAIN):$(OBJECTS)
$(CXX) $(CXXFLAGS) $(INCLUDES) -shared -o $(OUTPUTMAIN) $(OBJECTS) $(LFLAGS)
# 静态模式规则遍历所有的.o目标
$(OBJ)/%.o:$(SRC)/%.c
$(CXX) -c $(CXXFLAGS) $(INCLUDES) $< -o $@
# 伪目标,只能手动调用
.PHONY:clean
clean:
find $(OBJ) -name "*.o" | xargs rm -rf
rm -rf $(OUTPUTMAIN)
5.2. 编译当前目录所有源文件
遍历当前目录包括子目录所有源文件并编译,并将.o文件生成到Obj目录,引用编译.so文件,将执行文件生成到Bin目录。
# define the Cpp compiler to use
CXX = g++
# define any compile-time flags
CXXFLAGS := -std=c++17 -Wall -Wextra -g
# define library paths in addition to /usr/lib
# if I wanted to include libraries not in /usr/lib I'd specify
# their path using -Lpath, something like:
LFLAGS =
# define output directory
OUTPUT := ../Bin
# define source directory
SRC := .
# define include directory
INCLUDE := ../include
# define lib directory
LIB := ../Bin
# 通过shell获取当前目录,然后通过nodir获取当前目录名
CUR_DIR_NAME := $(notdir $(shell pwd))
OBJ := ../Obj/$(CUR_DIR_NAME)
MAIN := main
# 通过shell命令find找到当前目录中所有目录包括子目录
SOURCEDIRS := $(shell find $(SRC) -type d)
INCLUDEDIRS := $(shell find $(INCLUDE) -type d)
# 找到所有的.so动态库
LIBDIRS := $(shell find $(LIB) -name "*.so")
FIXPATH = $1
RM = rm -f
MD := mkdir -p
# define any directories containing header files other than /usr/include
INCLUDES := $(patsubst %,-I%, $(INCLUDEDIRS:%/=%))
# 通过patsubst函数获取所有目录中的所有.cpp文件并赋给SOURCES
SOURCES := $(wildcard $(patsubst %, %/*.cpp, $(SOURCEDIRS)))
# 遍历存在的.cpp文件生成对应的.o文件
OBJECTS := $(patsubst $(SRC)/%, $(OBJ)/%, $(SOURCES:.cpp=.o))
# define the C libs
LIBS := $(LIBDIRS)
#
# The following part of the makefile is generic; it can be used to
# build any executable just by changing the definitions above and by
# deleting dependencies appended to the file from 'make depend'
#
OUTPUTMAIN := $(call FIXPATH,$(OUTPUT)/$(MAIN))
# 在make解析规则之前展开执行
OBJ_DIR := $(dir $(OBJECTS))
# 顶级目标
all: $(OUTPUT) $(OBJ_DIR) $(MAIN)
@echo Executing 'all' complete!
# 创建输出目录
$(OUTPUT):
$(MD) $(OUTPUT)
# 多目标规则,自动遍历符合规则的所有目标目录
$(OBJ)/%:
$(MD) $@
# 生成main
$(MAIN): $(OBJECTS)
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $(OUTPUTMAIN) $(OBJECTS) $(LFLAGS) $(LIBS)
# 利用静态模式规则,遍历所有的目标文件并调用相应的.cpp文件
$(OBJ)/%.o:$(SRC)/%.cpp
$(CXX) -c $(CXXFLAGS) $(INCLUDES) $< -o $@
# 伪目标,只能手动调用
.PHONY:clean
clean:
find $(OBJ) -name "*.o" | xargs rm -rf
rm -rf $(OUTPUTMAIN)