Makefile 笔记

资料出处: http://hi.baidu.com/00%C6%F3%B6%EC/blog/item/326f012344d17541ad34deaf.html

注:以下的内容,足以应付中小型工程或一般的大型工程。更多详细的展开,可见那个中文的跟我写Makefile.


Makefile的要点:
一、基础(中小型工程):
1、规则
(1)普通规则:
make其实是一个普通意义上的依赖和执行的程序,普通规则很简单。
并且注意的是,只敲make,则在Makefile中的第一个规则被使用!
(2)这里要说的是隐含规则:
当规则(依赖关系和执行命令)明确给出时,make将严格按照你指定的来做,不另外给你加参数。
但当规则没有给出时(比如我们可能懒得写fun.o依赖于fun.c的规则,只写出a.out依赖于main.o和fun.o的规则即可),make将会使用它的隐含规则,
对c文件如下:
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
对cpp文件如下:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
而这些变量的值,也是使用的默认值。其中CFLAGS和CXXFLAGS是传给c/c++编译器的参数,CPPFLAGS是传给预处理器的参数(这个基本我不知道要传啥)
所以,如果我们想偷懒,让默认规则发挥作用,又要想自己控制,就给这些变量赋值就好!
比如:
CC = gcc
CFLAGS = -Wall -O -g   # -O表示优化,-g表示debug信息

2、变量
用法都是用CC := gcc定义变量,用 $(CC)引用之。但注意,如果 CC = $(MYCC)之类的,则CC的值一直和$(MYCC)保持相同,就是以后给MYCC赋值,CC也跟着变。若要CC的值和$(MYCC)不相关,只是一次性赋给,应该用 := (冒号加等号)。所以,没特殊要求,就用 :=,若要保持相关性,用 =。
还有一个有时有用的,叫做 var ?= value 表明,如果var没被定义过(被赋值),才令之为value,否则不改变其值。
还有一个var += value,表明追加value到var中。
所以,经常用到的是 := = ?= +=
(1)自定义变量
用途:
a. 贮存一个文件名列表:因为一串文件名,可能在多个地方被引用
b. 贮存可执行的文件名:比如对于编译器,不同平台编译器不一样,在一个平台上写的文件,放到另一个平台,用另一个编译器,只需要改变文件名变量即可。
c. 贮存编译器的标志:编译器有很多标志,可能在多个地方被引用。
(2)内部变量
有三个有用的内部变量,很好的消除了重复,应该使用:
a. $@: 用于在-o后,替代目的文件名,因为,写规则时,目的文件名已经在第一行写好,在第二行写命令时,再写出它就显得重复了。
b. $<: 表示第一行的依赖关系中,依赖列表中的第一个文件。这通常用于-c中,因为依赖关系中的第一个文件通常是个.c文件,然后后面是.h文件,而-c后面只跟出.c文件即可,用$< 可消除重复。
c. $^: 表示整个依赖列表,不管里面你怎么搞,$^可以用在链接多个.o文件时,直接接在gcc后面,如gcc $^ -o $@

3、假象目的和直接执行
有两种常见的情况,我们使用假象目的,
(1) 没有动作的假象目的: 
我们要生成多个最后文件:
这时,写
all: program1 program2
然后 make all即可。
这里,all就是一个假象目的,它没有动作,所以,make就认为是一个假象目的,并更新program1和program2
(2) 没有依赖文件的假象目的:
比如常见的:
clean:
    rm -rf *.o hello
这里同理,make发现没有依赖文件,认为clean是一个假象文件,执行rm。

这里有个问题是:如果正巧有个文件是clean,那么make就不把clean当假象目的了,并发现没有依赖,所以认为clean文件总是最新的,于是不执行rm。
为了解决这种情况,对于这种没有依赖文件的假象目的,一般再加上下面的语法,表示直接执行,假设依赖关系满足(即需要更新)。
.PHONY: clean ...(其他的假象目的)

4、让Makefile不变应万变的函数:
Make函数的用法是:
var = $(funname arg1,arg2)
万能makefile的内容用到了这样四个函数:
$(wildcard arg) 其中arg是一种形式,这个函数的作用是匹配满足这种形式的任意文件
$(patsubst from, to, strings) 其中patsubst是pattern substitute的意思,表示把满足from的pattern替换为满足to的pattern,strings为待处理的一些字符串名。其中 % 表示匹配0或若干字符。若from里面含有%,则to里面也含有的话,%指代的是from部分的%。
$(filter-out pattern1 files) 是把files中符合pattern1的项都删去
$(addprefix prefix, files) 表示为strings中的每个字符串,都加上prefix。

