上一次我們試圖引入多模塊,結果失敗了,今天我們來幫助失敗實現做母親的願望。在正式開始之前,先做一個孕前檢查
我們上一次的失敗原因有兩個
1. 編譯目標重定義。
2. 一些變量定義被覆蓋。
這些實際上是遞歸形式的多模塊makefile必然要遇到的兩個問題。
對症下藥的藥方就是makefile中的“多規則目標”(具體參看makefile必知必會),簡單而言就是三句話:
makefile允許目標的依賴關係通過多個規則定義,但是實現該目標的命令只能在一個規則中定義,實現時會自動合併該目標所有的依賴關係。
下面來解決第一個問題,clean目標重定義。既然clean目標的實現命令不能在多個規則中定義,那麼我們就爲clean目標建立一個依賴關係clean:$(TARGET).clean, 在依賴目標$(TARGET).clean中定義命令。
由於$(TARGET).clean,各個模塊必然不同,自然而然地就解決了目標重定義的問題。
# executable.mk
## 編譯生成obj文件的通用規則
include build/targets/object.mk
## 利用多規則定義,將clean目標轉發給$(TARGET).clean,從而避免出現clean目標重定義的問題
## 對all,不需要額外處理,因爲$(TARGET)本身就要求不同模塊目標不同
all: $(TARGET)
clean: $(TARGET).clean
## 定義可執行文件hello.exe的編譯規則
$(TARGET) : $(OBJS) $(LIBS)
$(LD) $(LDFLAGS) -o $@ $^
## 定義 目標clean,一般就是刪除所有obj文件,以及可執行文件。
.PHONY: $(TARGET).clean
$(TARGET).clean:
$(RM) $(TARGET) $(OBJS) $(DEPS)
下面來解決第二個問題, 首先稍微解釋一下這條規則$(ALL_TARGETS) : OBJS := $(OBJS) 它的與衆不同在於依賴目標是一個賦值表達式。
這條規則的妙處在於能將變量的值與目標$(ALL_TARGETS)綁定起來。簡單說來,不管之前OBJS的值是什麼,當需要生成目標$(ALL_TARGETS)時,由於依賴關係,會先去計算“OBJS := $(OBJS)“這條語句。那麼這裏面 $(OBJS)的值是怎麼算呢?它的值來源於makefile的第一次解析,因爲目標和依賴目標中的變量是立即展開的。於是乎
當makefile解析到模塊A時,假設ALL_TARGETS=a, OBJS=a.o, 這時上面的規則就變成 a : OBJS := a.o;
當解析到模塊B時, 假設ALL_TARGETS=b, OBJS=b.o. 這時上面的規則變成b : OBJS :=b.o;
然後makefile開始第二次掃描, 注意,在此之前OBJS的值已經是b.o了。當makefile去生成目標a時,會自動計算依賴目標,也就是表達式"OBJS := a.o", 於是OBJS又恰到好處的還原爲需要的值。
這個技術很像是變量存儲,因此我們新增了一個vars-stash.mk, 利用這個技術做變量存儲,防止被覆蓋。
# vars-stash.mk
$(info ALL_TARGETS=$(ALL_TARGETS))
$(ALL_TARGETS) : AR := $(AR)
$(ALL_TARGETS) : ARFLAGS := $(ARFLAGS)
$(ALL_TARGETS) : ARLIBS := $(ARFLAGS)
$(ALL_TARGETS) : CC := $(CC)
$(ALL_TARGETS) : CPP := $(CPP)
$(ALL_TARGETS) : CXX := $(CXX)
$(ALL_TARGETS) : CFLAGS := $(CFLAGS)
$(ALL_TARGETS) : CPPFLAGS := $(CPPFLAGS)
$(ALL_TARGETS) : CXXFLAGS := $(CXXFLAGS)
$(ALL_TARGETS) : LD := $(LD)
$(ALL_TARGETS) : LDLIBS := $(LDLIBS)
$(ALL_TARGETS) : LDFLAGS := $(LDFLAGS)
$(ALL_TARGETS) : OBJS := $(OBJS)
$(ALL_TARGETS) : DEPS := $(DEPS)
$(ALL_TARGETS) : TARGET := $(TARGET)
在executable.mk中,直接包含vars-stash.mk
## 定義 目標clean,一般就是刪除所有obj文件,以及可執行文件。
.PHONY: $(TARGET).clean
$(TARGET).clean:
$(RM) $(TARGET) $(OBJS) $(DEPS)
ALL_TARGETS := $(OBJS) $(TARGET) $(TARGET).clean
include build/targets/vars-stash.mk
至此,失敗終爲成功之母,簡單調整,多模塊的makefile就在襁褓之中。
爲hello文件新建一個module.mk, 配置好源文件列表,編譯目標,以及引用static-library.mk, 就可以正常編譯得到hello.a。同時修改主文件夾下的module.mk, 在依賴目標中增加hello.a
## main.mk
## 只要給定源文件目錄以及目標hello.exe
## 調用executable.mk,就可以自動編譯得到想要的可執行文件
SRC_FILES:= src/main.cpp
LDLIBS += src/hello/hello.a
TARGET:=hello.exe
## 定義瞭如何生成可執行文件的通用規則
include build/targets/executable.mk
今天的工作到此爲止。這一次的調整還是比較大的,中途也不太方便做測試。必須在調整完target之後,才能做多模塊化編譯。其實我中間也費了不少時間。