Makefile整理

本文深入讲解Makefile的基础规则与高级特性,包括显式与隐晦规则、变量定义、搜索规则、伪目标、多目标处理、自动生成依赖等功能,并介绍了如何高效使用Makefile中的变量与函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 基本规则

target : 依赖
    命令

make会由上往下的找target,也就是说,第一个target是最终目标。找到第一个target后,make寻找target文件,如果target不存在,则寻找target的依赖,一般最终目标的依赖都是“.o”的中间文件。如果有“.o”的文件不存在,make会继续往下寻找以该“.o”作为target的行,依次往下寻找。

Makefile会自动的推到“.o”“.c”文件是它的依赖,并且自动的有
CC xx.c命令。
所以可以简化Makefile

objs = x1.o x2.o
target0:  $(objs)
    $(CC) -o target0 $(objs)
x1.o : x11.h x12.h
x2.o : x21.h x22.h
.PHONY : clean        #表示clean是一个伪目标文件
clean :
    rm -rf $(objs)

拥有相同依赖.h的.o也可以一起作目标,而把依赖写于其后。这是一种另类的结构。

x1.o x2.o : x12.h x21.h

Makefile 里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

显式规则 : makefile的基本规则,
target : depends 
    command xx.c xx.c ...
隐晦规则 : 即makefile的自动推导等。

变量定义 : 可以用 '=',':=','?=','export','overide'

文件指示 : include

注释    :  #注释

2. 搜索规则

make命令默认搜索名为Makefile,makefile的文件。
使用-f选项,可以指定make搜索的文件名。

Makefile支持三种通配符” * “,”?”,”[…]”。

makefile中的文件默认在,makefile文件所在目录下搜索。
可以通过两种方式更改makefile搜索文件的PATH。

1.特殊变量VPATH
VPATH = src:.../inc
#上面指明了,两个搜索路径,src和.../inc。
2.关键字vpath
    2.1 vpath <pattern> <dir>
    #为符合模式<pattern>的文件指定搜索目录<dir>。
    2.2 vpath <pattern>
    #清除符合模式<pattern>的文件的搜索目录。
    2.3 vpath
    #清除所有已被设置好了的文件搜索目录。

vapth 使用方法中的<pattern>需要包含“ %”字符。“ %”的意思是
匹配零或若干字符,例如, “%.h”表示所有以“.h”结尾的文件。
    vpath的书写顺序就是搜索顺序。

3.伪目标

伪目标可以用来一次生成多个可执行文件,也可用来选择生成哪个可执行文件。
例1

all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o
prog2 : prog2.o
    cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
    cc -o prog3 prog3.o sort.o utils.o
#可以使用make命令一次生成多个可执行文件。

例2

.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
    rm program
cleanobj :
    rm *.o
cleandiff :
    rm *.diff
#可以选择性的执行make cleanall或cleanobj或cleandiff

4.多目标

Makefile 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖
于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的
生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自
动化变量“ $@”,这个变量表示着目前规则中所有的目标的集合。

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@
上述规则等价于:
bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

其中, -$(subst output,,$@)中的"$"表示执行一个 Makefile 的函数,函数名为 subst,后面的为参数。这里的这个函数是截取字符串的思,"$@"表示目标的集合,就像一个数组.

"$@"依次取出目标,并执于命令。

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。
语法:

<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...

targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行 一次依赖目标的定义。

例子:

