1. 程式人生 > >make命令與makefile檔案

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.c2.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.o2.o3.o。同樣,main.o依賴於main.c

a.h2.o依賴於2.ca.hb.h3.o依賴於3.cb.hc.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.o3.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.hb.hc.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.c3.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前須先建立myappinstall目標由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

四、參考資料