【原+转】⽤CMake代替makefile进⾏跨平台交叉编译
  在开始介绍如何使⽤CMake编译跨平台的静态库之前,先讲讲我在没有使⽤CMake之前所趟过的坑。因为很多开源的程序,⽐如png,都是⾃带编译脚本的。我们可以使⽤下列脚本来进⾏编译:
./configure  --prefix=/xxx/xx --enable-static=YES
make
make install
  相信⼿动在类Unix系统上⾯编译过开源程序的同学对上⾯的命令肯定⾮常熟悉。更悲惨的是,有些开源库是不提供configure配置⽂件的,只有⼀个Makefile或者。我的体会是,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呢?具体原因⼤家可以参考我在本⽂最后的参考链接⾥⾯的⼀书的第⼀章。我⾃⼰使⽤CMake的感受就是:我原来编写bash,配置configure参数,读各个开源库的INSTALL⽂件(因为不同库的configure参数有差别),配置各种编译flag,头⽂件包含等。最后3天时间,折腾了png和jepg两个库的编译。当然,中间我还写了android和linux的编译脚本。⽽换⽤CMake以后,我2天时间编译完了Box2D,spine和Chipmunk的编译。并且配置脚本相当简单,添加新的库,基本上只是拷贝脚本,修改⼀两个参数即可。有了CMake,编译跨平台静态库和⽣成跨平台可执⾏程序So Easy!
编写
  编写⼀个静态库的CMake配置⽂件过程如下:(这⾥我以Box2D为例)
1、指定头⽂件和源⽂件
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)
file(GLOB_RECURSE box2d_source_files "${CMAKE_CURRENT_SOURCE_DIR}/Box2D/*.cpp")
  我的和Box2D的⽂件结构关系如下图所⽰:
  这⾥的${CMAKE_CURRENT_SOURCE_DIR}表⽰所在的⽬录。⽽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写我们的编译脚本,另⼀种是新建⼀个.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倍,请记住这句话。
  以上就是编写⼀个配置⽂件的全部必要过程,⼀些更复杂的配置⽂件可能会增加⼀些其他东西,不过以上部分是基本逃不掉的。只要包含以上步骤就能成功交叉编译出⽬标平台的库⽂件。下⾯是⼀个完整的⽂件⽰例(⽂件名不能改):
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静态库
  我们有了配置完毕的⽂件,但不要以为这样就万事⼤吉了!不知道你发现了没,上述内容并不涉及⽬标平台的相关信息,因此编译出来的库只能在运⾏该配置⽂件的当前系统上使⽤。现在需要配合接下来的操作才能最终达到⽬的。
  编译iOS库,⼀般要先使⽤cmake指令⽣成xcode⼯程,再⽤xcode⼯程运⾏编译出静态库(也就是⼯程的product是静态库,⽽不是**.app)。插播⼀段MAC系统下cmake安装与使⽤⽅法介绍:
MAC默认是没有cmake指令的。要测试你的MAC是否已经装过cmake,可以这样做:打开Terminal,输⼊cmake --version,如果已经安装,则会显⽰具体的版本号;否则就是没安装或者没配置成功。
1、从这⾥mac.softpedia/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 .
  通过该命令可以⽣成⼀个deproject⼯程。但是,上述命令并不包含任何关于iOS的信息,因此该xcode⼯程只能⽤于MAC库的编译。不过我们可以借助ios-cmake
开源项⽬。下载这个toolchain⽂件,然后使⽤下列命令来⽣成ios⼯程:
cmake -DCMAKE_TOOLCHAIN_FILE=ake  -DCMAKE_IOS_DEVELOPER_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/ -DCMAKE_IOS_SDK_ROOT=/Applications/Xcode.app/Conten
  这个过程很容易出错,出错了不要慌,根据terminal的提⽰⼤胆地更改ake(记得提前备份)。我也是⼀步步调试过来的,以下的ake是我⾃⼰更改后
的,SDK是iOS8.3,Xcode6.3,如果环境跟我⼀样的话理论上说可以直接使⽤我的.cmake:
# This file is based off of the ake and ake
# 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 ake (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)
我的ake
  如果上⾯的操作都没错,就会顺利⽣成⼀个deproject⽂件,打开后记得做下⾯⼏件事情:
1、设置Product->Scheme->Edit Scheme为release模式
2、其他设置如图:
cmake如何使用
设置完毕后,点击运⾏,就能⽣成.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⽂件,这⾥我是从⾥⾯下载的。在使⽤这个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-andro
  这⾥的$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=../ake -DANDROID_ABI="armeabi" ..
编译Win32,wp8和winrt静态库
这⾥直接使⽤cmake-gui⽣成对应的VS⼯程,然后再⼿动编译即可。
关于Box2D完整的跨平台编译脚本可以参考
Reference:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。