開源技術教學文件網 為函式庫專案撰寫跨平台的 Makefile

最後修改日期為 JUN 20, 2018

在上一篇文章中,我們用 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 時,我們會分兩階段來設置。第一階段設置共通的部分。第二階段根據產出為 DEBUGRELEASE 而配置不同的參數。

重設 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 各自使用不同的指令,也要分開設置。

本文介紹了一些建立函式庫專案常見的技巧,讀者可以參考本文的內容,將可用的部分加入自己的專案中。

分享本文
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
追蹤本站
Facebook Facebook Twitter