位元詩人 [GNU Make] Makefile 教學:為函式庫專案撰寫跨平台的 Makefile

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

在上一篇文章中,我們用 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 各自使用不同的指令,也要分開設置。

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

關於作者

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

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