Makefile 速查笔记

作 Linux C++,一个稳定的工程,Makefile 是不多改动的。可是若是须要修改的时候,Makefile 的语法和用法一时半会就回忆不出来(原谅我记忆力差……)。在此把本身之前的 Makefile 学习笔记记录一下吧,也做为分享。本文假设读者已经懂得了 Makefile,所以主要是做为备忘和速查用。git

全文中尖括号部分表示变量。本文地址:http://www.javashuo.com/article/p-aaofsoxf-gb.htmlgithub

另外,速查系列还有这一篇:正则表达式速查笔记正则表达式

Make 介绍

Makefile 的基本规则就是:shell

target ...: prerequirements ...
    command ...
    ...

其中 target 是目标文件,能够有多个,能够是 .o 文件或者是可执行问价,甚至能够是一个标签。
Prerequisites 是先决条件,能够是文件,也能够是另外一个 target。
这就组成了一个依赖关系target 的先决条件定义在 prerequisites 中,而其生成规则又是由 command 决定的。若是包含多个规则的话,那么第一条规则就是整个 Makefile 的默认规则segmentfault


Make 的工做流程

  1. 在当前目录中查找 Makefile 或者 makefile 文件
  2. 将文件中第一条规则做为默认规则
  3. 若是目标不存在,则寻找对应的 .o 文件
  4. 若是 .o 文件不存在,则寻找 .o 的依赖关系以生成它

Makefile 有不少默认的生成规则,可是本文咱们不关心,由于绝大部分状况下,咱们是须要自行写规则的。这便于自定义、便于移植、便于交叉编译、便于调试。安全


Makefile 中的变量

变量的定义和调用格式:ide

name = value    # 注意变量的值是容许空格的
$(name)

变量值的部分可使用换行符 "\" 来作假换行,将两行内容链接成一行,从而缩短 Makefile 文件的宽度。函数

Makefile 综述

Makefile 里面都有啥?

文件指示:在一个 Makefile 里面能够制定另外一个 makefile,相似于 C 的 include学习

Makefile 还能够作条件包含动做,相似于 #if。Makefile 能够定一个变量为一个多行的命令。ui

Makefile 里面只有行注释而没有段注释。注释采用 # 开头。若是要使用 # 字符,则须要转义,写成 “\#”。

Makefile 规则内容里全部的 shell 命令都要以制表符 Tab 开头,注意,空格符是不行的。

默认的 make 文件名为:GNUmakefile, makefile, Makefile,当敲入 make 命令时,会自动搜寻这几个文件。约定俗成使用最后一个。

引用其余 Makefile

语法:

include filename ...        # 不容许 include 失败
-include filename ...       # 容许 include 失败

能够包含路径或者通配符,一行能够包含多个文件。

若是未指定绝对路径或者相对路径,那么 make 会按照一下的顺序去寻找:

  1. 当前目录
  2. 制定 make 时,在 -I 或者 --include-dir 的参数下寻找
  3. <prefix>/include(通常是 /usr/local/bin/usr/include

建议仍是手动指定吧,自动搜寻意外可能太多了。

环境变量 MAKEFILES

这里主要是要提醒:不要设置这个环境变量,不然会影响全局的 make include 动做。

Make 的几个工做方法

通配符

Make 支持三个通配符:*, ?, [...]。能够用在规则中,也能够用在变量中。

伪目标

伪目标就是 Makefile 里面颇为常见的 .PHONY 标识,好比:".PHONY: clean",表示这个规则名并不表明一个真实存在的、须要生成的文件名,而只是一条纯粹的规则。

  • 真目标的特色是:若是目标不存在,才会被执行
  • 伪目标的特色是:无视目标是否存在,必然执行

除了 make clean 以外,伪目标还有另外一种使用场景,就是一个 make 动做,实际上生成了多个目标。好比:

.PHONY: all
all: exe install    # 包含了生成目标文件,以及安装动做

多目标

规则的冒号前面能够有多个 target,表示多个 target 共用这条规则。


自动生成依赖关系

若是咱们使用中规中矩的 makefile 写法,那么对于每一个源文件都要好好写头文件依赖关系,从而在头文件更新的时候,能够自动从新编译依赖于这个头文件的源文件。
这实在是太麻烦了。好在 gcc 里有一个 -MM(注意不是 “-M”) 的选项,能够分析出 .c 文件依赖的头文件而且打印出来。所以制做 Makefile 的时候,就能够利用这一特性自动生成依赖。

实现方法有不少,这里贴出我本身使用的例子,也能够参见个人工程代码

EXCLUDE_C_SRCS =#
C_SRCS = $(filter-out $(EXCLUDE_C_SRCS), $(wildcard *.c))
C_OBJS = $(C_SRCS:.c=.o)

$(C_OBJS): $(C_OBJS:.o=.c)
    $(CC) -c $(CFLAGS) $*.c -o $*.o
    @$(CC) -MM $(CFLAGS) $*.c > $*.d  
    @mv -f $*.d $*.d.tmp  
    @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d  
    @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
    @rm -f $*.d.tmp

书写命令

这里的命令,指的是在 Makefile 规则里的 “command” 部分。

命令执行

将 “@” 放在一条命令的前面,表示实际执行的时候,不打印这条命令语句,能够节省屏幕内容,减小垃圾信息(特别是个人自动生成依赖的命令,调通了以后,那就是一堆无用信息)。若是将 “-” 放在命令前面,则表示无视这条命令的返回值是否为成功(0).

若是上一条命令的结果须要用于下一条命令时,须要将这些命令写在一行中。建议用 “\” 分开。这最典型的是 cd 命令及其以后的一连串命令。

Make 的时候加上 -n 选项或 --just-print 选项,则表示不执行 make,而只是把过程打印出来。


嵌套执行 make

在 Makefile 里能够到另外一个目录下执行 make,执行方式相似于普通的命令调用,但特别的是,make 能够识别出这是一条嵌套 make 指令,从而在 shell 中打印出 “专项哪里哪里 make” 的提示语法为:

subsystem:
    $(MAKE) -C subdir

这个作法的主要好处是能够向下级 Makefile 传递变量或者语法:

export VARIABLE ...     # 将相应变量变成当前 make 操做的全局变量

也恶意直接指定变量的值:

export VARIABLE = value

若是要传递全部变量(不推荐),直接写 export 就好。

注意由两个系统变量 SHELLMAKEFLAGS 是永远传递的。
此外还有一个全局变量 MAKELEVEL 用来表示当前的嵌套层数。


定义命令包

命令包相似于宏、子函数等等。使用 define 来定义,以 endif 结束,好比:

define run-yacc
    yacc $(firstword $^)
    mv y.tab.c $@
endif

注意若是是命令的话,须要以制表符开头。调用这个命令包的方式为:$(run-yacc)

使用变量

变量赋值

变量定义时必须赋值,至少赋一个空值(只有等号,等号右边什么都没有)

使用变量的时候虽然不强制、可是为了安全起见,应该使用括号或者打括号把变量包含起来。若是要使用字符 "$",则使用 "$$" 来转义

赋值时,等号右侧能够有未定义的变量,而且在其实际使用时才展开变量的内容。但这可能会致使循环引用。为了不这一点,可使用 ":=" 符号来避免使用未定义的变量

"+=" 的做用是 “追加” 值。若是右侧有变量未定义,则等价于 “:=
"?=" 的做用是:若是等号左侧的变量未定义,则使用等号右边内容定义,即:

ifeq ($(some_var), undefined)
some_var = some_val
endif

另外:
$@ 表示当前规则的编译目标
$^ 表示当前规则的全部依赖文件
$$< 表示当前规则的第一个依赖。


定义一个空格变量

NULL_STR :=#
SPACE_STR := $(NULL_STR) # end of line

注意第二行的注释与 “)” 之间是包含一个空格的。注释的 “#” 必须有,不然不会定义一个空格出来。


