在开始介绍如何使用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的头文件包含路径。总之,非常之繁琐。
为什么我们不使用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)