Makefile

ARM嵌入式Linux系統開發(完整版).pdfhtml

1.GNU makelinux

GNU make 最初是Unix 系統下的一個工具,設計之初是爲了維護C 程序文件沒必要要的
從新編譯,它是一個自動生成和維護目標程序的工具。在使用GNU的編譯工具進行開發時,
常常要用到GNU make工具。使用make工具,咱們能夠將大型的開發項目分解成爲多個更
易於管理的模塊,對於一個包括幾百個源文件的應用程序,使用make 和Makefile 工具就可數據庫

以高效的處理各個源文件之間複雜的相互關係,進而取代了複雜的命令行操做,也大大提升
了應用程序的開發效率,能夠想到的是若是一個工程具備上百個源文件時,可是採用命令行
逐個編譯那將是多麼大的工做量。
使用make工具管理具備多個源文件的工程,其優點是顯而易見的,舉一個簡單的例子,
若是多個源文件中的某個文件被修改,而有其餘多個源文件依賴該文件,採用手工編譯的方
法須要對全部與該文件有關的源文件進行從新編譯,這顯然是一件費時費力的事情,而若是
採用make工具則能夠避免這種繁雜的重複編譯工做,大大地提升了工做效率。
make 是一個解釋Makefile 文件中指令的命令工具,其最基本的功能就是經過Makefile
文件來描述源程序之間的相互關係並自動維護編譯工做,它會告知系統以何種方式編譯和鏈
接程序。一旦正確完成Makefile 文件,剩下的工做就只是在Linux 終端下輸入make 這樣的
一個命令,就能夠自動完成全部編譯任務,而且生成目標程序。一般情況之下GNU make的
工做流程以下。express

① 查找當前目錄下的Makefile文件
② 初始化文件中的變量
③ 分析Makefile中的全部規則
④ 爲全部的目標文件建立依賴關係
⑤ 根據依賴關係,決定哪些目標文件要從新生成
⑥ 執行生成命令編程

 

爲了比較形象地說明make 工具的工做原理,舉一個簡單的例子來介紹。假定一個項目
中有如下一些文件。
· 源程序:Main.c、test1.c、test.c。
· 包含的頭文件:head1.h、head2.h、head3.h。
· 由源程序和頭文件編譯生成的目標文件:
Main.o、test1.o、test2.o。
· 由目標文件連接生成的可執行文件:test。
這些不一樣組成部分的相互依賴關係如圖3.9
所示。c#


在該項目的全部文件當中,目標文件Main.o的依
賴文件是Main.c、head1.h、head2.h;test1.o的依賴文
件是head2.h、test1.c;目標文件test2.o的依賴文件是head3.h、test2.c;最終的可執行文件的
依賴文件是Main.o、test1.o和test2.o。執行make命令時,會首先處理test程序的全部依賴文
件(.o 文件)的更新規則,對於.o 文件,會檢查每一個依賴程序(.c 和.h 文件)是否有更新,
判斷有無更新的依據主要看依賴文件的創建時間是否比所生成的目標文件要晚,若是是,那
麼會按規則從新編譯生成相應的目標文件,接下來對於最終的可執行程序,一樣會檢查其依
賴文件(.o 文件)是否有更新,若是有任何一個目標文件要比最終可執行的目標程序新,則
從新連接生成新的可執行程序,因此,make工具管理項目的過程是從最底層開始的,是一個
逆序遍歷的過程。從以上的說明就可以比較容易理解使用make 工具的優點了,事實上,任
何一個源文件的改變都會致使從新編譯、連接生成可執行程序,使用者沒必要關心哪一個程序改
變、或者依賴哪一個文件,make工具會自動完成程序的從新編譯和連接工做。函數

 

執行make 命令時,只需在Makefile 文件所在目錄輸入make 指令便可,事實上,make
命自己可帶有這樣的一些參數:【選項】、【宏定義】、【目標文件】。其標準形式以下。
Make [選項] [宏定義] [目標文件]
Make命令的一些經常使用選項及其含義以下。
· -f file:指定Makefile的文件名。
· -n:打印出全部執行命令,但事實上並不執行這些命令。
· -s:在執行時不打印命令名。
· -w:若是在make執行時要改變目錄,則打印當前的執行目錄。
· -d:打印調試信息。
· -I<dirname>:指定所用Makefile所在的目錄。
· -h:help文檔,顯示Makefile的help信息。
舉例來說,在使用make 工具的時候,習慣把makefile 文件命名爲Makefile,固然也可
以採用其餘的名字來命名makefile 文件,若是要使用其餘文件做爲Makefile,則可利用帶-f
選項的make命令來指定Makefile文件。工具

舉例來說,在使用make 工具的時候,習慣把makefile 文件命名爲Makefile,固然也可
以採用其餘的名字來命名makefile 文件,若是要使用其餘文件做爲Makefile,則可利用帶-f
選項的make命令來指定Makefile文件。
# make –f Makefilenamepost

