在先前的文章中,我們都假定專案使用者使用某種類 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 可用的設定檔。