Makefile依赖问题-4自动依赖

FBI WARNING:内容较长,建议拉屎的时候看 or 事后点根烟看

背景

根据第3集:Makefile依赖问题-3,我们用简单、粗暴、无脑的方式解决头文件依赖的问题,但是这种解决方式有几个缺点;
1、比较容易想到的是,正儿八经的项目头文件都是比较多的,很容易出错。
2、当头文件改动,任何源文件都将被重新编译(编译低效)

对照缺点,好的解决方案应该是能自动就不要手动:
1、通过命令自动生成源文件.c对头文件的依赖。(什么命令?)
2、命令生成的依赖结果能够放到Makefile中。(怎么放?)
3、当头文件改动后,自动确认需要重新编译的文件。(怎么自动确认?)

命令生成文件依赖

这里有三个问题,我们一一解决:
1、什么命令可以说让我们给定源文件比如main.c,然后就能输出它所需要的头文件?
命令:

gcc -M main.c
gcc -MM main.c
gcc -MM -E main.c

说一下这几个的区别,主要是根据命令结果去解释,因为看一下结果就知道了:
首先还是抛出我们一直沿用的代码例子的组织结构:
图一 代码文件结构
gcc -M main.c:
在这里我改动了main.c:#include “func.h” —> #include"…/include/func.h",要不然会报错:
在这里插入图片描述

# gcc -M main.c
root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/deps/example3# gcc -M src/main.c
main.o: src/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/features-time64.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/timesize.h \
 /usr/include/x86_64-linux-gnu/sys/cdefs.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/11/include/stddef.h \
 /usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.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/time64.h \
 /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
 /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
 /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.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/types/struct_FILE.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/floatn.h \
 /usr/include/x86_64-linux-gnu/bits/floatn-common.h src/../include/func.h
 

gcc -MM main.c:

#gcc -MM main.c
root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/deps/example3/src# gcc -MM main.c
main.o: main.c ../include/func.h

加上-E选项
在这里我把#include “…/include/func.h” 改回 #include “func.h”
在这里插入图片描述
根据上面的结果,想必都能总结出来这三个命令的区别:

gcc -M 和 gcc -MM 是 GCC 编译器提供的两个选项,用于生成依赖关系文件,它们的主要区别在于处理系统头文件的方式。

gcc -M:
生成预处理的源文件的依赖关系,包括系统头文件。
生成的依赖关系文件中会列出所有直接和间接依赖的头文件,无论它们是项目中的头文件还是系统库的头文件。
gcc -MM:
生成预处理的源文件的依赖关系,不包括系统头文件。
生成的依赖关系文件中只会列出项目中的头文件,不会列出系统库的头文件。这通常会使依赖关系文件更加简洁。
-E选项:
-E 仅对依赖关系做初步解析,就只是预处理的工作。

总结:要生成依赖关系使用的命令是

gcc -MM -E [-I$HEADER_PATH] source_file.c

其实-E可以省略,我嫌麻烦,运行上边这几条命令就拉倒了。好了,第一个问题结束回答完毕!!!

include

2、源文件所需的头文件我们搞出来了,怎么放到Makefile中?
这块需要搞清楚Makefile中的一条指令:include
先看下官方手册:include directive对于include指令的描述:

The include directive tells make to suspend reading the current makefile and read one or more other makefiles before continuing. The directive is a line in the makefile that looks like this:

include filenames…

filenames can contain shell file name patterns. If filenames is empty, nothing is included and no error is printed.When make processes an include directive, it suspends reading of the containing makefile and reads from each listed file in turn. When that is finished, make resumes reading the makefile in which the directive appears.

include指令的用途:

One occasion for using include directives is when several programs, handled by individual makefiles in various directories, need to use a common set of variable definitions (see Setting Variables) or pattern rules (see Defining and Redefining Pattern Rules).

Another such occasion is when you want to generate prerequisites from source files automatically; the prerequisites can be put in a file that is included by the main makefile. This practice is generally cleaner than that of somehow appending the prerequisites to the end of the main makefile as has been traditionally done with other versions of make.

下面开始举例子说明include到底是怎么个事儿:
下面的内容跟第3集有重复:

example1

在这里插入图片描述
Makefile:

all:test

test:a.h
test:b.h
test:c.h
    @echo "prerequisites: $^";
    touch test;

结果:
在这里插入图片描述

example2

在这里插入图片描述
Makefile:

all:test

test:a.h
    @echo "this is a.h, prequisites: $^";
test:b.h
    @echo "this is b.h, prequisites: $^";
test:c.h
    @echo "this is c.h, prequisites: $^";
    touch test;

结果:
在这里插入图片描述
总结:同一Makefile文件中,如果有两个或两个以上规则具有相同的目标,则在这些规则中,任一规则中的依赖文件的更新都会且仅会导致最后一个规则中的命令被执行,前面规则的命令被忽略。
换句话说:,依赖中的文件累计,但是规则中的命令后边的覆盖前边的,这些规律都可从上图中的演示结果推出,在相应位置我做了标注。

example3

