Linux Makefile 中的陷阱

前言

每個編寫過Makefile的程序員都可能遇見過Makefile中內含的陷阱,本博文旨在展現陷阱,提醒自己,也供大家一起學習。

本博文會隨所遇見的Makefile陷阱有關的問題而進行後續的更新。


陷阱一:在定義變量的語句行末尾空格之後使用了‘#’註釋符

結果:導致變量的值並不是你所賦值的,而是把值與註釋符之間的空格一起賦值給了變量,使得執行違背自己的意願,而不容易察覺。

實例說明如下(Makefile版本:GNU MAKE 3.81):

TmpDir = /Source  #此處隨意定義了一個目錄,
				  #爲了驗證此陷阱,特意在賦值語句後空幾格並進行註釋,

ifeq ($(TmpDir), /Source)
Result = They are equal
else
Result = They are not equal
endif

all:
	@echo $(TmpDir)|||||||
    @echo $(Result)
make之後其結果爲 : 
/Source  |||||||      (注意:/Source與|之間的空格,其實是屬於TmpDir變量的)
They are not equal

若把 
ifeq ($(TmpDir), /Source) 
改爲
ifeq ($(TmpDir), /Source  )  
說明:/Source後面的空格需要跟定義TmpDir與註釋符之間的空格數相等

如此一來,再次make,結果爲:They are equal

**擴展一:**其實驗證的過程中也引申出了另一個陷阱,ifeq()語句中的陷阱,見陷阱二
擴展二 : 變量賦值語句存在這個陷阱,那宏定義語句呢?及類似於如下語句

CFLAGS  += -DTMP=1   #註釋語句
INCFLAGS += -I$(APP_COMMON_SRC_DIR)/Include   #註釋語句

main:mian.o
	gcc $< $(CFLAGS) $(INCFLAGS) -o $@ 

其實經過實測表明,這樣並不會影響宏定義“TMP”在源文件中的值, 以及“INCFLAGS ”所在的路徑值。

心得: 通過以上求證,註釋符會影響到Makefile文件內部定義使用的變量的值,而不會影響到諸如 -D , -I 後面的值。所以建議Makefile中註釋都不要寫在語句後面,而是語句的前一行,來避免類似的問題出現


陷阱二:ifeq語句的括號裏面,不要隨意使用空格

結果:makefile會吧參數後面的空格也當作參數的一部分來進行比較,導致結果違背自己的意願。

實例說明如下(Makefile版本:GNU MAKE 3.81):

TmpDir = /Source

#下方的/Source後面空了幾格
ifeq ($(TmpDir), /Source )  
Result = They are equal
else
Result = They are not equal
endif

all:
    @echo $(Result)
make之後其結果爲 : 
They are not equal

若把 
ifeq ($(TmpDir), /Source ) 
改爲
ifeq ($(TmpDir), /Source)  

如此一來,再次make,結果爲:They are equal

經過實測表明,$(TmpDir)後面空幾格沒有影響,唯獨/Source後面空格就會有影響了

**心得 : ** 在Makefile中,最好保證參數的一致性,是否空格等,不像C語言等語言編程一樣,那麼寬鬆。


陷阱三:在mingw環境下使用路徑時的陷阱

詳情:在正確使用並能生成.d依賴文件,理論上使得修改任一 .h 或者 .c 文件都能自動進行編譯的情況下,其結果偏偏就是在修改了.h文件而不能編譯與之相關的.c文件,即沒有檢查到有文件更新,從而沒有進行編譯。待仔細查看Makefile的內容,也不能輕易看出端倪。其實這背後存在一個不易察覺的陷阱。

例子大概如下:

TARGET = Temp
# abspath 函數:獲取其參數中的文件或者目錄的絕對路徑
APP_BASE = $(abspath ../..)
DEV_BLD_DIR = $(APP_BASE)/$(TARGET)/Build

TEMP = $(APPSRC:.c=.o)
APPOBJS_TMP = $(TEMP:.S=.o)
# addprefix 函數:把 APPOBJS_TMP 中的文件一一添加前綴 $(DEV_BLD_DIR)/
APPOBJS := $(addprefix $(DEV_BLD_DIR)/,$(APPOBJS_TMP))

APPDEPS_TMP = $(APPOBJS_TMP:.o=.d)
APPDEPS := $(addprefix $(DEV_BLD_DIR)/,$(APPDEPS_TMP))

all: Tmp.bin

-include $(APPDEPS)
......
#省略了若干內容
......
# subst 函數:把$@中的 Source 替換成 Build
# 該編譯的命令,在編譯源文件的同時,也生成了.d 依賴文件
$(DEV_BLD_DIR)/%.o: %.c
	$(info Compiling $< ...)
	$(CC) -c -o $(subst Source,Build,$@) $(CFLAGS) $(INCFLAGS) $< -MD -MF $(DEV_BLD_DIR)/$*.d -MP

請點擊進入 .d依賴文件 相關內容介紹

其實從結果上便能大致推測是.d依賴文件部分出現了問題,因爲改寫任一文件都要能重新編譯,本身就是.d依賴文件所要賦予的功能。

陷阱:目標路徑的問題,即同一文件目標的引用時要保持路徑一致。mingw環境下,windows路徑(e.g. c:\agc.o) 和 mingw路徑(/c/agc.o)都能夠識別,對於make而言, c:\abc.o 和 /c/abc.o 是兩個不同的目標。若要是不知道這一知識要點,很難發現 .d 文件開頭 c:\ 和 /c/ 的區別。(個人疑點:同一環境,不同工程,有些生成的.d依賴文件中.o目標路徑和make中引用的路徑是一樣的,目前也不知是什麼原因,總之這個陷阱還是存在的。)

實例陷阱說明:

#以下行將導入所有的.d依賴文件的內容,即以 /c/...開頭的內容
-include $(APPDEPS)
#而以下目標依賴關係中,指明目標的路徑則是以 c:\...開頭的路徑
$(DEV_BLD_DIR)/%.o: %.c
#其結果就是導致了因路徑表示的不同,而認爲不是同一目標的情況出現
#使得make不能找到.o目標文件依賴的所有依賴源文件,其中包括.h頭文件
#自然而然,也就不能因爲.h文件的更新,而重新編譯對應的.c文件來生成.o文件

解決方法:
既然知道了陷阱所在,就可以利用如下命令來解決該問題:

#通過增加sed命令,把生成的.d依賴文件中的.o目標路徑改寫就可以了。
$(DEV_BLD_DIR)/%.o: %.c
	$(info Compiling $< ...)
	$(CC) -c -o $(subst Source,Build,$@) $(CFLAGS) $(INCFLAGS) $< -MD -MF $(DEV_BLD_DIR)/$*.d.tmp -MP
	sed 's,.*\.o[ :]*,$@:,g' < $(DEV_BLD_DIR)/$*.d.tmp > $(DEV_BLD_DIR)/$*.d;\
		rm -f $(DEV_BLD_DIR)/$*.d.tmp
	@echo

**心得:**以後出現類似該情況,即表面上 makefile 中沒有什麼問題,但在使用了依賴文件,並修改.h 文件後,不重新編譯的情況,這個時候要考慮路徑問題。不同路徑的表示方法,所表示的目標文件在make中會認爲不是同一文件。

發佈了41 篇原創文章 · 獲贊 38 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章