make命令與makefile檔案
一、多個原始檔帶來的問題
在編寫c/c++測試程式時,我們習慣每次修改一處程式碼,然後就馬上編譯執行來檢視執行的結果。這種編譯方式對於小程式來說是沒有多大問題的,可對於大型程式來說,由於包含了大量的原始檔,如果每次改動一個地方都需要編譯所有的原始檔,這個簡單的直接編譯所有原始檔方式對程式設計師來說簡直是噩耗。
我們看一個例子:
// main.c
#include "a.h"
// 2.c
#include "a.h"
#include "b.h"
// 3.c
#include "b.h"
#include "c.h"
如果程式設計師只修改了標頭檔案c.h
,則原始檔main.c
和2.c
都無需編譯,因為它們不依賴這個標頭檔案。而對3.c
c.h
,所以在標頭檔案c.h
改動後,就必須得新編譯。 而如果改動了
b.h
可是忘記編譯了2.c
,那麼最終的程式就可能無法正常工作。 make 工具就是為了解決上述問題而出現的,它會在必要時重新編譯所有受改動影響的原始檔。
二、make 命令
make
命令本身支援許多選項,最常用的是-f
選項。如果我們直接執行
make
那麼make
命令會首先在當前目錄查詢名為makefile
的檔案,如果找不到,就會查詢名為Makefile
的檔案。
為了指示make
命令將哪個檔案作為makefile檔案,可以使用 -f
選項:
make -f Makefile1
三、makefile 檔案
上面提到makefile檔案,那麼什麼是makefile檔案呢?
make
命令功能雖然十分強大,但是光憑其自身無法瞭解如何構建應用程式的。這時,makefile就出來了,它告訴make
應用程式如何構建的。make
命令和makefile檔案的結合提供了一個在管理專案的十分強大的工具,它們不僅用於控制原始檔的編譯,而且還提供了將應用程式安裝到目標目錄等其他功能。
3.1 依賴關係
依賴關係定義了應用程式裡面每個檔案與其他原始檔之間的關係。例如在上面的例子中,我們可以定義最終應用程式依賴於目標檔案main.o
,2.o
和3.o
。同樣,main.o
依賴於main.c
a.h
,2.o
依賴於2.c
,a.h
和b.h
,3.o
依賴於3.c
,b.h
和c.h
。 在makefile檔案中,依賴關係的寫法是:先寫目標的名稱,然後緊跟一個冒號,接著是空格或者製表符tab,最後是用空格或者製表符tab隔開的檔案列表。上面的例子的依賴關係如下:
myapp: main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
這組依賴關係形成一個層次結構,展示了原始檔之間的關係。例如,如果原始檔b.h
發生改變,就需要重新編譯2.o
和3.o
,接下來還需要重新編譯myapp
。
3.2 規則
makefiel檔案中的規則定義了目標的建立方式。在上面的例子中,我們使用gcc -c 2.c
建立2.o
。這個gcc
命令即是目標2.o
的建立方式,也即是規則。
在makefile檔案中,規則都必須以tab開頭。
在原始檔所在的目錄下建立Makefile1
檔案,其內容如下。
myapp: main.o 2.o 3.o
gcc -o myapp main.o 2.o 3.o
main.o: main.c a.h
gcc -c main.c
2.o: 2.c a.h b.h
gcc -c 2.c
3.o: 3.c b.h c.h
gcc -c 3.c
三個標頭檔案a.h
,b.h
,c.h
內容都為空,原始檔的內容如下:
/* main.c */
#include <stdlib.h>
#include "a.h"
extern void function_two();
extern void function_three();
int main()
{
function_two();
function_three();
exit(EXIT_SUCCESS);
}
/* 2.c */
#include <stdio.h>
#include "a.h"
#include "b.h"
void function_two() {
printf("function two\n");
}
/* 3.c */
#include <stdio.h>
#include "b.h"
#include "c.h"
void function_three() {
printf("function three\n");
}
執行make
命令,:
$ make -f Makefile1
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
執行應用程式:
$ ./myapp
function two
function three
從輸出可以說明應用程式已被正確構建。
如果改變b.h
標頭檔案,makefile能夠正確處理這一變化,只有2.c
和3.c
發生重新編譯:
$ touch b.h
$ make -f Makefile1
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
3.3 註釋
makefile檔案使用#
來表示註釋,一直延續到這一行的結束。
3.4 巨集
不同的平臺下可能使用不同的編譯器,不同的環境(例如開發與線上環境)也可能使用不同的編譯器選項,為了便於修改makefile這些可變的引數,我們可以使用巨集來實現makefile。
makefile引用巨集定義的方法為$(MACRONAME)
。我們來看如何使用巨集來改寫上面的makefile檔案。
all: myapp
# 編譯器
CC = gcc
# include的搜尋路徑
INCLUDE = .
# 編譯器引數
CFLAGS = -g -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
我們習慣在makefile檔案中將第一個目標定義為all
,然後再列出其他從屬的目標,上面的makefile也遵循這個約定。
執行make
命令:
$ make -f Makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
同樣也正確構建了應用程式myapp
。
3.5 多個目標
makefile檔案除了定義編譯的目標外,還可以定義其他的目標。例如,增加一個clean
選項來刪除不需要的目標檔案,增加一個install
選項來將編譯成功的應用程式安裝到另一個目錄下,等等。
all: myapp
CC = gcc
INSTDIR = /usr/local/bin
INCLUDE = .
CFLAGS = -g -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
-rm main.o 2.o 3.o
install: myapp
@if [ -d $(INSTDIR) ]; \
then \
cp myapp $(INSTDIR);\
chmod a+x $(INSTDIR)/myapp;\
chmod og-w $(INSTDIR)/myapp;\
echo "Install in $(INSTDIR)";\
else \
echo "sorry, $(INSTDIR) does not exist";\
fi
上面的makefile檔案有幾點需要注意的。
(1)特殊目標all
只指定了myapp
這個目標,因此,在執行make
命令時未指定目標,它的預設行為就是建立目標myapp
。
(2)目標clean
用來測試編譯過程中產生的中間檔案。
(3)目標install
用於將應用程式安裝到指定目錄,它依賴於myapp
,即執行install
前須先建立myapp
。install
目標由shell指令碼組成,由於make
命令在執行規則時會呼叫一個shell,並且會針對每個規則使用一個新的shell,所以必須在上面每行程式碼的結尾加上一個\
,讓所有的shell指令碼都處於同一行。
指令碼以@
開頭,說明make
在執行這些規則之前不會在標準輸出顯示命令本身。
建立myapp
:
$ make -f Makefile3
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
將myapp
安裝到指到目錄:
$ make -f Makefile3 install
Install in /usr/local/bin
然後可以直接執行myapp
:
$ myapp
function two
function three
刪除中間檔案:
$ make -f Makefile3 clean
rm main.o 2.o 3.o