Linux下的Makefiles概述

從codeproject看到一篇文章,翻譯一下

http://www.codeproject.com/KB/cpp/makefiles_linux.aspx

文章中的sample和翻譯文檔的PDF文件已經打包上傳到資源區:http://download.csdn.net/source/991340

 

Makefiles in Linux: An Overview

By Ciro Sisman Pereira

Linux下的Makefiles概述

By [email protected]  2009-02-04

 

緒論

只有幾個模塊的C/C++程序是很容易管理的。開發者通常只需要直接用源文件名作爲輸入參數調用編譯器重新編譯即可。那是一種簡捷的方式。然而,當工程擁有越來越多的源文件而變得越來越複雜的時候,使用工具來幫助開發者管理工程變得十分必要。

 

我要介紹的這個工具就是"make"命令。"make"命令不僅僅能幫助開發者編譯程序,而且它還能被用來控制通過多個輸入文件來如何產生多個輸出文件。

 

這篇文章不是一個完整的make學習指南,只是專注於怎麼使用"make"命令和"makefile"來編譯用C語言寫的程序。本文同時提供了一個zip文件,該文件內部按照不同的目錄包含了多個示例的C語言工程。在這些示例文件中,我們關心的是"makefiles"而不是那些C語言的源代碼。你需要下載這些示例文件並通過"unzip"命令或其它的工具解壓縮這些文件。因此,爲了更好的理解這篇文章:

1、下載"make_samples.zip"

2、打開一個終端會話

3、在你的HOME目錄創建一個用於實驗的目錄

4、將"make_samples.zip"移到這個創建的目錄下

5、解壓縮這個文件:unzip make_samples.zip

 

目錄

1、Make工具:語法概述

2、makfiles的基本語法

    測試示例1

    測試示例2

3、Makefile中的假目標、宏和特殊的字符

   測試示例3

4、後綴規則:Makefiles語法簡介

    測試示例4-mkfile1

    更多的特殊字符

    測試示例4-mkfile2

    測試示例4-mkfile3和makfile4

5、在一個makefile中調用其它的makefiles

    測試示例5

6、總結

 

1、Make工具:語法概述

make 的命令語法是:

make [選項] [目標]

你可以在終端下輸入make --help 來獲得make支持的命令行選項。介紹所有的make選項顯然以及超出了本文的範圍,本文的重點是介紹makefile的結構以及它是如何工作的。出現在makefile中的目標是一個標記(或定義的一個名字),本文的稍後會介紹。

 

make需要由makefile來告訴它你的應用程序是怎麼編譯出來的。makefile通常和其它源代碼文件放在同一個目錄下,而且它的文件名可以是任意的。例如:如果你的makefile的文件名時"run.mk" 那麼你可以這樣執行make命令:

make -f run.mk

-f 選項告訴make命令:make命令需要處理的makefile文件是run.mk

 

-f選項不是必須的,有兩個特殊的makefile文件名可以不使用-f選項:"makefile"和"Makefile"。如果你運行"make"命令而沒有制定makefile的文件名,那麼make就會首先在當前目錄下查找是否有"makefile"這個文件。如果"makefile"不存在,那麼make會繼續尋找是否有"Makefile"這個文件。如果在當前目錄下,這兩個文件都存在,而且你運行make命令時也沒有指定makefile的文件名,如:

make <enter>

那麼make命令將會調用"makefile"來作爲其輸入的makefile文件,忽略掉"Makefile".在這種情況下,你如果想要使用"Makefile"這個文件作爲make命令的輸入maeifile,那麼你可以使用:make -f Makefile

2、makfiles的基本語法

lnxmk_01

圖1:Makefile通用語法

 

一個makefile由目標、依賴和規則組成。目標通常是一個將要創建或更新的文件。目標依賴於源代碼集或在依賴列表中描述的其它目標。規則是使用依賴列表創建目標的必要命令。

 