參數【目標文件】對於make命令來講也是一個可選項,若是在執行make命令時帶有該
參數,能夠輸入以下的命令。
# make target學習

target 是用戶Makefile 文件中定義的目標文件之一,若是省略參數target,make 就將生
成Makefile文件中定義的第一個目標文件。所以,常見的用法就是常常把用戶最終想要的目
標文件(可執行程序)放在Makefile文件中首要的位置,這樣用戶直接執行make命令便可。

2.Makefile規則語法

簡單地講,Makefile 的做用就是讓編譯器知道要編譯一個文件須要依賴哪些文件,同時
當那些依賴文件有了改變,編譯器會自動的發現最終的生成文件已通過時,而從新編譯相應
的模塊。Makefile 的內容規定了整個工程的編譯規則。一個工程中的許多源文件按其類型、
功能、模塊可能分別被放在不一樣的目錄中,Makefile 定義了一系列的規則來指定,好比哪些
文件是有依賴性的,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件須要從新編譯。
Makefile有其自身特定的編寫格式而且遵循必定的語法規則。

#註釋
目標文件:依賴文件列表
...............
<Tab>命令列表
...............

格式的說明以下。
· 註釋:和Shell腳本同樣,Makefile語句行的註釋採用「#」符號。

· 目標:目標文件的列表,一般指程序編譯過程當中生成的目標文件(.o文件)或最終的
可執行程序,有時也能夠是要執行的動做,如「clean」這樣的目標。
· 依賴文件:目標文件所依賴的文件,一個目標文件能夠依賴一個或多個文件。
· 「:」符號,分隔符,介於目標文件和依賴文件之間。
· 命令列表:make 程序執行的動做,也是建立目標文件的命令,一個規則能夠有多條
命令,每一行只能有一條命令。

每個命令行必須以[Tab]鍵開始,[Tab]告訴make程序該行是一個命令行,make按照命令完成
相應的動做。

從上面的分析能夠看出,Makefile 文件的規則其實主要有兩個方面,一個是說明文件之
間的依賴關係,另外一個是告訴make 工具如何生成目標文件的命令。下面是一個簡單的
makefile文件例子。

 

#Makefile Example
test:main.o test1.o test2.o
  gcc –o test main.o test1.o test2.o
main.o:main.c head1.h head2.h
  gcc –c main.c
test1.o:test1.c head2.h
  gcc –c test1.c
test2.o:test2.c head3.h
  gcc –c test2.c
install:
  cp test /home/tmp
clean:
  rm –f *.o

 

在這個makefile文件中,目標文件(target)即爲:最終的可執行文件test和中間目標文
件main.o、test1.o、test2.o,每一個目標文件和它的依賴文件中間用「:」隔開,依賴文件的列
表之間用空格隔開。每個.o文件都有一組依賴文件,而這些.o文件又是最終的可執行文件
test的依賴文件。依賴關係實質上就是說明了目標文件是由哪些文件生成的。

在定義好依賴關係後,在命令列表中定義瞭如何生成目標文件的命令,命令行以Tab鍵
開始。Make 工具會比較目標文件和其依賴文件的建立或修改日期,若是所依賴文件比目標
文件要新,或者目標文件不存在的話,那麼,make就會執行命令行列表中的命令來生成目標
文件。

 

3.Makefile文件中變量的使用

Makefile 文件中除了一系列的規則,對於變量的使用也是一個很重要的內容。Linux 下
的Makefile 文件中可能會使用不少的變量,定義一個變量(也常稱爲宏定義),只要在一行
的開始定義這個變量(通常使用大寫,並且放在Makefile文件的頂部來定義),後面跟一個=

號,=號後面即爲設定的變量值。若是要引用該變量,用一個$符號來引用變量,變量名須要
放在$後的()裏。

make工具還有一些特殊的內部變量,它們根據每個規則內容定義。
· $@:指代當前規則下的目標文件列表。
· $<:指代依賴文件列表中的第一個依賴文件。
· $^:指代依賴文件列表中全部依賴文件。
· $?:指代依賴文件列表中新於對應目標文件的文件列表。
變量的定義能夠簡化makefile的書寫,方便對程序的維護。例如前面的Makefile例程就
能夠以下書寫。

#Makefile Example
OBJ=main.o test1.o test2.o
CC=gcc
test:$(OBJ)
  $(CC) –o test $(OBJ)
main.o:main.c head1.h head2.h
  $(CC)–c main.c
test1.o:test1.c head2.h
  $(CC) –c test1.c
test2.o:test2.c head3.h
  $(CC) –c test2.c
install:
  cp test /home/tmp
clean:
  rm –f *.o

從上面修改的例子能夠看到,引入了變量OBJ 和CC,這樣能夠簡化makefile 文件的編
寫,增長了文件的可讀性,並且便於修改。舉個例子來講,假定項目文件中還須要加入另外
一個新的目標文件test3.o,那麼在該Makefile 中有兩處須要分別添加test3.o;而若是使用變
量的話只需在OBJ 變量的列表中添加一次便可,這對於更復雜的Makefile程序來講,會是一
個不小的工做量,可是,這樣能夠下降由於編輯過程當中的疏漏而致使出錯的可能。

 

通常來講,Makefile文件中變量的應用主要有如下幾個方面。
1.表明一個文件列表
Makefile文件中的變量經常存儲一些目標文件甚至是目標文件的依賴文件,引用這些文件
的時候引用存儲這些文件的變量便可,這給Makefile編寫和維護者帶來了很大的方便。
2.表明編譯命令選項
當全部編譯命令都帶有相同編譯選項時(好比-Wall -O2等),能夠將該編譯選項賦給一
個變量,這樣方便了引用。同時,若是改變編譯選項的時候,只需改變該變量值便可,而不

必在每處用到編譯選項的地方都作改動。
在上面的Makefile例子中,還定義了一個僞目標clean,它規定了make應該執行的命令,
即刪除全部編譯過程當中產生的中間目標文件。當make處理到僞目標clean時,會先查看其對
應的依賴對象。因爲僞目標clean沒有任何依賴文件,因此make命令會認爲該目標是最新的
而不會執行任何操做。爲了編譯這個目標體,必須手工執行以下命令。
# make clean
此時,系統會有提示信息:
rm -f *.o
另外一個常常用到的僞目標是install。它一般是將編譯完成的可執行文件或程序運行所需的其
他文件拷貝到指定的安裝目錄中,並設置相應的保護。例如在上面的例子中,若是用戶執行命令:
# make install
系統會有提示信息:
cp test1 /home /tmp
也便是將可執行程序test1 拷貝到系統/home/tmp 下。事實上,許多應用程序的Makefile
文件也正是這樣寫的,這樣便於程序在正確編譯後能夠被安裝到正確的目錄。

 

 

Linux_C編程一站式學習_最新版.pdf

1. 基本規則

只要執行了命令列表就算更新了目標,即便目標並無生成也算

 

命令前面加@和-字符的效果:

若是make執行的命令前面加了@字符,則不顯示命令自己而只顯示它的結果;

一般make執行的命令若是出錯(該命令的退出狀態非0)就馬上終止,再也不執行後續命令,但若是命令前面加了-號,即便這條命令出錯,make也會繼續執行後續命令。

一般rm命令和mkdir命令前面要加-號,由於rm要刪除的文件可能不存在,mkdir要建立的目錄可能已存在,這兩個命令都有可能出錯,但這種錯誤是應該忽略的。

若是存在clean這個文件,clean目標又不依賴於任何條件,make就認爲它不須要更新了。而咱們但願把clean看成一個特殊的名字使用,無論它存在不存在都要更新,能夠添一條特殊規則,把clean聲明爲一個僞目標:
.PHONY: clean

這條規則沒有命令列表。相似.PHONY這種make內建的特殊目標還有不少,各有不一樣的用途,詳見[
GNUmake]。在C語言中要求變量和函數先聲明後使用,而Makefile不太同樣,這條規則寫在clean:規則的後面也行,也能起到聲明clean是僞目標的做用

gcc處理一個C程序分爲預處理和編譯兩個階段,相似地,make處理Makefile的過程也分爲兩個階段:
1. 首先從前到後讀取全部規則,創建起一個完整的依賴關係圖,

2. 而後從缺省目標或者命令行指定的目標開始,根據依賴關係圖選擇適當的規則執行,執行Makefile中的規則和執行C代碼不同,並非從前到後按順序執行,也不是全部規則都要執行一遍,例如make缺省目標時不會更新clean目標,由於從上圖能夠看出,它跟缺省目標沒有任何依賴關係。

 

clean目標是一個約定俗成的名字,在全部軟件項目的Makefile中都表示清除編譯生成的文件,相似這樣的約定俗成的目標名字有:all,執行主要的編譯工做,一般用做缺省目標。install,執行編譯後的安裝工做,把可執行文件、配置文件、文檔等分別拷到不一樣的安裝目錄。clean,刪除編譯生成的二進制文件。distclean,不只刪除編譯生成的二進制文件,也刪除其它生成的文件,例如配置文件和格式轉換後的文檔,執行make distclean以後應該清除全部這些文件,只留下源文件。

只要符合本章所描述的語法的文件咱們都叫它Makefile,而它的文件名則不必定是Makefile。事實上,執行make命令時,是按照GNUmakefile、makefile、Makefile的順序找到第一個存在的文件並執行它,不過仍是建議使用Makefile作文件名。除了GNU make,有些UNIX系統的make命令不是GNU make,不會查找GNUmakefile這個文件名,若是你寫的Makefile包含GNU make的特殊語法,能夠起名爲GNUmakefile,不然不建議用這個文件名。