万能C文件Makefile,适合于所有的文件都在一个文件夹下的情况(我的改版,只包含最重要的东西):



# 用户自定义的地方 #
PROG := myprog                           #生成程序名
LIBS := pthread                          #库名
CFLAGS := -Wall -O -g                    #编译器参数
CC := gcc

# 准备给下列规则的参数 #
#(变量用函数赋值,才能展开*.c,但如果是依赖关系,直接在:后写通配符*.c就可以展开。其他有*, ? 和 [...])
SOURCE := $(wildcard *.c)                #源文件 
OBJS := $(patsubst %.c, %.o, $(SOURCE)) #目标文件
L_LIBS := $(addprefix -l, $(LIBS))       #对库名加-l前缀
DEPS := $(patsubst %.o, %.d, $(OBJS))    #.d文件
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)), $(DEPS)) #得到缺少的.d文件,这由于自己不小心删除了一个.d
CPPFLAGS += -MD                          #这样在.c->.o时,会生成.d文件
# 两点说明:
# 1. MISSING_DEPS能得到缺少的.d(即,一个.c没有.d),是因为 $(wildcard $(DEPS)),$(DEPS)是文件列表,但是wildcard是在当前目录下找符合$(DEPS)的文件,显然,因为$(DEPS)是“全的”,所以,如果其中有个.d实际没有,那么wildcard就不会在当前目录下找到匹配的,那当然$(wildcard $(DEPS))就不会含有没有的.d文件了,这样再经过一个filter-out,表示去掉$(DEPS)中的$(wildcard $(DEPS))部分,于是,最后结果就是缺少的.d。
# 2. DEPS, MISSING_DEPS 的作用,就是用于下面的ifneq()...endif,用于当.d被不小心删除了,仍然能重新生成。因为下面删除了.o,而删除.o必然导致重新生成.o和.d。


# 就一条规则,而其他的都采用隐式规则 #
$(PROG): $(OBJS)
    $(CC) $(CFLAGS) $^ -o $@ $(L_LIBS)

# 当.c有,而.d没有时,这里就会起作用
ifneq ($(MISSING_DEPS), )
$(MISSING_DEPS):
    rm -rf $(patsubst %.d, %.o, $@) 
    # .d文件不在makefile的依赖链中。但是,由于指定了CPPFLAGS+=-MD,使得.d文件是make的结果,所以,它会被考虑进一个单独的依赖链中。这里,缺少.d会导致删除.o。然后在正常的那条依赖链中会自动再生成.d和.o。这里表明,只要生成了这个东西,它的规则就会被使用。
endif

# 把各个.o的依赖导入(看看各个.d文件就知道导入的是啥,就是规则格式。这里的$(DEPS)文件是预处理器生成的,如果不导入这些,那么比如哪个.h文件更改了,make都不会重做,要重做,依赖必须显示写明)
-include $(DEPS)


# 两个假象目的,都属于每次都想让它执行的,所以加入.PHONY #
.PHONY: clean rebuild
clean:
    rm -rf *.o *.d $(PROG)
rebuild: clean $(PROG)



    
二、进阶(大型工程):
1、文件指示:
(1) 一个Makefile引用另一个Makefile
就是写:
include prog1.make prog2.make 
#假设prog1.make和prog2.make是两个makefile文件,它们在各自的环境中通过make -f prog1.make的形式使用。
(2) 条件Make:
----------------
ifneq (var1, var2)    # 另一个是 ifeq,这两个意思很显然,第一个变量,如果想判断为空,则写为(var1, )即可。
    #变量赋值,或规则
else
    #变量赋值,或规则
endif
----------------
ifdef var             # 另一个是 ifndef,和ifdef相反。这样写 var = value 才是def的,写其他任何形式,如“var=”(表示空)则为ndef.
    #变量赋值,或规则
else
    #变量赋值,或规则
endif

2、文件搜寻:
当工程大时,需要把源文件分类,放在不同的目录中,这会产生一些文件搜寻问题:
一个Makefile被执行,它查找相关文件的方式是:先在当前目录下找,找不到才上VPATH中去找。
VPATH = dir1:dir2:dir3.... 
这是一个Makefile内置变量,还有用vpath这个makefile关键字的,更灵活,语法为:
vpath %.h ../headers 如这个表示在 ../headers中去找寻 %.h文件。

3、执行多条命令:
比如你要cd到一个目录,再执行make,可以:
exec:
    cd lib1; make
如果换行写就错了!!
至于可不可以这样:
exec:
    cd lib1
    make
呢,回答是不可以 ── 分号的作用是:前面的命令的效果,会影响后面命令,典型的就是这里的cd后再干吗。
所以,如果,前后命令,没有影响,则可以分行不加分号写!!

