如何寫Makefile


在該文開始之前,在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”。

再順便提一下自動變量,$@ 擴展成當前規則的目的文件名, $< 擴展成依靠列表中的第一個依賴文件,而 $^ 擴展成整個依賴的列表(除掉了里面所有重 復的文件名)。讀者根據輸出內容自行查看情況。

五、總結

本文打算到這里就告一段落,由於時間關系,的確沒有什么時間可以去認真而又詳細的去描述一個知識點,這里只是一個框架,模板,在實際工程中也是相當復雜的。總之,不斷努力多摸索才能得到一些更深的體會,歡迎各位朋友討論。日后若有時間再寫其他一些更多的內容。最后費時較多,轉載注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM