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

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

    【分享文章】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤網站】
    Facebook Twitter Plurk
    臉書討論區