1974 words
10 minutes
【学习记录】makefile学习记录之四:书写命令、使用变量和条件判断

书写命令#

makefile中对命令的识别非常简单,只需要在一行中以tab键开头的都算命令。另外,跟在规则冒号后面同一行的内容也会被视作命令。make的命令默认是/bin/sh中UNIX的标准Shell解释执行的,但也可以手动指明别的shell解释执行。makefile中的行注释符号是井号“#”。

显示命令#

make默认将执行的每行命令都进行输出,这可以通过在命令开头拼接@符号的方式来避免。以下是一个示例:

exec:
    echo hello
out:
echo hello
hello
 
exec:
    @echo hello
out:
hello

另外,在用命令行调用make的时候通过参数-n、—just-print可以让make只显示命令而不执行;通过-s、—silent、—quiet可以让make全面禁止命令的显示。

命令执行#

如果想让一条命令的结果应用于下一条命令,在不考虑使用变量的情况下应该将两条命令写在同一行并且用分号分隔,示例:

# 假设当前目录是/home/user
exec:
    cd /home/user/dir
    pwd
out:
/home/user
 
exec:
    cd /home/user/dir; pwd
out:
/home/user/dir

命令出错#

make在执行时遇到命令出错时会终止当前规则的执行,这有可能终止所有规则的执行。有些时候命令执行出错不一定必须终止,比如mkdir命令这种。有以下几种方式避免这种情况:

  1. 在命令前加-,告诉make无论该条命令是否出错都认为它执行成功

  2. 在调用make时加上-i或—ignore-errors让make忽略所有错误

  3. 在makefile文件中定义.IGNORE伪目标,在.IGNORE下的规则中的命令的执行错误都会被忽略

  4. 在调用make时加上-k或者—keep-going参数让make在遇到命令出错时终止当前规则的执行但是不终止其他规则的执行

make的嵌套执行和“总控makefile”#

make允许用命令调用另一个make的执行,这通常用在一些源代码分文件夹存放的项目中,此时每个文件夹下都可以定义一个子makefile文件来负责本文件夹的make并且该makefile由父makefile调用,这样整个makefile的结构就会形成一个树形结构,我们把树的顶点称为“总控makefile”。调用语法:

exec
    cd ./dir; $(MAKE)

其中$(MAKE)意为执行make。

父级makefile可以选择将变量传递或者不传递到子makefile中,运用export和unexport即可。语法:

export <variable>
unexport <variable>

但SHELL和MAKEFLAG这两个环境变量始终会被传递下去。

另外,可以在调用make时使用参数-w或—print-directory来让make每次进入或者离开子文件夹时输出一行提示信息。

命令包#

命令包的存在允许你将可能重复用到的一系列命令提前定义好并且在需要使用时直接快速调用它们,其以define开头,endef结尾(注意只有一个d),以下是一个示例:

define commandPack
echo hello
echo deleting temporary files
rm -f *.o
endef

其中cammandPack是命令包名,以后可以用调用变量的方式用$符号调用它,如:

foo : foo.c
    gcc -c foo.c
    $(commandPack)
    gcc -o foo foo.c

使用变量#

makefile中的变量非常简单,其不具备数据类型而只是简单的字符串,在调用时只是简单将其展开到调用处,类似于c语言中的宏定义。makefile中变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有#、:、=或是空字符(空格、回车等)。变量名大小写敏感。作者建议使用驼峰命名的方式避免与系统变量冲突。自动化变量的内容不在这部分介绍。

变量在声明时需要赋予初值,变量使用时在其前加$符号,最好是将变量名用小括号或花括号包起来便于阅读区分。

变量的一般用法#

对于变量的应用只需掌握其展开的原理规则即可。变量支持嵌套,也就是支持多层展开,在展开过程中遵循由内向外展开的顺序。

还有一点值得注意的是如果要定义一个内容为空格的变量,可以用如下方式:

nullstring :=
space := $(nullstring) # end of the line

注意,注释“end of the line”必须存在且与右括号相距一个空格,原理是用注释来表示变量定义的终止,从而使变量内容包含空格。

变量高级用法#

高级用法主要是变量嵌套和变量值的替换。如果掌握了一门一般的编程语言那么嵌套问题不算难,值替换也只需了解其语法:

foo := a.o b.o c.o
bar := $(foo:.o=.c)
 
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
# 得到bar均为 a.c b.c c.c

其中第一种是普通的值替换,而第二种是以静态模式定义的,这二者分别与函数部分的字符串替换和模式替换十分相似。

变量还可以用+=运算符追加内容,会在原变量尾部添加一个空格后将右值附在后面。

还可以用override指令对一些变量的值进行覆盖。尤其是如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。此时如果想更改这类参数的值就需要用到override,语法如下:

override <variable>; = <new-value>
override <variable>; := <new-value>
override <variable>; += <new-value>
 
# 也可以覆盖多行变量
override define var
value1
value2
endef

其他类型变量#

环境变量#

环境变量应该用到比较少,主要是在嵌套执行make的时候传递一些值,不做太多的解释。

目标变量#

目标变量是在定义make目标时在行内定义的变量,它可以被所有这个目标下的规则使用。语法如下:

<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

一个示例:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

模式变量#

模式变量的作用跟目标变量很像,区别是其定义所在的目标是模式化的,它的值可以被运用到所有与该模式化的目标匹配的目标当中,语法:

<pattern ...>; : <variable-assignment>;
 
<pattern ...>; : override <variable-assignment>;

示例:

%.o : CFLAGS = -c
 
foo.o : foo.c
    $(CC) $(CFLAGS) $@ $<

因为foo.o与%.o的模式匹配,所以这里foo.o目标规则中的CFLAG的值就是-c。

条件判断#

条件判断的内容非常简单,基本只要学过编程语言就能很快掌握,这里就简单列一下语法。

注意,这里的条件判断只能判断两个元素展开后最后表示的字符串是否相等。

基础语法,其中else部分可以省略:

<conditional-judgement>
    <expression-if-true>
else
    <expression-if-false>
endif

具体写法:

# 判断两个参数是否相同,相同即为true
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
 
# 判断两个参数是否不同,不同即为true
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
 
# 判断变量是否为空,不为空则为true
ifdef <variable-name>
 
# 判断变量是否为空,为空则为true
ifndef <variable-name>

有两点要注意的事情:

  1. 不要把条件判断部分的内容以tab键开头,否则会被make视为命令

  2. 不要在条件判断语句中使用自动化变量如@@,<等,因为make会在makefile载入时就计算条件表达式的值,而自动化变量是在运行过程中才有的。

参考文章 使用条件判断 — 跟我一起写Makefile文档

【学习记录】makefile学习记录之四:书写命令、使用变量和条件判断
https://yinheee.pages.dev/posts/makefile-learning/makefile-4/
Author
TheColdSummer
Published at
2023-09-25