2. 隱含規則和模式規則

一個目標依賴的全部條件不必定非得寫在一條規則中,也能夠拆開寫,若是一個目標拆開寫多條規則,其中只有一條規則容許有命令列表,其它規則應該沒有命令列表,不然make會報警告而且採用最後一條規則的命令列表。

若是一個目標在Makefile中的全部規則都沒有命令列表,make會嘗試在內建的隱含規則(Implicit Rule)數據庫中查找適用的規則。make的隱含規則數據庫能夠用make -p命令打印,打印出來的格式也是Makefile的格式,包括不少變量和規則,其中和咱們這個例子有關的隱含規則有:

# default
OUTPUT_OPTION = -o $@
# default
CC = cc
# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
%.o: %.c
# commands to execute (built-in):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

 #號在Makefile中表示單行註釋,就像C語言的//註釋同樣。CC是一個Makefile變量,用CC = cc定義和賦值,用$(CC)取它的值,其值應該是cc。Makefile變量像C的宏定義同樣,表明一串字符,在取值的地方展開。cc是一個符號連接,一般指向gcc,在有些UNIX系統上可能指向另一種C編譯器。
$ which cc/usr/bin/cc$ ls -l /usr/bin/cclrwxrwxrwx 1 root root 20 2008-07-04 05:59 /usr/bin/cc -> /etc/alternatives/cc

$ ls -l /etc/alternatives/cclrwxrwxrwx 1 root root 12 2008-11-01 09:10 /etc/alternatives/cc -> /usr/bin/gcc

CFLAGS這個變量沒有定義,$(CFLAGS)展開是空,CPPFLAGS和TARGET_ARCH也是如此。這樣$(COMPILE.c)展開應該是cc空空空-c,去掉全部的「空」獲得cc-c,注意中間留下4個空格,因此%.o: %.c規則的命令$(COMPILE.c)$(OUTPUT_OPTION)$<展開以後是cc-c-o$@$<,和上面的編譯命令已經很接近了。

$@和$<是兩個特殊的變量,$@的取值爲規則中的目標,$<的取值爲規則中的第一個條件。%.o:%.c是一種特殊的規則,稱爲模式規則(Pattern Rule)。如今回顧一下整個過程,在咱們的Makefile中以main.o爲目標的規則都沒有命令列表,因此make會查找隱含規則,發現隱含規則中有這樣一條模式規則適用,main.o符合%.o的模式,如今%就表明main(稱爲main.o這個名字的Stem),再替換到%.c中就是main.c。因此這條模式規則至關於:
main.o: main.c

  cc -c -o main.o main.c

隨後,在處理stack.o目標時又用到這條模式規則,這時又至關於:
stack.o: stack.c cc -c -o stack.o stack.c

maze.o也一樣處理。這三條規則能夠由make的隱含規則推導出來,因此沒必要寫在Makefile中。
先前咱們寫Makefile都是以目標爲中心,一個目標依賴於若干條件,如今換個角度,以條件爲中心,Makefile還能夠這麼寫:

main: main.o stack.o maze.o
    gcc main.o stack.o maze.o -o main
main.o stack.o maze.o: main.h
main.o maze.o: maze.h
main.o stack.o: stack.h
clean:
    -rm main *.o
.PHONY: clean

 

咱們知道,寫規則的目的是讓make創建依賴關係圖,無論怎麼寫,只要把全部的依賴關係都描述清楚了就行。對於多目標的規則,make會拆成幾條單目標的規則來處理,例如

target1 target2: prerequisite1 prerequisite2

  command $< -o $@

這樣一條規則至關於:
target1: prerequisite1 prerequisite2

  command prerequisite1 -o target1

target2: prerequisite1 prerequisite2

  command prerequisite1 -o target2

注意兩條規則的命令列表是同樣的,但$@的取值不一樣。

3. 變量

這一節咱們詳細看看Makefile中關於變量的語法規則。先看一個簡單的例子:
foo = $(bar)

bar = Huh?

all:

  @echo $(foo)

咱們執行make將會打出Huh?。當make讀到foo = $(bar)時,肯定foo的值是$(bar),但並不當即展開$(bar),而後讀到bar = Huh?,肯定bar的值是Huh?,而後在執行規則all:的命令列表時才須要展開$(foo),獲得$(bar),再展開$(bar),獲得Huh?。所以,雖然bar的定義寫在foo以後,$(foo)展開仍是可以取到$(bar)的值。
這種特性有好處也有壞處。好處是咱們能夠把變量的值推遲到後面定義,例如:

main.o: main.c

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

CC = gcc

CFLAGS = -O -g

