Makefiles 是組織程式碼編譯的一種方式。透過這篇簡明教程,雖然你不能完整學會 make
指令,但是你可以使用 makefile 來組織小到中型的專案啦。
一個 簡單的例子
我們來從下面的三個檔案開始吧:hellomake.c
,hellofunc.c
,hellomake.h
。這是一個經典 C 語言程式,程式碼根據功能組織在不同的檔案中。
hellomake.c
#include <hellomake.h>
int main() {
// 呼叫另一個檔案裡的函式
myPrintHelloMake();
return (0);
}
hellofunc.c
#include <stdio.h>
#include <hellomake.h>
void myPrintHelloMake() {
printf("Hello makefiles!\n");
return;
}
hellomake.h
/* example include file */
void myPrintHelloMake(void);
一般情況下,我們透過下面的指令來編譯程式碼:
gcc -o hellomake hellomake.c hellofunc.c -I.
我們來說明下這個指令:
- 我們編譯兩個
.c
檔案 - 命名了編譯後的可執行檔案為
hellomake
-I.
告訴 gcc 在當前目錄中尋找hellomake.h
如果沒有使用 makefile,我們在除錯開發的時候,可以在終端上輸入 向上方向鍵
來快速顯示上次的指令(尤其是你有多個 .c
檔案需要編譯的時候)。
然而,透過上面的直接輸入編譯指令的方式存在兩個弊端:
- 弊端一:不方便呀!當你換了電腦之後,你要重新再輸入上面的指令。
- 弊端二:編譯效率低下!即使你只是修改了專案中的一個
.c
檔案,每次編譯時,還是需要編譯所有的檔案,這無疑是效率低下,浪費時間。
所以接下來,請出本文的主角 —— makefile。
Makefile1
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
把上述的內容,放入到 Makefile
或者 makefile
檔案,然後在命令列輸入 make
命令,就能夠直接執行編譯了。有以下幾點我們需要關注下:
- 如果
make
後面沒有跟任何引數,那麼他就會執行 makefile 的第一條規則。 - 把命令依賴的檔案放在第一行的
:
後面,這樣make
就能知道,當依賴檔案變化時,hellomake
規則需要重新執行。 - 注意,第二行
gcc
前面,是一個tab
製表符!不要使用空格!
透過這樣簡單的 Makefile,我們已經解決了弊端一的問題,即:我們不需要每次都輸入編譯指令了。
然而,現在還不夠高效,即使只修改了一個檔案,還是需要全量編譯(即編譯所有的原始檔)。為了使編譯更加高效,讓我們繼續往下看。
Makefile2
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
我們定義了兩個常量 CC
、 CFLAGS
,這兩個常量告訴 make
怎麼去編譯 hellomake.c
和 hellofunc.c
。其中 CC
告訴 make 使用哪個 C 編譯器,CFLAGS
說明了編譯指令的引數列表。透過把 hellomake.o
和 hellofunc.o
放到依賴列表中, make
指令就知道每次需要分別編譯 .c
檔案,然後再把他們編譯為可行性檔案 hellomake
。
終端執行效果如下:
➜ makefile-tourial git:(master) ✗ make
gcc -I. -c -o hellomake.o hellomake.c
gcc -I. -c -o hellofunc.o hellofunc.c
gcc -o hellomake hellomake.o hellofunc.o
➜ makefile-tourial git:(master) ✗
這種形式的 makefile 對小型的專案還是比較方便的。然而,還是有個問題,那就是依賴檔案的更新。設想下,即使你修改了hellomake.h
檔案,make
指令不會重新編譯檔案。
為了解決這個問題,我們需要告訴 make
一件事情:即.c
檔案和 .h
檔案間的依賴關係。好,我們繼續往下看。
Makefile3
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
相較於上個版本,我們先是增加了一個 DEPS
:這裡列出了 .c
檔案所依賴的 .h
檔案集合。
接著,我們定義了一個了規則 %.o: %.c $(DEPS)
:它說明了 .o
檔案是取決於 .c
檔案和 DEPS
裡的 .h
檔案。
接下來我們看下規則 $(CC) -c -o $@ $< $(CFLAGS)
,意思是說,為了生成這些 .o
檔案,make
指令使用了 CC
定義的編譯器來編譯 .c
檔案:
-c
說明了是為了生成目標檔案(object files)$@
代表:
左邊的內容,即:%.o
$<
是依賴列表裡的第一項,即:%.c
CFLAGS
和之前的說明一樣,就是編譯的指令引數了(flag)
執行效果如下:
➜ makefile-tourial git:(master) ✗ make
gcc -c -o hellomake.o hellomake.c -I.
gcc -c -o hellofunc.o hellofunc.c -I.
gcc -o hellomake hellomake.o hellofunc.o
➜ makefile-tourial git:(master) ✗
最後,我們再來做下簡化,使編譯更具通用性。我們使用 $@
和 $^
來分別表示 :
的左側和右側。在下面的例子裡,所有 include 檔案會作為 DEPS
的一部分,所有目標檔案(object files)會作為 OBJ
的一部分。
Makefile4
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
執行效果如下:
➜ makefile-tourial git:(master) ✗ make
gcc -c -o hellomake.o hellomake.c -I.
gcc -c -o hellofunc.o hellofunc.c -I.
gcc -o hellomake hellomake.o hellofunc.o -I.
讓我們來進一步思考下:
- 我們能不能把
.h
的檔案都放到一個專門的inlcude
目錄,把.c
檔案都放到一個專門的src
目錄? - 我們能不能把這些煩人的
.o
檔案都隱藏起來?
當然是可以的!我們會在下一個 makefile 中把對應的檔案放到 include
和 lib
資料夾中,並且把生成的目標檔案都放到 src
的 obj
子目錄中。除此之外,我們還可以定義任何我們想包含的庫檔案,比如常用的 math library -lm
。這個 makefile 放在 src
目錄裡。
需要注意的是,我們還定義了一個 clean
規則,用來把生成的目標檔案清除(使用 make clean
命令)。.PHONY
防止 make
清除名為 clean
的檔案。
檔案路徑為
➜ src git:(master) ✗ tree
.
├── hellofunc.c
├── hellomake
├── hellomake.c
├── makefile
└── obj
├── hellofunc.o
└── hellomake.o
1 directory, 6 files
Makefile5
IDIR = ../include
CC=gcc
CFLAGS=-I$(DIR)
ODIR=obj
LDIR=../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS=$(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ=$(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)(
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
執行結果
➜ src git:(master) ✗ make
gcc -c -o obj/hellomake.o hellomake.c -I../include
gcc -c -o obj/hellofunc.o hellofunc.c -I../include
gcc -o hellomake obj/hellomake.o obj/hellofunc.o -I../include
注意要在
src
目錄下執行,並且要把.h
檔案放到include
目錄裡
好了,到目前為止,你已經有了一個不錯的 makefile 了,現在你能 hold 住一箇中型的專案了。你也可以增加更多的規則到 makefile 裡,你甚至可以在一個規則中呼叫另一個規則。
想知道更多關於 makefile 和 make 的資訊,就去查閱 GNU Make Manual 吧!
本作品採用《CC 協議》,轉載必須註明作者和本文連結