makefile自动生成依赖

本文主要参考 Makefile自动生成头文件依赖 和GNU make 中文手册(这个文档网上挺多,可自行百度)。

网上关于makefile的资料很多,甚至我买的linux编程书上都对它有些简单的介绍,不过很多资料都不是很详细。这里我推荐“GNU makefile中文手册“,很适合作为小白学习makefile的入门书籍,不过可能有些过于详细,可以跳着看。

一般而言,实用的makefile文件应当能够实现 自动生产头文件依赖 功能。而自动产生头文件功能的实现则依赖于编译器提供的自动产生依赖关系的功能。对于GCC而言,编译时实用 ”-M“ 选项,其将输出源文件中包含的包括标准库头文件在内的依赖关系,而 ”-MM“ 选项的输出则不会包含标准库头文件。

如对于程序

#include<stdio.h>
#include "./a.h"

extern void function_two();
extern void function_three();

int main()
{
	function_two();
	function_three();

	return 0;
}
对于gcc,如果实用 -M 选项,输出为

main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.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/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
 /usr/include/_G_config.h /usr/include/wchar.h \
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h a.h

如果使用 -MM 选项,输出为

main.o: main.c a.h
具体区别非常清楚了。

可以看出使用 -MM 选项的GCC的输出和我们在makefile中写的依赖关系基本相同。那么makefile是如何使用编译器的这个功能呢?在给出答案之前,需要首先给出makefile的include功能。include 指示符告诉 make 暂停读取当前的 Makefile,而转去读取 include 指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。include功能具体可参考 GNU makefile中文手册 3.3节。我这里就不重复造轮子了。


准备工作完成,下面开始分析一个简单的自动依赖 makefile 例子。具体代码我以这篇文章为例。虽然作者讲的很清楚了,但是作为一个小白依然不得要领。我的计划是对作者的代码进行逐字逐句的详尽分析。作者给的模板是一个很通用的模板,自动依赖部分和 GNU make 中文手册 在 4.14 节给出的例子基本上一样。通过对这个例子进行分析,就能初步入门 makefile 编程了。下面我贴出作者的例子。


前六行为makefile变量(makefile变量部分具体可以参考 GNU make 中文手册 第6章)。简单来说,你可以把这些变量看做是C语言中的宏变量。 

然后

.PHONY:all 
这一行是一个伪目标,和之后的 
.PHONY:all 
是一样的。伪目标主要是为了通过 make 命令行明确指定它来执一些特定的命令,并保证命令总是执行,此外还可以提高编译效率,具体可参考 GNU make 中文手册第4.6节。

再之后的这一行

$(CC) -o $@ $^ $(LDFLAGS)
$@ 和 $^ 是makefile自动化变量,具体可见 GNU make 中文参考手册 第10.5.3节。简单来说,$@ 表示规则的目标文件名,$^ 表示规则的所有依赖文件列表,使用空格分隔。

所以这一行翻译出来其实就是简单的 gcc -o 目标  依赖文件;

再之后的这一行:

%.o:%.c
    $(CC) -o $@ -c $< $(CFLAGS) $(INCLUDEFLAGS)
$< 表示规则的第一个依赖文件名。这一句也是一个典型的语句。


关键是下面这段:


首先需要注意的是,m akefile中的shell,每一行都是一个新的进程,因此不同行之间变量值是不能传递的。 所以,如果需要,一定要写很长的一行。当然如果一行太长会使得阅读很麻烦。这个时候需要可以使用行连接符 \ 来进行两行之间的连接,这点和C语言是一样的。

第一个命令@set -e。@关键字告诉make不输出该行命令;set -e的作用是,当后面的命令的返回值非0时,立即退出。

第二个命令 rm -f $@。这个命令主要是防止出现目标文件已经存在的情况。先把目前目标文件删除一遍。

第三个命令


作用是根据源文件生成依赖关系,并保存到临时文件中。 > 即为简单重定向命令 $$$$ 为字符串 "$$" ,由于makefile中所有的$字符都是特殊字符( 即使在单引号之中! ),要得到普通字符$,需要用 $$ 来转义; 而 $$ 是shell的特殊变量,它的值为当前进程号;使用进程号为后缀的名称创建临时文件,是shell编程常用做法,这样可保证文件唯一性。

第四个命令


此部分涉及到linux下的另一个文本编辑器 sed。可以参考这篇博客 
 sed简介简单了解。首先解释一下这个命令的作用效果,其将这样一个依赖关系 
 
main.o : main.c defs.h
转换成
main.o main.d : main.c defs.h
那么它是如何工作的呢,首先 sed 中两个命令:命令 s 是替换命令。替换和取代文件中的文本可以通过 sed 中的 s 来实现, s 后包含在斜杠中的文本是正则表达式,后面跟着的是需要替换的文本。可以通过 g 标志对行进行全局替换。那么sed ‘’中的命令意思就很清楚了,就是做替换工作的。然后就是那一堆鬼画符似的正则表达式了。
首先 $*, GNU make 中文参考手册 第10.5.3节有着较为详尽的描述。简单来说表示的是目标除去了后缀后的文件名,也就是 %.d 当中的%部分。这篇博客对()和[]做了简单介绍,不太了解的可以看看。[] 后的*就简单了,它代表[]中的内容出现0次或者多次。
替换内容中, \1 表示前面()中的内容。其余的我就不用多说了。
重定向内容可以这样理解,从临时文件中读取依赖关系,sed进行转换后再将输出重定向至 .d 文件中。

完成后调用第五个命令删除临时文件


上述完成了描述依赖文件的 .d 文件的生成,但是这只是开始。为了利用它,我们使用 include 命令来将其包含进来。
-include $(OBJS:.o=.d)
include 后是变量引用置换功能。此功能具体可见 GNU make 中文参考手册 第6.3节。简单来说,对于一个已经定义的变量,可以使用“替换引用”将其值中的后缀字符(串)使用
指定的字符(字符串)替换。格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值多个字之间使用空格分开)。而对于变量其它部分的“A”字符不进行替换。所以此变量替换命令在此处的意义为是根据变量“OBJS”指定的.c文件自动产生对应的.d文件。然后 include 将.d文件包含进来。


最后的clean命令就不用多说了。


整理自多处,加上自己的理解,如有错误,还请留言指正。




猜你喜欢

转载自blog.csdn.net/m0_37511026/article/details/78534021