GNU Make 相容的 Makefile 教學:條件編譯

PUBLISHED ON JUN 10, 2018 — BUILD AUTOMATION

    在前文中,我們將 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 製作靜態函式庫。

    在命令列中指定 TARGETDebug 時,會加入 -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 的行為是更合理的使用方式。