[GNU Make] Makefile 教學:為應用程式專案撰寫跨平台的 Makefile

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

    在先前的文章中,我們都假定專案使用者使用某種類 Unix 系統,但實際上專案有可能在 Windows 系統上編譯;因此,本文考慮跨平台的需求來撰寫 Makefile。

    本文假設以下的情境:

    • 在 Windows 上,預設使用 Visual C++,但保留使用 MinGW (GCC) 的彈性
    • 在 Mac 上,預設使用 Clang,但保留使用 GCC 的彈性
    • 在 GNU/Linux 上,預設使用 GCC,但保留使用 Clang 的彈性

    考量目前桌面系統的市佔率,這樣的認定應足以應對大部分的使用者。

    由於這個 Makefile 較長,我們將完整的 Makefile 放到這裡,讀者可自行觀看。接著,我們會分段解說這個版本的 Makefile。

    首先,要讓 make 能偵測專案當下所在的平台:

    # Detect system OS.
    ifeq ($(OS),Windows_NT)
        detected_OS := Windows
    else
        detected_OS := $(shell sh -c 'uname -s 2>/dev/null || echo not')
    endif
    

    目前主流的平台中,除了 Windows 系統以外,都是某種類 Unix 系統,通常都會有 uname 指令,故我們借用系統上的 uname 來偵測專案所在的系統。

    接著,我們動態地決定專案所用的 C 編譯器:

    # Clean the default value of CC.
    CC=
    
    # Detect proper C compiler by system OS.
    ifndef CC
    	ifeq ($(detected_OS),Windows)
    		CC=cl
    	else ifeq ($(detected_OS),Darwin)
    		CC=clang
    	else
    		CC=gcc
    	endif
    endif
    

    我們採用的預設值是每個系統最常見的 C 編譯器。由於 make 對於 CC 內建的值是 cc,而 cc 在類 Unix 系統上通常是指向 GCC 的連結,但這個假設在 Windows 系統上會無法運作,故我們將預設值清空後重設。

    如果專案使用者想用其他的編譯器,仍可從命令列設定,實例如下:

    $ make CC=gcc-4.9
    

    接著,我們動態地設置 C 編譯器的參數:

    # Set CFLAGS for Release target.
    CFLAGS=
    ifndef CFLAGS
    	ifeq ($(CC),cl)
    		CFLAGS=/W4 /sdl
    	else
    		CFLAGS:=-Wall -Wextra -std=$(C_STD)
    	endif
    endif
    
    # Set CFLAGS for Debug target.
    ifneq (,$(DEBUG))
    	ifeq ($(CC),cl)
    		CFLAGS+=/DDEBUG /Zi /Od
    	else
    		CFLAGS+=-DDEBUG -g -O0
    	endif
    else
    	ifeq ($(CC),cl)
    		CFLAGS+=/O2
    	else
    		CFLAGS+=-O2
    	endif
    endif
    
    export CFLAGS
    

    由於 Visual C++ 的參數和 GCC (或 Clang) 不相容,所以我們使用條件句將其分開。

    另外,我們想要區分 Debug 和 Release 兩種版本,所以這段設定檔會分為兩段。將共通的部分寫在第一段,而 Debug 和 Release 有區分的部分寫在第二段。

    專案在 Windows 上編譯時,將變數 RM 重設:

    # Set proper RM on Windows.
    ifeq ($(detected_OS),Windows)
    	RM=del /q /f
    endif
    
    export RM
    

    這是因為 RM 預設值為 rm -f,Windows 上沒有這個指令,故我們將其換為等效的指令。

    根據不同的 C 編譯器設置目的檔名稱:

    # Modify it if more than one source files.
    SOURCE=$(PROGRAM:.exe=).c
    
    # Set object files.
    ifeq ($(CC),cl)
    	OBJS=$(SOURCE:.c=.obj)
    else
    	OBJS=$(SOURCE:.c=.o)
    endif  # OBJS
    
    export OBJS
    

    這是因為 Visual C++ 和 GCC (或 Clang) 對目的檔會使用不同的副檔名。

    根據不同 C 編譯器使用不同的參數來編譯執行檔:

    $(PROGRAM): $(OBJS)
    ifeq ($(CC),cl)
    	$(CC) /Fe:$(PROGRAM) $(OBJS) $(CFLAGS) $(LDFLAGS) $(LDLIBS)
    else
    	$(CC) -o $(PROGRAM) $(OBJS) $(CFLAGS) $(LDFLAGS) $(LDLIBS)
    endif
    

    同理,根據不同的 C 編譯器來編譯目的檔:

    %.obj: %.c
    	$(CC) /c $< $(CFLAGS)
    
    %.o: %.c
    	$(CC) -c $< $(CFLAGS)
    

    由本文可知,當我們考量的情境變多時,Makefile 也會變得更複雜。

    為什麼不直接用 Autotools 呢?由於 Windows 不支援 Autotools,我們如果以這個方式來發布專案,Windows 使用者需要安裝一套 MSYS 系統,建置上反而更加麻煩。

    除了採用本文提供的一些手法外,我們也可以用 CMake 撰寫跨平台的軟體建置設定檔,CMake 會根據不同的系統產生不同的設定檔,在類 Unix 系統上會產生相對應的 Makefile,在 Windows 系統上會產生 Visual Studio 可用的設定檔。

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