CPPFLAGS = -Iinclude

編譯命令能夠展開成gcc -O -g -Iinclude -c main.c。一般把CFLAGS定義成一些編譯選項,例如-O、-g等,而把CPPFLAGS定義成一些預處理選項,例如-D、-I等。用=號定義變量的延遲展開特性也有壞處,就是有可能寫出無窮遞歸的定義,例如CFLAGS = $(CFLAGS) -O,或者:A = $(B) B = $(A)

 

固然,make有能力檢測出這樣的錯誤而不會陷入死循環。有時候咱們但願make在遇到變量定義時當即展開,能夠用:=運算符,例如:

x := foo

y := $(x) bar

all:

  @echo "-$(y)-"

當make讀到y := $(x) bar定義時,當即把$(x)展開,使變量y的取值是foo bar,若是把這兩行顛倒過來:

y := $(x) bar
x := foo

那麼當make讀到y := $(x) bar時,x尚未定義,展開爲空值,因此y的取值是bar,注意bar前面有個空格。一個變量的定義從=後面的第一個非空白字符開始(從$(x)的$開始),包括後面的全部字符,直到註釋或換行以前結束。若是要定義一個變量的值是一個空格,能夠這樣:

nullstring :=

space := $(nullstring) # end of the line

nullstring的值爲空,space的值是一個空格,後面寫個註釋是爲了增長可讀性,若是不寫註釋就換行,則很難看出$(nullstring)後面有個空格。
還有一個比較有用的賦值運算符是?=,例如foo ?= $(bar)的意思是:若是foo沒有定義過,那麼?=至關於=,定義foo的值是$(bar),但不當即展開;若是先前已經定義了foo,則什麼也不作,不會給foo從新賦值。
+=運算符能夠給變量追加值,例如:

objects = main.o

objects += $(foo)

foo = foo.o bar.o

object是用=定義的,+=仍然保持=的特性,objects的值是main.o $(foo)(注意$(foo)前面自動添一個空格),但不當即展開,等到後面須要展開$(objects)時會展開成main.o foo.o bar.o。

再好比:
objects := main.o

objects += $(foo)

foo = foo.o bar.o
object是用:=定義的,+=保持:=的特性,objects的值是main.o $(foo),當即展開獲得main.o (這時foo還沒定義),注意main.o後面的空格仍保留。
若是變量尚未定義過就直接用+=賦值,那麼+=至關於=。
上一節咱們用到了特殊變量$@和$<,這兩個變量的特色是不須要給它們賦值,在不一樣的上下文中它們自動取不一樣的值。經常使用的特殊變量有:

$@,表示規則中的目標。

$<,表示規則中的第一個條件。

$?,表示規則中全部比目標新的條件,組成一個列表,以空格分隔。

$^,表示規則中的全部條件,組成一個列表,以空格分隔。

例如前面寫過的這條規則:
main: main.o stack.o maze.o

  gcc main.o stack.o maze.o -o main

能夠改寫成:

main: main.o stack.o maze.o

  gcc $^ -o $@

這樣即便之後又往條件裏添加了新的目標文件,編譯命令也不須要修改,減小了出錯的可能。
$?變量也頗有用,有時候但願只對更新過的條件進行操做,例若有一個庫文件libsome.a依賴於幾個目標文件:

libsome.a: foo.o bar.o lose.o win.o

  ar r libsome.a $?

  ranlib libsome.a

這樣,只有更新過的目標文件才須要從新打包到libsome.a中,沒更新過的目標文件本來已經在libsome.a中了,沒必要從新打包。
在上一節咱們看到make的隱含規則數據庫中用到了不少變量,有些變量沒有定義(例如CFLAGS),有些變量定義了缺省值(例如CC),咱們寫Makefile時能夠從新定義這些變量的值,也能夠在缺省值的基礎上追加。如下列舉一些經常使用的變量,請讀者體會其中的規律。
AR
靜態庫打包命令的名字,缺省值是ar。
ARFLAGS
靜態庫打包命令的選項,缺省值是rv。
AS
彙編器的名字,缺省值是as。
ASFLAGS
彙編器的選項,沒有定義。
CC
C編譯器的名字,缺省值是cc。
CFLAGS
C編譯器的選項,沒有定義。
CXX
C++編譯器的名字,缺省值是g++。
CXXFLAGS
C++編譯器的選項,沒有定義。

CPP
C預處理器的名字,缺省值是$(CC) -E。

CPPFLAGS
C預處理器的選項,沒有定義。
LD
連接器的名字,缺省值是ld。
LDFLAGS
連接器的選項,沒有定義。
TARGET_ARCH
和目標平臺相關的命令行選項,沒有定義。
OUTPUT_OPTION
輸出的命令行選項,缺省值是-o $@。
LINK.o
把.o文件連接在一塊兒的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)。
LINK.c
把.c文件連接在一塊兒的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)$(TARGET_ARCH)。
LINK.cc
把.cc文件(C++源文件)連接在一塊兒的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS)$(LDFLAGS) $(TARGET_ARCH)。
COMPILE.c
編譯.c文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。
COMPILE.cc
編譯.cc文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。
RM
刪除命令的名字,缺省值是rm -f。

