自動生成依賴關(guān)系
1、編譯行為帶來的缺陷
- 預(yù)處理器將頭文件中的代碼直接插入源文件
- 編譯器只通過預(yù)處理后的源文件產(chǎn)生目標(biāo)文件
- 因此,規(guī)則中以源文件為依賴,命令可能無法執(zhí)行
示例1觀察以下makefile文件是否正確:當(dāng)修改func.h中宏HELLO的內(nèi)容后,執(zhí)行make命令發(fā)現(xiàn),編譯器無法更新main.c和func.c,進(jìn)而無法更新執(zhí)行的結(jié)果:原因在于func.h中更新的內(nèi)容無法自動更新到func.c和main.c文件中,進(jìn)而導(dǎo)致編譯的hello.out文件結(jié)果無任何變化。
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 $^
解決方法1:修改makefile文件--將頭文件作為依賴條件出現(xiàn)于每個目標(biāo)對應(yīng)的規(guī)則中
OBJS := func.o main.o
hello.out := $(OBJS)
@gcc -o $@ $^
@echo "Target File => $@"
$(OBJS) : %.o : %.c func.h
@gcc -o $@ -c $<
優(yōu)點(diǎn):
頭文件的更改會更新到相關(guān)的源文件中,并更新到最終的目標(biāo)文件
缺點(diǎn):
- 當(dāng)頭文件改動,任何源文件都將被重新編譯(編譯低效)
- 當(dāng)項目中頭文件數(shù)量巨大時,makefile將很難維護(hù)
解決方案2:
- 通過命令自動生成對頭文件的依賴
- 將生成的依賴自動包含進(jìn)makefile中
- 當(dāng)頭文件改動后,自動確認(rèn)需要重新編譯的文件
針對解決方案2需要使用的技術(shù):
(1) linux的sed命令
sed是一個流編輯器,用于流文本的修改(增/刪/改/查)
sed可用于流文本中的字符串替換
-
sed的字符串替換方式為:sed 's:src:des:g'
11——1.png例如執(zhí)行下列語句:
echo "test=>abc+abc+abc" | sed 's:abc:xyz:g'
test的內(nèi)容將變?yōu)閤yz+xyz+xyz
sed的正則表達(dá)式支持
- 在sed中可以用正則表達(dá)式匹配替換目標(biāo)
- 并且可以使用匹配的目標(biāo)生成替換結(jié)果
例如
sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g'
示例2--sed用法
(2) 編譯器依賴生成選項gcc -MM(gcc -M)
- gcc -M des
獲取目標(biāo)des的完整依賴關(guān)系 -
gcc -MM des
獲取目標(biāo)des的部分依賴關(guān)系
(-E 僅對依賴關(guān)系做初步解析)
11-2-gcc M gcc MM.PNG
(3)makefile中的include關(guān)鍵字
- 類似C語言中的include
- 將其他文件的內(nèi)容原封不動的搬入當(dāng)前文件
- 語法:include filename
例如:
include foo.make
include *.mk
include $(var)
make對include關(guān)鍵字的處理方式
在當(dāng)前目錄搜索或指定目錄搜索目標(biāo)文件
- 搜索成功:將文件內(nèi)容搬入當(dāng)前makefile中
- 搜索失?。寒a(chǎn)生警告
-- 以文件名作為目標(biāo)查找并執(zhí)行相應(yīng)規(guī)則
-- 當(dāng)文件名對應(yīng)的規(guī)則不存在時,最終產(chǎn)生錯誤
示例3-1--include用法--目標(biāo)文件不存在,目標(biāo)規(guī)則不存在--無操作,直接報錯
.PHONY : all
include test.txt
all :
@echo "this is $@"
示例3-2--include用法--當(dāng)目標(biāo)文件不存在,目標(biāo)規(guī)則存在----以文件名查找規(guī)則,并執(zhí)行
.PHONY : all
include test.txt
all :
@echo "this is $@"
test.txt :
@echo "this is $@"
示例3-3--include用法--目標(biāo)文件存在,目標(biāo)規(guī)則存在
.PHONY : all
include test.txt
all :
@echo "this is $@"
test.txt :
@echo "creating $@"
@echo "other : ; @echo "this is other" " > test.txt
--執(zhí)行make命令,將執(zhí)行makefile中的第一條規(guī)則
--執(zhí)行make all命令將執(zhí)行all規(guī)則
include 暗黑操作一:
-
使用減號(-)不但關(guān)閉了include發(fā)出的警告,同時關(guān)閉了錯誤;當(dāng)錯誤發(fā)生時make將忽略做這些錯誤!
示例3-5-1--include不使用(-)會報告所有的錯誤和警告
.PHONY : all
include test.txt
all :
@echo "this is $@"
示例3-5-2--include(-)關(guān)閉了include發(fā)出的警告和錯誤
.PHONY : all
include test.txt
all :
@echo "this is $@"
include 暗黑操作二:
-
如果include觸發(fā)規(guī)則創(chuàng)建了文件,之后還會執(zhí)行規(guī)則中的命令,然后重新執(zhí)行include后的命令
示例3-6-1-include執(zhí)行的規(guī)則中不存在依賴;則會將規(guī)則直接包含進(jìn)makefile
.PHONY : all
-include test.txt
all :
@echo "this is $@"
test.txt :
@echo "creating $@ ..."
@echo "other"
示例3-6-2--include執(zhí)行時先判斷規(guī)則是否存在,如果存在會再檢查依賴是否是最新的,如果依賴比當(dāng)前規(guī)則要新,會直接執(zhí)行依賴;否則直接執(zhí)行規(guī)則
示例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 當(dāng)規(guī)則文件比依賴文件內(nèi)容要新--test.txt比b.txt時間戳更新,執(zhí)行make all結(jié)果如下:
示例3-6-2 當(dāng)依賴文件比規(guī)則文件內(nèi)容要新--b.txt比test.txt時間戳更新,執(zhí)行make all結(jié)果如下:
include的總結(jié):
-
當(dāng)目標(biāo)文件不存在----以文件名查找規(guī)則,并執(zhí)行
-
當(dāng)目標(biāo)文件不存在,且查找到的規(guī)則中創(chuàng)建了目標(biāo)文件----將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前makefile
-
當(dāng)目標(biāo)文件存在,將目標(biāo)文件包含進(jìn)當(dāng)前makefile;并以目標(biāo)文件名查找是否有相應(yīng)規(guī)則--如果有,比較規(guī)則的依賴關(guān)系,決定是否執(zhí)行規(guī)則的命令;否則,無操作。
-
當(dāng)目標(biāo)文件存在,且目標(biāo)名對應(yīng)的規(guī)則被執(zhí)行
如果規(guī)則中的命令更新了目標(biāo)文件--make重新包含目標(biāo)文件,替換之前包含的內(nèi)容
如果目標(biāo)文件未被更新,無操作。
(4) makefile中命令的執(zhí)行機(jī)制
- 規(guī)則中的每個命令默認(rèn)是在一個新的進(jìn)程中執(zhí)行(Shell)
- 可以通過接續(xù)符(;)將多個命令組合成一個命令
- 組合的命令依次在同一個進(jìn)程中被執(zhí)行
- set -e指定發(fā)生錯誤后立即退出執(zhí)行
示例4-1--規(guī)則中命令的執(zhí)行--無接續(xù)符--規(guī)則中的每個命令默認(rèn)是在一個新的進(jìn)程中執(zhí)行
.PHONY : all
all :
mkdir test
cd test
mkdir subtest
示例4-2--規(guī)則中命令的執(zhí)行--接續(xù)符--組合的命令依次在同一個進(jìn)程中被執(zhí)行
.PHONY : all
all :
set -e;\
mkdir test;\
cd test;\
mkdir subtest
解決方案2的初步思路
- 通過gcc -MM和sed得到.dep依賴文件(目標(biāo)的部分依賴)
技術(shù)點(diǎn):規(guī)則中命令的連續(xù)執(zhí)行 - 通過include指令包含所有的.dep依賴文件
技術(shù)點(diǎn):當(dāng).dep依賴文件不存在時,使用規(guī)則自動生成 - 當(dāng)include發(fā)現(xiàn).dep文件不存在時
通過規(guī)則和命令創(chuàng)建deps文件
將所有.dep文件創(chuàng)建到deps文件夾
.dep文件中記錄目標(biāo)文件的依賴關(guān)系
代碼部分設(shè)計如下:
$(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) %.c 語句存在問題:當(dāng)?shù)谝淮螆?zhí)行時會創(chuàng)建deps文件夾和對應(yīng)的.dep文件,而第二次deps文件夾內(nèi)容會被新的.dep文件進(jìn)行更新,導(dǎo)致第一次生成的.dep文件因為依賴更新而會被重復(fù)執(zhí)行。
解決方法如下:使用ifeq動態(tài)決定.dep目標(biāo)的依賴,實現(xiàn)代碼如下:
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)
自動生成依賴關(guān)系的解決方案:
- 將依賴文件名作為目標(biāo)加入自動生成的依賴關(guān)系中
- 通過include加載依賴文件時判斷是否執(zhí)行規(guī)則
- 在規(guī)則執(zhí)行時重新生成依賴關(guān)系文件
- 最后加載新的依賴文件
示例6--最終代碼實現(xiàn):

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)
小結(jié):
- makefile中可以將目標(biāo)的依賴拆分寫到不同的地方
- include關(guān)鍵字能夠觸發(fā)相應(yīng)規(guī)則的執(zhí)行
- 如果規(guī)則的執(zhí)行到政治依賴更新,可能導(dǎo)致再次解釋執(zhí)行相應(yīng)規(guī)則
- 依賴文件也需要依賴于源文件得到正確的編譯決策
- 自動生成文件間的依賴關(guān)系能夠提高makefile的移植性
