在開始介紹如何使用CMake編譯跨平台的靜態庫之前,先講講我在沒有使用CMake之前所趟過的坑。因為很多開源的程序,比如png,都是自帶編譯腳本的。我們可以使用下列腳本來進行編譯:
./configure --prefix=/xxx/xx --enable-static=YES make make install
相信手動在類Unix系統上面編譯過開源程序的同學對上面的命令肯定非常熟悉。更悲慘的是,有些開源庫是不提供configure配置文件的,只有一個Makefile或者Makefile.gcc。我的體會是,Makefile是一個很復雜的東西,沒有一定的積累我們是看不懂的,更別說去修改它了。而本文的CMake可以更傻瓜更簡單地達到我們的目的,你不需要理會復雜的makefile語法。Just follow me!
如果不配置編譯器和一些編譯、鏈接參數,這樣的操作,最后編譯出來的靜態庫只能在本系統上面被鏈接使用。比如你在mac上面運行上面的命令,編譯出來的靜態庫就只能給mac程序鏈接使用。如果在Linux上面運行上述命令,則也只能給Linux上面的程序所鏈接使用。如果我們想要在Mac上面編譯出ios和android的靜態庫,就必須要用到交叉編譯。
要進行交叉編譯,一般來說要指定目標編譯平台的編譯器,通常是指定一個CC環境變量,根據編譯的是c庫還是c++庫,要分別指定C_flags和CXX_flag,當然還需要指定c/c++和系統sdk的頭文件包含路徑。總之,非常之繁瑣。
為什么要使用CMake
為什么我們不使用autoconf?為什么我們不使用QMake,JAM,ANT呢?具體原因大家可以參考我在本文最后的參考鏈接里面的《Mastering CMake》一書的第一章。我自己使用CMake的感受就是:我原來編寫bash,配置configure參數,讀各個開源庫的INSTALL文件(因為不同庫的configure參數有差別),配置各種編譯flag,頭文件包含等。最后3天時間,折騰了png和jepg兩個庫的編譯。當然,中間我還寫了android和linux的編譯腳本。而換用CMake以后,我2天時間編譯完了Box2D,spine和Chipmunk的編譯。並且配置腳本相當簡單,添加新的庫,基本上只是拷貝腳本,修改一兩個參數即可。有了CMake,編譯跨平台靜態庫和生成跨平台可執行程序So Easy!
編寫CMakeLists.txt
編寫一個靜態庫的CMake配置文件過程如下:(這里我以Box2D為例)
1、指定頭文件和源文件
include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) file(GLOB_RECURSE box2d_source_files "${CMAKE_CURRENT_SOURCE_DIR}/Box2D/*.cpp")
我的CMakeLists.txt和Box2D的文件結構關系如下圖所示:
這里的${CMAKE_CURRENT_SOURCE_DIR}
表示CMakeLists.txt所在的目錄。而GLOB_RECURSE可以遞歸地去搜索Box2D目錄下面所有的.cpp文件來參與靜態庫的編譯。而include_directories和file指令,顯而易見,它們是用來指定靜態庫的頭文件和實現文件。
注:指定頭文件的原則是:可以多引入,但不能缺。交叉編譯本質也是編譯,因此基本的要求是語法沒問題,如果必要的頭文件缺少了自然編譯會失敗!所以,原則上可以把整個根目錄的頭文件都引入進去,不過這樣雖然省事,但是會導致生成的庫文件體積過大,但是會更保險一些,比如:
include_directories( "../../../myWindows" "../../../"#很殘暴地引入了整個根目錄 "../../../include_windows" )
2、添加環境變量(可選, added by 編程小翁, 博客園)
add_definitions( -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_REENTRANT -DENV_UNIX -DBREAK_HANDLER -DUNICODE -D_UNICODE)
如果需要判斷平台,可以這么寫:
IF(APPLE) add_definitions(-DENV_MACOSX) FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation ) ENDIF(APPLE)
其中-D_FILE_OFFSET_BITS=64表示定義一個環境變量_FILE_OFFSET_BITS且值為64。添加環境變量用在什么時候呢?我們常常可以在一些開源的項目工程代碼中看到這樣的形式:
#ifdef _UNICODE AString name = nameWindowToUnix2(fileName); #else const char * name = nameWindowToUnix(fileName); #endif
以上代碼中_UNICODE就是環境變量,那像這種變量該通過什么時候定義呢?一種是像上面一樣通過add_definitions寫我們的編譯腳本CMakeLists.txt,另一種是新建一個.h文件,寫在里面然后引用。兩種方式完全等效,我在我的交叉編譯工程中實踐過。例如,上面的add_definitions可以轉化為:
#define FILE_OFFSET_BITS 64 #define _LARGEFILE_SOURCE 1 #define _REENTRANT 1 #define ENV_UNIX 1 #define BREAK_HANDLER 1 #define UNICODE 1 #define _UNICODE 1
3、設置庫的名字跟類型
add_library(Box2D STATIC ${box2d_source_files})
這里add_library表示最終編譯為一個庫,static表示是靜態庫,如果想編譯動態庫,可以修改為shared. 至此,一個靜態庫的配置就完成了。當然,因為這個庫沒有包括其它外部的頭文件,所以會比較簡單。但這也遠比自己寫一個Makefile要簡單N倍,請記住這句話。
以上就是編寫一個CMakeLists.txt配置文件的全部必要過程,一些更復雜的配置文件可能會增加一些其他東西,不過以上部分是基本逃不掉的。只要包含以上步驟就能成功交叉編譯出目標平台的庫文件。下面是一個完整的CMakeLists.txt文件示例(文件名不能改):

