本節(jié)實(shí)驗(yàn)所需的源文件和頭文件:
原文件: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
問(wèn)題:
OBJS := func.o main.o
hello.out : $(OBJS)
@gcc -o $@ $^
@echo "Target File ==> $@"
$(OBJS) : %.o : %.c
@gcc -o $@ -c $<
此時(shí)看似可以編譯成功,但存在潛在隱患。
存在問(wèn)題:目標(biāo)文件只依賴(lài)于.c文件,而沒(méi)有關(guān)注.h文件,這樣當(dāng).h文件的內(nèi)容更新時(shí),不會(huì)重新編譯.c文件。
解決方案:
我們將.h文件也作為依賴(lài)寫(xiě)到Makefile中。
OBJS := func.o main.o
hello.out : $(OBJS)
@gcc -o $@ $^
@echo "Target File ==> $@"
$(OBJS) : %.o : %.c func.h
@gcc -o $@ -c $<
上述解決方案問(wèn)題:
頭文件作為依賴(lài)出現(xiàn)于每一個(gè)目標(biāo)文件對(duì)應(yīng)的規(guī)則中,當(dāng)頭文件改動(dòng),任何源文件都會(huì)被重新編譯(編譯低效),而且當(dāng)項(xiàng)目中頭文件數(shù)量巨大時(shí),Makefile件很難維護(hù)。
通過(guò)命令自動(dòng)生成對(duì)頭文件的依賴(lài),將生成的依賴(lài)自動(dòng)包含進(jìn)入Makefile中,當(dāng)頭文件改動(dòng)后,自動(dòng)確認(rèn)需要重新編譯的文件。
預(yù)備工作:
1.Linux命令sed,sed時(shí)一個(gè)流編輯器,用于流文本的修改(增、刪、查、改),文件替換,格式為:sed ‘s/abc/xyz/g’;
Sed可以支持正則表達(dá),sed ‘s/(.).o[ :]/objs/\1.o : /g’ 正則匹配目標(biāo)((.).o[ :]),替換值(objs/\1.o : )
2.編譯器選項(xiàng),生成依賴(lài)關(guān)系
gcc -MM 獲取目標(biāo)的完整依賴(lài)關(guān)系
gcc -M 獲取目標(biāo)的部分依賴(lài)關(guān)系
3.Makefile中目標(biāo)拆分技巧,將目標(biāo)的完整依賴(lài)拆分為多個(gè)部分依賴(lài)
.PHONY : test a b c
test : a b
test : b c
test :
@echo "$^"
輸出結(jié)果:a b c
思考:如果使用上面的預(yù)備工作實(shí)現(xiàn)頭文件的自動(dòng)依賴(lài)?
Make中的include關(guān)鍵字,類(lèi)似于C語(yǔ)言中的關(guān)鍵字,在處理是將所包含的文件的內(nèi)容原封不動(dòng)的搬到當(dāng)前文件。
語(yǔ)法:include filename
Eg: include foo.make *.mk $(var)
Make對(duì)include關(guān)鍵字的處理方式,在當(dāng)前目錄搜索或者指定目錄搜索目標(biāo)文件,搜索成功:將文件內(nèi)容搬入當(dāng)前Makefile中;搜索失敗,以文件名作為目標(biāo)查找并執(zhí)行對(duì)應(yīng)規(guī)則。當(dāng)文件名對(duì)應(yīng)的規(guī)則不存在時(shí),產(chǎn)生錯(cuò)誤。
下面的代碼怎么執(zhí)行,為什么?
.PHONY : all
include test.txt
all :
@echo "this is all"
test.txt :
@echo "test.txt"
@touch test.txt
初次執(zhí)行文件,自然搜索不到test.txt文件,然后會(huì)test.txt文件名作為目標(biāo)查找并執(zhí)行對(duì)應(yīng)規(guī)則,輸出結(jié)果:
注意:在include關(guān)鍵字前面加上-,可以消除警告。
1.Makefile中的命令執(zhí)行時(shí),每一條命令默認(rèn)都是一個(gè)新的進(jìn)程;(這樣當(dāng)我們希望使用上一個(gè)命令的執(zhí)行結(jié)果,繼續(xù)執(zhí)行命令時(shí)往往得不到結(jié)果,譬如下面的代碼);
.PHONY : all
all :
set -e;
mkdir test;
cd test;
mkdir subtest
輸出結(jié)果:
很顯然,沒(méi)有達(dá)到我們與其的目的(在test文件夾中創(chuàng)建subtest文件夾)
2.可以通過(guò)接續(xù)符(;)將多個(gè)命令組合成為一個(gè)命令,組合的命令一次在同一個(gè)進(jìn)程中被執(zhí)行;
3.可以使用set -e指定發(fā)生錯(cuò)誤時(shí)立即退出。
.PHONY : all
all :
set -e; \
mkdir test; \
cd test; \
mkdir subtest
輸出結(jié)果:
1.通過(guò)gcc -MM 和sed命令得到.dep文件(目標(biāo)的部分依賴(lài)),并使用接續(xù)符使得命令可以連續(xù)執(zhí)行;
2.通過(guò)include指令包含所有的.dep依賴(lài)文件(當(dāng).dep文件不存在時(shí),查找與.dep文件同名的規(guī)則并執(zhí)行)
.PHONY : all clean
MKDIR := mkdir
RM := rm -fr
CC := gcc
SRCS := $(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)
輸出結(jié)果:
我們此時(shí)已經(jīng)成功的生成了依賴(lài)文件main.dep和func.dep并在文件中記錄了目標(biāo)和依賴(lài)的關(guān)系。
思考:如果組織依賴(lài)文件相關(guān)的規(guī)則與源碼編譯相關(guān)的規(guī)則,進(jìn)而形成功能完整的Makefile?
如何在makefile中組織.dep文件到指定目錄?
解決思路:
當(dāng)include 發(fā)現(xiàn).dep文件不存在時(shí),通過(guò)規(guī)則和命令創(chuàng)建deps文件夾,將所有的.dep文件創(chuàng)建到deps文件夾,并在.dep文件中記錄目標(biāo)文件的依賴(lài)關(guān)系。
$(DIR_DEPS) :
$(MKDIR) $@
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
@echo "Creating $@ ..."
@set -e; \
$(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
這樣做確實(shí)解決了上述問(wèn)題,生成了deps文件夾:
但同時(shí)我們看到兩個(gè)問(wèn)題:
1.因?yàn)橐蕾?lài)中包含deps文件夾,以deps文件夾作為 gcc -MM 的輸入時(shí)沒(méi)有意義的,會(huì)報(bào)告warning,所以使用下面的方法過(guò)濾掉deps文件夾
$(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
2.func.dep被重復(fù)創(chuàng)建了多次?
問(wèn)題本質(zhì)分析:
deps文件夾的時(shí)間屬性會(huì)因?yàn)橐蕾?lài)文件創(chuàng)建而發(fā)生改變,make發(fā)現(xiàn)deps文件夾比對(duì)于的目標(biāo)更新時(shí),會(huì)觸發(fā)相應(yīng)規(guī)則的重新解釋和命令的執(zhí)行。
解決方案:使用ifeq動(dòng)態(tài)決定.dep目標(biāo)的依賴(lài);
ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
1.使用- 不但關(guān)閉了include發(fā)出的警告,同時(shí)關(guān)閉了錯(cuò)誤,當(dāng)發(fā)生錯(cuò)誤時(shí),make將忽略這些錯(cuò)誤。
2.如果include 觸發(fā)規(guī)則創(chuàng)建了文件則會(huì)發(fā)生下面的事情:
// 使用include 時(shí)的暗黑操作
if(如果目標(biāo)文件不存在)
{
//以文件名為規(guī)則查找并執(zhí)行,
if(查找到的規(guī)則中創(chuàng)建了文件)
{
//將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前makefile
}
}
else // 如果目標(biāo)文件存在
{
// 將目標(biāo)文件包含進(jìn)當(dāng)前makefile
if(以目標(biāo)文件名查找是否有相應(yīng)的規(guī)則)
{
if(比較規(guī)則的依賴(lài)關(guān)系,決定是否執(zhí)行規(guī)則的命令)
{
// (依賴(lài)文件更新,則執(zhí)行)
}
else
{
// 無(wú)操作
}
}
else
{
// 無(wú)操作
}
}
實(shí)驗(yàn)1:include包含的目標(biāo)文件不存在,并且以文件名為目標(biāo)的規(guī)則存在,并在規(guī)則中創(chuàng)建了文件
.PHONY : all
-include test.txt
all :
@echo "this is all"
test.txt :
@echo "creating $@ ..."
@echo "other : ; @echo "this is other" " > test.txt
我們期望了輸出結(jié)果因該是:this is all,因?yàn)閍ll是第一個(gè)(默認(rèn))目標(biāo)。
運(yùn)行結(jié)果:
原因在于當(dāng)出現(xiàn)上面的情況時(shí):以文件名為規(guī)則查找并執(zhí)行,同時(shí)如果查找到的規(guī)則中創(chuàng)建了文件,將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前makefile,此時(shí)在makefile中第一個(gè)目標(biāo)變成了other
實(shí)驗(yàn)2:
.PHONY : all
-include test.txt
all :
@echo "this is all"
test.txt : b.txt
@echo "creating $@ ..."
當(dāng)不存在b.txt時(shí)的運(yùn)行結(jié)果:
當(dāng)存在b.txt,但b.txt文件比test.txt文件舊時(shí)的運(yùn)行結(jié)果:
當(dāng)存在b.txt,但b.txt文件比test.txt文件新時(shí)的運(yùn)行結(jié)果:
結(jié)論:如果目標(biāo)文件存在:將目標(biāo)包含進(jìn)當(dāng)前makefile,以目標(biāo)文件名查找是否有相應(yīng)的規(guī)則
如果有則比較規(guī)則的依賴(lài)關(guān)系,決定是否執(zhí)行規(guī)則的命令(依賴(lài)文件更新,則執(zhí)行),如果規(guī)則中的命令更新了目標(biāo)文件,替換之前包含了的內(nèi)容。未更新,則無(wú)操作。
以目標(biāo)文件名查找是否有相應(yīng)的規(guī)則,不能找到,則無(wú)操作
實(shí)驗(yàn)3:
.PHONY : all
-include test.txt
all :
@echo "$@ : $^"
test.txt : b.txt
@echo "creating $@ ..."
@echo "all : c.txt" > test.txt
a.txt內(nèi)容:
all : a.txt
當(dāng)該文件中所需的所有文件都存在,并且test.txt的內(nèi)容為最新時(shí),make all輸出結(jié)果:
當(dāng)b.txt文件最新時(shí),make all輸出結(jié)果:
經(jīng)過(guò)前面的技巧學(xué)習(xí),我們現(xiàn)可以去完成這個(gè)自動(dòng)生成依賴(lài)關(guān)系的想法了
注意:
思考:我們?cè)?3節(jié)中最終創(chuàng)建出來(lái)的makefile是否存在問(wèn)題?
當(dāng).dep文件生成后,如果動(dòng)態(tài)的改變文件間的依賴(lài)關(guān)系,那么make可能無(wú)法檢測(cè)到這個(gè)改變,進(jìn)而做出錯(cuò)誤的判斷。
實(shí)例:
輸出結(jié)果:
解決方案:
將依賴(lài)文件的文件名作為目標(biāo)加入自動(dòng)生成的依賴(lài)關(guān)系中,通過(guò)include加載依賴(lài)文件時(shí)判斷是否執(zhí)行規(guī)則,在規(guī)則執(zhí)行時(shí)重新生成依賴(lài)關(guān)系文件,最后加載新的依賴(lài)文件。
舉個(gè)栗子:當(dāng)我們前面編譯過(guò)之后(生成了依賴(lài)文件),又添加了新的頭文件,這時(shí)根據(jù)include的暗黑操作,要去檢查與include所包含的依賴(lài)文件同名的規(guī)則是否存在,如果存在,則檢查這個(gè)目標(biāo)所對(duì)應(yīng)的依賴(lài)是否被更新,如果更新,則執(zhí)行相應(yīng)規(guī)則。
最終方案:
.PHONY : all clean rebuild
MKDIR := mkdir
RM := rm -fr
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, $^)
# $(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)
rebuild :
@$(MAKE) clean
@$(MAKE) all
總結(jié):
Makefile中可以將目標(biāo)的依賴(lài)拆分寫(xiě)到不同的地方;
include關(guān)鍵字能夠觸發(fā)相應(yīng)的規(guī)則的執(zhí)行;
如果規(guī)則的執(zhí)行導(dǎo)致依賴(lài)更新,可能導(dǎo)致再次解釋執(zhí)行相應(yīng)的規(guī)則;
依賴(lài)文件可需要依賴(lài)源文件得到正確的編譯決策
自動(dòng)生成文件的依賴(lài)關(guān)系能夠提高M(jìn)akefile的移植性。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性?xún)r(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專(zhuān)為企業(yè)上云打造定制,能夠滿(mǎn)足用戶(hù)豐富、多元化的應(yīng)用場(chǎng)景需求。