1. 程式人生 > >GCC+Make 自動生成 Makefile 依賴

GCC+Make 自動生成 Makefile 依賴

目錄

  • BASIS
    • wildcard
    • .PHONY
    • 靜態模式
    • 常用自動變數
  • 自動生成依賴(GCC)
    • -M 引數
    • 編寫 Makefile
    • Makefile 細節說明
    • 其他

本文內容基於 GNU MAKE。

BASIS

一些 makefile 的基礎知識。

wildcard

假設當前目錄下有檔案 a.cpp 和 b.cpp,定義:

eg1=*.cpp
eg2=$(wildcard *.cpp)

rm $(eg1) 的展開為 rm *.cpprm $(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 是對每個原始檔生成 .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)