千家信息网

makefile(05)_自动生成依赖关系

发表于:2024-10-18 作者:千家信息网编辑
千家信息网最后更新 2024年10月18日,11.自动生成依赖关系_上11.0. 实验原料本节实验所需的源文件和头文件:原文件:func.c#include "stdio.h"#include "func.h"void foo(){ pr
千家信息网最后更新 2024年10月18日makefile(05)_自动生成依赖关系

11.自动生成依赖关系_上

11.0. 实验原料

本节实验所需的源文件和头文件:
原文件:func.c

#include "stdio.h"#include "func.h"void foo(){    printf("void foo() : %s\n", HELLO);}

原文件:main.c

#include #include "func.h"int main(){    foo();    return 0;}  

头文件func.c

#ifndef FUNC_H#define FUNC_H#define HELLO "Hello D.T."void foo();#endif

11.1.问题和方案

问题:

  1. 目标文件.o是否只依赖于源文件.c?编译器如何编译源文件和头文件?
    编译器处理头文件中的代码直接插入源文件中,编译器只通过预处理后的原文件产生目标文件,因此,规则中以源文件为依赖,命令可能无法执行。
  2. 下面Makefile有没有问题?
OBJS := func.o main.ohello.out : $(OBJS)    @gcc -o $@ $^    @echo "Target File ==> $@"$(OBJS) : %.o : %.c    @gcc -o $@ -c $<

此时看似可以编译成功,但存在潜在隐患。
存在问题:目标文件只依赖于.c文件,而没有关注.h文件,这样当.h文件的内容更新时,不会重新编译.c文件。
解决方案:
我们将.h文件也作为依赖写到Makefile中。

OBJS := func.o main.ohello.out : $(OBJS)    @gcc -o $@ $^    @echo "Target File ==> $@"$(OBJS) : %.o : %.c func.h    @gcc -o $@ -c $<

上述解决方案问题:
头文件作为依赖出现于每一个目标文件对应的规则中,当头文件改动,任何源文件都会被重新编译(编译低效),而且当项目中头文件数量巨大时,Makefile件很难维护。

11.2.实现自动依赖

通过命令自动生成对头文件的依赖,将生成的依赖自动包含进入Makefile中,当头文件改动后,自动确认需要重新编译的文件。
预备工作:
1.Linux命令sed,sed时一个流编辑器,用于流文本的修改(增、删、查、改),文件替换,格式为:sed 's/abc/xyz/g';
Sed可以支持正则表达,sed 's/(.).o[ :]/objs/\1.o : /g' 正则匹配目标((.).o[ :]),替换值(objs/\1.o : )
2.编译器选项,生成依赖关系
gcc -MM 获取目标的完整依赖关系
gcc -M 获取目标的部分依赖关系
3.Makefile中目标拆分技巧,将目标的完整依赖拆分为多个部分依赖

.PHONY : test a b ctest : a btest : b ctest :         @echo "$^"

输出结果:a b c

思考:如果使用上面的预备工作实现头文件的自动依赖?

12.自动生成依赖关系_中

12.1.Include

Make中的include关键字,类似于C语言中的关键字,在处理是将所包含的文件的内容原封不动的搬到当前文件。
语法:include filename
Eg: include foo.make *.mk $(var)
Make对include关键字的处理方式,在当前目录搜索或者指定目录搜索目标文件,搜索成功:将文件内容搬入当前Makefile中;搜索失败,以文件名作为目标查找并执行对应规则。当文件名对应的规则不存在时,产生错误。
下面的代码怎么执行,为什么?

.PHONY : allinclude test.txtall :     @echo "this is all"test.txt :    @echo "test.txt"    @touch test.txt

初次执行文件,自然搜索不到test.txt文件,然后会test.txt文件名作为目标查找并执行对应规则,输出结果:

注意:在include关键字前面加上-,可以消除警告。

12.2.命令执行机制

1.Makefile中的命令执行时,每一条命令默认都是一个新的进程;(这样当我们希望使用上一个命令的执行结果,继续执行命令时往往得不到结果,譬如下面的代码);

.PHONY : allall :    set -e;    mkdir test;    cd test;    mkdir subtest

输出结果:

