Makefile笔记

入门

Makefile的核心和主线是下面这条规则

target: prerequisties
    command

target是个目标,是要生成的文件,也可以只是单纯的一个名称(伪目标(后面会介绍)),prerequisties则是生成这个target所需要的文件command则是如何生成target.

例子:

CC=gcc
CFLAGS =  -g  -lpthread
audit: audit_log.o logentry.o strops.o  filedb.o
    ${CC} ${CFLAGS} -o audit audit_log.o logentry.o strops.o filedb.o
audit_log.o: audit_log.c
    ${CC} ${CFLAGS} -c audit_log.c
logentry.o: logentry.c
    ${CC} ${CFLAGS} -c logentry.c
strops.o: strops.c
    ${CC} ${CFLAGS} -c strops.c
filedb.o: filedb.c
    ${CC} ${CFLAGS} -c filedb.c
clean:
    rm -rf *.o
    rm -rf audit

为了生成audit,需要依赖后面一大串.o文件,每一个.o文件有依赖对应的.c文件,很显然如果后面再添加一个.c就需要修改三处很麻烦因此Makefile提供了变量.

变量

通过将大量prerequisties使用变量来保存可以避免重复

CC=gcc
CFLAGS =  -g  -lpthread
Object = audit_log.o logentry.o strops.o  filedb.o
audit: ${Object}
    ${CC} ${CFLAGS} -o ${Object}
audit_log.o: audit_log.c
    ${CC} ${CFLAGS} -c audit_log.c
logentry.o: logentry.c
    ${CC} ${CFLAGS} -c logentry.c
strops.o: strops.c
    ${CC} ${CFLAGS} -c strops.c
filedb.o: filedb.c
    ${CC} ${CFLAGS} -c filedb.c
clean:
    rm -rf *.o
    rm -rf audit

make自动推导

make可以自动推导文件以及文件依赖关系后面的命令,例如make只要看到一个.o文件,就会自动把.c文件添加到依赖关系中,并且也会推到出相应的command

CC = gcc
CFLAGS =  -g  -lpthread -I
Object = audit_log.o logentry.o strops.o  filedb.o
audit: ${Object}
        ${CC} ${CFLAGS} -o ${Object}

# 会自动推导出来对应的依赖文件和命令,如果有额外的依赖添加在其后即可
# 这几个还可以进行合并
# audit_log.o logentry.o strops.o filedb.o:
audit_log.o: 
logentry.o:
strops.o:
filedb.o: 
clean:
        rm -rf *.o
        rm -rf audit

目标清除

clean:
    rm -rf *.o
    rm -rf audit

#更稳健的做法,.PHONY表示后面的target是一个伪目标
#rm前面的-号表示,也许某些文件出现问题了,但是不要管.
# 并且clean target总是放在最后,如果放在开头就是makefile的默认目标了
.PHONY: clean
clean:
    -rm -rf ${Object}
    -rm -rf audit

make执行

当使用make执行的时候,会在当前目录下搜索GNUmakefile makefile Makefile文件,没有找到就会报错,也可以使用-f来指定makefile文件

makefile引用

使用include关键字可以把其它makefile引入进来

include <makefile-name>
其中<makefile-name>可以是文件名,带通配符的文件名,绝对路径,相对路径

如果给出的是相对路径,那么会在当前目录下查找,执行make的时候如果添加了-I参数,还会在-I指定的目录下查找.查找不到会出现警告.当完成makefile载入后,会重新再次查找,如果再查找不到就会报错.可以在include前面添加-号使得忽略这些错误.

通配符

在规则中可以使用通配符,在变量赋值语句中也可以使用,但是两者效果不同

# 这里的*.o是展开成当前目录下所有.o结尾的文件名
audit: *.o
    gcc -o audit *.o 
clean:
    rm -rf *.o #*.o会展开成当前目录下所有.o结尾的文件名

#Makefile中的变量其实等同于C/C++中的宏,是啥样就是啥样,不做通配符替换和变量替换
Object = *.o #其含义和上述不一样这里Object就是 *.o而不是 *.o展开后的文件名
Object := $(wildcard *.o) 这样就可以了

伪目标

一般target都是一个文件,这个文件存在并且时间和其依赖的文件的一致,就不会触发command,但是对于一个没有任何依赖的target来说,如果当前目录又恰好有一个和target同名的文件,那就不好了,执行make的时候会发现target存在,并且没有依赖,那么就不会触发command,为此可以将这个target设置成伪目标来避免这个问题. 伪目标总是会执行的.

