[GNU Make] Makefile 教學:使用條件編譯建立靈活的 Makefile

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    在前文中,我們將 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 的行為是更合理的使用方式。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk
    臉書討論區
    標籤: GNU MAKE, MAKE, MAKEFILE