Makefile规则
一句话总结就是依赖关系,简单如下所示
target … : prerequisites …
command
…
…
target也就是一个目标文件,能够是Object File,也能够是运行文件。还能够是一个标签(Label),对于标签这样的特性,在后续的“伪目标”章节中会有叙述。
prerequisites就是,要生成那个target所须要的文件或是目标。
command也就是make须要运行的命令。(随意的Shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中假设有一个以上的文件比target文件要新的话,command所定义的命令就会被运行。这就是Makefile的规则。也就是Makefile中最核心的内容。
Makefile工作
1、make会在当前文件夹下找名字叫“Makefile”或“makefile”的文件。
2、假设找到,它会找文件里的第一个目标文件(target),在上面的样例中,他会找到“edit”这个文件,并把这个文件作为终于的目标文件。
3、假设edit文件不存在,或是edit所依赖的后面的 .o 文件的文件改动时间要比edit这个文件新,那么,他就会运行后面所定义的命令来生成edit这个文件。
4、假设edit所依赖的.o文件也不存在,那么make会在当前文件里找目标为.o文件的依赖性,假设找到则再依据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是运行文件edit了。
Makefile内容
Makefile里主要包括了五个东西:显式规则、隐晦规则、变量定义、文件指示和凝视。
1、显式规则。显式规则说明了,怎样生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐晦规则。由于我们的make有自己主动推导的功能,所以隐晦的规则能够让我们比較粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被运行时,当中的变量都会被扩展到对应的引用位置上。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指依据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5、凝视。Makefile中唯独行凝视,和UNIX的Shell脚本一样,其凝视是用“#”字符,这个就像C/C++中的“//”一样。假设你要在你的Makefile中使用“#”字符,能够用反斜框进行转义,如:“/#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
Make的工作方式
GNU的make工作时的运行步骤入下:(想来其他的make也是相似)
1、读入全部的Makefile。
2、读入被include的其他Makefile。
3、初始化文件里的变量。
4、推导隐晦规则,并分析全部规则。
5、为全部的目标文件创建依赖关系链。
6、依据依赖关系,决定哪些目标要又一次生成。
7、运行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,假设定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会全然立即展开,make使用的是迟延战术,假设变量出如今依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开
通配符
make支持三各通配符:“*”,“?”和“[…]”。这是和Unix的B-Shell是相同的。
波浪号(“~”)字符在文件名称中也有比較特殊的用途。假设是“~/test”,这就表示当前用户的$HOME文件夹下的test文件夹
文件搜寻
在一些大的工程中,有大量的源文件,我们通常的做法是把这很多的源文件分类,并存放在不同的文件夹中。所以,当make须要去找寻文件的依赖关系时,你能够在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自己主动去找。
Makefile文件里的特殊变量“VPATH”就是完成这个功能的,假设沒有指明这个变量,make仅仅会在当前的文件夹中去找寻依赖文件和目标文件。假设定义了这个变量,那么,make就会在当当前文件夹找不到的情况下,到所指定的文件夹中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个文件夹,“src”和“../headers”,make会依照这个顺序进行搜索。文件夹由“冒号”分隔。(当然,当前文件夹永远是最高优先搜索的地方)
另一个设置文件搜索路径的方法是使用make的“vpath”keyword(注意,它是全小写的),这不是变量,这是一个make的keyword,这和上面提到的那个VPATH变量非常相似,可是它更为灵活。它能够指定不同的文件在不同的搜索文件夹中。这是一个非常灵活的功能。它的使用方法有三种:
1、vpath
为符合模式的文件指定搜索文件夹。
2、vpath
清除符合模式的文件的搜索文件夹。
3、vpath
清除全部已被设置好了的文件搜索文件夹。
vapth使用方法中的须要包括“%”字符。“%”的意思是匹配零或若干字符,比如,“%.h”表示全部以“.h”结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的文件夹。比如:
vpath %.h ../headers
该语句表示,要求make在“../headers”文件夹下搜索全部以“.h”结尾的文件。(假设某文件在当前文件夹沒有找到的话)
我们能够连续地使用vpath语句,以指定不同搜索策略。假设连续的vpath语句中出现了相同的,或是被反复了的,那么,make会依照vpath语句的先后顺序来运行搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其表示“.c”结尾的文件,先在“foo”文件夹,然后是“blish”,最后是“bar”文件夹。
vpath %.c foo:bar
vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”文件夹,然后是“bar”文件夹,最后才是“blish”文件夹
伪目标
最早先的一个样例中,我们提到过一个“clean”的目标,这是一个“伪目标”,
clean:
rm .o temp
正像我们前面样例中的“clean”一样,即然我们生成了很多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)
由于,我们并不生成“clean”这个文件。“伪目标”并非一个文件,仅仅是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要运行。我们唯独通过显示地指明这个“目标”才干让其生效。当然,“伪目标”的取名不能和文件名称重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这样的情况,我们能够使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,无论是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
仅仅要有这个声明,无论是否有“clean”文件,要运行“clean”这个目标,唯独“make clean”这样。于是整个过程能够这样写:
.PHONY: clean
clean:
rm
.o temp
伪目标一般沒有依赖的文件。可是,我们也能够为伪目标指定所依赖的文件。伪目标相同能够作为“默认目标”,仅仅要将其放在第一个。一个演示例子就是,假设你的Makefile须要一口气生成若干个可运行文件,但你仅仅想简单地敲一个make完事,而且,全部的目标文件都写在一个Makefile中,那么你能够使用“伪目标”这个特性:
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
我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其他三个目标。由于伪目标的特性是,总是被运行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其他三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。
随便提一句,从上面的样例我们能够看出,目标也能够成为依赖。所以,伪目标相同也可成为依赖。看以下的样例:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm .o
cleandiff :
rm
.diff
“make clean”将清除全部要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们能够输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
参考
内容很多,在此停顿,详情参考下列文章
下面是使用过的两个模板
很nice 很perfect
模板一
目录结构如下
clib config edns_dial.spec.tmp include lib Makefile Makefile.bak src tags test thrif_file ymediald
TGT=edns_dial
SRCS=$(wildcard ./src/*.cpp)
LIBRAYS= -lrt -pthread -lssl -lcrypto -ldl -lz ./lib/*.a
COMPILE_FLAGS= -g -W -O2 -DHAVE_NETINET_IN_H -I./include -I./clib/include -I/usr/include/openssl
CC=g++
all:$(TGT)
@echo Generation target!
$(TGT):$(SRCS:.cpp=.o)
$(CC) -o $@ $^ $(LIBRAYS) $(COMPILE_FLAGS)
%.o : %.cpp
$(CC) -c $(COMPILE_FLAGS) $< -o $@
.PHONY: clean rpmclean
clean:
rm -rf $(TGT) $(SRCS:.cpp=.o)
RPM_VERSION = $(shell sed -ne 's/\#define\(\ \)\{1,\}MAJOR\(\ \)\{1,\}\"\(.*\)\"/\3/p' ./include/version.h)
COMMIT = $(shell git rev-list HEAD |head -1|cut -c 1-6)
#RPM_RELEASE = $(shell git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' -e 's/-/_/g')_$(COMMIT)
RPM_RELEASE = enterprise
RPM_TOP_DIR = $(shell rpm -E %{_topdir})
PRJHOME = $(shell pwd)
rpm:
@echo [RPM] ; \
sed -e "s/@VERSION@/$(RPM_VERSION)/g" -e "s/@RELEASE@/$(RPM_RELEASE)/g" $(TGT).spec.tmp > ${RPM_TOP_DIR}/SPECS/$(TGT).spec ; \
cp -a -r ${PRJHOME} /tmp/$(TGT)-$(RPM_VERSION) ; \
cd /tmp ; \
tar zcvf $(RPM_TOP_DIR)/SOURCES/$(TGT)-$(RPM_VERSION).tar.gz $(TGT)-$(RPM_VERSION) ; \
rm -rf $(TGT)-$(RPM_VERSION) ; \
rpmbuild -bb $(RPM_TOP_DIR)/SPECS/$(TGT).spec ; \
rpmclean:
cp -r ~/rpmbuild/RPMS/x86_64/$(TGT)*$(RPM_VERSION)* ./
rm -rf ~/rpmbuild/SOURCES/$(TGT)* \
~/rpmbuild/BUILD/$(TGT)* \
~/rpmbuild/RPMS/x86_64/$(TGT)* \
~/rpmbuild/SPEC/$(TGT)*
模板二
目录结构
clib config include lib Makefile README.md spec src tags
外层makefile
TGT=dialhp
DIALHP=./src
UDT = ./lib/udt4/
CLIB = ./clib/src/
all: build
build:
cd $(UDT) && $(MAKE)
cd $(CLIB) && $(MAKE)
cd $(DIALHP) && $(MAKE)
clean:
rm -rf *~ *.swp $(TGT)
cd $(DIALHP) && $(MAKE) clean
cd $(UDT) && $(MAKE) clean
cd $(CLIB) && $(MAKE) clean
.PHONY: rpmclean
RPM_VERSION=$(shell sed -ne 's/\#define\(\ \)\{1,\}VERSION\(\ \)\{1,\}\"\(.*\)\"/\3/p' ./include/version.h)
COMMIT = $(shell git rev-list HEAD |head -1|cut -c 1-6)
#RPM_RELEASE = $(shell git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' -e 's/-/_/g')_$(COMMIT)
RPM_RELEASE = dev
RPM_TOP_DIR = $(shell rpm -E %{_topdir})
PRJHOME = $(shell pwd)
rpm:
@echo [RPM] ; \
sed -e "s/@VERSION@/$(RPM_VERSION)/g" -e "s/@RELEASE@/$(RPM_RELEASE)/g" spec/$(TGT).spec.tmp > ${RPM_TOP_DIR}/SPECS/$(TGT).spec ; \
cp -a -r ${PRJHOME} /tmp/$(TGT)-$(RPM_VERSION) ; \
cd /tmp ; \
tar zcvf $(RPM_TOP_DIR)/SOURCES/$(TGT)-$(RPM_VERSION).tar.gz $(TGT)-$(RPM_VERSION) ; \
rm -rf $(TGT)-$(RPM_VERSION) ; \
rpmbuild -bb $(RPM_TOP_DIR)/SPECS/$(TGT).spec ; \
rpmclean:
cp -r ~/rpmbuild/RPMS/x86_64/$(TGT)*$(RPM_VERSION)* ./
rm -rf ~/rpmbuild/SOURCES/$(TGT)* \
~/rpmbuild/BUILD/$(TGT)* \
~/rpmbuild/RPMS/x86_64/$(TGT)* \
~/rpmbuild/SPEC/$(TGT)*
src内层makefile
TGT=dialhp
SRCS=$(wildcard *.cpp)
LIBRAYS= ../lib/udt4/libudt.a -lrt -pthread -lssl -lcrypto -ldl -lz ../lib/libdns.a ../lib/libjansson.a ../lib/libclib.a -lresolv
COMPILE_FLAGS= -g -W -O2 -I../include -I../clib/include
CC=g++
all:$(TGT)
cp $(TGT) ../
@echo Generation target!
$(TGT):$(SRCS:.cpp=.o)
$(CC) -o $@ $^ $(LIBRAYS) $(COMPILE_FLAGS)
%.o : %.cpp
$(CC) -c $(COMPILE_FLAGS) $< -o $@
clean:
rm -rf $(TGT) $(SRCS:.cpp=.o) .*.swp