在前文中,我們將 Makefile 參數化,通用性改善一些,但仍然有一些小缺失,像是 CC 寫死在 Makefile 中,每次要換編譯器時都要修改檔案;另外,CFLAGS 無法靈活更動,像是我們想在編譯函式庫時想加入 -g
參數,也是要開設定檔來修改。我們希望 Makefile 保留足夠的彈性,不用時時修改 Makefile 就能夠符合我們的需求。
我們將前文的 Makefile 加入條件編譯及一些小的語法特性後修改如下:
# Clean the default value of CC.
CC=
ifndef CC
CC=gcc
endif
ifndef CFLAGS
ifeq ($(TARGET),Debug)
CFLAGS=-Wall -Wextra -g -std=c99
else
CFLAGS=-Wall -Wextra -O2 -std=c99
endif # TARGET
endif # CFLAGS
OBJS=test_deque_int.o deque_int.o
TEST_PROG=test_deque_int.out
# Pure targets.
.PHONY: all dynamic static test clean
all: dynamic
dynamic:
$(CC) $(CFLAGS) -fPIC -c deque_int.c
$(CC) $(CFLAGS) -shared -o libalgodeque.so deque_int.o
static: deque_int.o
$(AR) rcs -o libalgodeque.a deque_int.o
test: $(TEST_PROG)
./$(TEST_PROG)
echo $$?
$(TEST_PROG): $(OBJS)
$(CC) $(CFLAGS) -o $(TEST_PROG) $(OBJS)
# Pattern rules.
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
$(RM) $(TEST_PROG) *.o *.so *.a
在這個專案中,除了原先的使用方式外,新增的使用方式如下文所述。
在命令列中修改 CC 參數時,可指定所用的 C 編譯器:
$ make CC=clang static
在本指令中,我們用 Clang 製作靜態函式庫。
在命令列中指定 TARGET 為 Debug 時,會加入 -g
參數:
$ make CC=gcc-4.9 TARGET=Debug
在本指令中,我們用 GCC-4.9 製作帶有除錯訊息的動態函式庫。
我們可以直接在命令列修改 CFLAGS:
$ make CC=clang CFLAGS="-Wall -Wextra -g -Werror" test
在本指令中,我們使用 Clang 編譯後執行測試程式,並採用更嚴格的參數 (-Werror
) 來測試。
接著,我們來看實作的部分。首先,來看 CC 的設定:
# Clean the default value of CC.
CC=
ifndef CC
CC=gcc
endif
在 #
所在的位置之後的同一行內是註解,註解是給程式設計者閱讀的,這個部分不會影響實質的設定。
一開始我們以 CC=
將 CC 設為空值,因為 CC 預設為 cc
,但我們不想使用這個預設值,故將其清空。接著,我們使用條件編譯在 CC 為空時將其設為 gcc
,在類 Unix 系統上,這是一個合理的預設值。
如果我們在命令列加入 CC 的值,則 make
會按照該值所設定的 C 編譯器來編譯程式;反之,則使用 gcc
來編譯程式。在這樣的設置下,缺點在於使用者設定的環境變數會無效,因為在 Makefile 中清空了。
如果想使用先前設置的環境變數,可參考以下指令:
$ make CC=$CC
接著,來看 CFLAGS 的設定:
ifndef CFLAGS
ifeq ($(TARGET),Debug)
CFLAGS=-Wall -Wextra -g -std=c99
else
CFLAGS=-Wall -Wextra -O2 -std=c99
endif # TARGET
endif # CFLAGS
在這裡,我們使用巢狀條件編譯。條件如下:
- 使用者在命令列設置 CFLAGS 則直接使用
- 如果使用者未自行設置 CFLAGS 時
- 若使用者將 TARGET 設為 Debug,則使用適合於除錯的 CFLAGS,
- 在其他情形,使用適合於發布的 CFLAGS
透過這樣的設置,我們就不會將 CFLAGS 寫死,較先前更靈活。
我們加入了一個先前沒有使用的設置:
# Pure targets.
.PHONY: all dynamic static test clean
.PHONY 的意思是說,該任務不代表某個檔案,所以一定會執行。基本上,非檔案名稱的任務都應設置此項目。如果刻意將某個檔案名稱設為 .PHONY 則該任務會強迫執行。
接下來,我們看一項 pattern rule,先前沒有此項設置:
# Pattern rules.
%.o: %.c
$(CC) $(CFLAGS) -c $<
Pattern rule 算是 Makefile 中的通用規則;在本例中,代表每個 .o 檔案都對應到同名的 .c 檔案。在指令中,$<
指向某個來源檔案,在本例中即為某個 .c 檔。善用 pattern rules,可使 Makefile 更通用,但也會較難閱讀。
在加入一些新的語法特性後,這個 Makefile 比先前來得靈活一些,我們不需要每次都手動編輯 Makefile,可以直接透過命令列更改 make
的行為。如果專案有上版本控制,能在命令列動態修正 make
的行為是更合理的使用方式。