很显然,没有达到我们与其的目的(在test文件夹中创建subtest文件夹)
2.可以通过接续符(;)将多个命令组合成为一个命令,组合的命令一次在同一个进程中被执行;
3.可以使用set -e指定发生错误时立即退出。

.PHONY : allall :        set -e; \        mkdir test; \        cd test; \        mkdir subtest

输出结果:

12.3.实现自动生成依赖

1.通过gcc -MM 和sed命令得到.dep文件(目标的部分依赖),并使用接续符使得命令可以连续执行;
2.通过include指令包含所有的.dep依赖文件(当.dep文件不存在时,查找与.dep文件同名的规则并执行)

.PHONY : all cleanMKDIR := mkdirRM := rm -frCC := gccSRCS := $(wildcard *.c)DEPS := $(SRCS:.c=.dep)-include $(DEPS)all :        @echo "all"%.dep : %.c        @echo "Creating $@ ..."        @set -e; \        $(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@clean :        $(RM) $(DEPS)

输出结果:

我们此时已经成功的生成了依赖文件main.dep和func.dep并在文件中记录了目标和依赖的关系。
思考:如果组织依赖文件相关的规则与源码编译相关的规则,进而形成功能完整的Makefile?

13.自动生成依赖关系_下

13.1.遗留问题

如何在makefile中组织.dep文件到指定目录?
解决思路:
当include 发现.dep文件不存在时,通过规则和命令创建deps文件夹,将所有的.dep文件创建到deps文件夹,并在.dep文件中记录目标文件的依赖关系。

$(DIR_DEPS) :    $(MKDIR) $@$(DIR_DEPS)/%.dep : $(DIR_DEPS)  %.c    @echo "Creating $@ ..."    @set -e; \    $(CC) -MM -E  $^  | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@

这样做确实解决了上述问题,生成了deps文件夹:

但同时我们看到两个问题:
1.因为依赖中包含deps文件夹,以deps文件夹作为 gcc -MM 的输入时没有意义的,会报告warning,所以使用下面的方法过滤掉deps文件夹

$(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@

2.func.dep被重复创建了多次?
问题本质分析:
deps文件夹的时间属性会因为依赖文件创建而发生改变,make发现deps文件夹比对于的目标更新时,会触发相应规则的重新解释和命令的执行。
解决方案:使用ifeq动态决定.dep目标的依赖;

ifeq ("$(wildcard $(DIR_DEPS))", "")$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.celse$(DIR_DEPS)/%.dep : %.cendif

13.2.Include黑暗操作

1.使用- 不但关闭了include发出的警告,同时关闭了错误,当发生错误时,make将忽略这些错误。
2.如果include 触发规则创建了文件则会发生下面的事情:

// 使用include 时的暗黑操作if(如果目标文件不存在){    //以文件名为规则查找并执行,    if(查找到的规则中创建了文件)    {        //将创建成功的目标文件包含进当前makefile    }}else // 如果目标文件存在{    // 将目标文件包含进当前makefile    if(以目标文件名查找是否有相应的规则)    {        if(比较规则的依赖关系,决定是否执行规则的命令)        {            // (依赖文件更新,则执行)        }        else        {            // 无操作        }    }    else    {        // 无操作    }}

实验1:include包含的目标文件不存在,并且以文件名为目标的规则存在,并在规则中创建了文件

.PHONY : all-include test.txtall :     @echo "this is all"test.txt :    @echo "creating $@ ..."    @echo "other : ; @echo "this is other" " > test.txt

我们期望了输出结果因该是:this is all,因为all是第一个(默认)目标。
运行结果:

原因在于当出现上面的情况时:以文件名为规则查找并执行,同时如果查找到的规则中创建了文件,将创建成功的目标文件包含进当前makefile,此时在makefile中第一个目标变成了other
实验2:

.PHONY : all-include test.txtall :     @echo "this is all"test.txt : b.txt    @echo "creating $@ ..."

当不存在b.txt时的运行结果:

当存在b.txt,但b.txt文件比test.txt文件旧时的运行结果:

当存在b.txt,但b.txt文件比test.txt文件新时的运行结果:

结论:如果目标文件存在:将目标包含进当前makefile,以目标文件名查找是否有相应的规则
如果有则比较规则的依赖关系,决定是否执行规则的命令(依赖文件更新,则执行),如果规则中的命令更新了目标文件,替换之前包含了的内容。未更新,则无操作。
以目标文件名查找是否有相应的规则,不能找到,则无操作
实验3:

.PHONY : all-include test.txtall :     @echo "$@ : $^"test.txt : b.txt    @echo "creating $@ ..."    @echo "all : c.txt" > test.txt

a.txt内容:

all : a.txt

当该文件中所需的所有文件都存在,并且test.txt的内容为最新时,make all输出结果:

当b.txt文件最新时,make all输出结果:

14.自动生成依赖关系_续

经过前面的技巧学习,我们现可以去完成这个自动生成依赖关系的想法了
注意:
思考:我们在13节中最终创建出来的makefile是否存在问题?
当.dep文件生成后,如果动态的改变文件间的依赖关系,那么make可能无法检测到这个改变,进而做出错误的判断。
实例:

输出结果:

解决方案:
将依赖文件的文件名作为目标加入自动生成的依赖关系中,通过include加载依赖文件时判断是否执行规则,在规则执行时重新生成依赖关系文件,最后加载新的依赖文件。
举个栗子:当我们前面编译过之后(生成了依赖文件),又添加了新的头文件,这时根据include的暗黑操作,要去检查与include所包含的依赖文件同名的规则是否存在,如果存在,则检查这个目标所对应的依赖是否被更新,如果更新,则执行相应规则。
最终方案:

.PHONY : all clean rebuildMKDIR := mkdirRM := rm -frCC := gccDIR_DEPS := depsDIR_EXES := exesDIR_OBJS := objsDIRS := $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)EXE := app.outEXE := $(addprefix $(DIR_EXES)/, $(EXE))SRCS := $(wildcard *.c)OBJS := $(SRCS:.c=.o)OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))DEPS := $(SRCS:.c=.dep)DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))all : $(DIR_OBJS) $(DIR_EXES) $(EXE)ifeq ("$(MAKECMDGOALS)", "all")include $(DEPS)endififeq ("$(MAKECMDGOALS)", "")include $(DEPS)endif$(EXE) : $(OBJS)    $(CC) -o $@ $^    @echo "Success! Target => $@"$(DIR_OBJS)/%.o : %.c    $(CC) -o $@ -c $(filter %.c, $^)#   $(CC) -o $@ -c $(filter %.c, $^)$(DIRS) :    $(MKDIR) $@ifeq ("$(wildcard $(DIR_DEPS))", "")$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.celse$(DIR_DEPS)/%.dep : %.cendif    @echo "Creating $@ ..."    @set -e; \    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o $@ : ,g' > $@clean :    $(RM) $(DIRS)rebuild :    @$(MAKE) clean    @$(MAKE) all

