LV02-05-Makefile-07-条件判断和函数
本文主要是makefile——条件判断和函数相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
参考方向 | 参考原文 |
Makefile | 跟我一起写Makefile |
一、条件判断
1.条件判断格式
Makefile 编译文件时,可能会遇到需要分条件执行的情况,比如在一个工程文件中,可编译的源文件很多,但是它们的类型可能是不相同的,所以编译文件使用的编译器也可能是不同的。这个时候就用到了条件判断。
条件表达式语法如下:
1 | 1.语法格式一 |
<conditional-directive> 表示的是条件关键字。在这一行上,多余的空格是被允许的,但是不能以 Tab 键做为开始(不 然就被认为是命令)。注释符 # 同样也是安全的。 else 和 endif 也一样,只要不 是以 Tab 键开始就可以。
关键字 | 功能 |
ifeq | 判断参数是否相等,相等为 true,不相等为 false。 |
ifneq | 判断参数是否不相等,不相等为 true,相等为 false。 |
ifdef | 判断是否有值,有值为 true,没有值为 false。 |
ifndef | 判断是否有值,没有值为 true,有值为 false。 |
【注意】
(1)在 make 读取 Makefile 文件时就会计算表达式的值,并根据表达式的值决定判断语句中的哪一个部分作为此 Makefile 所要执行的内容。因此在条件表达式中不能使用自动化变量,自动化变量在规则命令执行时才有效。
(2)为了避免混乱, make 不允许把整个条件语句分成两部分放在不同的 Makefile 的文件中。
2. ifeq
- ifeq 判断参数是否相同,相同为 true
1 | ifeq (ARG1, ARG2) |
比较参数 ARG1 和 ARG2 的值是否相同,相同为 true ,不相同为 false ,例如
1 | CC = a |
在终端执行 make test ,会看到输出了以下内容:
1 | False |
3. ifneq
- ifneq 判断参数是否不相等,不相等为 true
1 | ifneq (ARG1, ARG2) |
比较参数 ARG1 和 ARG2 的值是否不相同,不相同为 true ,相同为 false ,例如
1 | CC = a |
在终端执行 make test ,会看到输出了以下内容:
1 | True |
4. ifdef
- ifdef 判断参数是否非空,非空值为 true
1 | ifdef VARIABLE_NAME |
判断参数 VARIABLE_NAME 是否非空值,非空值表达式为 true ,若为空值则表达式为 false ,例如
1 | a:= |
在终端执行 make test ,会看到输出了以下内容:
1 | False |
5. ifndef
- ifndef 判断参数是否是非空值,空值为 true
1 | ifndef VARIABLE_NAME |
判断参数 VARIABLE_NAME 是否为空值,若为空值表达式为 true ,非空值的话表达式为 false 。
1 | a:= |
在终端执行 make test ,会看到输出了以下内容:
1 | True |
二、 Makefile 函数
1.函数的调用
make 提供了一些函数供 Makefile 调用,函数调用后,函数的返回值可以当做变量来使用。函数的调用与变量很类似,一般语法格式如下:
1 | $(<function> arg1, arg2, ...) |
function 就是函数名, argN 是函数的参数。
【注意】
(1)函数名与参数之间通过空格( space )分隔开。
(2)函数的多个参数之间使用 , 分割开来,并且在函数中可以使用变量。
(3)为了风格的统一,函数和变量的括号最好一样,如使用 $(subst a,b,$(x)) 这样的形式。
2.字符串处理函数
2.1字符串替换函数
2.1.1 subst
字符串替换函数 subst 一般语法格式如下:
1 | $(subst <source_str>,<target_str>,<text>) |
【函数说明】该函数会将字符串 text 中的 source_str 字符串替换成 target_str 。
【返回值】函数返回被替换过后的字符串。
【注意】 <source_str>,<target_str>,<text> 的 , 号之间最好不要有空格,否则可能替换后会多个空格哦。
2.1.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o |
2.2模式字符串替换函数
2.2.1 patsubst
模式字符串替换函数 patsubst 一般语法格式如下:
1 | $(patsubst <pattern>,<replacement>,<text>) |
【函数说明】该函数会查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”,“换行”分隔)是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换。
【返回值】函数返回被替换过后的字符串。
【注意】
(1) <pattern>,<replacement>,<text> 的 , 号之间最好不要有空格,否则可能替换后会多个空格哦。
(2) <pattern> 可以包括通配符 % , 表示任意长度的字符串。如果 <replacement> 中也包含 % ,那么, <replacement> 中的这个 % 将是 <pattern> 中的那个 % 所代表的字串。(另外可以用 \ 来转义一些字符,例如,以 % 来表示真实含义的 % 字符)。
2.2.2使用实例
这个函数与变量替换的时候很类似,可以看下边的例子。 Makefile 文件内容如下:
1 | var1 := main.o test.o |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o |
2.3去空格函数
2.3.1 strip
去空格函数 strip 一般语法格式如下:
1 | $(strip <string>) |
【函数说明】该函数会去掉字符串 <string> 中开头和结尾的空字符,并且将字符串中的空格合并成为一个空格。
【返回值】函数返回被去掉空格的字符串值。
2.3.2使用实例
Makefile 文件内容如下:
1 | var1 := ma in.o tes t.o |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=ma in.o tes t.o |
2.4查找字符串函数
2.4.1 findstring
查找字符串函数 findstring 一般语法格式如下:
1 | $(findstring <find_str>,<string>) |
【函数说明】该函数会在字符串 <string> 中查找 <find_str> 字符串。
【返回值】函数如果找到要查找的字符串 <find_str> ,那么返回 <find_str> ,否则返回空字符串。
【注意】
(1) <find_str>,<string> 的 , 号之间最好不要有空格,否则可能会与预期的不太一样。
2.4.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o |
2.5过滤函数
2.5.1 filter
过滤函数 filter 一般语法格式如下:
1 | $(filter <pattern...>,<text>) |
【函数说明】该函数会过滤出 <text> 中符合模式 <pattern> 的字符串,可以有多个 <pattern> 。
【返回值】函数返回符合模式 <pattern> 的字符串。
【注意】
(1) <pattern…>,<text> 的 , 号之间最好不要有空格,否则可能会与预期的不太一样。
2.5.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o main.c test.s test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o main.c test.s test.c |
2.6反过滤函数
2.6.1 filter-out
反过滤函数 filter-out 一般语法格式如下:
1 | $(filter-out <pattern...>,<text>) |
【函数说明】该函数会过滤出 <text> 中不符合模式 <pattern> 的字符串(就是会把符合的给去掉),可以有多个 <pattern> 。
【返回值】函数返回不符合模式 <pattern> 的字符串。
【注意】
(1) <pattern…>,<text> 的 , 号之间最好不要有空格,否则可能会与预期的不太一样。
2.6.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o main.c test.s test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o main.c test.s test.c |
2.7排序函数
2.7.1 sort
排序函数 sort 一般语法格式如下:
1 | $(sort <list>) |
【函数说明】该函数给字符串 <list> 中的单词排序(升序)。
【返回值】函数返回排序后的字符串。
2.7.2使用实例
Makefile 文件内容如下:
1 | var1 := a.o b.c d.s a.c x.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=a.o b.c d.s a.c x.c |
2.8取单词函数
2.8.1 word
取单词函数 word 一般语法格式如下:
1 | $(word <n>,<text>) |
【函数说明】该函数会取字符串 <text> 中第 n 个单词。( n 从 1 开始)。
【返回值】函数返回字符串 <text> 中第 n 个单词。如果 n 比 <text> 中的单词数要大,那么返回空字符串。
2.8.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o main.c test.s test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o main.c test.s test.c |
2.9单词个数统计函数
2.9.1 words
单词个数统计函数 words 一般语法格式如下:
1 | (words <text>) |
【函数说明】该函数会统计字符串 <text> 中的单词个数。
【返回值】函数返回字符串 <text> 中的单词数。
2.9.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o main.c test.s test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o main.c test.s test.c |
2.10首单词函数
2.10.1 firstword
首单词函数 firstword 一般语法格式如下:
1 | $(firstword <text>) |
【函数说明】该函数会取出字符串 <text> 中的第一个单词。
【返回值】函数返回字符串 <text> 中的第一个单词。
2.10.2使用实例
Makefile 文件内容如下:
1 | var1 := main.o test.o main.c test.s test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.o test.o main.c test.s test.c |
2.11综合应用
- 指定编译器头文件搜索路径
1 | CFLAGS += = $(patsubst %,-I %,$(subst :, ,$(VPATH))) |
点击查看变量赋值分析
我们先假设 VPATH:=./src : ./include ,那么:
CFLAGS | 是代表C编译器的选项 |
+= | 是代表追加赋值 |
$(VPATH) | 文件搜索路径,在这里表示 ./src : ./include |
$(subst :, ,$(VPATH)) | 调用 subst 字符串替换函数,将 ./src : ./include 中的 : 替换为空值(将会产生一个空格),最终得到字符串 ./src ./include |
patsubst | 模式字符串替换函数,将会把 ./src ./include 的每个单词进行整体匹配,会得到 -I ./src -I ./include |
所以最终我们会得到 cc 或 gcc 搜索头文件路径的参数。放入 Makefile 文件,
1 | VPATH := ./src : ./include |
接着执行 make test ,会看到输出如下:
1 | var1=./src : ./include |
若修改为以下内容,限制会更强一点:
1 | CFLAGS += $(patsubst %,-I %,$(filter %include,$(subst :, ,$(VPATH)))) |
这样最终就只剩下 -I ./include 了。
3.文件名操作函数
3.1取目录函数
3.1.1 dir
取目录函数 dir 一般语法格式如下:
1 | $(dir <names...>) |
【函数说明】该函数会从文件名序列 <names…> 中取出目录部分。目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./ 。
【返回值】函数返回文件名序列的目录部分。
3.1.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c ./include/test.h ./src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c ./include/test.h ./src/test.c |
3.2取文件函数
3.2.1 notdir
取文件函数 notdir 一般语法格式如下:
1 | $(notdir <names...>) |
【函数说明】该函数会从文件名序列 <names…> 中取出非目录部分。非目录部分是指最后一个反斜杠( / )之后的部分。
【返回值】函数返回文件名序列的目录部分。
3.2.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c ./include/test.h ./src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c ./include/test.h ./src/test.c |
3.3取后缀函数
3.3.1 suffix
取后缀函数 suffix 一般语法格式如下:
1 | $(suffix <names...>) |
【函数说明】该函数会从文件名序列 <names…> 中取出各个文件的后缀。
【返回值】函数返回文件名序列 <names …> 的后缀序列,如果文件没有后缀,则返回空字串。
3.3.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c ./include/test.h ./src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c ./include/test.h ./src/test.c |
3.4取前缀函数
3.4.1 basename
取前缀函数 basename 一般语法格式如下:
1 | $(basename <names...>) |
【函数说明】该函数会从文件名序列 <names…> 中取出各个文件的前缀部分。
【返回值】函数返回文件名序列 <names …> 的前缀序列,如果文件没有前缀,则返回空字串。
【注意】取出的前缀会包含目录部分(如果有目录部分的话)。
3.4.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c ./include/test.h ./src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c ./include/test.h ./src/test.c |
3.5加后缀函数
3.5.1 addsuffix
加后缀函数 addsuffix 一般语法格式如下:
1 | $(addsuffix <suffix>,<names...>) |
【函数说明】该函数会把后缀 <suffix> 加到 <names…> 中的每个单词后面。
【返回值】函数返回加过后缀的文件名序列。
3.5.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c ./include/test.h ./src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c ./include/test.h ./src/test.c |
3.6加前缀函数
3.6.1 addprefix
加前缀函数 addprefix 一般语法格式如下:
1 | $(addprefix <prefix>,<names...>) |
【函数说明】该函数会把前缀 <prefix> 加到 <names…> 中的每个单词前面。
【返回值】函数返回加过前缀的文件名序列。
3.6.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c include/test.h src/test.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c include/test.h src/test.c |
3.7连接函数
3.7.1 join
连接函数 join 一般语法格式如下:
1 | $(join <list1>,<list2>) |
【函数说明】该函数会把 <list2> 中的单词对应地加到 <list1> 的单词后面。
【返回值】函数返回连接过后的字符串。
【注意】如果 <list1> 的单词个数要比 <list2> 的多,那么, <list1> 中的多出来的单词将保持原样。如果 <list1> 的单词个数要比 <list2> 少,那么, <list2> 多出来的单词将被复制到 <list1> 中。
3.7.2使用实例
Makefile 文件内容如下:
1 | var1 := 111 112 |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=111 112 |
3.8获取匹配模式文件名函数
3.8.1 wildcard
获取匹配模式文件名函数 wildcard 一般语法格式如下:
1 | $(wildcard <pattern>) |
【函数说明】该函数会列出当前目录下所有符合模式的 <pattern> 格式的文件名,可以有多个 <pattern> ,中间用空格隔开。
【返回值】函数返回返回值为空格分隔并且存在当前目录下的所有符合模式 <pattern> 的文件名。
【注意】
(1)在这个函数中似乎不太适合使用 % 来匹配,可以使用其他的通配符来进行匹配。
3.8.2使用实例
Makefile 文件内容如下:
1 | var1 := $(wildcard *.c *.o) |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c test.c main.o test.o |
便会列出当前目录下所有的 .c 和 .o 文件。
4.其它常用函数
4.1 foreach 函数
4.1.1 foreach
循环函数 foreach 一般使用格式如下:
1 | $(foreach <var>,<list>,<text>) |
【函数说明】把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式。每一次 <text> 会返回一个字符串。
循环过程中, <text> 的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候, <text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
所以 <var> 最好是一个变量名, <list> 可以是一个表达式,而 <text> 中一般会只用 <var> 这个参数来一次枚举 <list> 中的单词。
【注意】 foreach 中的 <var> 参数是一个临时的局部变量, foreach 函数执行完后,参数 <var> 的变量将可用,其作用域只在 foreach 函数当中。
4.1.1使用实例
Makefile 文件内容如下:
1 | var1 := a b c d |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=a b c d |
$(var1) 中的单词会被挨个取出,并存到变量 n 中, $(n).o 每次根据 $(n) 计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回值。
4.2 if 函数
4.2.1 if
条件函数 if 一般使用格式如下:
1 | $(if <condition>,<then-part>) |
【函数说明】 <condition> 参数是 if 的表达式,如果其返回的是非空的字符串,那么这个表达式就相当于返回真,于是, <then-part> 就会被计算,否则 <else-part> 会被计算。
【返回值】
如果 <condition> 为真(非空字符串),那么 <then-part> 会是整个函数的返回值。
如果 <condition> 为假(空字符串),那么 <else-part> 将会是这个函数的返回值。此时如果 <else-part> 没有被定义(也就是说没有这一部分),那么整个函数返回空字串符。所以, <then-part> 和 <else-part> 只会有一个被计算。
4.2.2使用实例
Makefile 文件内容如下:
1 | var1 := a b c d |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=a b c d |
4.3 call 函数
4.3.1 call
传递参数函数 call 一般使用格式如下:
1 | $(call <expression>,<parm1>,<parm2>...) |
【函数说明】 <expression> 参数是 call 的表达式,我们可以在表达式 <expression> 中定义许多的参数,然后通过 call 函数向表达式传入 <parm1>,<parm2>… 参数。
【返回值】 <expression> 的返回值就是函数的返回值。
【注意】
(1) <expression> 可以定义为以下格式:
1 | var1 = $(1) $(2) $(3) |
$(1) $(2) $(3) 分别表示第一个参数,第二个参数,第三个参数,参数的次序是可以自定义的,不一定是顺序的。另外说明一点就是,这里定义的时候不能用 := 否则无法对传入变量进行引用。
(2)传入参数多余或者少于表达式中定义的参数,都不会报错,若引用参数的编号超出了传入参数数量,会多出一个空格,下边例子中就有。
4.3.2使用实例
Makefile 文件内容如下:
1 | var1 = $(1) $(2) $(3) |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1= |
4.4 origin 函数
4.4.1 origin
溯源函数(哈哈🤣,我自己起的名字,感觉叫着方便) origin 一般使用格式如下:
1 | $(origin <variable>) |
【函数说明】不操作变量的值,只是告诉我们 <variable> 这个变量是哪里来的。
【返回值】这个函数的返回值就多了:
undefined | variable从来没有定义过,origin 函数返回这个值“undefined”。 |
default | variable是一个默认的定义,比如“CC”这个变量, |
environment | variable是一个环境变量,并且当 Makefile 被执行时,“-e”参数没有被打开。 |
file | variable这个变量被定义在 Makefile 中。 |
command line | variable这个变量是被命令行定义的。 |
override | variable是被 override 指示符重新定义的。 |
automatic | variable是一个命令运行中的自动化变量。 |
4.4.2使用实例
Makefile 文件内容如下:
1 | var1 := main.c |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=main.c |
4.5 shell 函数
4.5.1 shell
shell 函数一般使用格式如下:
1 | $(shell <command>) |
【函数说明】 <command> 为函数的参数,可以是 shell 中的命令。这个函数就是可以调用 shell 命令,它与反引号 ` 功能相同。
【返回值】 shell 命令运行结果。
【注意】这个函数会新生成一个 Shell 程序来执行命令,所以我们需要注意其运行性能,如果我们的 Makefile 中有一些比较复杂的规则,并大量使用了这个函数,那么对于我们的系统性能是有害的。特别是 Makefile 的隐晦的规则可能会让 shell 函数执行的次数比想像的多得多。
4.5.2使用实例
Makefile 文件内容如下:
1 | var1 := $(shell pwd) |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | var1=/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/test1 |
三、 make 控制函数
Makefile 中提供了两个控制 make 运行方式的函数。其作用是当 make 执行过程中检测到某些错误时为用户提供消息,并且可以控制 make 执行过程是否继续。
1. error 函数
1.1 error
一般语法格式:
1 | $(error <text ...>) |
【函数说明】产生致命错误,并提示 <text …> 信息给用户,并退出 make 的执行。
error 函数是在函数展开时(函数被调用时)才提示信息并结束 make 进程。因此如果函数出现在命令中或者一个递归的变量定义时,读取 Makefile 时不会出现错误。而只有包含 error 函数引用的命令被执行,或者定义中引用此函数的递归变量被展开时,才会提示致命信息 <text …> 同时退出 make 。
【返回值】无
【注意】 error 函数一般不出现在直接展开式的变量定义中,否则在 make 读取 Makefile 时将会提示致命错误。
1.2使用实例
- 读取时直接报错
Makefile 文件内容如下:
1 | ERROR := E1 |
然后在终端中执行 make test 命令,可以看到输出如下:
1 | Makefile:4: *** error is E1。 停止。 |
- 执行时报错
Makefile 文件内容如下:
1 | ERROR=$(error found an error!) |
然后在终端中执行 make error 命令,可以看到输出如下:
1 | Makefile:4: *** found an error!。 停止。 |
在 make 读取 Makefile 时不会出现致命错误,只有目标 error 被作为是一个目标被执行时才会出现。
2. warning 函数
2.1 warning
一般语法格式:
1 | $(warning <text ...>) |
【函数说明】产生警告信息 <text …> , make 的执行过程会继续。
【返回值】无
【注意】用法和 error 类似,展开过程相同。
四、 Makefile 中的 for 循环
1. for 格式
在 Makefile 中也是可以使用 for 循环的,格式如下:
1 | for var in $(LIST);do command;done |
【注意】使用 var 的时候,一定要是 $$var ,且不能用 () 或者 { } 将 var 包裹。
2.使用实例
1 | x = +O |
在终端执行 make main 会有如下输出:
1 | a +O a.o |