在該文開始之前,在chianunix推薦一篇有關Makefile的論壇文章“跟我一起寫Makefile”:http://www.chinaunix.net/old_jh/23/408225.html
在csdn見陳浩專欄:http://blog.csdn.net/haoel/article/details/2886#comments
而本文主要關注如何有效的編寫一個實用的Makefile,達到在短時間內可以利用Makefile進行編程的目的,所以會略去很多詳細的細節,對此有疑問的可以參照上文鏈接進行閱讀。本文分兩塊來闡述,一是makefile和源文件在同一目錄下,二是makefile和源文件不在同一目錄下,下面將會針對這兩種情況來闡述。
一、同目錄下的makefile
1.簡單運用
首先,假定在同一目錄下有這樣幾個文件:a.c b.c c.c h.h mian.c。
其中a.c的內容為:
#ifdef __cplusplus extern "C"{ #endif // _cplusplus #include <stdio.h> int fun_a() { printf("called fun_a!\n"); return 0; } #ifdef __cplusplus } #endif // _cplusplus
h.h的內容為:
#ifndef __HEADER__ #define __HEADER__
#ifdef __cplusplus extern "C"{ #endif // _cplusplus int fun_a(); int fun_b(); int fun_c(); #ifdef __cplusplus } #endif // _cplusplus
#endif // __H_H_H__
main.c的內容為:
#ifdef __cplusplus extern "C"{ #endif // _cplusplus #include "h.h" int main() { fun_a(); fun_b(); fun_c(); return 0; } #ifdef __cplusplus } #endif // _cplusplus
限於本文篇幅不再展示b.c的內容和c.c的內容,與a.c基本相同,唯獨不同的是二者函數名稱分別為fun_b 和 fun_c,並在函數體內輸出調用該函數的語句,在此不在列舉。
然后在該目錄下新建一個名為“Makefile”的文件,注意沒有引號並且沒有后綴,直接命名為Makefile,然后在其中輸入以下內容:
1 test : a.o b.o c.o main.o 2 cc -o test a.o b.o c.o main.o 3 4 a.o : a.c 5 cc -c a.c 6 7 b.o : b.c 8 cc -c b.c 9 10 c.o : c.c 11 cc -c c.c 12 13 main.o : main.c h.h 14 cc -c main.c 15 16 clean : 17 rm test.exe a.o b.o c.o main.o
保存后,進入該目錄直接輸入make命令。然后輸入./test.exe即可看到程序輸出結果。先對該Makefile解釋如下。
保存后,進入該目錄直接輸入make命令。然后輸入./test.exe即可看到程序輸出結果。這時若查看目錄,則會發現多了這樣幾個文件:a.o b.o c.o mian.o test.exe ,接着輸入make clean,再次查看目錄則發現以上多出的文件已經不存在了。現對該Makefile解釋如下。
首先第一行test : a.o b.o c.o main.o 其前段test:為該工程最后的目標名,即我們要生成一個名為test.exe的可執行文件。然而為了生成這樣一個文件,它依賴於a.o b.o c.o main.o這些目標文件。然后執行第二句命令用所依賴的文件生成最終目標—test.exe,並且每一行命令必須用tab開頭。
接着就會發現a.o 又是依賴於a.c ,為了得到a.o 就會執行第五行生成a.o,同理生成b.o 和 c.o 以及main.o。至此,當我們輸入make命令時,make命令所執行的就是以上步驟,注意它並沒有執行第16和第17兩行。當我們輸入make clean時才開始執行這兩行語句,用來刪除中間文件和最后的.exe文件,就是上文提到的那幾個多出的文件。
注意:寫Makefile時盡量注意復制粘貼,粘貼別人的makefile有時可能會出問題,因為在粘貼過程中破壞了命令行前的tab。所以這時最后自己再檢查一遍。
現在,你可以仿照該Makefile在同一目錄下寫自己任何想寫的程序了。下面再來看看其他一些技巧。
2.使用變量
再次對該Makefile中的內容改成以下內容。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 6 a.o : a.c 7 cc -c a.c 8 9 b.o : b.c 10 cc -c b.c 11 12 c.o : c.c 13 cc -c c.c 14 15 main.o : main.c h.h 16 cc -c main.c 17 18 clean : 19 rm test.exe $(Objs)
然后同樣輸入make 和 ./test.exe查看輸出內容,然后make clean,就會發現與前文所說一樣。這不過在這次的Makefile中我們使用了變量,即makefile第一行Objs = a.o b.o c.o main.o,這行表示將a.o b.o c.o main.o賦給Objs,即可以通俗的認為現在Objs里就是a.o b.o c.o main.o,而為了a.o b.o c.o main.o,可以使用美元符$,即$(Objs)現在就是a.o b.o c.o main.o,當然以下就不用多說了,用$(Objs)生成最終目標,最后刪除$(Objs)。
注意:也可以用${Objs}來引用a.o b.o c.o main.o,讀者可自行測試,但一般的“潛規則”直接使用小括號。
3.自動推導
繼續將該Makefile改成以下內容,然后執行同上文一樣的操作。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 a.o : a.c 6 b.o : b.c 7 c.o : c.c 8 main.o : main.c h.h 9 10 clean : 11 rm test.exe $(Objs)
這次我們沒有顯示的執行生成中間目標文件的命令,如cc -c a.c。但Makefile卻工作完好,這時因為make命令具有自動推導的功能,即只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關系中,並且 cc -c whatever.c 也會被推導出來;即如果make找到一個whatever.o,那么whatever.c,就會是whatever.o的依賴文件。所以這次的Makefile變得更加清爽。
既然make可以自動推導,那我們像下面這樣會不會有問題了?
Objs = a.o b.o c.o main.o test : $(Objs) cc -o test $(Objs) clean : rm test.exe $(Objs)
當然沒有問題,這次讓make自己去全部利用.o去推導相應的.c。
注意:以上文件因為都是自身並沒有包含其他頭文件,即a.c 就是簡單的實現一個函數,沒有其他頭文件引入,所以可以全部略去a.o的生成命令。若其還包含其他頭文件,則要顯示引入該頭文件,如a.o:other.h,然后a.c 文件make自己推導。
4.偽目標
再次對該Makefile改成以下內容。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 6 .PHONY : clean 7 clean : 8 -rm test $(Objs)
這次多了第6行——.PHONY : clean;還記得前面我們刪除中間文件時都輸入“make clean”,這時才會執行clean:行下的刪除文件命令。其實,這個clean就是一個標簽,make命令本身並不認識,所以make的時候它不會執行,但如果顯示的調用時——make clean ,這時make就會知道該執行clean:這句下面的命令了,所以起到了刪除文件的效果。當然,你可以將它改成你喜歡的任何名字,如I_Like:,然后make I_Like同樣執行位於I_Like下的命令。但這時就會產生問題,若你有個文件名為clean,直接你再執行刪除命令make clean時卻發現起不到任何效果。
這時使用. PHONY : clean ,即使你現在有一個名為clean的文件,也不會對make clean的作用起到任何影響。申明這句后,輸入make XXX時,make只執行Makefile中的XXX。
還有在rm test $(Objs)的前面多了一個“-”,這個只是用來忽略執行該語句的錯誤,即執行該語句產生的任何錯誤都將會被忽略掉,以使位於該語句下面的語句得以執行,否則,該語句錯誤,則make會直接返回而不執行以下的語句。讀者可以自行測試這種情況,如加入幾行rm命令(本文全是一行),依次刪除一個文件,看看效果。
二、不同目錄下的Makefile
1.環境變量
這次在同一目錄下建立這樣幾個文件夾:DirA, DirB, DirC, Include, Main,分別將上面的a.c ,b.c, c.c, h.h, main.c放到這幾個目錄下,然后在和這些文件夾同一目錄下建立一個Makefile。輸入以下內容。
1 Objs = DirA/a.o DirB/b.o DirC/c.o Main/main.o 2 CFLAGS += -I./Include 3 4 test : $(Objs) 5 cc -o test $(Objs) 6 7 .PHONY : clean 8 clean : 9 -rm *.exe $(Objs)
其他大家現在都應該已經明白,唯獨第二行是看起來比較詭異的。CLFAGS是環境變量,由於時間關系,很多東西都介紹不了,細節大家自己去查找資料參照。這里只是告訴make去Include文件夾下去找需要的文件。並且也可以將$(CFLAGS)添加到第五行末尾,或者不加(如本文)。
2.其他
再次更改該Makefile內容如下。
1 Objs = DirA/a.o DirB/b.o DirC/c.o Main/main.o 2 CFLAGS += -I./Include 3 Target = test 4 5 all : $(Target) 6 $(Target) : $(Objs) 7 $(CC) -o $(Target) $(Objs) 8 9 .PHONY : clean 10 clean : 11 -$(RM) $(Target) $(Objs)
繼續執行輸入make或者make all,將會看到效果是一致的,唯獨$(CC)和$(RM)讀者已經猜到就是cc和rm,這樣我們的makefile更加通用。
3.使用函數
同樣,將Makefile內容改成以下內容。
1 Sources = $(wildcard DirA/*.c) $(wildcard DirB/*.c) $(wildcard DirC/*.c) 2 3 Sources += $(wildcard Main/*.c) 4 Objs = $(Sources:%.c=%.o) 5 CFLAGS += -I./Include 6 Target = test 7 8 all : $(Target) 9 $(Target) : $(Objs) 10 $(CC) -o $(Target) $(Objs) 11 12 .PHONY : clean 13 clean : 14 -$(RM) $(Target) $(Objs)
這次不同的是使用了函數wildcard,第一行表示分別從DirA ,DirB,DirC中取出所有的以.c結尾的文件並存在Sources中,第二句則為為Sources追加值,即將從main文件夾里取出的.c文件也存到Sources中去。第四句則是將Sources中所有以.c結尾的文件全部替換成.o結尾的文件,其他同於前文。
三、其他應用
再次更改Makefile文件內容如下:
1 ProjectDir = ./ 2 ConfigMakefile = $(ProjectDir)/Makefile.config 3 -include $(ConfigMakefile) 4 5 Sources = $(wildcard DirA/*.c) $(wildcard DirB/*.c) $(wildcard DirC/*.c) 6 Sources += $(wildcard Main/*.c) 7 8 CFLAGS += -I./Include 9 Target = test 10 11 GlobalMakefile = $(ProjectDir)/Makefile.global 12 -include $(GlobalMakefile)
同時另外新建兩個文件,名為:Makefile.config和Makefile.global。分別在其中輸入以下內容:
#This is Makefile.config
CC = gcc
RM = rm
#This is Makefile.global
Objs = $(Sources:%.c=%.o)
all : $(Target)
$(Target) : $(Objs)
$(CC) -o $(Target) $(Objs)
.PHONY : clean
clean :
-$(RM) $(Target).exe $(Objs)
然后輸入make,make clean等驗證結果。
現簡要說明Makefile,前三行分別是指定當前工程目錄為當前目錄,指定ConfigMakefile 為當前目錄下的Makefile.config,接着加入該文件,4——9行同前文。11——12行同Makefile.config文件。
四、舉例
經過這些,在短時間內寫一些Makefile應該問題不大,或者手頭要用的時候可以套用,完了再回頭來看其他細節問題。現最后舉一例來復習和看看新的東西。
假設我們的目錄是在"E:\makefile\test2",將我們本文中用到的a.c b.c c.c main.c 和h.h都放在該目錄下,在同目錄下寫一Makefile,其內容如下:
1 CROSS_COMPILE := 2 3 TARGET = $(notdir $(CURDIR)) 4 5 C_FLAGS += -Wall -g 6 7 LD_FLAGS += -lpthread 8 9 SOURCES = $(wildcard *.c) 10 HEADERS = $(wildcard *.h) 11 12 OBJFILES = $(SOURCES:%.c=%.o) 13 14 DATE := $(shell date '+%Y%m%d') 15 16 all: clean $(TARGET) 17 18 $(TARGET): $(OBJFILES) 19 @echo Linking $@ form $^... 20 $(CROSS_COMPILE)gcc $(LD_FLAGS) -o $@ $^ 21 cp $(TARGET) $(TARGET)-$(DATE) 22 $(OBJFILES): %.o: %.c $(HEADERS) 23 @echo Compiling $@ from $<... 24 $(CROSS_COMPILE)gcc $(C_FLAGS) -c -o $@ $< 25 clean: 26 @echo Removing files 27 @rm -rf $(OBJFILES) $(TARGET) *~ *.d .dep
然后執行make,會看到以下信息:
1 $ make 2 Removing files 3 Compiling b.o from b.c... 4 gcc -Wall -g -c -o b.o b.c 5 Compiling main.o from main.c... 6 gcc -Wall -g -c -o main.o main.c 7 Compiling a.o from a.c... 8 gcc -Wall -g -c -o a.o a.c 9 Compiling c.o from c.c... 10 gcc -Wall -g -c -o c.o c.c 11 Linking normal form b.o main.o a.o c.o... 12 gcc -lpthread -o normal b.o main.o a.o c.o 13 cp normal normal-20121122
簡單說明下:第一行輸出為Makefile第26行,@echo 的作用是顯示其后的內容,所以在Makefile中適當加入信息可以查看Makefile的執行情況,若去掉@,直接用echo,則終端會連echo一起輸出,讀者自行驗證。Makefile第27行前也加了@,對命令加@后,則在終端不會顯示該命令直接執行。
Makefile第1行為交叉編譯,它的賦值是采用“:=”形式的,這里隨便提一下,Makefile中的賦值可以是先賦值后聲明,如:a = $(b) b=2。但這樣問題是如果這樣賦值:a=$(b) b = $(a)就會出現問題,所以采用“:=”這種形式就是只能賦給已經做出聲明的變量。
Makefile第3行 中的 notdir 為一函數,該函數作用是提取取文件函數,即提取目錄地址中“\”后的內容,而$(CURDIR)表示當前目錄,所以TARGET最后應該等於我們的目錄后的最后一位“test2”。
再順便提一下自動變量,$@ 擴展成當前規則的目的文件名, $< 擴展成依靠列表中的第一個依賴文件,而 $^ 擴展成整個依賴的列表(除掉了里面所有重 復的文件名)。讀者根據輸出內容自行查看情況。
五、總結
本文打算到這里就告一段落,由於時間關系,的確沒有什么時間可以去認真而又詳細的去描述一個知識點,這里只是一個框架,模板,在實際工程中也是相當復雜的。總之,不斷努力多摸索才能得到一些更深的體會,歡迎各位朋友討論。日后若有時間再寫其他一些更多的內容。最后費時較多,轉載注明出處。