makefile-- 自动生成依赖关系 示例

自动生成依赖关系


1、编译行为带来的缺陷

  • 预处理器将头文件中的代码直接插入源文件
  • 编译器只通过预处理后的源文件产生目标文件
  • 因此,规则中以源文件为依赖,命令可能无法执行

示例1观察以下makefile文件是否正确:当修改func.h中宏HELLO的内容后,执行make命令发现,编译器无法更新main.c和func.c,进而无法更新执行的结果:原因在于func.h中更新的内容无法自动更新到func.c和main.c文件中,进而导致编译的hello.out文件结果无任何变化。

func.h

#ifndef FUNC.H
#define FUNC.H

#define HELLO "hello makefile"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);
    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();
    
    return 0;
}

makefile

OBJS := func.o main.o

hello.out := $(OBJS)
    @gcc -o $@ $^
    @echo "Target File => $@"
    
$(OBJS) : %.o : %.c
    @gcc -o $@ -c $^
11--1-1.PNG

解决方法1:修改makefile文件--将头文件作为依赖条件出现于每个目标对应的规则中

OBJS := func.o main.o

hello.out := $(OBJS)
    @gcc -o $@ $^
    @echo "Target File => $@"
    
$(OBJS) : %.o : %.c func.h
    @gcc -o $@ -c $<
11--1-2.PNG

优点:

头文件的更改会更新到相关的源文件中,并更新到最终的目标文件

缺点:

  • 当头文件改动,任何源文件都将被重新编译(编译低效)
  • 当项目中头文件数量巨大时,makefile将很难维护

解决方案2:

  • 通过命令自动生成对头文件的依赖
  • 将生成的依赖自动包含进makefile中
  • 当头文件改动后,自动确认需要重新编译的文件

针对解决方案2需要使用的技术:

(1) linux的sed命令

  • sed是一个流编辑器,用于流文本的修改(增/删/改/查)

  • sed可用于流文本中的字符串替换

  • sed的字符串替换方式为:sed 's:src:des:g'


    11——1.png

    例如执行下列语句:

echo "test=>abc+abc+abc" | sed 's:abc:xyz:g'

test的内容将变为xyz+xyz+xyz

sed的正则表达式支持

  • 在sed中可以用正则表达式匹配替换目标
  • 并且可以使用匹配的目标生成替换结果
    例如
sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g'

示例2--sed用法

11--2.PNG

(2) 编译器依赖生成选项gcc -MM(gcc -M)

  • gcc -M des
    获取目标des的完整依赖关系
  • gcc -MM des
    获取目标des的部分依赖关系
    (-E 仅对依赖关系做初步解析)


    11-2-gcc M gcc MM.PNG

(3)makefile中的include关键字

  • 类似C语言中的include
  • 将其他文件的内容原封不动的搬入当前文件
  • 语法:include filename
    例如:
    include foo.make
    include *.mk
    include $(var)

make对include关键字的处理方式

在当前目录搜索或指定目录搜索目标文件

  • 搜索成功:将文件内容搬入当前makefile中
  • 搜索失败:产生警告
    -- 以文件名作为目标查找并执行相应规则
    -- 当文件名对应的规则不存在时,最终产生错误

示例3-1--include用法--目标文件不存在,目标规则不存在--无操作,直接报错

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-1.PNG

示例3-2--include用法--当目标文件不存在,目标规则存在----以文件名查找规则,并执行

.PHONY : all

include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "this is $@"
11-3-2.PNG

示例3-3--include用法--目标文件存在,目标规则存在

.PHONY : all

include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "creating $@"
    @echo "other : ; @echo "this is other" " > test.txt

--执行make命令,将执行makefile中的第一条规则