正如你在圖1中所看到的一樣,規則中的在每行命令都必須以<TAB>鍵開頭。如果使用<SPACE>鍵將會引起錯誤,同樣如果在每個規則行的末尾包含有空格也會引發make的錯誤。

 

Make命令通過比較makefile中目標所依賴的源文件的日期和時間(時間戳)來決定怎麼編譯目標文件。如果任何一個依賴目標的時間戳比最後一次通過make構建目標的時間要新,那麼make命令就會執行相關的make規則來構建新的目標文件。

2.1測試示例1

下面我們來做一個實驗,我們使用源代碼中的sample1.這個目錄下共有4個文件:

App.c and inc_a.h: 一個簡單的C應用程序。

Makefile.r: 一個正確的makefile(擴展名.r表示正確的)

Makefile.w:一個不完整的或書寫錯誤的makefile(擴展名.w表示錯誤的)。但這不意味着這個文件有語法錯誤。

 

看下面的示例:

mkfile.r

# This is a very simple makefile

 

app: app.c inc_a.h

cc -o app app.c

and 

mkfile.w 

# This is a very simple makefile

 

app: app.c 

cc -o app app.c

 

表面上來看,這兩個文件的差異毫不相干,但在特定情況下它們確實是相關的。首先我們先檢查一下makefile的部分:

a、 在makefile中字符"#"表示插入註釋內容。從"#"後面開始一直到行結束的內容會全部被make忽略掉。

b、 App是要生成的目標。

c、 App.c和inc_a.h是目標app的依賴文件(inc_a.h只存在於makefile.r中)。

d、 "cc -o app app.c"是目標的依賴列表中有任何改變後重新編譯的規則。

 

下面我們通過一系列的命令來舉例說明他們是如何工作的(如圖2):

lnxmk_02

圖2:sample1的命令序列

 

1、 使用make 命令調用mkfile.r 這個makefile來創建app這個可執行性文件。

2、 爲了使make重新編譯app,我們將app 刪除掉。

3、 使用make命令調用mkfile.w這個makefile來創建app這個可執行性文件,獲得和步驟1一樣的結果。

4、 使用make 命令調用mkfile.r 這個makefile來創建app這個可執行性文件,因爲目標是最新的,所以這次不會執行任何規則。

5、 使用make命令調用mkfile.w這個makefile來創建app這個可執行性文件,和步驟4一樣的結果。

6、 使用touch命令來改變inc_a.h文件的時間戳,來模擬修改了文件內容。

7、 Mkfile.w這個makefile不能檢測到inc_a.h這個文件已經被修改了。

8、 Mkfile.r這個makefile能檢測到inc_a.h這個文件已經被修改,它能按照我們的預期重新產生目標app。

9、 使用touch命令來改變app.c文件的時間戳,來模擬修改了文件內容.

10、 Mkfile.w這個makefile能按照預期重新產生目標app。

11、 使用touch命令來改變app.c文件的時間戳,來模擬修改了文件內容。

12、 Mkfile.r這個makefile能按照預期重新產生目標app。

現在你應該知道mkfiel.w這個makefile是如何被認爲是一個錯誤的或不完整的makefile了。因爲它所描述的目標app的依賴列表中沒有包含inc_a.h這個文件,所以也就不能檢測到inc_a.h這個文件的時間戳,從而沒能按照我們的預期執行make規則。

2.2測試示例2

Sample2是另外一個簡單的makefile例子,但它不只一個目標。同樣,還是有兩個makefile:mkfile.r和 mkfile.w 來舉例說明正確的和錯誤的編寫makefile的方法.

 

正如你所注意到的,最終的可執行文件(目標 app)由三個哦bject文件組成:main.o、 mod_a.o 和mod_b.o。每個object都是其依賴列表中源文件的目標文件。

 

這兩個makefile都是完整的。它們最大的不同就是目標在makefiel中出現的順序不一樣而已。

看下面的示例:

mkfile.r 

