本文內容基於 GNU MAKE。
BASIS
一些 makefile 的基礎知識。
wildcard
假設當前目錄下有文件 a.cpp 和 b.cpp,定義:
eg1=*.cpp
eg2=$(wildcard *.cpp)
則 rm $(eg1)
的展開為 rm *.cpp
,rm $(eg2)
的展開為 rm a.cpp b.cpp
.PHONY
.PHONY
用於表示其后的目標文件是一個偽目標文件。
在 makefile 的一般格式 targets : prerequisitions
中,targets
為目標文件,一般是實際存在的文件,如 a.o
等;但有些規則並不生成實際存在的文件或不生成文件 targets
中指定的文件,這樣的目標文件稱為偽目標文件。
典型例子如常用於清理中間文件的偽目標文件 clean
。通常其生成命令並不生成一個名為“clean”的文件。此時若不將其指定為偽目標文件,且項目當前目錄下恰好存在一個目錄或文件名為“clean”,則 make clean
時 make 會檢查該目錄或文件的時效性,故有可能直接提醒目標文件“clean”已經最新而不執行編寫的命令。
靜態模式
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
直接用例子說明
objects = foo.o bar.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
等價於
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
常用自動變量
本節摘自:陳皓《跟我一起寫 Makefile》
$@
表示規則中的目標文件集。在模式規則中,如果有多個目標,那么,"$@"就是匹配於目標中模式定義的集合。$<
依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那么"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。$^
所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。$+
這個變量很像"$^",也是所有依賴目標的集合。只是它不去除重復的依賴目標$*
目標模式中通配符%
之前的部分,例如目標是dir/a.foo.b
,模式為a.%.b
,則$*
的值就是dir/a.foo
此外,自動變量后可加 D 或 F 以實現取目錄部分或文件部分。例如,當 $@
為 dir/foo.o
時,$(@D)
為 dir
,$(@F)
為 foo.o
。對用當前目錄,取目錄時值為 .
。
自動生成依賴(GCC)
-M 參數
GCC 的 -M
和 -MM
參數可以生成指定源文件的依賴項。如,在 a.cpp 中 include a.h 和 b.h,則執行以下命令
g++ -MM a.cpp
會得到輸出如下
a.o : a.h b.h
-MM
生成的依賴項不包含標准庫等依賴,而 -M
包含。
編寫 Makefile
GNU 建議對每個源文件生成一個 .d
后綴的依賴說明文件,內容即上節編譯器生成的內容。
直接 include 上一節中生成的依賴就可以通過 make 的自動推導進行編譯了,但如果需要指定編譯參數、在 make 過程中加入回顯,可以考慮在 .d
直接加入命令。
對每個源文件建立 .d
依賴文件會使得項目文件翻倍,因此此處將所有的 .d
文件合並為一個文件。這樣做帶來的缺點是每次 make 依賴項都需要全部重新生成。
下面直接給出 makefile。
sources=a.cpp b.cpp c.cpp
# 替換后綴
dependencies=$(sources:.cpp=.d)
$(dependencies) : %.d : %.cpp
@set -e; \
g++ -MM $(flags) $< > $@.$$$$; \
sed 's,$(*F).o,$*.o,g' < $@.$$$$ > $@; \
echo ' @g++ -c $< -o $(@:.d=.o) $(flags)' >> $@; \
cat $@ >> dependencies.d; \
rm -f $@.$$$$; rm -f $@
cleand:
rm -f dependencies.d; \
dependencies.d : cleand $(dependencies)
include dependencies.d
Makefile 細節說明
*.d
文件是生成的臨時依賴項,最終會被刪除。$$$$
展開為隨機的數字,用於建立臨時文件。
$(depencencies)
的生成命令中,第三行用於在單個文件的依賴項中補全路徑。例如,執行 g++ -MM src/a.cpp
后,GCC 給出的依賴項會是 a.o : xxx
,但項目中需要 src/a.o : xxx
的形式以避免命名沖突。sed
是 Linux 指令,此處用於進行文本替換。
生成命令的第四行向依賴文件中寫入編譯命令,此處也可以使用 echo 報告進度。第五行將該文件的依賴寫入總的依賴文件。
最后 include 總的依賴文件。第一次執行 make 時依賴文件不存在,因此定義生成生成依賴文件的規則。生成前需要先刪除舊的依賴文件,因為生成時使用的是追加方式。
注意使用這種方式編寫的 makefile 在第一次 make 時由於不存在依賴會導致編譯失敗,第二次即可恢復正常,因為此時依賴文件 dependencies.d 就已經建立了。
其他
以下 Makefile 是對每個源文件生成 .d
依賴文件的版本。
$(dependencies) : %.d : %.cpp
@set -e; rm -f $@; \
g++ -MM $(flags) $< >> $@; \
sed 's,\($*\)\.o[ :]*,\1.o : $@ ,g' < $@ > $@.$$$$; \
sed 's,$(*F).o,$*.o,g' < $@.$$$$ > $@; \
echo ' @g++ -c $< -o $(@:.d=.o) $(flags)' >> $@; \
rm -f $@.$$$$
include $(dependencies)