您好,登錄后才能下訂單哦!
本節實驗所需的源文件和頭文件:
原文件:func.c
#include "stdio.h"
#include "func.h"
void foo()
{
printf("void foo() : %s\n", HELLO);
}
原文件:main.c
#include <stdio.h>
#include "func.h"
int main()
{
foo();
return 0;
}
頭文件func.c
#ifndef FUNC_H
#define FUNC_H
#define HELLO "Hello D.T."
void foo();
#endif
問題:
OBJS := func.o main.o
hello.out : $(OBJS)
@gcc -o $@ $^
@echo "Target File ==> $@"
$(OBJS) : %.o : %.c
@gcc -o $@ -c $<
此時看似可以編譯成功,但存在潛在隱患。
存在問題:目標文件只依賴于.c文件,而沒有關注.h文件,這樣當.h文件的內容更新時,不會重新編譯.c文件。
解決方案:
我們將.h文件也作為依賴寫到Makefile中。
OBJS := func.o main.o
hello.out : $(OBJS)
@gcc -o $@ $^
@echo "Target File ==> $@"
$(OBJS) : %.o : %.c func.h
@gcc -o $@ -c $<
上述解決方案問題:
頭文件作為依賴出現于每一個目標文件對應的規則中,當頭文件改動,任何源文件都會被重新編譯(編譯低效),而且當項目中頭文件數量巨大時,Makefile件很難維護。
通過命令自動生成對頭文件的依賴,將生成的依賴自動包含進入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 c
test : a b
test : b c
test :
@echo "$^"
輸出結果:a b c
思考:如果使用上面的預備工作實現頭文件的自動依賴?
Make中的include關鍵字,類似于C語言中的關鍵字,在處理是將所包含的文件的內容原封不動的搬到當前文件。
語法:include filename
Eg: include foo.make *.mk $(var)
Make對include關鍵字的處理方式,在當前目錄搜索或者指定目錄搜索目標文件,搜索成功:將文件內容搬入當前Makefile中;搜索失敗,以文件名作為目標查找并執行對應規則。當文件名對應的規則不存在時,產生錯誤。
下面的代碼怎么執行,為什么?
.PHONY : all
include test.txt
all :
@echo "this is all"
test.txt :
@echo "test.txt"
@touch test.txt
初次執行文件,自然搜索不到test.txt文件,然后會test.txt文件名作為目標查找并執行對應規則,輸出結果:
注意:在include關鍵字前面加上-,可以消除警告。
1.Makefile中的命令執行時,每一條命令默認都是一個新的進程;(這樣當我們希望使用上一個命令的執行結果,繼續執行命令時往往得不到結果,譬如下面的代碼);
.PHONY : all
all :
set -e;
mkdir test;
cd test;
mkdir subtest
輸出結果:
很顯然,沒有達到我們與其的目的(在test文件夾中創建subtest文件夾)
2.可以通過接續符(;)將多個命令組合成為一個命令,組合的命令一次在同一個進程中被執行;
3.可以使用set -e指定發生錯誤時立即退出。
.PHONY : all
all :
set -e; \
mkdir test; \
cd test; \
mkdir subtest
輸出結果:
1.通過gcc -MM 和sed命令得到.dep文件(目標的部分依賴),并使用接續符使得命令可以連續執行;
2.通過include指令包含所有的.dep依賴文件(當.dep文件不存在時,查找與.dep文件同名的規則并執行)
.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)
輸出結果:
我們此時已經成功的生成了依賴文件main.dep和func.dep并在文件中記錄了目標和依賴的關系。
思考:如果組織依賴文件相關的規則與源碼編譯相關的規則,進而形成功能完整的Makefile?
如何在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) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
1.使用- 不但關閉了include發出的警告,同時關閉了錯誤,當發生錯誤時,make將忽略這些錯誤。
2.如果include 觸發規則創建了文件則會發生下面的事情:
// 使用include 時的暗黑操作
if(如果目標文件不存在)
{
//以文件名為規則查找并執行,
if(查找到的規則中創建了文件)
{
//將創建成功的目標文件包含進當前makefile
}
}
else // 如果目標文件存在
{
// 將目標文件包含進當前makefile
if(以目標文件名查找是否有相應的規則)
{
if(比較規則的依賴關系,決定是否執行規則的命令)
{
// (依賴文件更新,則執行)
}
else
{
// 無操作
}
}
else
{
// 無操作
}
}
實驗1:include包含的目標文件不存在,并且以文件名為目標的規則存在,并在規則中創建了文件
.PHONY : all
-include test.txt
all :
@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.txt
all :
@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.txt
all :
@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輸出結果:
經過前面的技巧學習,我們現可以去完成這個自動生成依賴關系的想法了
注意:
思考:我們在13節中最終創建出來的makefile是否存在問題?
當.dep文件生成后,如果動態的改變文件間的依賴關系,那么make可能無法檢測到這個改變,進而做出錯誤的判斷。
實例:
輸出結果:
解決方案:
將依賴文件的文件名作為目標加入自動生成的依賴關系中,通過include加載依賴文件時判斷是否執行規則,在規則執行時重新生成依賴關系文件,最后加載新的依賴文件。
舉個栗子:當我們前面編譯過之后(生成了依賴文件),又添加了新的頭文件,這時根據include的暗黑操作,要去檢查與include所包含的依賴文件同名的規則是否存在,如果存在,則檢查這個目標所對應的依賴是否被更新,如果更新,則執行相應規則。
最終方案:
.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
總結:
Makefile中可以將目標的依賴拆分寫到不同的地方;
include關鍵字能夠觸發相應的規則的執行;
如果規則的執行導致依賴更新,可能導致再次解釋執行相應的規則;
依賴文件可需要依賴源文件得到正確的編譯決策
自動生成文件的依賴關系能夠提高Makefile的移植性。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。