4. 自動處理頭文件的依賴關係

如今咱們的Makefile寫成這樣:
all: main

main: main.o stack.o maze.o

  gcc $^ -o $@

main.o: main.h stack.h maze.h

stack.o: stack.h main.h

maze.o: maze.h main.h

clean:

  -rm main *.o

.PHONY: clean

按照慣例,用all作缺省目標。如今還有一點比較麻煩,在寫main.o、stack.o和maze.o這三個目標的規則時要查看源代碼,找出它們依賴於哪些頭文件,這很容易出錯,一是由於有的頭文件包含在另外一個頭文件中,在寫規則時很容易遺漏,二是若是之後修改源代碼改變了依賴關係,極可能忘記修改Makefile的規則。爲了解決這個問題,能夠用gcc的-M選項自動生成目標文件和源文件的依賴關係:

 $ gcc -M main.c

main.o: main.c /usr/include/stdio.h /usr/include/features.h \ /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \ /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \ /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \ /usr/include/bits/types.h /usr/include/bits/typesizes.h \ /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \ /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \ stack.h maze.h

-M選項把stdio.h以及它所包含的系統頭文件也找出來了,若是咱們不須要輸出系統頭文件的依賴關係,能夠用-MM選項:

$ gcc -MM *.c

main.o: main.c main.h stack.h maze.hmaze.o: maze.c maze.h main.hstack.o: stack.c stack.h main.h

接下來的問題是怎麼把這些規則包含到Makefile中,GNU make的官方手冊建議這樣寫:

 

all: main

main: main.o stack.o maze.o

  gcc $^ -o $@

clean:

  -rm main *.o

.PHONY: clean

sources = main.c stack.c maze.c

include $(sources:.c=.d)

%.d: %.c

  set -e; rm -f $@; \

  $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \

  sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \

  rm -f $@.$$$$

sources變量包含咱們要編譯的全部.c文件,$(sources:.c=.d)是一個變量替換語法,把sources變量中每一項的.c替換成.d,因此include這一句至關於:
include main.d stack.d maze.d

相似於C語言的#include指示,這裏的include表示包含三個文件main.d、stack.d和maze.d,這三個文件也應該符合Makefile的語法。若是如今你的工做目錄是乾淨的,只有.c文件、.h文件和Makefile,運行make的結果是:

$ make

Makefile:13: main.d: No such file or directory

Makefile:13: stack.d: No such file or directory

Makefile:13: maze.d: No such file or directory

set -e; rm -f maze.d; \ cc -MM maze.c > maze.d.$$; \ sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \ rm -f maze.d.$$

set -e; rm -f stack.d; \ cc -MM stack.c > stack.d.$$; \ sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \ rm -f stack.d.$$

set -e; rm -f main.d; \ cc -MM main.c > main.d.$$; \ sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \ rm -f main.d.$$

cc -c -o main.o main.c

cc -c -o stack.o stack.c

cc -c -o maze.o maze.c

gcc main.o stack.o maze.o -o main

一開始找不到.d文件,因此make會報警告。可是make會把include的文件名也看成目標來嘗試更新,而這些目標適用模式規則%.d: %c,因此執行它的命令列表,好比生成maze.d的命令:

set -e; rm -f maze.d; \ cc -MM maze.c > maze.d.$$; \ sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \ rm -f maze.d.$$

注意,雖然在Makefile中這個命令寫了四行,但實際上是一條命令,make只建立一個Shell進程執行這條命令,這條命令分爲5個子命令,用;號隔開,而且爲了美觀,用續行符\拆成四行來寫。執行步驟爲:

1. set -e命令設置當前Shell進程爲這樣的狀態:若是它執行的任何一條命令的退出狀態非零則馬上終止,再也不執行後續命令。

2. 把原來的maze.d刪掉。
3. 從新生成maze.c的依賴關係,保存成文件maze.d.1234(假設當前Shell進程的id是1234)。注意,在Makefile中$有特殊含義,若是要表示它的字面意思則須要寫兩個$,因此Makefile中的四個$傳給Shell變成兩個$,兩個$在Shell中表示當前進程的id,通常用它給臨時文件起名,以保證文件名惟一。

4. 這個sed命令比較複雜,就不細講了,主要做用是查找替換。maze.d.1234的內容應該是maze.o: maze.c maze.h main.h,通過sed處理以後存爲maze.d,其內容是maze.o maze.d:maze.c maze.h main.h。
5. 最後把臨時文件maze.d.1234刪掉。

 #####################################################################################