1 cmake_minimum_required(VERSION 3.2) 2 3 #1、添加頭文件目錄,可以多引用,但是不能缺,因為缺了就編譯不過 4 include_directories( 5 "../../../myWindows" 6 "../../../" 7 "../../../include_windows" 8 ) 9 10 #2、添加環境變量,請結合實際項目要求,不是必須的 11 add_definitions( -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_REENTRANT -DENV_UNIX -DBREAK_HANDLER -DUNICODE -D_UNICODE) 12 13 IF(APPLE) 14 add_definitions(-DENV_MACOSX) 15 FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation ) 16 ENDIF(APPLE) 17 18 #3、源文件 19 file(GLOB_RECURSE src_files 20 "../../../../C/7zCrc.c" 21 "../../../../C/7zCrcOpt.c" 22 "../../../../C/7zStream.c" 23 "../../../../C/Aes.c" 24 "../../../../C/Alloc.c" 25 "../../../../C/Bra.c" 26 "../../../../C/Bra86.c" 27 ) 28 29 #4、設置生成靜態庫以及名稱 30 add_library(myLibName STATIC ${src_files}) 31 32 IF(APPLE) 33 TARGET_LINK_LIBRARIES(myLibName ${COREFOUNDATION_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) 34 ELSE(APPLE) 35 36 IF(HAVE_PTHREADS) 37 TARGET_LINK_LIBRARIES(myLibName ${CMAKE_THREAD_LIBS_INIT}) 38 ENDIF(HAVE_PTHREADS) 39 ENDIF(APPLE)
編譯iOS靜態庫
我們有了配置完畢的CMakeLists.txt文件,但不要以為這樣就萬事大吉了!不知道你發現了沒,上述內容並不涉及目標平台的相關信息,因此編譯出來的庫只能在運行該配置文件的當前系統上使用。現在需要配合接下來的操作才能最終達到目的。
編譯iOS庫,一般要先使用cmake指令生成xcode工程,再用xcode工程運行編譯出靜態庫(也就是工程的product是靜態庫,而不是**.app)。插播一段MAC系統下cmake安裝與使用方法介紹:
MAC默認是沒有cmake指令的。要測試你的MAC是否已經裝過cmake,可以這樣做:打開Terminal,輸入cmake --version,如果已經安裝,則會顯示具體的版本號;否則就是沒安裝或者沒配置成功。 1、從這里http://mac.softpedia.com/get/Development/Compilers/CMake.shtml下載cmake.app,然后安裝到默認位置; 2、將cmake.app與terminal相鏈接。打開terminal,輸入以下命令:export PATH=/Applications/CMake.app/Contents/bin:$PATH 3、配置成功。這次再輸入cmake就有效了。不過,以上鏈接只對本terminal窗口有效,一旦關閉或者其他新建的terminal同樣要再做一遍!
回到iOS交叉編譯上來,使用cmake命令生成xcode工程可以這么做:
cmake -GXcode .
通過該命令可以生成一個project.xcodeproject工程。但是,上述命令並不包含任何關於iOS的信息,因此該xcode工程只能用於MAC庫的編譯。不過我們可以借助ios-cmake開源項目。 下載iOS_64.cmake這個toolchain文件,然后使用下列命令來生成ios工程:
cmake -DCMAKE_TOOLCHAIN_FILE=iOS_64.cmake -DCMAKE_IOS_DEVELOPER_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/ -DCMAKE_IOS_SDK_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/ -GXcode .
這個過程很容易出錯,出錯了不要慌,根據terminal的提示大膽地更改iOS_64.cmake(記得提前備份)。我也是一步步調試過來的,以下的iOS_64.cmake是我自己更改后的,SDK是iOS8.3,Xcode6.3,如果環境跟我一樣的話理論上說可以直接使用我的.cmake:

# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake # files which are included with CMake 2.8.4 # It has been altered for iOS development # Options: # # IOS_PLATFORM = OS (default) or SIMULATOR # This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders # OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. # SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. # # CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder # By default this location is automatcially chosen based on the IOS_PLATFORM value above. # If set manually, it will override the default location and force the user of a particular Developer Platform # # CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder # By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. # In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. # If set manually, this will force the use of a specific SDK version # Standard settings set (CMAKE_SYSTEM_NAME Darwin) set (CMAKE_SYSTEM_VERSION 1 ) set (UNIX True) set (APPLE True) set (IOS True) # Force the compilers to gcc for iOS include (CMakeForceCompiler) #CMAKE_FORCE_C_COMPILER (gcc gcc) #CMAKE_FORCE_CXX_COMPILER (g++ g++) CMAKE_FORCE_C_COMPILER ("/usr/bin/gcc" gcc) CMAKE_FORCE_CXX_COMPILER ("/usr/bin/g++" g++) # Skip the platform compiler checks for cross compiling set (CMAKE_CXX_COMPILER_WORKS TRUE) set (CMAKE_C_COMPILER_WORKS TRUE) # All iOS/Darwin specific settings - some may be redundant set (CMAKE_SHARED_LIBRARY_PREFIX "lib") set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") set (CMAKE_SHARED_MODULE_PREFIX "lib") set (CMAKE_SHARED_MODULE_SUFFIX ".so") set (CMAKE_MODULE_EXISTS 1) set (CMAKE_DL_LIBS "") set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") # Hidden visibilty is required for cxx on iOS set (CMAKE_C_FLAGS "") set (CMAKE_CXX_FLAGS "-headerpad_max_install_names -fvisibility=hidden -fvisibility-inlines-hidden") set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names") set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names") set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") # hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree # (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache # and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun) # hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) # Setup iOS platform if (NOT DEFINED IOS_PLATFORM) set (IOS_PLATFORM "OS") endif (NOT DEFINED IOS_PLATFORM) set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform") # Check the platform selection and setup for developer root if (${IOS_PLATFORM} STREQUAL "OS") set (IOS_PLATFORM_LOCATION "iPhoneOS.platform") elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") else (${IOS_PLATFORM} STREQUAL "OS") message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") endif (${IOS_PLATFORM} STREQUAL "OS") # Setup iOS developer location if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) set (CMAKE_IOS_DEVELOPER_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer") endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") # Find and use the most recent iOS sdk if (NOT DEFINED CMAKE_IOS_SDK_ROOT) file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*") if (_CMAKE_IOS_SDKS) list (SORT _CMAKE_IOS_SDKS) list (REVERSE _CMAKE_IOS_SDKS) list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT) else (_CMAKE_IOS_SDKS) message (FATAL_ERROR "No iOS SDK's found in default seach path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.") endif (_CMAKE_IOS_SDKS) message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}") endif (NOT DEFINED CMAKE_IOS_SDK_ROOT) set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK") # Set the sysroot default to the most recent SDK set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support") # set the architecture for iOS - using ARCHS_STANDARD_32_BIT sets armv6,armv7 and appears to be XCode's standard. # The other value that works is ARCHS_UNIVERSAL_IPHONE_OS but that sets armv7 only set (CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD_64_BIT)" CACHE string "Build architecture for iOS") # Set the find root to the iOS developer roots and to user defined paths set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root") # default to searching for frameworks first set (CMAKE_FIND_FRAMEWORK FIRST) # set up the default search directories for frameworks set (CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks ${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks ${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks ) # only search the iOS sdks, not the remainder of the host filesystem set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
如果上面的操作都沒錯,就會順利生成一個project.xcodeproject文件,打開后記得做下面幾件事情:
1、設置Product->Scheme->Edit Scheme為release模式
2、其他設置如圖:
設置完畢后,點擊運行,就能生成.a靜態庫了。這時候,你可以使用下面的命令測試一下生成的靜態庫是否真的是iOS下的庫。
打開terminal,cd到.a所在目錄,假設靜態庫名字為libMyLib.a,輸入: lipo -info libMyLib.a ,如果顯示 Architectures in the fat file: lib7z_C++_938.a are: armv7 arm64 就說明操作無誤了。然后,盡情享用你的靜態庫吧!
編譯linux靜態庫(含64位和32位)
編譯linux的靜態庫是非常簡單的,只需要安裝好cmake以后,運行以下命令即可:
cmake . make
注意,如果是64位的系統,那么這樣只能生成64位的靜態庫。想要編譯出32位的靜態庫,則必須要先安裝32位系統的編譯工具鏈。
sudo apt-get install libx32gcc-4.8-dev sudo apt-get install libc6-dev-i386 sudo apt-get install lib32stdc++6 sudo apt-get install g++-multilib
然后,只需要指定cxx_flags為-m32即可,對應的CMake的寫法為:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
最后用cmake生成makefile並make即可生成32位的靜態庫。
編譯mac靜態庫
這個比較簡單,直接Xcode -GXcode
,然后用xcodebuild命令即可。
編譯Andoird靜態庫
編譯android庫我們同樣可以引入一個toolchain文件,這里我是從android-cmake里面下載的。 在使用這個toolchain文件之前,我們先要使用ndk自帶的make-standalone-toolchain.sh腳本來生成對應平台的toolchain.這個腳本位於你的NDK的路徑下面的buil/tools目錄下。
比如要生成arm平台的toolchain,我們可以使用下列命令:
sh $ANDROID_NDK/build/tools/make-standalone-toolchain.sh --platform=android-$ANDROID_API_LEVEL --install-dir=./android-toolchain --system=darwin-x86_64 --ndk-dir=/Users/guanghui/AndroidDev/android-ndk-r9d/ --toolchain=arm-linux-androideabi-4.8
這里的$ANDROID_NDK
為你的NDK的安裝路徑。這段命令可以生成arm的toolchain,最終可以編譯出armeabi和armeabi-v7a靜態庫。 如果想生成x86的toolchain,指需要使用下列命令:
sh $ANDROID_NDK/build/tools/make-standalone-toolchain.sh --platform=android-$ANDROID_API_LEVEL --install-dir=./android-toolchain-x86 --system=darwin-x86_64 --ndk-dir=/Users/guanghui/AndroidDev/android-ndk-r9d/ --toolchain=x86-4.8
export PATH=$PATH:./android-toolchain/bin export ANDROID_STANDALONE_TOOLCHAIN=./android-toolchain cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_ABI="armeabi" ..
編譯Win32,wp8和winrt靜態庫
這里直接使用cmake-gui生成對應的VS工程,然后再手動編譯即可。
關於Box2D完整的跨平台編譯腳本可以參考子龍山人Github
Reference:
以上內容除iOS部分比較多原創外,其他內容大部分轉自子龍山人bolg。
##THAT IS ALL.
=======================================
原創文章,轉載請注明 編程小翁@博客園,郵件zilin_weng@163.com,微信Jilon,歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!
=======================================