一、Makefile的入门学习
编译工具及构建工具介绍
在之前的课程,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。
- cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。
- qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。
- ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成。
- auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
- make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。
二.Makefile的简单讲解
-
编译的四个阶段
回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembliang)、链接(Linking)。
-
Makefile的规则
TARGET....: prerequisites... <tab缩进>command <tab缩进>... <tab缩进>...
TARGET 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是一个标签( Label),对于标签这种特性,在后续的讲“伪目标”中会有叙述。prerequisites 就是,要生成那个 target 所需要的文件或是目标。command 也就是 make 需要执行的任意shell命令。
Makefile一个示例:
#这是一个Makefile debug: @echo "hello world"
Makefile采用
#
进行一行注释,采用\
作为续行符。make
后,打印hello world
,@
的作用是不打印echo指令本身。如果,我们要编译下面这个最简单的例子:
#include <stdio.h> int main(int argc, char *argv[]) { printf("hello world!\n"); return 0 }
Makefile修改如下:
debug : @echo "hello world" hello : gcc -o hello hello.c clean : @rm -f hello .PHONY: clean
在终端只输入
make
,执行第一个命令即make debug
,make hello
相当于在终端输入gcc -o hello hello.c
,同理make clean
相当于在终端输入rm -f hello
。.PHONY
为伪目标,如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标,可以避免这种情况,强制执行其命令。 -
变量赋值和预定义变量
Makefile中的变量赋值运算符有四种,分别是
=
、:=
、?=
和+=
。=
表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。:=
表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。?=
表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。+=
表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。$@
表示生成的目标文件;$^
表示所有的依赖文件;$<
代表第一个依赖文件。
示例:
TARGET = hello CC := gcc TARGET1 := $(CC) $(TARGET)#'='为延迟赋值 ':='为立即赋值 CC ?= g++# "?=" 表示如果前面没有定义CC,就定义为g++,如果定义了CC,就不定义了 CC += -g#'+='为追加。在CC后面追加-g $(TARGET): hello.c $(CC) -o $@ $^ compile: $(TARGET) clean: @rm -rf $(TARGET_DIR) .PHONY: clean compile
make compile
编译的结果为gcc -g -o hello hello.c
。
三.Makefile的一些常见函数
Makefile函数的基本格式是: ( ) 或者是 ( )或者是 ()或者是{ },其中,是函数名,是函数的参数,参数之间要用逗号分隔开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号或者大括号。
-
wildcard
通配符Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 * 和 ?,分别表示任意长度的任意字符和单个任意字符。格式如下:
#格式:$(wildcard argments) #示例: SRC = $(wildcard src/*.c) #SRC赋值为src目录中的所有.c文件
-
shell
shell
为在终端执行后面的命令并返回结果。#格式:$(shell <cmd> <args>) #示例: SRC = $(shell find . -name *.c) #找到当前文件下的所有.c文件
-
patsubst
替换函数#格式:$(patsubst pattern,replacement,text) #`patsubst`将text中的pattern全部替换为replacement,pattern和replacement可以包含%通配符。 #示例: SRC = $(wildcard src/*.c) OBJ = $(patsubst %.c,%.o,$(SRC)) #将SRC中的所有.c文件替换为.o文件
-
subst
替换函数#格式:$(subst from,to,text) #subst和patsubst类似,是将text中的from替换为to,并返回修改后的字符串。 #示例: $(subst ee,EE,feet on the street) #返回结果:fEEt on the strEEt
-
dir
#格式:$(dir NAMES...) #dir函数是一个用于从文件名序列中提取目录部分的函数。 #示例: SRC = $(wildcard ./src/*.c) OBJ = $(subst .c,,$(SRC)) TARGET := $(patsubst ./src/%,./obj/%,$(subst .c,,$(SRC))) TARGET_DIR = $(dir $(TARGET)) #SRC为src目录下的所有.c文件,OBJ为将SRC的结果去掉.c,TARGET将OBJ的结果的目录从src变为obj,最后通过TARGET_DIR得到TARGET中的目录,即:./obj/
-
suffix
#格式:$(suffix <names...>) #suffix为从文件名序列中取出各个文件名的后缀,返回文件名序列的后缀序列,如果文件没有后缀,则返回空子串。 #示例: $(suffix ./src/foo.c ./src-1.0/bar.c hacks) #返回结果:.c .c
-
basename
#格式:$(basename <names...>) #从文件名序列中取出各个文件名的前缀部分,返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。 #示例: $(basename ./src/foo.c ./src-1.0/bar.c hacks) #返回结果:./src/foo ./src-1.0/bar hacks
-
addsuffix
#格式:$(addsuffix <suffix>,<names...>) #把后缀加到每个单词后面,返回加过后缀的文件名序列。 #示例: $(addsuffix .c,foo bar) #返回结果:foo.c bar.c
-
addprefix
#格式:$(addprefix <prefix>,<names...>) #把后缀加到每个单词后面,返回加过后缀的文件名序列。 #示例: $(addprefix src/,foo bar) #返回结果:src/foo src/bar
-
foreach
循环函数#格式:$(foreach <var>,<list>,<text>) #把list中使用空格分割的单词依次取出并赋值给变量var, 然后执行text表达式。 #示例: #$(I_FLAGS)可以-I包含INCLUDE里的库 INCLUDE = /usr/inclue \ /usr/local/inclue I_FLAGS := $(foreach var,$(INCLUDE),-I$(var)) @echo $(I_FLAGS) #输出结果:-I/usr/include -I/usr/local/include
-
条件判断
-
ifeq/ifneq
语句:#ifeq语句:判断参数是否相等,相等为true,否则是false。 ifeq (arg1, arg2) #arg1 arg2 相等执行这里的语句 else #arg1 arg2 不相等执行这里的语句 endif #ifneq语句:判断参数是否不等,不等为true,否则为false。 ifneq (arg1, arg2) #arg1 arg2 不相等执行这里的语句 else #arg1 arg2 相等执行这里的语句 endif
-
ifdef/ifndef
语句:#ifdef语句:判断参数是否有值,有值为true,否则是false。 ifdef var #如果定义了var,执行这里的内容 else #如果没定义var,执行这里的内容 endif #ifndef:判断参数是否没有值,没有值为true,否则为false。 infdef var #如果没定义var,执行这里的内容 else #如果定义var,执行这里的内容 endif
-