$*
這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對於構造有關聯的文件名是比較有較。若是目標中沒有模式的定義,那麼"$*"也就不能被推導出,可是,若是目標文件的後綴是make所識別的,那麼"$*"就是除了後綴的那一部分。例如:若是目標是"foo.c",由於".c"是make所能識別的後綴名,因此,"$*"的值就是"foo"。這個特性是GNU make的,頗有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$*",除非是在隱含規則或是靜態模式中。若是目標中的後綴是make所不能識別的,那麼"$*"就是空值。

 

Now let's look at the metacharacters that allow us to select any individual portion of a string that is
matched and recall it in the replacement string. A pair of escaped parentheses are used in sed to enclose
any part of a regular expression and save it for recall. Up to nine "saves" are permitted for a single line.
"\n" is used to recall the portion of the match that was saved, where n is a number from 1 to 9
referencing a particular "saved" string in order of use.

sed中 \(\)用於選擇表達式中的任意部分,保存以供後用,單行可用9個保存區域。

\n用於調用保存區的內容。

s/\(See Section \)\([1-9][0-9]*\.[1-9][0-9]*\)/\1\\fB\2\\fP/

Two pairs of escaped parentheses are specified. The first captures "See Section " (because this is a
fixed string, it could have been simply retyped in the replacement string). The second captures the
section number. The replacement string recalls the first saved substring as "\1" and the second as "\2,"
which is surrounded by bold-font requests.

$ cat test1
first:second
one:two
$ sed 's/\(.*\):\(.*\)/\2:\1/' test1
second:first
two:one

The larger point is that you can recall a saved substring in any order, and multiple times, as you'll see in
the next example.

其餘sed替換用法參見下面文章:

sed替換的基本語法

做者:RISEBY
連接:https://www.imooc.com/article/50039
來源:慕課網

 Linux sed 命令字符串替換使用方法詳解

#######################################################################################

無論是Makefile自己仍是被它包含的文件,只要有一個文件在make過程當中被更新了,make就會從新讀取整個Makefile以及被它包含的全部文件,如今main.d、stack.d和maze.d都生成了,就能夠正常包含進來了(假如這時尚未生成,make就要報錯而不是報警告了),至關於在Makefile中添了三條規則:

main.o main.d: main.c main.h stack.h maze.h

maze.o maze.d: maze.c maze.h main.h

stack.o stack.d: stack.c stack.h main.h

 

若是我在main.c中加了一行#include "foo.h",那麼:
一、main.c的修改日期變了,根據規則main.o main.d: main.c main.h stack.h maze.h要從新生成main.o和main.d。生成main.o的規則有兩條:
main.o: main.c main.h stack.h maze.h%.o: %.c# commands to execute (built-in): $(COMPILE.c) $(OUTPUT_OPTION) $<
第一條是把規則main.o main.d: main.c main.h stack.h maze.h拆開寫獲得的,第二條是隱含規則,所以執行cc命令從新編譯main.o。生成main.d的規則也有兩條:
main.d: main.c main.h stack.h maze.h%.d: %.c set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
所以main.d的內容被更新爲main.o main.d: main.c main.h stack.h maze.h foo.h。
二、因爲main.d被Makefile包含,main.d被更新又致使make從新讀取整個Makefile,把新的main.d包含進來,因而新的依賴關係生效了。

以上說法不是很準確:

更改了一下Makefile

all: main
main: main.o stack.o maze.o
    gcc $^ -o $@
clean:
    -rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
include $(sources:.c=.d)
maze.o: maze.c maze.h main.h
    @echo "maze"
    gcc -c maze.c
%.d: %.c
    set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    cat $@
    rm -f $@.$$$$
幾個要點:

1. 首先從前到後讀取全部規則,創建起一個完整的依賴關係圖

好比上面這個Makefile:

all: main
main: main.o stack.o maze.o
    gcc $^ -o $@
clean:
    -rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c

#若是三個.d文件不存在,這就是空

main.o main.d: main.c main.h stack.h maze.h

maze.o maze.d: maze.c maze.h main.h

stack.o stack.d: stack.c stack.h main.h

maze.o: maze.c maze.h main.h
    @echo "maze"
    gcc -c maze.c
%.d: %.c
    set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    cat $@
    rm -f $@.$$$$

2. 而後從缺省目標或者命令行指定的目標開始,根據依賴關係圖選擇適當的規則執行,執行Makefile中的規則和執行C代碼不同,並非從前到後按順序執行,也不是全部規則都要執行一遍,例如make缺省目標時不會更新clean目標,由於從上圖能夠看出,它跟缺省目標沒有任何依賴關係。

.d文件都不存在時候make

zhangyi@xxx:~/work/idyll_project/ctest/mftest$ make
Makefile:8: main.d: No such file or directory
Makefile:8: stack.d: No such file or directory
Makefile:8: maze.d: No such file or directory
set -e; rm -f maze.d; \
cc -MM  maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$

