一、為什麼要用到 make 命令和 makefile 文件 在 Linux 下編寫一個程式,每次編譯都需要在命令行一行一行的敲命令。如果是一個很小的程式還好說,命令不怎的複雜,編譯速度也挺快,但是對於大型程式來說,這樣無疑很麻煩,且不說可能會敲錯命令,有時候僅僅改動了一個小地方,卻需要將整個程式全部 ...
一、為什麼要用到 make 命令和 makefile 文件
在 Linux 下編寫一個程式,每次編譯都需要在命令行一行一行的敲命令。如果是一個很小的程式還好說,命令不怎的複雜,編譯速度也挺快,但是對於大型程式來說,這樣無疑很麻煩,且不說可能會敲錯命令,有時候僅僅改動了一個小地方,卻需要將整個程式全部重新編譯一遍,顯然很浪費時間。Linux 提供了 make 命令來解決上述問題,它會在必要時重新編譯所有受改動影響的源文件。同時,還提供了一個 makefile 文件,它告訴 make 命令如何構建應用程式。這裡用一個簡單的例子提前演示一下:
/* hello.c */
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("hello world!\n"); exit(0); }
/* Makefile */
hello: hello.c gcc -o hello.s -S hello.c gcc -o hello.o -c hello.s gcc -o hello hello.o clean: -rm hello hello.s hello.o
這裡提供了兩段代碼,第一段代碼是一個簡單的 HelloWorld 程式,第二段代碼是為這個程式編寫的一個 makefile 文件。此時,只需要在命令行輸入 make 命令,就可以對源文件 hello.c 進行編譯,如下:
執行 make 命令時,make 命令會讀取 makefile 文件,並按照 makefile 文件中給出的命令來創建文件,同時會在執行時將命令列印到標準輸出。執行完 make 命令後,源文件所在目錄下多了三個文件:hello、hello.o 和 hello.s,其中 hello 是可執行文件,使用命令 ./hello 即可查看程式的輸出結果。這和直接在命令行使用 gcc 命令所得到的結果是一樣的,而且,當你修改了源文件時,也只需要再次使用 make 命令即可重新編譯,十分方便。
二、make 命令
make 命令用於從一個名為 makefile 的文件中獲得構建一個程式的依賴關係。make 命令會根據 makefile 文件來確定目標文件的創建順序以及正確的規則調用順序。
make 命令的一些常用參數
1)-k 參數:
使用 -k 參數可以讓 make 命令在發現錯誤時仍然繼續執行,而不是在檢測到第一個錯誤時就停下來。利用這個選項可以在一次操作中發現所有未編譯成功的源文件;
2)-n 參數:
使用 -n 參數,讓 make 命令輸出將要執行的操作步驟,而不是真正執行這些操作;
3)-f 參數:
使用 -f 參數,後面可以接一個文件名,用於指定一個文件作為 makefile 文件。如果沒有使用 -f 選項,則 make 命令會在當前目錄下查找名為 makefile 的文件,如果該文件不存在,則查找名為 Makefile 的文件。
三、makefile 文件
makefile 文件由一組依賴關係和規則構成。每個依賴關係都由一個目標(即將要創建的文件)和一個該目標所依賴的源文件組成;規則描述瞭如何通過這些依賴文件創建目標。簡單的來說,makefile 文件的寫法如下:
target: prerequisites
command1
command2
...
其中,target 是即將要創建的目標(通常是一個可執行文件),target 後面緊跟一個冒號,prerequisite 是生成該目標所需要的源文件(依賴),一個目標所依賴的文件可以有多個,依賴文件與目標之間以及各依賴文件之間用空格或製表符 Tab 隔開,這些元素組成了一個依賴關係。隨後的命令 command 就是規則,也就是 make 需要執行的命令,它可以是任意的 shell 命令。另外,makefile 文件中,註釋以 # 號開頭,一直延續到該行的結束。
3.1 依賴關係
依賴關係定義了最終應用程式里的每個文件與源文件之間的關係。一個依賴關係列表由目標和該目標的零個或多個依賴組成,語法是:先寫目標,然後接一個冒號,再用一個空格或製表符隔開,最後是用空格或製表符隔開的依賴文件列表,如下:
target: prerequisite1 prerequisite2 prerequisite3 ...
依賴關係表明瞭這樣一件事:目標文件 target 依賴於文件 prerequisite1、prerequisite2、prerequisite3 ...,即,要生成 target,需要有這幾個依賴文件的存在,而且,若其中一個依賴文件發生了改變,則需要重新生成 target。目標所依賴的文件可以有一個或多個,也可以沒有依賴文件 —— 該目標總被認為是過時的,在執行 make 命令時,若指定了該目標,則該目標所對應的規則將總被執行(如目標 clean)。
makefile 文件中可以有很多個目標,每個目標都有自己對應的規則。make 命令預設創建的是 makefile 文件中的第一個目標。也可以自己指定一個目標讓 make 命令去創建,只需要將該目標的名字作為參數放到 make 命令之後即可(如常用的 make clean)。實際上,更好的做法是,將 makefile 文件中的第一個目標定義為 all,然後再 all 後面列出其他從屬目標,這將告訴 make 命令,在未指定特定目標時,預設情況下將創建哪個目標。此外,使用目標 all ,還可以使 make 命令一次性創建多個文件,這取決於 all 後面所接的從屬目標的個數。
舉個例子說明一下文件與文件之間的依賴關係:
/* sum.c */ #include <stdio.h> #include <stdlib.h> extern int add(int i,int j); int main() { printf("%d\n",add(1,2)); exit(0); } /* add.c */ #include <stdio.h> int add(int i,int j) { int k; k = i + j; return k; }
這是一個簡單的加法程式,包含兩個文件:sum.c 和 add.c,其中,sum.c 中的 main 函數調用了 add.c 中的 add 函數。這個程式的依賴關係表如下:
sum: sum.o add.o
sum.o: sum.c stdio.h stdlib.h
add.o: add.c stdio.h
其中,最終所需要的目標文件是 sum,sum.o 和 add.o 是依賴 —— 要生成目標文件 sum ,需要先生成 sum.o 和 add.o。同樣的,作為目標的 sum.o 依賴於 sum.c、stdio.h 和 stdlib.h;add.o 依賴於 add.c 和 stdio.h。這組依賴關係形成了一個層次結構,它顯示了源文件之間的關係。
可以看出來,如果 add.c 發生了改變,那麼就需要重新編譯 add.o,而由於 add.o 發生了改變,目標文件 sum 也需要被重新創建,同時,由於 add.c 的改變並沒有影響到 sum.o(sum.o 不依賴於 add.c),因此,sum.o 並不需要被重新編譯。也就是說,通過使用 makefile 文件和 make 命令,我們可以實現,只重新編譯所有受到改動影響的源文件,沒有受到影響的源文件不必重新編譯。這比把整個程式全部重新編譯一遍顯然要快上很多,尤其是對於大型程式。
3.2 規則
makefile 文件里另一部分內容是規則,它們定義了目標的創建方式。 規則的內容可以是任意的 shell 命令。關於規則,有以下兩點需要註意:
1)規則所在行必須以製表符 tab 開頭,不能用空格;
2)規則所在行最好不要以空格結尾,可能會導致 make 命令執行失敗;
3)如果一行不足以寫下所有內容,需要在每行代碼的結尾加上一個反斜杠符 “\”,以讓所有的命令在邏輯上處於同一行。
兩個特殊字元 - 和 @:
1)在規則中,若命令之前加上了符號 “-”,則表明 make 命令將忽略該命令產生的所有錯誤;
2)若在命令之前加上了符號“@”,則表明 make 在執行該命令前,不會將該命令顯示在標準輸出上。
/* Makefile */
all: sum sum: sum.o add.o gcc -o sum add.o sum.o sum.o: sum.c gcc -c sum.c add.o: add.c gcc -c add.c
clean: -rm sum sum.o add.o
這是 3.1 中 sum.c 程式的 makefile 文件。其中 gcc 、rm 命令等行就是規則,它們告訴了 make 命令將如何去創建目標。
兩個特殊的目標:clean 和 install
目標 clean 和 install 是兩個特殊的目標,它們並不用於創建文件,而是有其他用途。
目標 clean 在前面已經提到過,它使用 rm 命令來刪除目標文件。rm 命令通常以減號 - 開頭,表示讓 make 命令忽略該命令的執行結果,這意味著,即使由於文件不存在而導致 rm 命令返回錯誤,命令 make clean 也能成功執行。
目標 install 用於按照命令的執行順序將應用程式安裝到指定的目錄,還是用上面的 sum.c 程式來演示一下目標 install 的用法:
all: sum # 安裝目錄 INSTDIR = /tmp sum: sum.o add.o gcc -o sum add.o sum.o sum.o: sum.c gcc -c sum.c add.o: add.c gcc -c add.c clean: rm sum sum.o add.o install: sum @if [ -d $(INSTDIR) ];\ then\ cp sum $(INSTDIR);\ chmod a+x $(INSTDIR)/sum;\ chmod og-w $(INSTDIR)/sum;\ echo "Installed in $(INSTDIR)";\ else\ echo "The directory $(INSTDIR) dose not exist!";\ fi
使用這個 makefile 文件,make 命令將會把 sum 安裝到目錄 /tmp 下(實際上,應用程式一般是安裝在 /usr/local/bin 下的,這裡為了方便就放到 /tmp 下了) 。執行 make install 命令,將得到如下結果:
輸出結果顯示 sum 已被成功安裝到了 /tmp 目錄下(實際上就是把可執行文件 sum 複製到 /tmp 目錄下)。再進入 /tmp 目錄查看,可以看到可執行文件 sum,其文件許可權是 rwxr-xr-x,與 makefile 文件中所設置的一致。
3.3 makefile 文件中的巨集
在 makefile 文件中定義一個巨集很簡單,如下:
MACRONAME=value
這裡定義了一個巨集 MACRONAME,引用巨集的方法是使用 $(MACRONAME) 或 ${MACRONAME} 。使用巨集定義,可以讓 makefile 文件的可移植性更強。除了自己定義一些巨集以外,make 命令還內置了一些特殊的巨集定義,使得 makefile 文件變得更加簡潔:
巨集 | 說明 |
$? | 當前目標所依賴的文件列表中比當前目標文件還要新的文件 |
$@ | 當前目標的名字 |
$< | 當前依賴文件的名字 |
$* | 不包括尾碼名的當前依賴文件的名字 |
除了在 makefile 文件裡面定義巨集以外,還可以調用 make 命令時,在命令行上給出巨集定義。命令行上的巨集定義將 覆蓋在 makefile 文件中的巨集定義。需要註意的是,在 make 命令後接巨集定義時,巨集定義必須以單個參數的形式傳遞,因此,需要避免在巨集定義中使用空格或加引號。
參考資料:
《Linux 程式設計 第四版》
https://www.ibm.com/support/knowledgecenter/zh/ssw_aix_71/com.ibm.aix.cmds3/make.htm