app: main.o mod_a.o mod_b.o 

cc -o app main.o mod_a.o mod_b.o

main.o: main.c inc_a.h inc_b.h

cc -c main.c 

mod_a.o: mod_a.c inc_a.h

cc -c mod_a.c

mod_b.o: mod_b.c inc_b.h 

cc -c mod_b.c

and

mkfile.w 

main.o: main.c inc_a.h inc_b.h

cc -c main.c 

mod_a.o: mod_a.c inc_a.h

cc -c mod_a.c

mod_b.o: mod_b.c inc_b.h 

cc -c mod_b.c

app: main.o mod_a.o mod_b.o 

cc -o app main.o mod_a.o mod_b.o

下面我們通過一系列的命令來舉例說明他們是如何工作的(如圖3):

lnxmk_03

圖3:sample2的命令序列

 

1、make命令調用mkfile.w這個makefile來產生目標,並且第一個規則被執行。

2、刪除所以得.o文件。

3、make命令調用mkfile.r這個makefile來產生目標,並且所有的模塊都被正確的創建。

4、目標app也能被執行。

5、刪除所有的.o文件。

6、make命令調用mkfile.w這個makefile來產生目標,同時把目標app作爲make命令的輸入參數,所有的模塊都被正確的創建。

 

那麼mkfile.w這個makefile到底錯在哪裏呢?從技術上講,當你使用步驟6種的方法時mkfile.w也是正確的。然而,當你不顯示的指明make的目標時,make命令只會處理makefile中的第一個目標。在mkfile.w中,第一個目標是main.o,而這個目標只是告訴make命令從main.c、inc_a.h和inc_b.h中產生main.o這個目標,而無須作其它的事情。Make命令不會處理接下來的目標。

 

注意:makefile中的第一個目標決定了make如何處理其它的目標以及處理這些目標的順序。因此,makefile中的第一個目標應該是最主要的目標,這個目標可以使依賴於其它的目標而產生的。

我們再來看目標app,它被放在兩個makefile中的不同位置,但它們都具有相同的語法表示。而且圖3中的步驟3和步驟6都能產生同樣的結果:

a)  目標app告訴make命令它又三個依賴文件:main.o mod_a.o mod_b.o,make應該在產生可執行性的app之前先處理這三個依賴文件。

b) 然後,make卡時查找目標main.o,並處理它的依賴關係和編譯規則

c) 接着是處理mod_a.o的依賴關係和編譯規則。

d) 最後是處理mod_b.o的依賴關係和編譯規則。

e) 這三個目標被創建後,目標app的編譯規則被處理,app可執行性文件被產生。

 

3、Makefile中的假目標、宏和特殊的字符

有時候makefiel中的目標並不是要產生一個文件,而只是執行一些其它的動作,那麼這樣的目標被稱爲假目標。

例如:

getobj:

