在學習該篇文章之前,首先你需要有一定的makefile理論基礎,如果不懂可以參考:Makefile教程(絕對經典,所有問題看這一篇足夠了)建議看2遍以上。其中關于include可以參考Makefile 中的 include (依賴檔案) 的執行過程。
在Linux C語言開發的時候我們需要自己手動編譯新添加的檔案。如果一個工程非常龐大這樣将會是程式員的噩夢。使用
-MM
選項擷取檔案的依賴,并且自動推導檔案依賴關系。
Demo1:以.c和.h在同一個目錄來講解
首先使用
cc -MM
分析目前目錄下面所有的源檔案的依賴關系,并且把結果生成到對應的.d檔案中
#擷取目前目錄的所有.c檔案
src := $(wildcard *.c)
#生成.c對應的.d目标
d_obj := $(src:.c=.d)
#最終目标,暫時什麼都不做
main:$(d_obj)
#makefile 靜态編譯模式,$(d_obj)中所有xx.d的檔案依賴對應的xx.c檔案
$(d_obj):%.d:%.c
cc -MM $< -o $@
運作結果:可以看到對應的.d檔案,以及.d檔案的内容就是對應cpp的依賴項。

此時可以看見.d的檔案内容就是我們需要的檔案依賴關系,并且可以直接通過cc編譯。是以如果我們需要自動編譯生成主程式main,我們首先分需要析一下依賴關系:
- main 需要依賴 *.o
- *.o可以通過include *.d檔案生成
- *.d通過
cc -MM
生成
是以推導的makefile檔案内容如下:
#擷取目前目錄的所有.c檔案
src := $(wildcard *.c)
o_obj:=$(src:.c=.o)
#生成main目标
main:$(o_obj)
gcc -o $@ $^
-include $(src:.c=.d)
#靜态模式
%.d:%.c
cc -MM $< -o $@
這樣我們就可以每次執行make,自動更新依賴并且編譯檔案了。
是以最終的makefile如下:
ifndef GXX
GXX := g++
endif
ifndef CFLAGS
CFLAGS := -g
endif
src = $(wildcard *.c)
objs = $(src:.c=.o)
main:$(objs)
$(GXX) -o $@ $(CFLAGS) $^
-include $(src:.c=.d)
%.d:%.c
cc -MM $< -o $@
.PHONY:clean
clean:
rm -rf *.o *.d main
Demo2:.c 在src目錄,.h在include目錄,src、include、makefile同級
Demo1和Demo2的差別就是是否需要指定 -I選項,指明頭檔案的位置。
注意事項:
$<,$@,$^
使用這兩個變量的時候擷取到的目标是帶有相對路徑的。
vpath %.h ./include
vpath %.c ./src
add.o:add.h
@echo $<
是以和demo1相比我們需要處理.d的生成方式。變化主要兩點:
-
的内容需要指定 .d
參數(通過-I
指令修改 sed
生成的-MM
内容).d
-
位置不會和.d
同目錄,将會和makefile同目錄(為什麼不将.c
和.d
同目錄是因為在make目錄執行的時候 -I 參數為 .c
,但是在src目錄 -I參數為 ./include
,為了保持參數一緻,是以将.d和make保持同級)../include
%.d : %.c
rm -f $(@F); \
$(GXX) -MM $(INCLUDE) $< > $(@F).$$$$; \sed -e 's,^.*:,$*.o:,g' -e 's,$$,; $(GXX) -c $(CFLAGS) $(INCLUDE) $<,g' < $(@F).$$$$ > $(@F); \
rm -f $(@F).$$$$
$(@F),$(<F)
:目标檔案的檔案名稱
$(@D),$(<D)
:目标檔案的相對目錄位置
$*
:目标模式中"%"及其之前的部分,eg:test%.d 比對的是 testmain.d那麼
$*
表示testmain
sed -e 's,^.*:,$*.o:,g'
将‘,’替換成’/'就可以看出是一個s/parten1/paretn2/g的替換模式
是以上述語義 以main.d為例,則替換之後的
sed -e 's/^.*:/$*.o:/g'
正則表達
式為
sed -e 's/^.*:/main.o:/g'
eg:
$@為./src/main.o
則
$(@F)為main.o
,
$(@D)為./src/
至于
sed
的用法請百度,sed用法很靈活。是以最終的makefile内容為:
vpath %.c ./src
vpath %.h ./include
ifndef GXX
GXX := g++
endif
ifndef CFLAGS
CFLAGS := -g
endif
INCLUDE := -I ./include
src = $(wildcard ./src/*.c)
c_objs = $(notdir $(src))
objs = $(c_objs:.c=.o)
main:$(objs)
$(GXX) -o $@ $(CFLAGS) $^
-include $(objs:.o=.d)
%.d : %.c
rm -f $(@F); \
$(GXX) -MM $(INCLUDE) $< > $(@F).$$$$; \sed -e 's,^.*:,$*.o:,g' -e 's,$$,; $(GXX) -c $(CFLAGS) $(INCLUDE) $<,g' < $(@F).$$$$ > $(@F); \
rm -f $(@F).$$$$
.PHONY:clean
clean:
rm -rf *.o *.d main
編寫Makefile注意事項:
對于動态依賴的檔案(*.o)不要随便更改位置,否則容易導緻看似依賴檔案存在卻找不到的錯誤
demo:将生成的.o檔案放在bin目錄,bin和src,include同級
1 vpath %.h ./include
2 vpath %.c ./src
3 vpath %.o ./bin
4
5 src = $(wildcard ./src/*.c)
6
7 src_file_name=$(notdir $(src))
8
9 o_obj = $(src_file_name:.c=.o)
10
11 main:$(o_obj)
12 gcc -o $@ $^
13
14 $(o_obj):%.o:%.c
15 gcc -c $< -o ./bin/$@ -I ./include
運作結果:第一次沒有找到.o檔案,導緻編譯失敗。此時在執行一次,因為.o檔案實際已經存在在bin目錄,是以第二次成功了(此時大家肯定疑問,我明明指定了vpath的啊,為什麼會失敗呢)
為了弄清楚原因,我們把執行日志列印出來,看看兩次有什麼差別
main:$(o_obj)
@echo $^
gcc -o $@ $^
此時運作一下make,檢視
$(o_obj)
的輸出(
注意我們上文提到$<,$@,$^是相對路徑的位置輸出
),是以為什麼make失敗顯而易見,因為
$(o_obj)
是目前目錄下的
*.o
此時我們再次運作一次make,看看
$(o_obj)
的輸出,此時為什麼make成功,顯而易見,因為
$(o_obj)
是
./bin/
目錄下的
*.o
,這個目标檔案我們在第一次make的時候已經生成了。
此時我們可能會有疑問為什麼第一次沒有在指定的vpath裡面尋找
*.o
呢?事實上是查找了的,隻是沒找到而已,在make的過程中我們需要知道:
一旦檔案位置被定位了,在整個make的執行過程中都不會改變
。下面我們來分析一下:make關于.o查找的執行過程。
第一次 make:
—>到
$(o_obj)= add.o sub.o main.o
指定的位置查找即目前目錄查找,沒找到
—>到vpath指定的目錄查找,沒找到
—>最終沒找到,是以
$(o_obj)
,最終被定位的位置為目前目錄
是以雖然後面動态生成了.o但是.o檔案位置并不在目前目錄,是以make失敗
第二次 make:
—>到
$(o_obj)= add.o sub.o main.o
指定的位置查找即目前目錄查找,沒找到
—>到vpath指定的目錄查找,找到OK
—>是以
$(o_obj)
,最終被定位的位置為./bin/
是以此時第二次make是成功的
為了驗證上述make關于.o查找的執行過程是否正确,我們把./bin目錄下的.o檔案拷貝到make同級目錄,此時.o檔案将會存在兩份,位置分别位于目前目錄和./bin。此時我們make -n檢視一下*.o的定位位置。
結果符合預期
$(o_obj)
指定位置優先,之後才是vpath位置。
是以需要解決上述看似存在卻找不到的bug很簡單,隻需要将上述第15行代碼
gcc -c $< -o ./bin/$@ -I ./include
修改為:
gcc -c $< -o $@ -I ./include
不要随便更改目标依賴檔案的位置
///
MakeFile的遞歸調用
#include 會在目前位置展開makefile檔案内容,類似宏的展開
-include makefile
#$(MAKE) -C dir,會進入dir目錄,然後執行make指令
#可以通過export 向下層傳遞變量
object:
$(MAKE) -C dir
假設目錄結構如下:
--main.c
--add
--add.h
--add.c
--makefile
--makefile
add目錄makefile内容如下:
#$(CFLAGS)由上層makefile通過export傳遞過來
add.o:add.c
g++ -o $@ -c $^ $(CFLAGS)
.PHONY:clean
clean:
-rm -rf *.o
main 目錄下的makefile如下:
vpath %.h ./include ./add
vpath %.o ./add
CC = g++
#像下層makefile傳遞變量CFLAGS
export CFLAGS = -g
add_make=./add/makefile
main:main.o add.o
$(CC) -o $@ $^
main.o:main.c
$(CC) -o $@ -c $^ -I ./include -I ./add $(CFLAGS)
#-include $(add_make)
add.o:
$(MAKE) -C add
-cp add/$@ .
.PHONY:clean
clean:
-rm -rf *.o main core.*
$(MAKE) clean -C add
此時我們可以遞歸調用make clean,執行過程如下: