Makefile基础
一.Makefile基础
1.1 步骤:编译-链接
编译(compile):把源文件编译成中间目标文件(object file/.o .obj文件)
链接(link):将中间目标文件合成执行文件
库文件(library file):中间目标文件太多,将中间目标文件打包。(.lib 或 .a 文件)
1.2 makefile的规则
Target ... : prerequisites ...
Command
...
...
Target: 可以是object file,也可以是一个执行文件,还可以是一个标签(label)
Prerequisites: 生成该target所依赖的文件或target
Command:该target要执行的命令(任意的shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说:
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
这就是makefile的规则,也就是makefile中最核心的内容。
1.3 一个简单的例子
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o |
1.4 make 工作流程
- A. Make会在当前目录下找名字叫makefile或Makefile的文件
- B. 如果找到,它会找文件中第一个目标文件(edit),并把这个文件作为最终的目标文件
- C. 如果edit文件不存在或是edit所依赖的.o文件的文件修改时间比edit新,那么会执行后面的command生成edit
- D. 如果edit所依赖的.o文件也不存在,那么make会在当前文件找目标为.o文件的依赖性,如果找到则再根据规则生成.o文件。
比如,如果我们改变了command.h,那么kdb.o,command.o,file.o都会重新编译,并且edit会重新链接。
1.5 make自动推导
隐晦规则:
只要make看到一个.o文件,它自动把.c文件加到依赖关系中,并且cc -c .c的command也会被推导出来。
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) $(objects) : defs.h kbd.o command.o files.o : command.h display.o insert.o search.o files.o : buffer.h .PHONY : clean clean : rm edit $(objects) |
1.6 makefile内容
A. 显示规则
B. 隐晦规则
C. 变量定义
D. 文件指示( include foo.make *.mk $(bar) )
E. 注释(#)
F. 命令(必须以Tab键开始)
1.7 make的工作方式
A. 读入所有的Makefile
B. 读入被include的其它Makefile
C. 初始化文件中的变量
D. 推导隐晦规则,并分析所有规则
E. 为所有的目标文件创建依赖关系链
F. 根据依赖关系链,决定哪些目标要重新生成
G. 执行生成命令
1.8 规则中的通配符
~ :~/test = $HOME/test
~usr/test = usr宿主目录下的test
* : *.c 所有后缀为c的文件,文件中有通配符的用转义字符 (如*)
1.9 搜索路径
VPATH = src: ../headers (目录由冒号分割)
make 依赖和目标文件在当前目录找不到的情况下会在VPATH目录中寻找
2.0 伪目标
.PHONY : clean
表示并不生成clean这个文件,clean只是一个标签,make 无法生成它的依赖关系和决定它是否执行,只有显示的指明这个标签才能让其生效。
2.1 多目标
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 |
2.2 静态模式
静态模式可以更容易地定义多目标的规则
<targets ...> : <target-pattern> : <prereq-patterns ...> <commands> 例子: 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 |
2.3 定义命令包
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
2.4 变量
Variable = xxx Variable变量 = 赋值
$(Variable) 括号是为了更安全的使用变量
X := foo
Y := $(X) bar
X := later
用:= 赋值变量 ,前面的变量不能使用后面的变量,只能使用前面已经定义好的变量
?= 如果变量已经定义,则什么也不做
赋值语句后#的注释注意:
Dir := /foo/bar # directory
那么$(Dir)/file 会是/foo/bar /file,因为赋值后有四个空格
2.5 变量替换
$(var : a=b) 把变量var中所有以a字串结尾的a替换成b字串
$(var:%.o=%.c)
2.6 追加变量值
Var += other
Var := $(Var) other
2.7 环境变量
2.8 条件判断
ifeq ifneq ifdef ifndef
else
endif
ifeq (<arg1>, <arg2>) ifeq '<arg1>' '<arg2>' ifeq "<arg1>" "<arg2>" ifeq "<arg1>" '<arg2>' ifeq '<arg1>' "<arg2>" |
2.9 make的运行
其他功能
• all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。 • clean:这个伪目标功能是删除所有被make创建的文件。 • install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目 标中去。 • print:这个伪目标的功能是例出改变过的源文件。 • tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件。 • dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。 • TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。 • check和test:这两个伪目标一般用来测试makefile的流程。 |
3.0 隐含规则
常用的隐含规则
1. 编译C程序的隐含规则。 <n>.o 的目标的依赖目标会自动推导为<n>.c ,并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS) 2. 编译C++程序的隐含规则。 <n>.o 的目标的依赖目标会自动推导为<n>.cc 或是<n>.C ,并且其生成命令是 $(CXX) -c $(CPPFLAGS) $(CFLAGS)。(建议使用.cc作为C++源文件的后缀,而 不是.C) |
模式规则示例
%.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ |
二.Makefile分析
#当前模块的配置 CURRENT_MODULE_PATH:=$(shell pwd) INC_FLAGS:=-I$(CURRENT_MODULE_PATH)/inc #加入模块头文件路径 #工程的公共配置 ROOT_PATH:=$(CURRENT_MODULE_PATH)/.. PROJECT_COMMON_PATH:=$(ROOT_PATH)/common INC_FLAGS+=-I$(PROJECT_COMMON_PATH)/inc #加入工程公共头文件路径 INC_FLAGS+=-I$(PROJECT_COMMON_PATH)/inc/test #加入工程公共头文件路径 LIB_FLAGS:=-L$(PROJECT_COMMON_PATH)/lib #加入工程公共库文件路径 #相关的配置 SRV_PATH:=$(ROOT_PATH)/SRV SRV_INC_PATH:=$(SRV_PATH)/include SRV_LIB_PATH:=$(SRV_PATH)/lib SRV_INC_FLAGS:=-I$(SRV_INC_PATH) -I$(SRV_INC_PATH)/pub-sub -I$(SRV_INC_PATH)/infra -I$(SRV_INC_PATH)/3rdparty -I$(SRV_INC_PATH)/saturn -I$(SRV_INC_PATH)/common INC_FLAGS+=$(SRV_INC_FLAGS) #加入SRV头文件路径 LIB_FLAGS+=-L$(SRV_LIB_PATH) #需要连接的库 SYS_LIBS:=-lpthread -lz -ldl -lrt -lcrypto -lssl -lm SRV_LIBS:=-lpubsub -linfra -lentry -lsaturn -ltimer -lprotobuf -lrestful_server -lserved -lre2 -lboost_system -ldpdk -lcurl PROJECT_COMMON_LIBS:= # TEST_LIBS:= -lgtest_main -lmockcpp LINK_LIBS:= $(SRV_LIBS) $(SYS_LIBS) $(PROJECT_COMMON_LIBS) #测试代码路径 CURRENT_MODULE_TEST_PATH:=$(CURRENT_MODULE_PATH)/test GCOVRFLAGS:= -fprofile-arcs -ftest-coverage #中间文件相关 CURRENT_MODULE_BUILD_PATH:=$(CURRENT_MODULE_PATH)/build MODULE_SRC:=$(notdir $(wildcard $(CURRENT_MODULE_PATH)/src/*.cpp)) MODULE_DEBUG_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/debug/,$(patsubst %.cpp,%.o,$(MODULE_SRC))) MODULE_RELEASE_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/release/,$(patsubst %.cpp,%.o,$(MODULE_SRC))) MODULE_TEST_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/test/,$(filter-out main.o,$(patsubst %.cpp,%.o,$(MODULE_SRC)))) COMMON_SRC:=$(notdir $(wildcard $(PROJECT_COMMON_PATH)/src/*.cpp)) COMMON_DEBUG_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/debug/,$(patsubst %.cpp,%.o,$(COMMON_SRC))) COMMON_RELEASE_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/release/,$(patsubst %.cpp,%.o,$(COMMON_SRC))) COMMON_TEST_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/test/,$(patsubst %.cpp,%.o,$(COMMON_SRC))) TEST_SRC:=$(notdir $(wildcard $(CURRENT_MODULE_TEST_PATH)/*.cpp)) TEST_OBJS:=$(addprefix $(CURRENT_MODULE_BUILD_PATH)/test/,$(patsubst %.cpp,%.o,$(TEST_SRC))) #顶级目标 DEBUG_TARGET:=$(CURRENT_MODULE_PATH)/build/DLocationCalc RELEASE_TARGET:=$(CURRENT_MODULE_PATH)/build/RLocationCalc TEST_TARGET:=$(CURRENT_MODULE_PATH)/build/TLocationCalc RELEASE_FLAGS:= -DNDEBUG DEBUG_FLAGS:=-g -DDEBUG TEST_FLAGS:=-g -DDEBUG -DTEST CXXFLAGS+=-std=c++11 .PHONY:default release debug test clean pre_build default:debug pre_build: mkdir -p $(CURRENT_MODULE_BUILD_PATH)/release rm -f $(CURRENT_MODULE_BUILD_PATH)/release/RLocationCalc mkdir -p $(CURRENT_MODULE_BUILD_PATH)/debug rm -f $(CURRENT_MODULE_BUILD_PATH)/debug/DLocationCalc mkdir -p $(CURRENT_MODULE_BUILD_PATH)/test rm -f $(CURRENT_MODULE_BUILD_PATH)/test/TLocationCalc #编译调试版本 debug:pre_build $(DEBUG_TARGET) $(DEBUG_TARGET): $(MODULE_DEBUG_OBJS) $(COMMON_DEBUG_OBJS) $(CXX) $(CXXFLAGS) -o $@ $^ $(LIB_FLAGS) $(LINK_LIBS) #编译发布版本 release:pre_build $(RELEASE_TARGET) $(RELEASE_TARGET):$(MODULE_RELEASE_OBJS) $(COMMON_RELEASE_OBJS) $(CXX) $(CXXFLAGS) -o $@ $^ $(LIB_FLAGS) $(LINK_LIBS) #编译测试用例并运行 move_sample: cp $(CURRENT_MODULE_TEST_PATH)/test.csv $(CURRENT_MODULE_BUILD_PATH)/ test:pre_build move_sample $(TEST_TARGET) export LD_LIBRARY_PATH=$(SRV_LIB_PATH) && ./build/TLocationCalc --gtest_output=xml:./build/gtest.xml && gcovr -r . && lizard src -l cpp -C 8 -L 100 -a 6 $(TEST_TARGET): $(MODULE_TEST_OBJS) $(COMMON_TEST_OBJS) $(TEST_OBJS) $(CXX) $(CXXFLAGS) -o $@ $^ $(LIB_FLAGS) $(TEST_LIBS) $(LINK_LIBS) $(GCOVRFLAGS) #清除 clean: rm -rf $(CURRENT_MODULE_BUILD_PATH) # debug 版本 $(CURRENT_MODULE_BUILD_PATH)/debug/%.o:$(CURRENT_MODULE_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(INC_FLAGS) -o $@ -c $^ $(CURRENT_MODULE_BUILD_PATH)/debug/%.o:$(PROJECT_COMMON_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(INC_FLAGS) -o $@ -c $^ #release 版本 $(CURRENT_MODULE_BUILD_PATH)/release/%.o:$(CURRENT_MODULE_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(INC_FLAGS) -o $@ -c $^ $(CURRENT_MODULE_BUILD_PATH)/release/%.o:$(PROJECT_COMMON_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(INC_FLAGS) -o $@ -c $^ #test 版本 $(CURRENT_MODULE_BUILD_PATH)/test/%.o:$(CURRENT_MODULE_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(INC_FLAGS) $(TEST_FLAGS) $(GCOVRFLAGS) -o $@ -c $^ $(CURRENT_MODULE_BUILD_PATH)/test/%.o:$(PROJECT_COMMON_PATH)/src/%.cpp $(CXX) $(CXXFLAGS) $(INC_FLAGS) $(TEST_FLAGS) $(GCOVRFLAGS) -o $@ -c $^ $(CURRENT_MODULE_BUILD_PATH)/test/%.o:$(CURRENT_MODULE_TEST_PATH)/%.cpp $(CXX) $(CXXFLAGS) $(INC_FLAGS) $(TEST_FLAGS) -o $@ -c $^ |
三.常用命令
A. 通配符自动展开 在makefile的规则中,通配符会被自动展开,但是在变量的定义和函数引用时,通配符将失效,这种情况下如要用通配符,就要用函数wildcard,用法$(wildcard PATTERN..) 例子:objects := $(wildcard *.c) 来获取工作目录中所有.c 文件列表 B . 模式字符串替换patsubst函数 例子:$(patsubst %.c, %.o, $(dir)) 把$(dir) 中的变量符合.c结尾的全部替换为.o C. 去掉路径notdir($src) D. 字符串替换subst $(subst FROM, TO, TEXT) 将TEXT中的FROM替换成TO E. 过滤函数filter 例子:$(filter %.o, $(files)) 从files中过滤出.o 的文件 F. 首单词函数firstword G. $( strip <string>) 去掉字串开头和结尾的空字符 H. $(findstring <find>,<in>) 在in中查找find字串,找到返回find,否则返回空字串 I. $(filter <pattern...>, <text>)以pattern模式过滤text中的单词,保留符合模式的单词,模式可以多个 J. $(filter-out <pattern...>, <text>) 反过滤函数,返回不符合模式的单词 K. $(sort <list>) 排序(升序),去掉相同的单词 L. word,wordlist,firstword,words M. $(dir <names ...>) 取目录 N. $(notdir <names ...>) 取文件函数 O. $(suffix <names ...>)取后缀函数 P. $(basename <names...>)取前缀函数 Q. $(addsuffix <suffix>,<names...>) 添加后缀 R. $(addprefix <prefix>,<names...>) 添加前缀 S. $(join <list1>,<list2>)把list2中的单词对应加到list1的后面 T. $(foreach <var>,<list>,<test>)将list中单词逐个取出放到var变量中,然后在执行test所包含的表达式 U. $(if <condition>,<then-part>,<else-part>) |
四. 自动变量
$@: 目标集合
$<: 所有的依赖目标集第一个目标,如果依赖是模式定义的,取出一系列
$^: 所有的依赖目标的集合,以空格分割
$+ : 类似$^,去除重复的依赖
$? : 所有比目标更新的依赖集合
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 |
五. 编译器g++
gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步 1.预处理,生成.i的文件[预处理器cpp] 2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs] 3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as] 4.连接目标代码,生成可执行程序[链接器ld] 常用参数: -ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 -c 只编译并生成目标文件。 -DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。 -g 生成调试信息。GNU 调试器可利用该信息。 -IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。 -LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。 -lLIBRARY 连接时搜索指定的函数库LIBRARY。 -o FILE 生成指定的输出文件。用在生成可执行文件时。 [参数详解] -x language filename 设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性,决定你的C代码文件的后缀名是.pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。 可以使用的参数吗有下面的这些 `c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `assembler-with-cpp'. 看到英文,应该可以理解的。 例子用法: gcc -x c hello.pig
-x none filename 关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型 例子用法: gcc -x c hello.pig -x none hello2.c
-c 只激活预处理,编译,和汇编,也就是他只把程序做成obj文件 例子用法: gcc -c hello.c 他将生成.o的obj文件 -S 只激活预处理和编译,就是指把文件编译成为汇编代码。 例子用法 gcc -S hello.c 他将生成.s的汇编代码,你可以用文本编辑器察看 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面. 例子用法: gcc -E hello.c > pianoapan.txt gcc -E hello.c | more 慢慢看吧,一个hello word 也要与处理成800行的代码 -o 制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感,改掉它,哈哈 例子用法 gcc -o hello.exe hello.c (哦,windows用习惯了) gcc -o hello.asm -S hello.c -pipe 使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题 gcc -pipe -o hello.exe hello.c -ansi 关闭gnu c中与ansi c不兼容的特性,激活ansi c的专有特性(包括禁止一些asm inline typeof关键字,以及UNIX,vax等预处理宏, -fno-asm 此选项实现ansi选项的功能的一部分,它禁止将asm,inline和typeof用作关键字。 -fno-strict-prototype 只对g++起作用,使用这个选项,g++将对不带参数的函数,都认为是没有显式的对参数的个数和类型说明,而不是没有参数. 而gcc无论是否使用这个参数,都将对没有带参数的函数,认为城没有显式说明的类型
-fthis-is-varialble 就是向传统c++看齐,可以使用this当一般变量使用.
-fcond-mismatch 允许条件表达式的第二和第三参数类型不匹配,表达式的值将为void类型
-funsigned-char -fno-signed-char -fsigned-char -fno-unsigned-char 这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前两个参数)或者 signed char(后两个参数)
-include file 包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设定,功能就相当于在代码中使用#include<filename> 例子用法: gcc hello.c -include /root/pianopan.h
-i macros file 将file文件的宏,扩展到gcc/g++的输入文件,宏定义本身并不出现在输入文件中
-D macro 相当于C语言中的#define macro
-D macro=defn 相当于C语言中的#define macro=defn
-U macro 相当于C语言中的#undef macro -undef 取消对任何非标准宏的定义
-Idir 在你是用#include"file"的时候,gcc/g++会先在当前目录查找你所制定的头文件,如果没有找到,他回到缺省的头文件目录找,如果使用-I制定了目录,他 回先在你所制定的目录查找,然后再按常规的顺序去找. 对于#include<file>,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺省的头文件目录查找
-I- 就是取消前一个参数的功能,所以一般在-Idir之后使用
-idirafter dir 在-I的目录里面查找失败,讲到这个目录里面查找.
-iprefix prefix -iwithprefix dir 一般一起使用,当-I的目录查找失败,会到prefix+dir下查找
-nostdinc 使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置
-nostdin C++ 规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创libg++库使用
-C 在预处理的时候,不删除注释信息,一般和-E使用,有时候分析程序,用这个很方便的
-M 生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用gcc -M hello.c来测试一下,很简单。
-MM 和上面的那个一样,但是它将忽略由#include<file>造成的依赖关系。
-MD 和-M相同,但是输出将导入到.d的文件里面
-MMD 和-MM相同,但是输出将导入到.d的文件里面
-Wa,option 此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选项,然后传递给会汇编程序
-Wl.option 此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然后传递给会连接程序.
-llibrary 制定编译的时候使用的库 例子用法 gcc -lcurses hello.c 使用ncurses库编译程序
-Ldir 制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然 编译器将只在标准库的目录找。这个dir就是目录的名称。
-O0 -O1 -O2 -O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 -g 只是编译器,在编译的时候,产生调试信息。
-gstabs 此选项以stabs格式声称调试信息,但是不包括gdb调试信息.
-gstabs+ 此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息.
-ggdb 此选项将尽可能的生成gdb的可以使用的调试信息. -static 此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么 动态连接库,就可以运行. -share 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库. -traditional 试图让编译器支持传统的C语言特性 |
六.make命令参数
下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异,不过其它产商的make的具体参数还是请参考各自的产品文档。 “-b” “-B” “-C <dir>” “—debug[=<options>]” “-d” “-e” “-f=<file>” “-h” “-i” “-I <dir>” “-j [<jobsnum>]” “-k” “-l <load>” “-n” “-o <file>” “-p” “-q” “-r” “-R” “-s” “-S” “-t” “-v” “-w” “--no-print-directory” “-W <file>” “--warn-undefined-variables” |