文章转载:https://blog.csdn.net/sinat_27953939/article/details/102488082
返回知识列表: GUN Make项目管理知识列表
make命令行选项
make --just-print #显示目标命令但并不实际执行
make --print-data-base #或-p,列出make具有的默认规则和变量
make --no-builtin-rules #或-r,不使用内置规则
第二章、规则
makefile显式规则:
target: prereq1 prereq2
commands
工作目标: 必要条件1 必要条件2 ...
命令
自动变量
核心自动变量
$@ #工作目标文件
$% #archive member结构中的文件名元素
$< #第一个必要条件的文件名
$? #时间戳在工作目标之后的所有必要条件
$^ #所有必要条件的文件名
$+ #同$^,允许重复的文件名
$* #工作目标的主文件名
VPATN变量 vpath命令
VPATH = src include #告诉make在src和include中搜索代码文件
vpath pattern directory-list
vpath %.l %.c src #在src目录中寻找.c、.h文件
vpath %.h include #在include中寻找.h文件
模式规则
%.o: %.c
command
静态模式规则
$(OBJECTS): %.o: %.c #使该规则只作用于变量OBJECTS中的文件
command
后缀规则(过时)
.c.o:
command
#相当于
%.o: %.c
command
.p:
command
#相当于
%: %.p
command
# .SUFFIXES设定已知的扩展名
.SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l
# 删除所有已知扩展名
.SUFFIXES:
隐含规则
当make检查一个工作目标时,如果找不到可以更新它的显式规则,就会使用隐含规则。要使用隐含规则:当你将工作目标加入makefile时,只要不指定脚本就行了。
查看内置规则库:
make --print-data-base #或-p,列出make具有的默认规则和变量
假想工作目标
任何不代表文件的工作目标就叫假想工作目标。要声明一个假想工作目标,只需要将该工作目标指定为.PHONY(一个特殊工作目标)的一个必要条件即可。如 声明假想工作目标clean:
.PHONY: clean #声明它的必要条件不代表一个实际的文件,而且应被视为尚未更新
clean:
rm -f *.o
标准的假想工作目标
all #执行所有工作
install #从已编译的二进制文件进行应用程序的安装
clean #删除产生自源代码的二进制文件
distclean #删除编译过程中产生的任何文件
TAGS #建立可供编辑器使用的标记表
info #从Texinfo源代码来创建GUN info文件
check #执行与应用程序相关的任何测试
特殊工作目标
特殊工作目标是一个内置的假想工作目标,用来变更make的默认行为。除了.PHONY以外,还有以下特殊工作目标:
.INTERMEDIATE #其必要条件会被视为中间文件,make结束时会被自动删除
.SECONDARY #其必要条件会被视为中间文件,但不会被自动删除,但加入档案库(archive)后就会被删除
.PRECIOUS #如果make在运行期被中断,它会删除除它正在更新的工作目标文件,确保不会留下未完成编译的文件。指定.PRECIOUS以阻止其删除行为
.DELETE_ON_ERROR #与.PRECIOUS作用相反,发生错误时删除工作目标文件
自动产生依存关系
一种makefile自动解决头文件依赖问题的解决方案,是通过如下的一个关键脚本:
include $(subst .c,.d,$(SOURCES)) #包含所有.c文件同名的.d文件
%.d: %.c
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
解释一下上面的命令:
gcc -M source.c
-M选项会搜索代码source.c所引用的所有头文件,并输出成makefile形式的“目标:依赖”形式。
… > @ . @.@.$KaTeX parse error: Can't use function '$' in math mode at position 34: …输出重定向到文件名<code>$̲@.KaTeX parse error: Can't use function '$' in math mode at position 17: …/code>。其中<code>$̲@</code>就是工作目标的…
在shell中被扩展为当前所运行shell的进程ID,.$$$$
是为了确保产生唯一的文件名。综上,第一行命令用每一个.c文件生成了一个.d.pid文件,.d文件中描述了同名.c文件对应目标文件的头文件依赖。
读懂第二行命令需要先了解shell中的sed
命令。sed的s命令是执行字符串替换,将模式\($*\).o[ :]*
替换为\1.o $@ :
。主要作用是将.d文件名追加到.d文件中的工作目标中。
看文字有点绕,举个例子一个hello world程序main.c,gcc -M main.c > main.d生成的main.d的内容如下:
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/libio.h \
/usr/include/x86_64-linux-gnu/bits/_G_config.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h
执行上述sed命令之后,通过创建并删除一个main.d.2196821968的临时文件,生成main.d内容为:
main.o main.d : main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/libio.h \
/usr/include/x86_64-linux-gnu/bits/_G_config.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h
第三行命令很明显,清理临时文件.d.pid。
管理程序库
ar命令选项:
r #replace,替换或创建库文件
v #verbosely,显示详细信息
t #列出程序库中的目标文件名
gcc链接程序库文件:
gcc a.o libc.a /lib/libfl.a -o main #需要指定路径,不建议使用
gcc a.o -lc -lfl -o main #方便,便于移植
gcc a.o -L. -lc -lfl -o main #指定库搜索路径,-L必须放到系统程序库之前,且作用于所有-l选项
libcounter.a: counter.o lexer.o
$(AR) $(ARFLAGS) $@ $^ #更新.a时,其中的成员全部更新
libcounter.a: counter.o lexer.o
$(AR) $(ARFLAGS) $@ $? #只更新新的成员
libcounter.a: libcounter.a(lexer.o) libcounter.a(counter.o) #libcounter.a(lexer.o)
libgraphics.a(bitblt.o): bitblt.o
$(AR) $(ARGLAGS) $@ $< #libcounter.a(lexer.o)# 引用库文件中的成员,在隐含规则中,可以省略
#可以使用路径名
xpong: $(OBJECTS) /lib/X11/libX11.a /lib/X11/libXaw.a
$(LINK) $^ -o $@
#也可以使用-l语法,会优先搜索共享程序库
xpong: $(OBJECTS) -lX11 -lXaw
$(LINK) $^ -o $@
如果makefile已经将程序库文件指定为工作目标,它就不能在必要条件里对该文件使用-l选项。如果要在makefile里进行程序库的编译工作,必须使用文件名的语法。
程序库在命令行上的顺序很重要,程序库A、B循环引用的一个解决方法是:-lA -lB -lA
。但是makefile的自动变量$^
会丢弃重复的部分,此时就应该使用$+
。
通常当一个工作目标多次出现时,所有必要条件都会被衔接成一个列表,只会执行一个命令脚本进行更新,前面的规则会被后面的覆盖掉。
对于双冒号规则,相同的工作目标会被视为一个独立的实体,可以以不同的命令来更新同一个工作目标,实际执行的命令取决于哪个依赖文件被更改。
file-list:: generate-list-script
chmod +x $<
generate-list-script $(files) > file-list
file-list:: $(files)
generate-list-script $(files) > file-list