<target-parrtern>定义成“ %.o”,意思是我们的<target>集合中都是以“ .o”结尾 的
<prereq-parrterns>定义成“ %.c”,意思是对<target-parrtern> 所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“ %”(也 就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“ %”这个字符,如果你的文件名 中有“ %”那么你可以使用反斜杠“ \”进行转义,来标明真实的“ %”字符。

例子:

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

例子:

files = foo.elc bar.o lose.o
    $(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $<


##$(filter %.o,$(files))表示调用 Makefile 的 filter 函数,过滤##"$filter"集, 只要其中模式为"%.o"的内容。

5. 自动生成依赖

如果是一个比较大型的工程,你必需清楚哪些 C 文件包含了哪些头文件,并且,你在 加入或删除头文件时,也需要小心地修改 Makefile,这是一个很没有维护性的工作。为了 避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的 C/C++ 编译器都支持一个“ -M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。

例如

main.c中有
#include<stdio.h>
#include<defs.h>
命令
    gcc -M main.c
相当于
    gcc main.c stdio.h defs.h

GNU 组织建议把编译器为每一个源 文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个 “ name.d”的 Makefile 文件, [.d]文件中就存放对应[.c]文件的依赖关系。

可以写出[.c]文件和[.d]文件的依赖关系,并让 make 自动更新或自成[.d]文 件,并把其包含在主 Makefile 中,这样,就可以自动化地生成每个文件的依赖 关系了。

这里,给出了一个模式规则来产生[.d]文件:

%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$

第一行的意思是,所有的[.d]文件依赖于[.c]文件,“ rm -f $@”的意思是删除所有的目标,也就是[.d]文件.

第二行的意思是,为每个依赖文件“ $<”,也就是[.c]文件生成 依赖文件,“ $@”表示模式“ %.d”文件,如果有一个 C 文件是 name.c,那么“ %”就是“ name”, “ $$$$”意为一个随机编号,第二行生成的文件有可能是
“ name.d.12345”,

第三行使用 sed 命令做了一个替换,关于 sed 命令的用法请参看相关的使用文档。

第四行就是删除临时文件。

6. 变量

Makefile中的变量相当于#define的常量。放在文件开头,一般用OBJS变量代替最终target的依赖,CC代替编译命令。

声明变量:

1.
变量名 = 值12 ... 值n 用/换行继续写。
引用变量:
$(变量名)

2.
变量也可以用:=赋值。这样可以检测有么有递归调用。
变量名 := $(shell pwd)获得当前位置。

3.
?=相当于ifndef
a ?=b
相当于
a如果没定义,则a=b
高级变量使用方法

一.替换
示例:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,先定义了一个“ $(foo)”变量,而第二行的意思是把" $(foo)"中所有 以".o"字串"结尾"全部替换成" .c",所以 $(bar)的值就是" a.c b.c c.c"。


二.把变量的值再当成变量
x = y
y = z
a := $($(x))


三.追加变量值
+=


四. override 指示符
如果有变量是通常 make 的命令行参数设置的,那么 Makefile 中对这个变量的赋值会被忽略。如果你想在 Makefile 中设置这类参数的值,那么,可以使用"override"指示符。
其语法是:
override <variable> = <value>
override <variable> := <value>
当然,你还可以追加:
override <variable> += <more text>
对于多行的变量定义,我们用 define 指示符,在 define 指示符前,也同样可以使用
ovveride 指示符,如:
override define foo
bar
endef


五.多行变量
define 关键字。使用 define 关键字设置变量的值可以有换行,这有利于定义一系列的命令(“命令包”的技术就是利用这个关键字)。

define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字结束。其工作方式和“ =”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用 define 定义的命令变量中没有以[Tab]键开头,那么 make 就不会把其认为是命令。

下面的这个示例展示了 define 的用法:
define two-lines
    echo foo
    echo $(bar)
endef


六.环境变量
export
环境变量是make运行时带入的,可在makefile中定义,然后带入嵌套的makefile。


七.局部变量
前面都是全局变量
局部变量就像 
target : 依赖


八.模式变量
模式变量名以%开头
语法与局部变量一样。

7. 函数及其高级函数

函数使用方法
$(functionname    arguments)

1.替换函数
subst   from,to,inin中from换位to

2.模式替换
patsubst 

3.去掉开头的空格
strip

4.查找
find  find,in

5.过滤
filter  string1 ...,in
返回符合模式的

6.反向过滤
filter-out
去掉符合模式的

7.升序排序
sort  list

8.取第n个单词
word n,text

9.取m-n个单词
word-list n,m,text

10.统计词数
words text

11.取第一个单词
firstword text

12.取文件的目录
dir  file...

13.取非目录
notdir file...
取出file...中非目录名的

14.取后缀
suffix file...
返回值带点

15.取前缀
basename 

16.加后缀
addsuffix  .x,file....

17.加前缀
addprefix  pre,file...

18.链接
join  aa bb,11 22 33
结果
aa11 bb22 33


高级函数;
1. foreach函数

语法是:
$(foreach <var>,<list>,<text>)
把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中, <text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时, <text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以, <var>最好是一个变量名, <list>可以是一个表达式,而<text>中一般会使用<var>这个参数来依次枚举<list>中的单词。

例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中, $(name)中的单词会被挨个取出,并存到变量“ n”中,“ $(n).o”每次根据“ $(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,$(files)的值是“ a.o b.o c.o d.o”。
注意, foreach 中的<var>参数是一个临时的局部变量, foreach 函数执行完后,参数<var>的变量将不在作用,其作用域只在 foreach 函数当中。


2.if 函数

很像 GNU 的 make 所支持的条件语句——ifeq, if 函数的
语法是:
$(if <condition>,<then-part>)
或是
$(if <condition>,<then-part>,<else-part>)

可见, if 函数可以包含“ else”部分,或是不含。即 if 函数的参数可以是两个,也可以是三个。 

<condition>参数是 if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是, <then-part>会被计算,否则<else-part>会被计算。

而 if 函数的返回值是,如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。

所以, <then-part>和<else-part>只会有一个被计算。

3.call函数

call 函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,可以定义许多参数,然后可以用 call 函数来向这个表达式传递参数。

其语法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
当 make 执行这个函数时, <expression>参数中的变量,如$(1), $(2), $(3)等,会被参数<parm1>, <parm2>, <parm3>依次取代。而<expression>的返回值就是 call 函数的返回值。例如:

reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么, foo 的值就是“ a b”

4.origin函数
origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的.

其语法是:
$(origin <variable>)

注意, <variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“ $”字符。 Origin 函数会以其返回值来告诉你这个变量的“出生情况”,

下面,是 origin函数的返回值:
“ undefined”
如果<variable>从来没有定义过, origin 函数返回这个值“ undefined”。
“ default”
如果<variable>是一个默认的定义,比如“ CC”这个变量,这种变量我们将在后面
讲述。
“ environment”
如果<variable>是一个环境变量,并且当 Makefile 被执行时,“ -e”参数没有被
打开。
“ file”
如果<variable>这个变量被定义在 Makefile 中。
“ command line”
如果<variable>这个变量是被命令行定义的。
“ override”
如果<variable>是被 override 指示符重新定义的。
“ automatic”
如果<variable>是一个命令运行中的自动化变量。

5.shell函数
shell 命令
在make中执行shell命令

6.errorwarning
error text...
报错并退出make
warning
报错不退出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值