# 如果当前目录下存在clean文件,那么command就不会被触发
clean:
    rm -rf *.o

# 改成伪目标
.PHONY: clean
clean:
    rm -rf *.o

多目标

Makefile支持多target,并且提供了一个自动变量$@这个自动变量表示了当前规则中的目标集合

静态模式

更加容易的定义多目标规则

::....>
    

targets 定义了一系列的目标文件
target-parrtern指明了targets的模式,也就是目标集模式
prereq-parrterns 是目标的依赖模式

上面的解释还是不够清楚明白,有很多targets,然后后面接着一个target-pattern,这个pattern会匹配前面的诸多target中的一个或多个然后根据target-pattern和prereq-patterns确定依赖.下面是一个具体点的例子:

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

上面的例子表示 %.o匹配前面的一批target(也就是$(objects)代表的一批target)中的以.o结尾的target,%号.o结尾的target文件名的点号前面的部分比如foo.o那么%号就是foo.最后的%.c表示的含义就是利用target-parrtern匹配到的内容也就是%号这个部分,然后添加.c构成依赖.至于command部分的$<表示是所有的依赖目标集,$@则是目标集.

上面的Makefile展开后就是下面的样子

objects = foo.o bar.o
all: $(objects)
foo.o:foo.c
    $(CC) -c $(CFLAGS) $< -o $@

shell命令

command部分可以使用所有的shell命令,一行一个命令,如果要接多个命令按照分号分割就可以了.make会按照顺序一条一条执行.make默认会显示每次执行的command,如果不想显示执行的命令,可以在命令的前面加上@符号 make -n则只会显示命令,并不执行命令 make -s 则是全面禁止命令的显示如果想让上条命令的结果作用于下一条命令,需要将多条命令写在一行,并且使用分号分割.比如:

cd /root
ls
#上面的结果是显示的当前目录下的文件
#下面才是显示root目录下的文件
cd /root;ls

命令出错

make在执行command的时候,每执行一条命令就会判断其返回码,判断是否出错,如果出错就终止执行,否则就继续执行,有的时候command执行错误并不代表真正的
错误,因此为了避免这个问题可以在可能出错的命令前面加上-号.

嵌套执行make

  • cd到子目录然后make
  • make -C 子目录

上面两种方式都是可以的.并且还可以使用export varname来传递变量到子makefile中.使用export后面什么也不跟就是表示传递所有的变量到子makefile中

命令包

如果在makefile中经常出现一些频繁出现的命令序列,可以将这个命令序列组合起来成为一个命令包,命令包的定义方式如下:

define print-format
@echo "+++++++++++++++++++++++"
ls
@echo "+++++++++++++++++++++++"
endef

clean:
    $(print-format)

变量

变量的定义方式:

第一种: varname = value
第二种: varnaem := value 

变量的值可以是另外一个变量在第一种方法中另外一个变量可以在其后定义,第二种方法则不行.第一种方法会造成循环引用的问题.第二种可以避免这个问题

A = $(B)
B = $(A)

clean:
    echo $(A)

上面的Makefile运行会报错,错误为 *** Recursive variableA’ references itself (eventually). Stop.` 改成第二种变量定义方式就可以避免这个问题此外还有第三种定义变量的方式.

# 如果varname没有定义过,那么变量varname的值就是value,如果varname之前定义过,那么这条语句什么也不做.
varname ?= value 

除了上面的变量定义方式外,变量还具有很多高级用法.例如变量值替换:

object := 1.o 2.o
foo := $(object:.o=.c)

上面的代码会把object中的值的末尾的.o替换成.c.这还有另外一种写法,使用静态模式.

object := 1.o 2.o
bar := $(object:%.o=%.c)

除了变量值替换外,还可以把变量的值再当成变量

x = y
y = z
a := $($(x)) 

那么此时 (x)y, ( (x)) (y)也就是z,还可以嵌套多层.还可以把几个变量的值组合成另外一个变量.还可以使用+=操作符给变量追加值.上面介绍了很多关于变量的一些基本和高级用法,除了上面介绍的变量外,其实还有目标变量和模式变量.目标变量就是在某一个目标内是有效的变量会覆盖全局的变量,如下:

var = 222
prog: var = 111
prog:
    echo $var

上面的makefile的输出是111,模式变量其实和目标变量本质上是一样的,只不过模式变量会应用到所有匹配的模式中.如下:

var = 222

%.o: var = 111
1.o: 1.c
    echo $var

2.o: 2.c
    echo $var

main: 1.o 2.o
    echo $var

上面的代码中1.o和2.o这两个目标的输出都是111,而main这个目标的输出是222.最后一个关于变量的知识点就是变量覆盖,执行make命令的时候是可以传递变量到makefile中,如果此时makefile中已经有了同名变量,那么makefile中的变量就会被覆盖,如果不想被覆盖就可以使用override关键字如下:

override varname := valueoverride varname = value

条件判断

语法格式如下:

<条件语句>;
<符合条件的语句>;
endif

or 
<条件语句>;
<成功情况的语句>;
else
<失败情况的语句>
endif

条件语句主要有四种,如下:

ifeq (;,;)
ifneq (;,;)
ifdef ;
ifndef ;

函数

语法格式如下:

$(<fucntion> <arguments>;)
或者是
${<function> <arguments>;}

上面的function是函数名,arguments是函数参数,函数参数之间用逗号分割.函数名和函数之间使用空格分隔.

字符串处理函数

$(subst <from>,<to>,<text>)
把<text>中的<from>字符串替换成<to> 函数返回替换过后的字符串
$(subst "ee","EE","feet on the street")
上面的例子中返回的结果是fEEt on the strEEt
$(patsubst ,,<text>)
查找<text>中的单词(单词是以空格,tab,回车,换行分隔的)是否匹配,如果匹配就用进行替换,中可以像静态模式那样使用%代表匹配到的字符,如果也使用了%那么这个%表示的内容就是%号表示的内容.
$(patsubst %.c,%.o,x.c.c bar.c)
返回的结果是x.c.o bar.o
$(strip <string>)
去掉<string>中开头和结尾的空字符
$(findstring <find>,<in>)
在字符串<in>中,查找<find>字符,找到就返回<find>,否则返回空字符
$(filter <pattren...>,<text>)
以<pattern>模式过滤<text>中的单词,保留符合模式的单词
$(filter-out <pattren...>,<text>)
以<pattern>模式过滤<text>中的单词,保留不符合模式的单词
$(sort <list>)
排序函数,对给定字符串<list>中的单词进行排序(升序),返回排完序后的单词
$(word <n>,<text>)
取单词函数,返回<text>中第n个单词
$(wordlist <s>,<e>,<list>)
取单词串函数,返回list中第s个到第e个单词串
$(words <text>)
单词个数统计,返回<text>中单词的个数
$(firstword <text>)
返回<text>中第一个单词

文件名操作函数

$(dir <text..>)        取目录名
$(nodeir <text..>)     取文件名
$(suffix <text..>)     取文件后缀
$(basename <text..>)       取文件前缀
$(addsuffix ,<text>)   加文件后缀
$(addprefix ,<text>)   加文件前缀
$(join ,)        连接把list2中的单词对应加到list1中的单词后面(不是把list2整体放到list1后面)

其它函数

$(foreach ,,<text>)
每次从中取出一个单词赋值给,然后通过<text>计算出一个新的单词,按照空格分隔每次求出的结果.请看下面的例子:
obj = "a b c"
$(foreach n,$(obj),$(n).o)
得到的结果就是a.o b.o c.o

$(if <condition>,,<else-part>)
如果<condition>条件成立,就执行部分,否则执行<else-part>部分

$(call <expression>,,....)
call函数是唯一一个可以用来创建一个新的参数化函数。<expression>是一个表达式,表达式中可以使用$(1),$(2)来代替传递过来的参数,例如下面的这个例子:
reverse = $(2) $(1)
$(call $(reverse),a,b)
上面的执行结果就是b a

$(origin )
测试变量是从哪里来的,如果返回undefined表示从未定义过,返回default表示是默认定义的。返回enviroment表示是环境变量,返回file表示这个变量是被定义
在Makefile中,返回"command line"表示是在命令行中定义的。

$(shell )
可以直接在Makefile中使用shell命令

Make参数

-n
--just-print
--dry-run
--recon
把规则,连带规则下的命令打印出来,但是不执行。

-t
--touch
更新target的时间戳

-q
--question
查询是否有target,如果存在什么也不输出,不存在则打印错误信息

-B 
--always-make
不管目标是否更新,总是执行

-C
指定Makefile的读取目录,默认是当前目录

-I 指定Makefile的搜索目标(用于Makefile中的include搜索要包含的makefile)

你可能感兴趣的:(程序员基础)