变量替换

第一个方式为:$(var: .o = .c),意思是将等号左边的字符换成右边的字符
第二个方式为所谓的 “静态模式”:$(var: %.o = %.c)


把变量值做为变量

这很相似于指针,只是地址值变成了变量值。能够用变量值生成变量名,好比:a := $($(var)) 或者是 $($(var)_$(idx)) 之类的写法。


override

在命令行调用 make 时,能够直接指定某个变量的全局值,使得它在整个 make 的过程当中一直不变。为了防止这个特性,可使用这个关键字来处理:
override <variable> = <value>
等号也能够用 :=?=


目标变量(局部变量)

若是某条约束里面不想使用已经定义了的全局变量,能够这样写:

prog: CFLAGS = -g
prog: a.o b.o
    $(CC) $(CFLAGS) a.o b.o

条件判断

语法

<条件语句>
<true 执行语句>
else
<false 执行语句>
endif

其中条件语句有四种情形:

一、表示是否相等

ifeq (<arg1>, <arg2>)    # 推荐
    ifeq '<arg1>' '<arg2>'
    ifeq "<arg1>" "<arg2>"

二、表示是否不等,上面的 ifeq 换成 ifneq
三、ifdef
四、ifndef

使用函数

Make 的全部函数都是内置函数,不能本身定义(命令包除外)。下面列出经常使用的函数,若是看不懂再详细查阅。

常规函数

字符串替换

$(subst <from>, <to>, <text>)

模式字符串替换

$(patsubst <pattern>, <replacement>, <text>)

去开头和结尾的空格

$(strip <string>)

查找字符串

$(findstring <find>, <in>)

反过滤

$(filter-out <pattern_or_string>, <text>)

排序(单词升序)

$(sort <list>)

取单词

$(word <n>, <text>)

取单词串

$(wordlist <n_start>, <n_end>, <text>)

单词个数统计

$(words <text>)

去掉每一个单词的最后文件名部分,只剩下目录部分

$(dir <names ...>)

去掉每一个单词的目录部分,只剩下文件名部分

$(notdir <names ...>)

读取各文件名的后缀

$(suffix <names ...>)

加后缀

$(addsuffix <suffix>, <names ...>)

加前缀

加先后缀在动态建立局部变量颇有用
$(addprefix <prefix>, <names ...>)

链接字符串

$(join <list1>, <list2>)


for 循环

$(foreach <var>, <list>, <text>)
这实际上是一个函数,做用是:将 list 的单词逐一取出,放到 var 指定的变量中,而后执行 text 的表达式。返回值则是 text 的最终执行值。


Shell 函数

执行 shell 命令,而且将 stdout 做为返回值返回,如:
contents := $(shell ls -la)


控制 make 输出

$(error <text ...>)
$(warning <text ...>)
这也同时是调试和定位 make 的好方法。


判断文件是否存在

ifeq ($(FILE), $(wildcard $(FILE)))
...
endif
相关文章
相关标签/搜索