mv obj/*.o .  2>/dev/null

 

目標getobj執行一個簡單的動作將obj目錄下的所有.o文件移到當前目錄下。可能你會有疑問:"如果obj目錄下沒有任何文件執行這樣的動作會怎麼樣的?"這是一個好問題,在這種情況下,mv命令將會向make命令返回一個錯誤。

 

注意:make命令在執行規則時檢測到錯誤時,其缺省行爲將會終止執行make命令。

 

當然,obj目錄下的確有可能是空目錄。那麼這時該如何避免make命令遇到這個錯誤是不要終止執行makefile中的其它內容呢?

 

你可以使用一個特殊的字符"-"(減號)來處理mv命令,如:

getobj:

-mv obj/*.o .  2>/dev/null

 

"-"告訴make命令忽略錯誤。這兒還有一個特殊的字符:"@" 它告訴make在執行命令前不要將命令的內容輸出到標準輸出設備上。你可以將這兩個字符組合在一起來處理命令:

getobj:

-@mv obj/*.o .  2>/dev/null

 

還有一個特殊的假目標:all。使用all這個假目標,你可以將make的目標分組,並引導make命令來處理所有這些目標。

例如:

all: getobj app install putobj

 

make命令將會按順序執行目標:getobj, app, install and putobj. 

另外一個有趣的特性:make命令能支持makefile中的宏定義。我們可以這樣定義宏:

MACRONAME=value

並且通過$(MACRONAME) or ${MACRONAME}來訪問這個宏的值。例如:

EXECPATH=./bin

INCPATH=./include

OBJPATH=./obj

CC=cc

CFLAGS=-g -Wall -I$(INCPATH)

當上面的內容被執行時,make將$(MACRONAME)替換成實際的定義內容。

3.1測試示例3

Sample3 是一個使用了宏、假目標和特殊字符的較複雜的makefile示例。在sample3的目錄下有3個子目錄:

a) include -存放所有的.h文件

b) obj -編譯後產生的obj文件搬移到這個目錄下和重新編譯之前將這些obj文件搬回到原來的位置。

c) bin -最終的可執行性文件被拷貝到這裏。

 

所有的.c文件和makefile一起都放在sample3的根目錄下。當這個makefile的文件名是"makefile"時,調用make命令時就不需要有使用-f參數。

 

所有的文件被分類放在不同的目錄下使這個例子更接近實用。Makefile的內容如下:

lnxmk_04

圖4:sample3的makefile內容

行號是不會出現在makefile中的。在這裏表上行號只是爲了更好的分析它。

a) 第7-13行:定義了一些宏

a) INSTPATH,  INCPATH and OBJPATH 表示子目錄. 

b) CC and CFLAGS 表示編譯器和通用的編譯器選項。注:你可以將CC改稱gcc。 

c) COND1 and COND2 表示執行一系列的命令。

 

b) 第17行:假目標all作爲makefile的第一個目標。目標all按從左到右定義了這些目標的生成順序。

目標getobj是第一個依賴目標,其次是app install和putobj。

c) 第19-29行:列出了所有的目標是如何產生的。使用CC和CFLAGS這兩個宏,在make執行時會替換成這些宏的值,$(CC)被替換成cc,CFLAGS被替換成-g -Wall -I./include.所以第20上被make翻譯成如下的內容:

-g -Wll -I./include -o app main.o mod_a.o mod_b.o

d) 第31-34行:列出了目標getobj和putobj。這兩個假目標是爲了幫助或組織編譯過程的:

a) 目標getobj 被目標all作爲第一個依賴文件和最先被執行。在編譯之前它將所有的obj文件按從 obj 目錄($(OBJPATH)) 搬到sample3的根目錄下。因此,make 命令可以比較這些object文件的時間戳來重新編譯被改變了那些源文件。 

b) 目標putobj 與目標getobj 起相反的作用。在成功編譯後,將所有的object文件移到obj目錄下。

e) 第38行:目標install是另一個假目標,用來說明如何使用 if 這個shell語句,shell編程已經超出了本文的範圍,所以你可以使用google來獲得更多有關這方面的信息本文的後續內容會講解目標isntall的作用。

f) 第47行:目標cleanall 刪除所有的目標文件以強迫make命令重新產生這些文件。在正常編譯時不會調用到這個目標。你需要通過給make命令傳遞參數的方式來調用它:

make cleanall

 

你可能也已經注意到在目標getobj, putobj, install and cleanall.前的那些特殊字符(- and @)。如之前的說明,- 告訴make命令即使出現了錯誤也繼續處理餘下的makefile內容;@告訴make命令在執行命令前不要將該命令向標準輸出設備打印出來。

 

注意:在目標install的每一行都用"/"結尾,且"/"是每行的最後一個字符(其後不允許有空格),否則make命令會出現如下的錯誤:

line xxx: syntax error: unexpected end of file

其中xxx表示這一行是代碼塊的開始。

實際上,當你使用"/"來組合一組命令時,"/"必須是其所在行的最後一個字符。

下面我們試驗如下的命令序列(圖5):

lnxmk_05

圖5:sample3的命令序列

1. 因爲makefile的文件名是"makefile",所以make 命令不需要輸入參數。 

o 1.1 如果 obj  目錄下沒有任何文件,那麼 getobj 將會出現錯誤. 在mv命令之前的-(減號)阻止了make命令因這個錯誤而終止執行(第32行)。

o 1.2 這個消息只有當if條件爲TRUE (line 39)纔會出現。if 語句前的@字符使得該語句下的所有語句都不會被打印出來。這個條件語句比較由COND1 and COND2 宏(lines 12-13)展開的兩個字符串。這個條件語句結合了shell命令來判斷目標app and ./bin/myapp 的時間戳是否不同。如果不同那麼目標app 將以myapp 的名字被拷貝到 ./bin 目錄下,同時改變該文件的訪問權限。如果沒有這個if條件,那麼目標 install將會在make執行的每次都會被執行。

2. make 再次被執行,但只有目標 getobj and putobj 被執行,同時沒有編譯新的目標文件,目標install也沒有被執行。

3. touch 命令改變了inc_a.h 文件的時間戳。

4. make 命令被調用,但只有依賴於文件inc_a.h的那些目標被編譯。 

5. 列出目錄./bin 的內容,注意 myapp 的文件權限。

6. 使用目標cleanall的例子。

4、後綴規則:Makefiles語法簡介

當你的工程由於越來越多的源文件和變得越來越複雜的時候,爲每一個源文件創建一個目標和依賴關係列表變得不太現實。例如,如果工程中有20個.c的文件來編譯出一個可執行性文件,而且每個源文件都是用同樣的編譯器參數,那麼你可以採用一種方式指導make命令向每個源文件使用同樣的命令參數。

 

這種方式就是我們將要介紹的後綴規則或基於文件擴展名的規則。例如,下面的後綴規則:

.c.o:

    cc -c $<

這個規則告訴make命令:每個目標文件都以.o爲擴展名,而且每個.o都依賴於與他同名的.c文件(同樣的文件名,只改變了文件擴展名)。因此,文件main.c將會產生一個main.o的文件。注意:.c.o 不是一個目標,只是兩個擴展名(.c 和.o)。

後綴規則的語法如下:

lnxmk_06

圖6:後綴規則語法

特殊字符$<將會在後面介紹。

4.1測試示例4-mkfile1

下面我們來試驗sample4。這個例子中有幾個makefile,第一個mkfile1:

.c.o:

    cc -c $<

這個makfile只包含了一個後綴規則。

我們來試驗如下的命令序列(圖7):

lnxmk_07

圖7:sample4的命令序列-mkfile1

1. make 命令調用mkfile1 這個makefile。由於沒有定義目標,所以make不會做任何事情。

2. make 命令再次被調用,並以目標main.o 作爲其輸入參數。這次make 命令根據.c.o 的後綴規則編譯了main.c 。

3. make 命令被調用,並傳遞了兩個目標參數。 

4. make命令被調用,並傳遞了三個目標參數,由於這些目標已經被編譯了,所以這次命令不會再產生它們。 

這裏有個問題點需要理解,mkfile1只定義了後綴規則,沒有定義目標,那麼main.c是如何被編譯的呢?

如果mkfile1中定義瞭如下的main.o的目標,那麼make命令將會編譯main.o:

main.o: main.c

    cc -c main.c

因此,.c.o 後綴規則告訴make命令:對於每個xxx.o目標都必須依賴於一個xxx.c來編譯。

如果make 命令被如此調用:

make -f mkfile1 mod_x.o

那麼make命令將會返回一個錯誤,因爲這個目錄下沒有moc_x.c這個文件。

4.2 更多的特殊字符

在後綴規則中使用的 $< 是什麼意思呢? 它的含義是:當前依賴的文件名。在使用.c.o 這個後綴規則時, 當規則被執行時 $< 將被解釋成 xxxxx.c 文件. 還有其它的一些特殊字符:

$? 

比當前目標的時間戳更新的依賴文件列表

$@

當前目標的文件名

$<

當前的依賴文件名

$*

當前的依賴文件名(不包含擴展名)

下面的示例將會介紹其它的這些特殊字符。

4.3 測試示例4-mkfile2

mkfile2 展示了後綴規則的另一種是用方法,將file.txt改名爲file.log:

.SUFFIXES: .txt .log

.txt.log:

@echo "Converting " $< " to " $*.log

mv $< $*.log

關鍵字.SUFFIXES: 告訴make 命令哪些擴展名將會在makefile 中使用。在mkfile2中將使用擴展名.txt and .log。有一些擴展名像.c and .o 默認就被makefile支持的,無須使用關鍵字.SUFFIXES來定義。

我們來試驗下面的命令序列(圖8):

lnxmk_08

圖8:sample4的命令序列-mkfile2

1. 創建一個file.txt 文件。

2. make 命令被調用,同時將file.log 作爲其目標參數。

後綴規則.txt.log 告訴make 命令:對於每個xxx.log目標,都必須由一個xxx.txt文件依賴產生。這個命令的執行方式等同於將目標file.log 定義在mkfile2文件中:

file.log: file.txt

    mv file.txt file.log

3. 展示文件file.txt 被重命名爲file.log. 

 

 

4.4 測試示例4-mkfile3和makfile4

到目前爲止,我們知道了如何定義後綴規則,但還沒有在實際項目中使用這個規則。接下來我們就使用一個更接近實際工程應用的例子sample4.

先看文件mkfile3:

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o 

後綴規則被放到了目標app之前。我們來試驗如下的命令序列(圖9): 

lnxmk_09

圖9:sample4的命令序列-mkfile3

1. make 命令調用mkfile3。Make命令讀取目標 app 並處理它的依賴關係: main.o, mod_a.o and mod_b.o -根據後綴規則.c.o make 命令知道:對於每一個xxx.o都依賴於xxx.c來產生。

2. make命令再次調用,因目標app 已經是最新的了,所以make不產生任何目標文件。

3. main.c 的訪問時間被更新。

4. make 命令按照預期重新編譯 main.c 。

5. make命令再次調用,因目標app 已經是最新的了,所以make不產生任何目標文件。

6. inc_a.h 文件的時間戳被更新(它被文件 main.c and mod_a.c所包含) make 命令需要重新編譯這些目標文件。

7. make 命令被調用,但沒有按照預期的結果執行?

那麼步驟7出了什麼問題呢?原來我們沒有告訴make 命令文件main.c or mod_a.c 依賴於inc_a.h。

一種爲每個目標寫出依賴關係列表的解決方法:

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o

main.o: inc_a.h inc_b.h

mod_a.o: inc_a.h

mod_b.o: inc_b.h

你可以編輯和添加上面的三行到mkfiel3種,並試驗圖9中的命令序列。在試驗之前採用下面的命令先刪除掉object文件:

rm -f *.o app

當然,爲每個模塊列舉出相對應的依賴文件列表示行的,但不實用。想象一下一個擁有50個.c文件和30.h文件的工程,得打多少字!

Mkfile4中提供了一個更實用的解決方法: 

OBJS=main.o mod_a.o mod_b.o

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o

$(OBJS): inc_a.h inc_b.h

下面我們來試驗如下的命令序列(圖10):

lnxmk_10

圖10:sample4命令序列-mkfile4

1. make 命令被調用,同時makefile使用 mkfile4 。

2. 文件mod_a.c 的時間戳被更新。

3. make 命令按照預期重新編譯 mod_a.c. 

4. inc_a.h (that is included by main.c and mod_a.c) 的時間戳被更新。 

5. make 命令重新編譯了所有的目標文件? 

在步驟5種爲什麼文件 mod_b.c 會被重新編譯呢?

mkfile4 定義了 inc_a.h 是 mod_b.c 的依賴源,而實際上mod_b.c和inc_a.h沒有依賴關係(mod_b.c中沒有包含inc_a.h)。而makefile卻告訴make 命令inc_a.h and inc_b.h 是所有目標的依賴源。這樣編寫mkfile4 是因爲這樣更實用,而且不會出錯。當然你也可以爲每個模塊單獨列出依賴的頭文件。

提示:當處理大型工程時,我更傾向於只將一些主要的頭文件放到依賴關係中。也就是說,這些主要的頭文件會被其它所有的模塊文件按所包含。

5、在一個makefile中調用其它的makefile

當你工作於一個包含了不同模塊如:靜態庫文件,動態鏈接庫,可執行文件的大型工程時,將它們分門別類的放到各自的目錄下,不失爲一個好主意。這樣每個源代碼目錄就可能有一個各自的makefile ,同時它可以被稱爲master makefile的makefile所調用。

master makefile 是放在工程源文件根目錄下的一個makefile。它能調用每個子目錄下的各個模塊的makefile,同時它的內容應該很簡單。

這裏有一個竅門:你需要知道如何強迫make命令改變到其它的目錄下。我們先來看一個例子(文件名爲makefile):

target1:

@pwd     

cd dir_test     

@pwd

通過make命令調用的結果如下:

lnxmk_11

圖11:一種錯誤的改變當前目錄的方法

pwd 命令是用來打印出當前目錄的命令。在上面的例子中當前目錄是/root .第二個 pwd 命令在執行cd dir_test後打印出和第一個pwd命令同樣的結果,這是爲什麼呢?

你需要更多有關shell命令的知識如(cp, mv and so on),同時要區分在make和shell中調用這些命令的區別(make中調用shell命令的方式如下):

  • 打開一個新的shell實例
  • 執行shell命令
  • 關閉shell實例

實際上,make 在處理上述makefile時make創建了三個shell實例來處理這些shell命令。

cd dir_test 這個shell命令只在make創建的第二個shell實例中被正確執行。 

上面問題的解決方法如下:

target1:

(pwd;cd dir_test;pwd)

執行結果如下:

lnxmk_12

圖12:一種正確的改變目錄的方法

圓括號保證只在一個shell實例中處理這些命令,make 只會創建一個shell實例來執行這三個命令。

如果不使用圓括號,執行這個makefile會出現什麼樣的結果呢: 

target1:

    cd dir_test

    make

結果如下:

lnxmk_13

圖13:遞歸的make調用

可以看到make在無休止的遞歸調用這個maekfile。Make[37]表示這是make命令的第37個實例。

5.1測試示例5

sample5 展示了通過master makefile 怎麼調用其它的makefiles 。sample5 的目錄結構如下:

  • tstlib目錄:包含了一個簡單的靜態庫文件的源代碼(tlib.a)。
  • application目錄:包含了這個應用程序的源代碼,並且這些文件需要鏈接到tlib.a。
  • makefile:mastermakefile.
  • runmk:一個調用mastermakefile的shell腳本.這個腳本不是必須的,它只是爲了避免make在進入或退出一個目錄時產生的一個惱人的提示信息。

master makefile 內容如下:

COND1='stat app 2>/dev/null | grep Modify'

COND2='stat ./application/app 2>/dev/null | grep Modify'

all: buildall getexec 

buildall:

@echo "****** Invoking tstlib/makefile"

(cd tstlib; $(MAKE))

@echo "****** Invoking application/makefile"

(cd application; $(MAKE))

getexec:

@if [ "$(COND1)" != "$(COND2)" ];/

then/

echo "Getting new app!";/

cp -p ./application/app . 2>/dev/null;/

chmod 700 app;/

else/

echo "Nothing done!";/

fi

cleanall:

-rm -f app

@echo "****** Invoking tstlib/makefile"

@(cd tstlib; $(MAKE) cleanall) 

@echo "****** Invoking appl/makefile"

@(cd application; $(MAKE) cleanall) 

這個工程相對比較簡單,只有4個假目標。目標 all 先調用目標 buildall ,並且make 試圖在不同的目錄下調用makefile創建不同的目標。注意: $(MAKE) 宏默認是被make支持的,它在執行時被替換成make,無須定義就可以使用。

目標cleanall 被間接的用來刪除所有的object文件和可執行性文件。出現在括號前的@字符阻止make 不要輸出這些命令。

我們來試驗下面的命令序列(圖14):

lnxmk_14

圖14:sample5命令序列

1. make 命令通過執行shell腳本 runmk 來處理 master makefile. 

o 目標all 調用目標 buildall ,因其是第一個假目標,所以它總是會被執行。首先make調用tstlib目錄下的makefile併產生 tlib.a 。該目錄下的makefile內容如下:

TLIB=tlib.a

OBJS=tstlib_a.o tstlib_b.o

CC=cc

INCPATH=.

CFLAGS=-Wall -I$(INCPATH)

.c.o:

$(CC) $(CFLAGS) -c $<

$(TLIB): $(OBJS)

ar cr $(TLIB) $(OBJS)

$(OBJS): $(INCPATH)/tstlib.h

cleanall:

-rm -f *.o *.a

o 接下來application目錄下的makefile 被調用,同時目標app 被創建。該目錄下的makefile 如下:

OBJS=main.o mod_a.o mod_b.o

CC=cc

INCLIB=../tstlib

LIBS=$(INCLIB)/tlib.a

CFLAGS=-Wall -I. -I$(INCLIB) 

.c.o:

$(CC) $(CFLAGS) -c $<

app: $(OBJS) $(LIBS)

$(CC) $(CFLAGS) -o app $(OBJS) $(LIBS)

$(OBJS): inc_a.h inc_b.h $(INCLIB)/tstlib.h

cleanall:

-rm -f *.o app

注意目標app依賴於tlib.a。 因此不管tlib.a 是否被重新編譯,目標app都會重新鏈接它。

o 目標getexec 被master makefile所調用。由於可執行性文件app 不在 sample5的目錄下,所以它從application 目錄下被拷貝出來。

2. touch 命令改變了tstlib/tstlib_b.c文件的時間戳。

3. make 命令通過執行shell腳本 runmk 來處理 master makefile. 

o tlib.a 被重新編譯

o 由於tlib.a被改變所以app 需要重新鏈接它。

o 目標getexec被 master makefile調用。由於application/app 和app 的時間戳不一樣,所以sample5 目錄下的app被更新。

4. touch 命令改變了i application/inc_a.h的時間戳。

5. make 命令通過執行shell腳本 runmk 來處理 master makefile. 

o 由於依賴文件沒有更新,tlib.a 不需要更新。

o app 的所有模塊被重新編譯因爲所有的.c文件都依賴於inc_a.h 。

o 目標getexec被 master makefile調用。由於application/app 和app 的時間戳不一樣,所以sample5 目錄下的app被更新。

6. cleanall 目標被執行。 

 

 http://blog.csdn.net/edgar_wu/archive/2009/02/04/3863034.aspx

6、總結

就像你在本文所看到的那樣,make命令十分強大的工具,用好它將對你的工程應用提供非常多的幫助。就像開篇所說的,這篇文章不是一個make命令的完全指南,而只是一個如何編寫makefile的參考文檔。在internet和一些相關的書籍上有許多關於make和makefile的資料,這些資料包含了本文沒有提到的一些特性。

希望這篇文章能對你有所幫助。

文章歷史版本

初始版本。

文章著作權

本文,包括其相關的源代碼和文件都遵循The Code Project Open License (CPOL)

關於作者

Ciro Sisman Pereira

http://blog.csdn.net/edgar_wu/archive/2009/02/04/3863034.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章