位元詩人 [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 的行為是更合理的使用方式。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。