总结:
Makefile中可以将目标的依赖拆分写到不同的地方;
include关键字能够触发相应的规则的执行;
如果规则的执行导致依赖更新,可能导致再次解释执行相应的规则;
依赖文件可需要依赖源文件得到正确的编译决策
自动生成文件的依赖关系能够提高Makefile的移植性。

文件 目标 规则 命令 生成 结果 编译 文件名 文件夹 问题 自动生成 更新 输出 源文件 内容 方案 错误 面的 成功 关键 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 饥荒加不进好友的服务器 研究生数据库方向有前途吗 服务器的防护级别 网络安全等成热门专业 厦门市互联网科技公司 现在最安全的云服务器 计算机网络技术读出来作什么 安徽视觉传感器软件开发 北京软件开发公司哪家专业 云服务器可能发生的故障 虚拟机sql数据库 工业图像用什么软件开发 金融互联网科技龙头企业 err数据库设计软件 扫描二维码的数据库 软件开发报价模板前言 ntp时间服务器ip地址 国家税务总局纳税信用数据库业务 2022年学生网络安全教育内容 宁波慈溪财务软件开发 服务器系统管理员 职责 数据库安全系统特性有哪些 查询软件开发部的员工姓名年龄 网络安全的三个主要特征 网络安全vim的作用 山西推广软件开发中心 金山区专业软件开发服务不二之选 5g网络技术高级资格证书 学软件开发需要培训吗 黄浦区基础网络技术服务
0