在本章一開始我們提到過 make 的功能是可以簡化編譯過程裏面所下達的命令,同時還具有很多很方便的功能!那麼底下咱們就來試看看使用make 簡化下達編譯命令的流程吧!
先來想像一個案例,假設我的運行檔裏面包含了四個原始碼文件,分別是 main.c haha.c sin_value.c cos_value.c 這四個文件,這四個文件的目的是:
- main.c :主要的目的是讓使用者輸入角度數據與呼叫其他三支副程序;
- haha.c :輸出一堆有的沒有的信息而已;
- sin_value.c :計算使用者輸入的角度(360) sin 數值;
- cos_value.c :計算使用者輸入的角度(360) cos 數值。
這四個文件你可以到 http://vbird.dic.ksu.edu.tw/linux_basic/0520source/main.tgz來下載。由於這四個文件裏面包含了相關性,並且還用到數學函式在裏面,所以如果你想要讓這個程序可以跑,那麼就需要這樣編譯:
# 1. 先進行目標檔的編譯,最終會有四個 *.o 的檔名出現: [root@www ~]# gcc -c main.c [root@www ~]# gcc -c haha.c [root@www ~]# gcc -c sin_value.c [root@www ~]# gcc -c cos_value.c # 2. 再進行連結成爲運行檔,並加入 libm 的數學函式,以產生 main 運行檔: [root@www ~]# gcc -o main main.o haha.o sin_value.o cos_value.o \ > -lm -L/usr/lib -L/lib # 3. 本程序的運行結果,必須輸入姓名、360 度角的角度值來計算: [root@www ~]# ./main Please input your name: VBird <==這裏先輸入名字 Please enter the degree angle (ex> 90): 30 <==輸入以 360 度角爲主的角度 Hi, Dear VBird, nice to meet you. <==這三行爲輸出的結果喔! The Sin is: 0.50 The Cos is: 0.87 |
編譯的過程需要進行好多動作啊!而且如果要重新編譯,則上述的流程得要重新來一遍,光是找出這些命令就夠煩人的了!如果可以的話,能不能一個步驟就給他完成上面所有的動作呢?那就利用 make 這個工具吧!先試看看在這個目錄下創建一個名爲 makefile 的文件,內容如下:
# 1. 先編輯 makefile 這個守則檔,內容只要作出 main 這個運行檔 [root@www ~]# vim makefile main: main.o haha.o sin_value.o cos_value.o gcc -o main main.o haha.o sin_value.o cos_value.o -lm # 注意:第二行的 gcc 之前是 <tab> 按鍵產生的空格喔! # 2. 嘗試使用 makefile 制訂的守則進行編譯的行爲: [root@www ~]# rm -f main *.o <==先將之前的目標檔去除 [root@www ~]# make cc -c -o main.o main.c cc -c -o haha.o haha.c cc -c -o sin_value.o sin_value.c cc -c -o cos_value.o cos_value.c gcc -o main main.o haha.o sin_value.o cos_value.o -lm # 此時 make 會去讀取 makefile 的內容,並根據內容直接去給他編譯相關的文件羅! # 3. 在不刪除任何文件的情況下,重新運行一次編譯的動作: [root@www ~]# make make: `main' is up to date. # 看到了吧!是否很方便呢!只會進行升級 (update) 的動作而已。 |
或許你會說:『如果我創建一個 shell script 來將上面的所有動作都集結在一起,不是具有同樣的效果嗎?』呵呵!效果當然不一樣,以上面的測試爲例,我們僅寫出 main 需要的目標檔,結果 make 會主動的去判斷每個目標檔相關的原始碼文件,並直接予以編譯,最後再直接進行連結的動作!真的是很方便啊!此外,如果我們更動過某些原始碼文件,則 make 也可以主動的判斷哪一個原始碼與相關的目標檔文件有升級過,並僅升級該文件,如此一來,將可大大的節省很多編譯的時間呢!要知道,某些程序在進行編譯的行爲時,會消耗很多的CPU 資源呢!所以說, make 有這些好處:
- 簡化編譯時所需要下達的命令;
- 若在編譯完成之後,修改了某個原始碼文件,則 make 僅會針對被修改了的文件進行編譯,其他的object file 不會被更動;
- 最後可以依照相依性來升級 (update) 運行檔。
既然 make 有這麼多的優點,那麼我們當然就得好好的瞭解一下 make 這個令人關心的傢伙啦!而 make 裏面最需要注意的大概就是那個守則文件,也就是 makefile 這個文件的語法啦!所以底下我們就針對 makefile 的語法來加以介紹羅。
make 的語法可是相當的多而複雜的,有興趣的話可以到 GNU (注1)去查閱相關的說明,鳥哥這裏僅列出一些基本的守則,重點在於讓讀者們未來在接觸原始碼時,不會太緊張啊!好了,基本的 makefile 守則是這樣的:
標的(target): 目標檔1 目標檔2
<tab> gcc -o 欲創建的運行檔 目標檔1 目標檔2
|
那個標的 (target) 就是我們想要創建的資訊,而目標檔就是具有相關性的 object files ,那創建運行檔的語法就是以 <tab> 按鍵開頭的那一行!特別給他留意喔,『命令列必須要以 tab 按鍵作爲開頭』才行!他的守則基本上是這樣的:
- 在 makefile 當中的 # 代表註解;
- <tab> 需要在命令行 (例如 gcc 這個編譯器命令) 的第一個字節;
- 標的 (target) 與相依文件(就是目標檔)之間需以『:』隔開。
同樣的,我們以剛剛上一個小節的範例進一步說明,如果我想要有兩個以上的運行動作時,例如下達一個命令就直接清除掉所有的目標檔與運行檔,該如何製作呢?
# 1. 先編輯 makefile 來創建新的守則,此守則的標的名稱爲 clean : [root@www ~]# vi makefile main: main.o haha.o sin_value.o cos_value.o gcc -o main main.o haha.o sin_value.o cos_value.o -lm clean: rm -f main main.o haha.o sin_value.o cos_value.o # 2. 以新的標的 (clean) 測試看看運行 make 的結果: [root@www ~]# make clean <==就是這裏!透過 make 以 clean 爲標的 rm -rf main main.o haha.o sin_value.o cos_value.o |
如此一來,我們的 makefile 裏面就具有至少兩個標的,分別是 main 與 clean ,如果我們想要創建 main 的話,輸入『make main』,如果想要清除有的沒的,輸入『makeclean』即可啊!而如果想要先清除目標檔再編譯 main 這個程序的話,就可以這樣輸入:『make clean main』,如下所示:
[root@www ~]# make clean main
rm -rf main main.o haha.o sin_value.o cos_value.o
cc -c -o main.o main.c
cc -c -o haha.o haha.c
cc -c -o sin_value.o sin_value.c
cc -c -o cos_value.o cos_value.c
gcc -o main main.o haha.o sin_value.o cos_value.o -lm
|
這樣就很清楚了吧!但是,你是否會覺得,咦! makefile 裏面怎麼重複的數據這麼多啊!沒錯!所以我們可以再藉由 shellscript 那時學到的『變量』來更簡化 makefile 喔:
[root@www ~]# vi makefile LIBS = -lm OBJS = main.o haha.o sin_value.o cos_value.o main: ${OBJS} gcc -o main ${OBJS} ${LIBS} clean: rm -f main ${OBJS} |
與 bash shell script 的語法有點不太相同,變量的基本語法爲:
- 變量與變量內容以『=』隔開,同時兩邊可以具有空格;
- 變量左邊不可以有 <tab> ,例如上面範例的第一行 LIBS 左邊不可以是 <tab>;
- 變量與變量內容在『=』兩邊不能具有『:』;
- 在習慣上,變量最好是以『大寫字母』爲主;
- 運用變量時,以 ${變量} 或 $(變量) 使用;
- 在該 shell 的環境變量是可以被套用的,例如提到的 CFLAGS 這個變量!
- 在命令列模式也可以給予變量。
由於 gcc 在進行編譯的行爲時,會主動的去讀取 CFLAGS這個環境變量,所以,你可以直接在 shell 定義出這個環境變量,也可以在makefile 文件裏面去定義,更可以在命令列當中給予這個咚咚呢!例如:
[root@www ~]# CFLAGS="-Wall" make clean main # 這個動作在上 make 進行編譯時,會去取用 CFLAGS 的變量內容! |
也可以這樣:
[root@www ~]# vi makefile LIBS = -lm OBJS = main.o haha.o sin_value.o cos_value.o CFLAGS = -Wall main: ${OBJS} gcc -o main ${OBJS} ${LIBS} clean: rm -f main ${OBJS} |
咦!我可以利用命令列進行環境變量的輸入,也可以在文件內直接指定環境變量,那萬一這個CFLAGS 的內容在命令列與 makefile 裏面並不相同時,以那個方式輸入的爲主?呵呵!問了個好問題啊!環境變量取用的守則是這樣的:
- make 命令列後面加上的環境變量爲優先;
- makefile 裏面指定的環境變量第二;
- shell 原本具有的環境變量第三。
此外,還有一些特殊的變量需要了解的喔:
- $@:代表目前的標的(target)
所以我也可以將 makefile 改成:
[root@www ~]# vi makefile LIBS = -lm OBJS = main.o haha.o sin_value.o cos_value.o CFLAGS = -Wall main: ${OBJS} gcc -o $@ ${OBJS} ${LIBS} <==那個 $@ 就是 main ! clean: rm -f main ${OBJS} |
這樣是否稍微瞭解了 makefile (也可能是 Makefile) 的基本語法?這對於你未來自行修改原始碼的編譯守則時,是很有幫助的喔!^_^!
###################################################################
例解 Linux 下 Make 命令
Linux 下 make 命令是系統管理員和程序員用的最頻繁的命令之一。管理員用它通過命令行來編譯和安裝很多開源的工具,程序員用它來管理他們大型複雜的項目編譯問題。本文我們將用一些實例來討論 make 命令背後的工作機制。
Make 如何工作的
對於不知道背後機理的人來說,make 命令像命令行參數一樣接收目標。這些目標通常存放在以 “Makefile” 來命名的特殊文件中,同時文件也包含與目標相對應的操作。更多信息,閱讀關於 Makefiles 如何工作的系列文章。
當 make 命令第一次執行時,它掃描 Makefile 找到目標以及其依賴。如果這些依賴自身也是目標,繼續爲這些依賴掃描 Makefile 建立其依賴關係,然後編譯它們。一旦主依賴編譯之後,然後就編譯主目標(這是通過 make 命令傳入的)。
現在,假設你對某個源文件進行了修改,你再次執行 make 命令,它將只編譯與該源文件相關的目標文件,因此,編譯完最終的可執行文件節省了大量的時間。
Make 命令實例
下面是本文所使用的測試環境:
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
會強制編譯所有的目標文件以及最終的執行文件。