我们再对Makefile文件做一些改动,并且新建file.dep文件。把Makefile文件中以test为目标的第一条规则抠出来放在文件file.dep中,并在原位置使用include命令把file.dep文件包含进来,修改后的Makefile文件和file.dep文件的内容分别如下:
在这里插入图片描述
Makefile:

all:test

include file.dep
test:b.h
    @echo "this is b.h";
test:c.h
    @echo "this is c.h";
    touch test;

file.dep:

test: a.h
    @echo "this is a.h"

结果:
在这里插入图片描述
example3的结果跟example2的执行逻辑是完全一致的,这里可以说明:When make processes an include directive, it suspends reading of the containing makefile and reads from each listed file in turn. 到这里可以说Makefile中的include和C语言中的include都是被包换文件的内容替换,但是后边我们可以看到Makefile中的include指令没有这么简单

example4

在这里插入图片描述
Makefile:

root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/test_include/example4# cat Makefile 
all:test
include file.dep
file.dep:b.h
        echo "test:d.h" > file.dep;
test:c.h
        touch test;

结果:
在这里插入图片描述
执行make -d,追踪执行流程

GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Reading makefiles...
Reading makefile 'Makefile'...
Reading makefile 'file.dep' (search path) (no ~ expansion)...
Updating makefiles....
 Considering target file 'file.dep'.
   Considering target file 'b.h'.
    Looking for an implicit rule for 'b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 's.b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'SCCS/s.b.h'.
    No implicit rule found for 'b.h'.
    Finished prerequisites of target file 'b.h'.
   No need to remake target 'b.h'.
  Finished prerequisites of target file 'file.dep'.
  Prerequisite 'b.h' is older than target 'file.dep'.
 No need to remake target 'file.dep'.
 .....
 Updating goal targets....
Considering target file 'all'.
 File 'all' does not exist.

首先include命令先把file.dep 文件包含进Makefile文件,包含进Makefile文件的内容为file.dep:a.h。检查是否有能使得file.dep文件发生更新的规则,此时,规则

file.dep:a.h

和规则

file.dep:b.h
@echo “test:d.h” > file.dep;

都不能使file.dep文件发生更新,include命令在包含一次file.dep文件后执行结束。Makefile接着去执行all目标的规则。

更新b.h

GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Reading makefiles...
Reading makefile 'Makefile'...
Reading makefile 'file.dep' (search path) (no ~ expansion)...
Updating makefiles....
 Considering target file 'file.dep'.
   Considering target file 'b.h'.
    Looking for an implicit rule for 'b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 's.b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'SCCS/s.b.h'.
    No implicit rule found for 'b.h'.
    Finished prerequisites of target file 'b.h'.
   No need to remake target 'b.h'.
  Finished prerequisites of target file 'file.dep'.
  Prerequisite 'b.h' is newer than target 'file.dep'.
 Must remake target 'file.dep'.
echo "test:d.h" > file.dep;
...
Re-executing[1]: make -d
GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Reading makefiles...
Reading makefile 'Makefile'...
Reading makefile 'file.dep' (search path) (no ~ expansion)...
Updating makefiles....
 Considering target file 'file.dep'.
   Considering target file 'b.h'.
    Looking for an implicit rule for 'b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 's.b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'SCCS/s.b.h'.
    No implicit rule found for 'b.h'.
    Finished prerequisites of target file 'b.h'.
   No need to remake target 'b.h'.
  Finished prerequisites of target file 'file.dep'.
  Prerequisite 'b.h' is older than target 'file.dep'.
 No need to remake target 'file.dep'.
 ...
 Updating goal targets....
Considering target file 'all'.
 File 'all' does not exist.
 ..

首先include命令先把file.dep文件包含进Makefile文件,包含进Makefile文件的内容为file.dep:a.h。然后,检查是否有能使得file.dep文件发生更新的规则,此时,规则

file.dep:a.h

不能使file.dep文件发生更新,但是规则

file.dep:b.h
@echo “test:d.h” > file.dep;

却可以使file.dep文件发生更新,所以include命令会将更新后的file.dep文件再次包含进Makefile文件,而更新后的file.dep文件的内容也变为test:d.h。然后继续检查是否有规则能使file.dep文件发生更新,此时以file.dep为目标的规则只有

file.dep:b.h
@echo “test:d.h” > file.dep;

并且此时的file.dep比b.h新,所以该规则中的命令不会被执行且该规则也不能使file.dep文件发生更新,include命令到此执行结束,最终包含在Makefile文件的内容为test:d.h。

接下来就是去执行all目标的规则了。

到这里把example4图中的几个箭头解释清楚了。有个问题,如果我更新的不是b.h,而是a.h,file.dep中的文件内容是啥?
结合example1分析
在这里插入图片描述

GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Reading makefiles...
Reading makefile 'Makefile'...
Reading makefile 'file.dep' (search path) (no ~ expansion)...
Updating makefiles....
 Considering target file 'file.dep'.
   Considering target file 'b.h'.
    Looking for an implicit rule for 'b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h,v'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'RCS/b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 's.b.h'.
    Trying pattern rule with stem 'b.h'.
    Trying implicit prerequisite 'SCCS/s.b.h'.
    No implicit rule found for 'b.h'.
    Finished prerequisites of target file 'b.h'.
   No need to remake target 'b.h'.
   Considering target file 'a.h'.
    Looking for an implicit rule for 'a.h'.
    Trying pattern rule with stem 'a.h'.
    Trying implicit prerequisite 'a.h,v'.
    Trying pattern rule with stem 'a.h'.
    Trying implicit prerequisite 'RCS/a.h,v'.
    Trying pattern rule with stem 'a.h'.
    Trying implicit prerequisite 'RCS/a.h'.
    Trying pattern rule with stem 'a.h'.
    Trying implicit prerequisite 's.a.h'.
    Trying pattern rule with stem 'a.h'.
    Trying implicit prerequisite 'SCCS/s.a.h'.
    No implicit rule found for 'a.h'.
    Finished prerequisites of target file 'a.h'.
   No need to remake target 'a.h'.
  Finished prerequisites of target file 'file.dep'.
  Prerequisite 'b.h' is older than target 'file.dep'.
  Prerequisite 'a.h' is newer than target 'file.dep'.
 Must remake target 'file.dep'.
echo "test:d.h" > file.dep;

example5

这个例子没啥特殊的,如果从example1~example4门清儿,这个结果就很好分析。
在这里插入图片描述
依据命令行输出结果和file.dep文件的内容这两个方面,我们再来分析一下以上结果的产生过程:

首先,include命令将file.dep文件包含进Makefile文件,然后在集合U中查看是否有规则能使file.dep文件发生更新,而集合U2(U2包含于U)中正好有一条能使file.dep发生更新的规则,

file.dep:a.h

@echo "file.dep:b.h" > file.dep;

@echo "    @echo \"hello world.\"" >> file.dep;

touch b.h;

接着这条规则中的三条命令被执行。这样,file.dep文件的内容被重写,b.h文件被更新,输出“touch b.h”。这样完成对file.dep文件的更新后,接着把这个刚更新完的file.dep文件再次包含进Makefile文件,然后跳转到断点A继续往下执行。此时,Makefile文件中包含了file.dep文件的内容:

file.dep:b.h

@echo "hello world."

注意,这也是一个以file.dep为目标的规则,此时,file.dep没有b.h新,根据依赖关系此规则里的命令会被执行,于是有了输出”hello world.”。但该规则并没有使file.dep文件再次发生更新,所以Makefile文件中最终包含的file.dep文件即此时的file.dep文件,内容为:

file.dep:b.h

@echo "hello world."

接下来执行all目标的规则,输出“this is c.h”。

到这里关于include的东西说的差不多了,它不仅仅是简单被包含文件的内容替换,而且还会因为以这个文件的目标的规则进行重复include,有重复就会有问题。

除了Makefile中的include指令外,要写自动依赖,还需要掌握shell中的三剑客之一 sed 命令。

sed

sed提供的功能是替换。

  • sed是一个流编辑器,用于流文本的修改(增/删/改/查)
  • sed可用于流文本中的字符串替换
  • sed的字符串替换方式为:sed ‘s:src:des:g’

在这里插入图片描述
举例:
在这里插入图片描述
sed的正则表达式支持:

  • 在sed中可以用正则表达式匹配替换目标
  • 并且可以使用匹配的目标生成替换结果,就是正则表达式是的捕获
    下面的例子书写的时候注意空格。
root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/deps# gcc -MM -E  -Iinc src/main.c
main.o: src/main.c inc/func.h
root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/deps# gcc -MM -E  -Iinc src/main.c | sed 's,\(.*\)\.o[ :]*,obj/\1.o : ,g'
obj/main.o : src/main.c inc/func.h
root@wgg-MacBookPro:/home/wgg/unpv22e/mknotes/deps# 

少年,现在是时候使用组合技了,盖亚!!
在这里插入图片描述
其实应该是这样的:
在这里插入图片描述

终点站

在这里插入图片描述
写个Makefile,使用自动依赖。
Makefile:

vpath %.c src
vpath %.h inc
override CFLAGS += -I$(DIR_HEADER)
MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps
DIR_EXES := exes
DIR_OBJS := objs
DIR_SRCS := src
DIR_HEADER := inc
DIRS:= $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)

EXE := app.out
EXE := $(addprefix $(DIR_EXES)/,$(EXE))

SRCS := $(wildcard $(DIR_SRCS)/*.c)
OBJS := $(patsubst $(DIR_SRCS)/%.c,$(DIR_OBJS)/%.o,$(SRCS))
DEPS := $(patsubst $(DIR_SRCS)/%.c,$(DIR_DEPS)/%.dep,$(SRCS))




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) $(CFLAGS) -o $@ -c $(filter %.c,$^) 

$(DIRS) :
        $(MKDIR) $@

# 判断目录是否存在,否则无限循环,文件的更新导致目录时间更改,我用的4.3版本的make能检测这个东西,不会循环。。。。
ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

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

clean :
        $(RM) $(DIRS) *.o *.out
        @echo $(DEPS)
        @echo $(MAKECMDGOALS)

在这里插入图片描述
先写到这,include那个地方再做补充。。

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值