建立Makefile

此篇文章十分清楚易懂,可作為初學之用

使用make好處

  • 方便專案管理
  • 會透過檔案比對,依照相依性來編譯,不會全都編浪費時間
  • 可以同時編譯函式庫或是檔案

make常用指令

  • make -k: 會讓make在遇到錯誤的時候仍然運行,而不會在第一個問題中斷
  • make -n: 只印出將會進行的工作,而不會真的執行
  • make -f makefile_name: make預設執行名為makefile的檔案,此命令可指定makefile檔案名稱和位置
  • #: 註解

make指令格式

預設的target是"all", 若makefile中沒有all, 則是第一個target

1
2
3
make [option] [target]
make -n all clean # 多個target
make -f makefile2 install

撰寫makefile檔案

makefile是由一堆「目標」和其「相依性檔案」還有「法則」所組成的

  • [target] 目標 - 產生出來的東西
    • 用了 .PHONY 來指定 clean 為 fake 項目,所以 make 不會去檢查目錄中是否存在了一個名為 clean 的檔案。如此也可以提昇 make 的執行效率
      • 常用的偽target
1
2
3
4
5
6
7
.PHONY: all clean install
all: ...
...
install: myapp app.doc #安裝套件
cp myapp app.doc /usr/local/myapp/
clean: #刪除產生出來的目的檔
rm -rf *.o
  • [dependency] 相依性項目 - 若 dependency 的檔案有改動過,則重新產生 target
  • [rule] 法則 - 如何產生目標
    • 使用Tab作為開頭
    • 使用 Shell Script 語法
1
2
3
4
5
[target]: [dependency] [dependency]
[rule]
[rule]
[target]: [dependency]
[rule]

註1: makefile的命令和shell不同的地方: 每行命令在分開的shell中獨立執行

1
2
3
4
5
6
7
8
9
wrongClean:
cd junkdir
rm -f * # don't do that!

correctClean1:
cd junkdir && rm -f *

correctClean2:
rm -f junkdir/*

~如果寫錯的話,至少錯誤的makefile被刪除了~

註2: 在命令行首加上@,代表執行程式但不顯示在螢幕上。在命令行首加上-,代表執行命令時回傳非零值仍然繼續執行()

例:要產生all的話,需要兩個檔案myapp和app.doc(主程式和說明檔),make開始會去找尋如何產生myapp和 app.doc的方法,所以myapp會成為下一個要產生出來的目標。用gcc main.o a.o b.o -o myapp來產生myapp……,以此類推

1
2
3
4
5
6
7
8
9
all: myapp app.doc
myapp: main.o a.o b.o
gcc main.o a.o b.o -o myapp
main.o: main.c a.h
gcc -c main.c
a.o: a.c a.h
gcc -c a.c
b.o: b.c b.h
gcc -c b.c

在makefile中, 相依性順序是很重要的

Makefile的變數和巨集(macro)

設定變數的方法

  • 將export命令放在shell啟動script .bashrc.zshrc
    • 永遠都有效
  • 在shell中設定變數 export CC=gcc
    • 終端機開啟期間有效
  • 在執行命令前設定 CC=gcc | make
    • 針對特定命令的變數
    • 設定變數需要在實際命令之前
  • make可以直接設定變數
    • make CFLAGS="-g -Wall"
    • CFLAGS="-g -Wall" make
  • 在makefile中設定
    • ?=:若變數未定義,則替它指定新的值。否則,採用原有的值。
      • FOO ?= bar: 若 FOO 未定義,則 FOO = bar;若 FOO 已定義,則 FOO 的值維持不變。

指定時,等號兩側不可有空格

改變副檔名

  • SRC=a.c b.c
  • OBJ=$(SRC:.c=.o) # equals to a.o b.o

有幾個特別的內部巨集,讓makeifle更加簡明

  • $? 代表需要重建的相依性項目(檔案有被更新過),也就是 dependencies 中,比 targets 的修改日期還新檔案。
  • $@ 目前的target
  • $* 不含副檔名的target
  • $< 第一個 dependency

還有兩個有用的特別字元,可以加在要執行的命令之前

  • - 即使該行指令出錯,也不會中斷執行
  • @ 不會在terminal顯示該行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CC = gcc
CFLAGS = -Wall -ansi -g
OBJS = main.o a.o b.o
INSTALL_PATH = /usr/local/myapp/

all: myapp app.doc
myapp: $(OBJS)
$(CC) $(OBJS) -o $@
main.o: main.c a.h
$(CC) $(CFLAGS) -c -o $@ $<
a.o: a.c a.h
$(CC) $(CFLAGS) -c -o $@ $<
b.o: b.c b.h
$(CC) $(CFLAGS) -c -o $@ $<
install: myapp app.doc
cp myapp app.doc $(INSTALL_PATH)

Makefile 隱性法則(implicit rule)

1
2
3
4
5
6
7
8
9
10
CC = gcc
CFLAGS = -Wall -ansi -g
OBJS = main.o a.o b.o

all: myapp app.doc
myapp: $(OBJS)
$(CC) $(OBJS) -o $@
main.o: main.c a.h
a.o: a.c a.h
b.o: b.c b.h

makefile會在main.o自動產生規則 gcc -Wall -ansi -g -c -o main.o main.c

  • 若dependency為c:$(CC) $(CFLAGS) $(LDFLAGS) [dependencies]
  • 若dependency為c++:$(CXX) $(CXXFLAGS) $(LDFLAGS) [dependencies]
  • 若dependency為object(建立執行檔):$(CC) $(LDFLAGS) [dependencies] $(LDLIBS)

隱性法則列表

Makefile 檔尾法則

使用檔尾的延伸檔名作為法則,格式 .[old_suffix].[new_suffix]

1
2
3
4
5
6
7
8
9
10
11
12
.c.o:
$(CC) $(CFLAGS) -c -o $@ $<
.cpp.o:
g++ -c $<

INCLUDE_PATH = include

all: myapp.exe app.doc
myapp.exe: $(OBJS)
$(CC) $(OBJS) -o $@
.c.o:
$(CC) -I$(INCLUDE_PATH) $(CFLAGS) -c -o $@ $<

檔尾法則只可以用在本目錄

這目錄下面所有的.c檔變成.o檔,而法則就是去編譯它,而如果你想更懶一點的話還可以完全不寫,直接使用內建的法則,這樣也可以直接把目錄下面的所有檔都編好,為什麼呢?因為你要編出myapp的時候需要使用到$(OBJS)所以,就算你不寫.c.o或是任何的法則,make預設都會自己產生.o檔讓你可以連結出主程式。

makefile也有支援萬用字元

1
2
%.o: %.c
$(CC) -I$(INCLUDE_PATH) $(CFLAGS) -c -o $@ $<

萬用字元法則比較適合用於編譯一個大型的函式庫,而檔尾法則適合編譯一個目錄下面所有的檔案。

專案討論

Make Makefile with Release and Debug build

simple-makefile-with-release-and-debug-builds-best-practices
how-can-i-configure-my-makefile-for-debug-and-release-builds

參考資料

  • 撰寫Makefile教學
  • Makefile 語法簡介