cat maze.d
maze.o maze.d : maze.c maze.h main.h
set -e; rm -f stack.d; \
cc -MM  stack.c > stack.d.$$; \
sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \
rm -f stack.d.$$

cat stack.d
stack.o stack.d : stack.c stack.h main.h
set -e; rm -f main.d; \
cc -MM  main.c > main.d.$$; \
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \
rm -f main.d.$$

cat main.d
main.o main.d : main.c main.h stack.h

#無論是Makefile自己仍是被它包含的文件,只要有一個文件在make過程當中被更新了,make就會從新讀取整個Makefile以及被它包含的全部文件,如今main.d、stack.d和maze.d都生成了,就能夠正常包含進來了
cc    -c -o main.o main.c
cc    -c -o stack.o stack.c
maze
gcc -c maze.c
gcc main.o stack.o maze.o -o main

 

若是maze.c刪除或加入一個頭文件:從缺省目標開始

all->main->1.main.o 2.stack.o 3.maze.o

按順序尋找各個.o

all: main
main: main.o stack.o maze.o
    gcc $^ -o $@
clean:
    -rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c

main.o main.d: main.c main.h stack.h maze.h

maze.o maze.d: maze.c maze.h main.h

stack.o stack.d: stack.c stack.h main.h

maze.o: maze.c maze.h main.h
    @echo "maze"
    gcc -c maze.c
%.d: %.c
    set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    cat $@
    rm -f $@.$$$$

 

maze.c改變了,因此maze.o maze.d 須要從新生成。

make的執行過程以下:
1. 依次讀取變量「MAKEFILES」定義的makefile文件列表
2. 讀取工做目錄下的makefile文件(缺省的是根據命名的查找順序「GNUmakefile」,「makefile」,「Makefile」,首先找到那個就讀取那個)
3. 依次讀取工做目錄makefile文件中使用指示符「include」包含的文件
4. 查找重建全部已讀取的makefile文件的規則(若是存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成之後從第一步開始從新執行)
5. 初始化變量值並展開那些須要當即展開的變量和函數並根據預設條件肯定執行分支
6. 根據「終極目標」以及其餘目標的依賴關係創建依賴關係鏈表
7. 執行除「終極目標」之外的全部的目標的規則(規則中若是依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件)
8. 執行「終極目標」所在的規則

zhangyi@xxx:~/work/idyll_project/ctest/mftest$ make
set -e; rm -f maze.d; \
cc -MM  maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$
cat maze.d
maze.o maze.d : maze.c
cc    -c -o main.o main.c
cc    -c -o stack.o stack.c
maze
gcc -c maze.c
gcc main.o stack.o maze.o -o main

採用如下規則生成maze.d
%.d: %.c
    set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    cat $@
    rm -f $@.$$$$

顯式規則優先級 > 隱藏規則 因此採用如下規則生成maze.o

maze.o: maze.c maze.h main.h
    @echo "maze"
    gcc -c maze.c

 

5. 經常使用的make命令行選項

-n選項只打印要執行的命令,而不會真的執行命令,這個選項有助於咱們檢查Makefile寫得是否正確,因爲Makefile不是順序執行的,用這個選項能夠先看看命令的執行順序,確認無誤了再真正執行命令。
-C選項能夠切換到另外一個目錄執行那個目錄下的Makefile,好比先退到上一級目錄再執行咱們的Makefile(假設咱們的源代碼都放在testmake目錄下):
$ cd ..

$ make -C testmake

make: Entering directory `/home/akaedu/testmake'

cc -c -o main.o main.c

cc -c -o stack.o stack.c

cc -c -o maze.o maze.c

gcc main.o stack.o maze.o -o main

make: Leaving directory `/home/akaedu/testmake'
一些規模較大的項目會把不一樣的模塊或子系統的源代碼放在不一樣的子目錄中,而後在每一個子目錄下都寫一個該目錄的Makefile,而後在一個總的Makefile中用make -C命令執行每一個子目錄下的Makefile。例如Linux內核源代碼根目錄下有Makefile,子目錄fs、net等也有各自的Makefile,二級子目錄fs/ramfs、net/ipv4等也有各自的Makefile。
在make命令行也能夠用=或:=定義變量,若是此次編譯我想加調試選項-g,但我不想每次編譯都加-g選項,能夠在命令行定義CFLAGS變量,而沒必要修改Makefile編譯完了再改回來:
$ make CFLAGS=-g

cc -g -c -o main.o main.c

cc -g -c -o stack.o stack.c

cc -g -c -o maze.o maze.c

gcc main.o stack.o maze.o -o main若是在Makefile中也定義了CFLAGS變量,則命令行的值覆蓋Makefile中的值。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息