在上一篇文章中,我們用 GNU Make 製作跨平台的應用程式專案。本文繼續這個主題,會以 GNU Make 製作跨平台的函式庫專案。
由於本範例 Makefile 較長,我們把完整的 Makefile 程式碼放在這裡,文章中會節錄部分內容。
一開始先偵測宿主系統:
# Detect underlying system.
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell sh -c 'uname -s 2>/dev/null || echo not')
endif
export detected_OS
現行的桌面環境不是 Windows 就是 Unix,所以這樣的設置是可行的。
設置系統預設 C 編譯器:
# Set default C compiler.
# Clean implict CC variable.
CC=
ifndef CC
ifeq ($(detected_OS),Windows)
CC=cl
else ifeq ($(detected_OS),Darwin)
CC=clang
else
CC=gcc
endif
endif # CC
export CC
原本在 Unix 上,CC
會設為 cc
。而 cc
在 Unix 上會自動指向系統內定 C 編譯器。但在 Windows 上,這項設置會失效,所以我們清除原本的設置,根據系統預設 C 編譯器重新設置。
設置 C 標準:
# Clean C_STD variable.
C_STD=
ifndef C_STD
ifeq ($(CC),cl)
C_STD=
else
C_STD=c11
endif
endif # C_STD
export C_STD
在編譯 C 程式 (應用程式或函式庫) 時,最好鎖定 C 標準,讓專案使用者有明確的依循標準。但在 Visual C++ 中無法設置 C 標準,故將 C_STD
設為空值。
設置 CFLAGS
變數:
# 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
由於 MSVC 和 GCC 兩大 C 編譯器系統的參數不相容,我們得將參數用條件敘述分開。
在設置 CFLAGS
時,我們會分兩階段來設置。第一階段設置共通的部分。第二階段根據產出為 DEBUG 或 RELEASE 而配置不同的參數。
重設 RM
變數:
# Set proper RM on Windows.
ifeq ($(detected_OS),Windows)
RM=del /q /f
endif
export RM
原本在 Unix 上,RM
會設為 rm -f
。但 Windows 上沒有 rm(1)
指令可用,所以我們將 RM
重設為 Windows 內建的指令。
設置函式庫名稱:
# Set proper library name.
PROGRAM=mylib
ifeq ($(detected_OS),Windows)
ifeq ($(CC),cl)
DYNAMIC_LIB=$(PROGRAM).dll
else
DYNAMIC_LIB=lib$(PROGRAM).dll
endif # $(CC)
else
ifeq ($(detected_OS),Darwin)
DYNAMIC_LIB=lib$(PROGRAM).dylib
else
DYNAMIC_LIB=lib$(PROGRAM).so
endif # $(detected_OS),Darwin
endif # $(detected_OS),Windows
export DYNAMIC_LIB
ifeq ($(CC),cl)
STATIC_LIB=$(PROGRAM).lib
else
STATIC_LIB=lib$(PROGRAM).a
endif
export STATIC_LIB
在不同宿主系統及 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
MSVC 和 GCC 使用不同的目的檔名稱,故我們根據慣例來設置。
設置編譯動態函式庫的指令:
dynamic: $(OBJS)
ifeq ($(detected_OS),Windows)
ifeq ($(CC),cl)
link /DLL /DEF:$(DYNAMIC_LIB:.dll=.def) /out:$(DYNAMIC_LIB) $(LDFLAGS) $(LDLIBS) $(OBJS)
else
$(CC) $(CFLAGS) -shared -o $(DYNAMIC_LIB) $(OBJS) -I. -L. $(LDFLAGS) $(LDLIBS)
endif
else
$(CC) $(CFLAGS) -shared -o $(DYNAMIC_LIB) $(OBJS) -I. -L. $(LDFLAGS) $(LDLIBS)
endif
在 Windows 上,可能會有 MSVC 或 MinGW 兩種不同的 C 編譯器,故要設置兩種指令。在其他系統上則共用指令。
設置編譯靜態函式庫的指令:
static: $(OBJS)
ifeq ($(CC),cl)
lib /out:$(STATIC_LIB) $(OBJS)
else ifeq ($(detected_OS),Darwin)
libtool -static -o $(STATIC_LIB) $(OBJS)
else
$(AR) rcs $(STATIC_LIB) $(OBJS)
endif
Visual C++、macOS、GCC (Windows 或 Unix) 各自使用不同的工具來編譯靜態函式庫,故我們各自設置不同指令。
設置編譯目的檔的指令:
%.obj: %.c
ifneq (,$(findstring $(MAKECMDGOALS),$(DYNAMIC)))
$(CC) /c $< $(CFLAGS) /MD
else
$(CC) /c $< $(CFLAGS) /MT
endif
%.o: %.c
ifneq (,$(findstring $(MAKECMDGOALS),$(DYNAMIC)))
$(CC) -c $< $(CFLAGS) -fPIC
else
$(CC) -c $< $(CFLAGS)
endif
編譯給動態函式庫或給靜態函式庫的目的檔的指令相異,故要分開設置。此外,MSVC 和 GCC 各自使用不同的指令,也要分開設置。
本文介紹了一些建立函式庫專案常見的技巧,讀者可以參考本文的內容,將可用的部分加入自己的專案中。