![11-3-3.PNG](https://upload-images.jianshu.io/upload_images/10834716-7e0bc3e0a74793c1.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

--执行make all命令将执行all规则


11-3-4.PNG

include 暗黑操作一:

  • 使用减号(-)不但关闭了include发出的警告,同时关闭了错误;当错误发生时make将忽略做这些错误!

示例3-5-1--include不使用(-)会报告所有的错误和警告

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-5-1.PNG

示例3-5-2--include(-)关闭了include发出的警告和错误

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-5-2.PNG

include 暗黑操作二:

  • 如果include触发规则创建了文件,之后还会执行规则中的命令,然后重新执行include后的命令

示例3-6-1-include执行的规则中不存在依赖;则会将规则直接包含进makefile

.PHONY : all

-include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "creating $@ ..."
    @echo "other"
11-3-6--1.PNG

示例3-6-2--include执行时先判断规则是否存在,如果存在会再检查依赖是否是最新的,如果依赖比当前规则要新,会直接执行依赖;否则直接执行规则
示例3-6-2
makefile

.PHONY : all

-include test.txt

all :
    @echo "this is $@"
    
test.txt : b.txt
    @echo "creating $@ ..."
    @echo "all : c.txt" > test.txt

test.txt

all : a.txt

示例3-6-2 当规则文件比依赖文件内容要新--test.txt比b.txt时间戳更新,执行make all结果如下:

11-3-6--2.PNG

示例3-6-2 当依赖文件比规则文件内容要新--b.txt比test.txt时间戳更新,执行make all结果如下:

11-3-6--3.PNG

include的总结:

  • 当目标文件不存在----以文件名查找规则,并执行

  • 当目标文件不存在,且查找到的规则中创建了目标文件----将创建成功的目标文件包含进当前makefile

  • 当目标文件存在,将目标文件包含进当前makefile;并以目标文件名查找是否有相应规则--如果有,比较规则的依赖关系,决定是否执行规则的命令;否则,无操作。

  • 当目标文件存在,且目标名对应的规则被执行

    如果规则中的命令更新了目标文件--make重新包含目标文件,替换之前包含的内容
    如果目标文件未被更新,无操作。

(4) makefile中命令的执行机制

  • 规则中的每个命令默认是在一个新的进程中执行(Shell)
  • 可以通过接续符(;)将多个命令组合成一个命令
  • 组合的命令依次在同一个进程中被执行
  • set -e指定发生错误后立即退出执行
    示例4-1--规则中命令的执行--无接续符--规则中的每个命令默认是在一个新的进程中执行
.PHONY : all

all :
    mkdir test
    cd test
    mkdir subtest
11--4--1.PNG

示例4-2--规则中命令的执行--接续符--组合的命令依次在同一个进程中被执行

.PHONY : all

all :
    set -e;\
    mkdir test;\
    cd test;\
    mkdir subtest
11--4--2.PNG

解决方案2的初步思路

  • 通过gcc -MM和sed得到.dep依赖文件(目标的部分依赖)
    技术点:规则中命令的连续执行
  • 通过include指令包含所有的.dep依赖文件
    技术点:当.dep依赖文件不存在时,使用规则自动生成
  • 当include发现.dep文件不存在时
    通过规则和命令创建deps文件
    将所有.dep文件创建到deps文件夹
    .dep文件中记录目标文件的依赖关系
    代码部分设计如下:
$(DIR_DEPS):
    $(MKDIR) $@

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

观察上述代码,(DIR_DEPS)/%.dep :(DIR_DEPS) %.c 语句存在问题:当第一次执行时会创建deps文件夹和对应的.dep文件,而第二次deps文件夹内容会被新的.dep文件进行更新,导致第一次生成的.dep文件因为依赖更新而会被重复执行。
解决方法如下:使用ifeq动态决定.dep目标的依赖,实现代码如下:

ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

示例5
func.h

#ifndef FUNC_H
#define FUNC_H

#define HELLO "hello world"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();

    return 0;
}

makefile

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/,$(DEPS))

ifeq ("$(MAKECMDGOALS)","all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)","")
-include $(DEPS)
endif

all :
    @echo "$@"
    
$(DIR_DEPS) :
    $(MKDIR) $@
    
ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

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

11-5.PNG

自动生成依赖关系的解决方案:

  • 将依赖文件名作为目标加入自动生成的依赖关系中
  • 通过include加载依赖文件时判断是否执行规则
  • 在规则执行时重新生成依赖关系文件
  • 最后加载新的依赖文件

示例6--最终代码实现:

11-2最终文件依赖关系.png

define.h

#ifndef DEFINE_H
#define DEFINE_H

#define HELLO "hello world"

#endif

func.h

#ifndef FUNC_H
#define FUNC_H

#include "define.h"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();

    return 0;
}

makefile

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps
DIR_EXES := exes
DIR_OBJS := objs

DIRS:= $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)

EXE := app.out
EXE := $(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)
endif

ifeq ("$(MAKECMDGOALS)","")
-include $(DEPS)
endif

$(EXE) : $(OBJS)
    $(CC) -o $@ $^
    @echo "Success! Target => $@"

$(DIR_OBJS)/%.o : %.c
    $(CC) -o $@ -c $(filter %.c,$^) 

$(DIRS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

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

clean :
    $(RM) $(DIRS)
11-6.PNG

小结:

  • makefile中可以将目标的依赖拆分写到不同的地方
  • include关键字能够触发相应规则的执行
  • 如果规则的执行到政治依赖更新,可能导致再次解释执行相应规则
  • 依赖文件也需要依赖于源文件得到正确的编译决策
  • 自动生成文件间的依赖关系能够提高makefile的移植性
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 来自陈浩的一片老文,但绝对营养。 示例工程:3 个头文件*.h,和 8 个 C 文件*.c。 初 编译过程,源文件...
    周筱鲁阅读 4,675评论 0 17
  • makefile关系到整个工程的编译规则,一个工程中的源文件不计其数,按其类型、功能、模块分别放在若干的目录当中,...
    Joe_HUST阅读 1,870评论 0 3
  • 1.Makefile规范 target 这 一 个 或 多 个 的 目 标 文 件 依 赖 于prerequisi...
    G风阅读 1,876评论 0 3
  • 文/文子先生 自由的白衬衫 忘不了中分头的傻样 眯起眼的小黑镜 任由拖鞋嘀嗒响 流氓的口哨 伴着嘻哈小曲儿叽喳唱 ...
    文子先生阅读 348评论 0 3
  • 1980年版的《中国古典文学名著题解》,是我打开书籍的启蒙。伴我案头大概有快三十年了吧。后来读的很多書,都是受它的...
    大树壹棵阅读 136评论 0 0