4、嵌套Makefile
(1) 编译下层目录的源文件:
all: lib1 lib2
lib1:
    cd lib1dir; make
lib2:
    cd lib2dir; make
注意,在一个规则的命令内,cd进去了,你要想出来,就得cd ..。但是如果你make all,那么lib1完成后,它会自动cd出来的,所以在make lib2的时候,lib2的规则仍然写cd lib2dir;make 即可。
看看提示信息就知道了:
-------------------- 这是进第一个子目录,注意下面的Entering和Leaving --------------------
cd src; make
make[1]: Entering directory `/home/evan/tmp/c++/make/src'
gcc -Wall -O -g                       -c -o cmdline.o cmdline.c
gcc -Wall -O -g                       -c -o job.o job.c
gcc -Wall -O -g                       -c -o main.o main.c
gcc -Wall -O -g                       -c -o term.o term.c
gcc -Wall -O -g                       -c -o utility.o utility.c
gcc -Wall -O -g                     cmdline.o job.o main.o term.o utility.o -o myprog -lpthread       
make[1]: Leaving directory `/home/evan/tmp/c++/make/src'
-------------------- 这是进第二个子目录,注意下面的Entering和Leaving --------------------
cd src2; make
make[1]: Entering directory `/home/evan/tmp/c++/make/src2'
gcc -Wall -O -g                       -c -o cmdline.o cmdline.c
gcc -Wall -O -g                       -c -o job.o job.c
gcc -Wall -O -g                       -c -o main.o main.c
gcc -Wall -O -g                       -c -o term.o term.c
gcc -Wall -O -g                       -c -o utility.o utility.c
gcc -Wall -O -g                     cmdline.o job.o main.o term.o utility.o -o myprog -lpthread       
make[1]: Leaving directory `/home/evan/tmp/c++/make/src2'

可能没有Entering和Leaving信息?那么换 cd lib1dir; make为 make -C lib1dir 即可。两者等效,但第二个肯定会输出这些信息的(其实-C是默认打开了-w选项,-w表示make过程中输出相关信息)

另外,注意make[1],中的序号,这个表示当前处理的 makefile 的所在层次。数字一样的,表示一个层次的,上面两次的 Entering和Leaving的目录,都处于一个层次的,所以make后一直是[1],如果在这个子目录中,还有子目录,还要进去make的话,那么就会看到make[2]。

(2) 传递变量:
上层Makefile的变量值,要想传到下层目录的Makefile,就得用: 
export var := value (:=当然可以是=) 或
export var += value (表示追加)

(3) 传递Make参数:
一些特殊的内置变量,不用export,也相当于export了,比如:
MAKEFLAGS 这个变量会传给下层所有的Makefile,它是make参数变量!,你可以加你想要的参数进去。比如你在最上层的Makefile中写了 MAKEFLAGS := -s ,而且写了Make -C subdir,那么在subdir中make的时候,这个参数则一样有效。-s的作用使得不会输出任何信息。

另外,参数写在你make后,比如敲击 make -s,也一样会被传给下面嵌套的所有调用的make。(不必像书上说的那样,非得写 $(MAKE),写嵌套的make就写make即可)

另外,一个有用的参数 -w ,用于在make一个东西前后,打印出Enter 和 LEAVE,便于查看。这个在 -C 参数打开时,是默认有效的。


5、命令包
就是无参数的函数,使得重复的命令序列不重复出现,语法很简单:
define fun
    cmd1
    cmd2
endef
用的时候:
foo.o: foo.c
    $(fun)
即可


6、高级函数:
(1) 内置函数:
很多有用的,主要对字符串处理,对文件名处理,循环函数,if函数...如果需要,查手册。
如果看中文的,可以看跟我写makefile的38页到50页。
(2) shell函数:
这个可以极大的扩展make的功能,因为:
var := $(shell ls) 表明,在shell中执行ls,然后将结果返回给var。
只要系统有的命令,都可以在这里使用。




三、有用的主题:
1、make参数可以看看:
比如关闭显示,减少编译时间:
make -s
2、ld对库的链接,最小单位是目标文件,所以,为减少最后生成程序的大小,在编写一个库时,应当尽量一个函数一个文件。当然,其他平台上的链接器可能不是这个样子。 
3、为了找出一个目标文件依赖的所有文件,比如file.c包含file.h,file.h又包含cow.h ....:
用gcc -M file.c 给出目标文件file.o 依赖的所有文件,包括file.c和一大堆齐全的header文件(<>和""引起来的)
用gcc -MM file.c 比gcc -M 少给出系统header文件,只给自己的头文件(实际上它是看<>或"",它只给出""引起来的)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值