無論是在Linux還是在Unix環境中,make都是一個非常重要的
編譯命令。不管是自己進行
項目開發還是安裝應用軟件,我們都經常要用到
make或make install。利用make工具,我們可以將
大型的開發項目分解成為多個更易於管理的模塊,對於一個
包括幾百個源文件的應用程序,使用
make和
makefile工具就可以簡潔明快地
理順各個源文件之間紛繁復雜的相互關系。而且
如此多的源文件,如果每次都要鍵入gcc命令進行編譯的話,那對程序員來說簡直就是一場災難。
而
make工具則可自動完成編譯工作,並且可以只對程序員在
上次編譯後修改過的部分進行編譯。因此,有效的利用
make和 makefile工具可以大大提高項目開發的效率。同時掌握make和makefile之後,您也不會再面對著Linux下的應用軟件手足無措了。
但令人遺憾的是,在許多講述Linux應用的書籍上都
沒有詳細介紹這個功能強大但又非常復雜的
編譯工具。在這裡我就向大家詳細介紹一下
make及其描述文件makefile。
Makefile文件
Make工具最主要也是最基本的功能就是
通過makefile文件來描述
源程序之間的相互關系並自動
維護編譯工作。而makefile 文件需要按照
某種語法進行編寫,文件中需要說明如何
編譯各個
源文件並連接生成
可執行文件,並要求定義
源文件之間的依賴關系。
makefile
文件是許多編譯器--包括 Windows NT 下的編譯器--維護編譯信息的常用方法,只是在集成開發環境中,用戶通過友好的界面修改 makefile 文件而已。
在 UNIX 系統中,習慣使用
Makefile 作為 makfile 文件。如果要使用其他文件作為 makefile,則可利用類似
下面的 make 命令選項指定 makefile 文件:
$ make -f Makefile.debug 例如,
一個名為prog的程序由三個C源文件filea.c、fileb.c和filec.c以及庫文件LS編譯生成,這三個文件還分別包含自己的
頭文件a.h 、b.h和c.h。通常情況下,C編譯器將會輸出三個目標文件filea.o、fileb.o和filec.o。假設filea.c和fileb.c
都要聲明用到一個名為defs的文件,但filec.c不用。即在filea.c和fileb.c裡都有這樣的聲明:
#include "defs"
那麼下面的文檔就描述了這些文件之間的相互聯系:
---------------------------------------------------------
#It is a example for describing makefile
prog : filea.o fileb.o filec.o cc filea.o fileb.o filec.o -LS -o prog
filea.o : filea.c a.h defs
cc -c filea.c
fileb.o : fileb.c b.h defs
cc -c fileb.c
filec.o : filec.c c.h
cc -c filec.c
----------------------------------------------------------
這個描述文檔就是一個簡單的makefile文件。
從上面的例子注意到,
第一個字符為 # 的行為注釋行。第一個
非注釋行指定prog由
三個目標文件filea.o、fileb.o和filec.o鏈接生成。第三行描述了如何
從prog所依賴的文件建立可執行文件。接下來的
4、6、8行分別指定三個目標文件,以及它們所
依賴的.c和.h文件以及defs文件。而
5、7、9行則指定了如何從
目標所依賴的文件建立目標。
當filea.c或a.h文件在編譯之後又被修改,則 make 工具可
自動重新編譯filea.o,如果在前後兩次編譯之間,
filea.C 和a.h 均沒有被修改,而且 test.o 還存在的話,就沒有必要重新編譯。這種依賴關系在
多源文件的程序編譯中尤其重要。通過這種依賴關系的定義,make 工具可避免許多
不必要的編譯工作。當然,利用 Shell
腳本也可以達到自動編譯的效果,但是,
Shell 腳本將全部編譯任何源文件,包括那些不必要重新編譯的源文件,而
make 工具則可根據目標
上一次編譯的時間和目標所依賴的源文件的更新時間而自動判斷應當編譯哪個
源文件。
Makefile文件作為一種
描述文檔一般需要包含以下內容:
◆ 宏定義
◆
源文件之間的相互依賴關系
◆ 可執行的命令
Makefile中允許使用
簡單的宏指代
源文件及其相關編譯信息,在Linux中也稱
宏為變量。在引用宏時只需在變量前加$符號,但值得注意的是,如果變量名的長度超過一個字符,在引用時就必須加圓括號()。 下面都是有效的宏引用:
$(CFLAGS)
$2
$Z
$(Z)
其中最後兩個引用是完全一致的。
需要注意的是一些
宏的預定義變量,在Unix系統中,
$*、$@、$?和$1zap /usr/bin/make -dp | grep -v TIME>2zap
diff 1zap 2zap
rm 1zap 2zap lint: dosys.c donamc.c file.c main.c misc.c version.c gram.c
$(LINT) dosys.c donamc.c file.c main.c misc.c version.c
gram.c
rm gram.c
arch:
ar uv /sys/source/s2/make.a $(FILES)
----------------------------------------------------------
通常在
描述文件中應象上面一樣定義要求輸出將要執行的命令。在執行了
make命令之後,輸出結果為:
$ make
cc -c version.c cc -c main.c
cc -c donamc.c
cc -c misc.c
cc -c file.c
cc -c dosys.c
yacc gram.y mv y.tab.c gram.c
cc -c gram.c cc version.o main.o donamc.o misc.o file.o dosys.o gram.o
-LS -o make
13188+3348+3044=19580b=046174b
最後的數字信息是執行"@size make"命令的輸出結果。之所以
只有輸出結果而沒有相應的命令行,是因為"@size make"命令以"@"起始,這個符號禁止打印輸出它所在的命令行。
描述文件中的最後幾條命令行在維護編譯信息方面非常有用。其中
"print"命令行的作用是
打印輸出在執行過上次"make print"命令後所有改動過的文件名稱。系統使用一個名為print的
0字節文件來確定執行print命令的
具體時間,
而宏$?則指向那些在print 文件改動過之後進行修改的文件的文件名。如果想要指定執行print命令後,將
輸出結果送入某個指定的文件,那麼就可
修改P的宏定義:
make print "P= cat>zap"
在Linux中大多數軟件提供的是
源代碼,而
不是現成的可執行文件,這就要求用戶根據自己系統的實際情況和自身的需要來配置、編譯源程序後,軟件才能使用。只有掌握了make工具,才能讓我們真正享受到到Linux這個自由軟件世界的帶給我們無窮樂趣。
==========================================
Makefile 初探
==========================================
Linux 的內核配置文件有兩個,一個是隱含的.config文件,嵌入到主Makefile中;另一個是include/linux/autoconf.h,嵌入到各個c源文件中,它們由make config、make menuconfig、make xconfig這些過程創建。
幾乎
所有的
源文件都會通過
linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依賴關系 (.depend),只要更新過
autoconf.h,就會造成所有源代碼的重新編繹。
為了優化make過程,減少不必要的重新編繹,
Linux開發了專用的mkdep工具,用它來取代gcc來生成
.depend文件。
mkdep在處理源文件時,忽略linux/config.h這樣的頭文件,識別源文件宏指令中具有"CONFIG_"特征的行。例如,
如果有"#ifdef CONFIG_SMP"這樣的行,它就會在.depend文件中輸出$(wildcard /usr/src/linux/include/config/smp.h)。
include/config/下的文件是另一個工具 split-include從autoconf.h中生成,它利用autoconf.h中的CONFIG_標記,
生成與mkdep相對應的文件。例如,如果autoconf.h中有"#undef CONFIG_SMP"
這一行,它就生成include/config/smp.h文件,內容為"#undef CONFIG_SMP"。這些
文件名只在
.depend文件中出現,內核源文件是
不會嵌入它們的。每
配置一次內核,運行split-include一次。
split-include會
檢查舊的子文件的內容,確定是不是要更新它們。這樣,不管autoconf.h修改日期如何,只要其配置不變,make就不會重新編繹內核。
如果系統的
編繹選項發生了變化,Linux也能進行增量編繹。為了做到這一點,make每編繹一個源文件時生成一個 flags文件。例如編繹sched.c時,會在相同的目錄下生成
隱含的.sched.o.flags文件。它是Makefile的一個片斷,當make 進入
某個子目錄編繹時,會搜索其中的flags文件,將它們嵌入到Makefile中。這些flags代碼測試當前的編繹選項與原來的是不是相同,
如果相同,就將自已對應的目標文件加入FILES_FLAGS_UP_TO_DATE列表,然後,系統從編繹對象表中刪除它們,得到
FILES_FLAGS_CHANGED列表,最後,將它們設為目標進行更新。
Linux 下
make 命令是系統管理員和程序員用的最頻繁的命令之一。管理員用它通過命令行來編譯和安裝很多開源的工具,程序員用它來管理他們大型復雜的項目編譯問題。本文我們將用一些實例來討論 make 命令背後的工作機制。
Make 如何工作的
對於不知道背後機理的人來說,make 命令像命令行參數一樣接收目標。這些目標通常存放在以 “Makefile” 來命名的特殊文件中,同時文件也包含與目標相對應的操作。更多信息,閱讀關於 Makefiles 如何工作的系列文章。
當 make 命令第一次執行時,它掃描 Makefile 找到目標以及其依賴。如果這些依賴自身也是目標,繼續為這些依賴掃描 Makefile 建立其依賴關系,然後編譯它們。一旦主依賴編譯之後,然後就編譯主目標(這是通過 make 命令傳入的)。
現在,假設你對某個源文件進行了修改,你再次執行 make 命令,它將只編譯與該源文件相關的目標文件,因此,編譯完最終的可執行文件節省了大量的時間。
Make 命令實例
下面是本文所使用的測試環境:
[code]OS —— Ubunut 13.04
Shell —— Bash 4.2.45
Application —— GNU Make 3.81
下面是工程的內容:
$ ls
anotherTest.c Makefile test.c test.h
下面是 Makefile 的內容:
all: test
test: test.o anotherTest.o
gcc -Wall test.o anotherTest.o -o test
test.o: test.c
gcc -c -Wall test.c
anotherTest.o: anotherTest.c
gcc -c -Wall anotherTest.c
clean:
rm -rf *.o test
現在我們來看 Linux 下一些 make 命令應用的實例:
1.
一個簡單的例子
為了編譯整個工程,你可以簡單的使用
make
或者在 make 命令後帶上目標
all
。
$ make
gcc -c -Wall test.c
gcc -c -Wall anotherTest.c
gcc -Wall test.o anotherTest.o -o test
你能看到 make 命令第一次創建的依賴以及實際的目標。
如果你再次查看目錄內容,裡面多了一些 .o 文件和執行文件:
$ ls
anotherTest.c anotherTest.o Makefile test test.c test.h test.o
現在,假設你對 test.c 文件做了一些修改,重新使用 make 編譯工程:
$ make
gcc -c -Wall test.c
gcc -Wall test.o anotherTest.o -o test
你可以看到只有 test.o 重新編譯了,然而另一個 Test.o 沒有重新編譯。
現在清理所有的目標文件和可執行文件 test,你可以使用目標
clean
:
$ make clean
rm -rf *.o test
$ ls
anotherTest.c Makefile test.c test.h
你可以看到所有的 .o 文件和執行文件 test 都被刪除了。
2.
通過 -B 選項讓所有目標總是重新建立
到目前為止,你可能注意到 make 命令不會編譯那些自從上次編譯之後就沒有更改的文件,但是,如果你想覆蓋 make 這種默認的行為,你可以使用 -B 選項。
下面是個例子:
$ make
make: Nothing to be done for `all’.
$ make -B
gcc -c -Wall test.c
gcc -c -Wall anotherTest.c
gcc -Wall test.o anotherTest.o -o test
你可以看到盡管 make 命令不會編譯任何文件,然而
make -B
會強制編譯所有的目標文件以及最終的執行文件。
3.
使用 -d 選項打印調試信息
如果你想知道 make 執行時實際做了什麼,使用 -d 選項。
這是一個例子:
$ make -d | more
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
This program built for x86_64-pc-linux-gnu
Reading makefiles…
Reading makefile `Makefile’…
Updating makefiles….
Considering target file `Makefile’.
Looking for an implicit rule for `Makefile’.
Trying pattern rule with stem `Makefile’.
Trying implicit prerequisite `Makefile.o’.
Trying pattern rule with stem `Makefile’.
Trying implicit prerequisite `Makefile.c’.
Trying pattern rule with stem `Makefile’.
Trying implicit prerequisite `Makefile.cc’.
Trying pattern rule with stem `Makefile’.
Trying implicit prerequisite `Makefile.C’.
Trying pattern rule with stem `Makefile’.
Trying implicit prerequisite `Makefile.cpp’.
Trying pattern rule with stem `Makefile’.
--More--
這是很長的輸出,你也看到我使用了
more
命令來一頁一頁顯示輸出。
4.
使用 -C 選項改變目錄
你可以為 make 命令提供不同的目錄路徑,在尋找 Makefile 之前會切換目錄的。
這是一個目錄,假設你就在當前目錄下:
[code]$ ls
file file2 frnd frnd1.cpp log1.txt log3.txt log5.txt
file1 file name with spaces frnd1 frnd.cpp log2.txt log4.txt
但是你想運行的 make 命令的 Makefile 文件保存在 ../make-dir/ 目錄下,你可以這樣做:
$ make -C ../make-dir/
make: Entering directory `/home/himanshu/practice/make-dir’
make: Nothing to be done for `all’.
make: Leaving directory `/home/himanshu/practice/make-dir
你能看到 make 命令首先切到特定的目錄下,在那執行,然後再切換回來。
5.
通過 -f 選項將其它文件看作 Makefile
如果你想將重命名 Makefile 文件,比如取名為 my_makefile 或者其它的名字,我們想讓 make 將它也當成 Makefile,可以使用 -f 選項。
make -f my_makefile
通過這種方法,make 命令會選擇掃描 my_makefile 來代替 Makefile。
一、確認已經裝好了GCC和Make的軟件包
可以使用whereis命令查看:
如果whereis gcc和whereis make命令有結果,說明安裝了這兩個軟件,可以繼續往下做。
二、使用GCC編譯運行一個HelloWorld程序(只涉及單個文件)
可以在任何一個目錄編寫C程序然後編譯運行,我這個實例在自己主目錄進行:
然後就進入了編寫程序的界面:
按下鍵盤”i”進入編輯界面,然後輸入程序:
按ESC(進入命令行模式),然後輸入”:wq”,冒號表示開始輸入命令,字母w代表保存文件,字母q代表退出編輯器:
按回車退出vim編輯器,退回到終端,以下是之後的編譯運行截圖:
三、使用GCC編譯運行一個多文件程序(包含主程序和子程序)
這裡我們要寫兩個C程序文件,一個文件裡面寫個被調函數,另外一個文件中main函數調用第一個文件的函數,如下所示:
ex_display.c的代碼如下,同樣的寫完後ESC然後輸入:wq退出:
輸入如下的main函數代碼:
然後保存退出,如下是編譯運行過程:
四、使用Makefile解決多文件編譯運行的問題
正如上節的紅框框裡面所敘述,如果一個程序涉及的文件很多的話,每個都得寫出來,很是麻煩,所以Makefile就出現了,請看教程:
進入makefile的編輯界面後,輸入如下內容:
然後保存退出,運行make命令:
五、Makefile和shell script方法的對比
有人說,我把之前的所有命令,全寫到shell script裡面,不就達到Makefile的效果了,沒錯確實最終效果是相同的,但是Makefile卻有這些好處:
簡化編譯執行的命令(並沒有gcc –c的過程)
一次make後,下次只會編譯改動的文件,其它的文件不會再編譯了
其它還有一些優點,不過這第二個優點,對於大型項目來說,好處太大了!