主要涉及如下主题:
- 目录布局
- 定义全局编译选项
- 交叉编译
- 编译开发者自己的库
- 编译第三方软件
- 引用库
- 编译可执行程序
- 安装配置文件
- 安装启动脚本
- CPack打包
如下是示例工程:
git clone https://github.com/sronzheng/cmake-demo.git
1. 目录布局
目录布局包括两方面,一是源代码的布局,二是部署到目标系统后的布局。
源代码布局如下:
- cmake是自定义的CMake脚本,3th-party是第三方软件,platform是依赖特定平台特性的库,common是平台无关的库,apps是应用程序,scripts是软件包的脚本,如启动脚本等。
- 库的目录下:
- include是对外开放的C++头文件,src下是只在库内部使用的C++头文件和实现文件,test是单元测试程序,example是使用库的例子。
- apps的程序目录下:
- src是C++头文件和实现文件, etc是配置文件。
├── cmake
├── 3th-party
│ ├── gtest
│ ├── jsoncpp
│ └── protobuf
├── platform
├── common
│ └── arithm
│ ├── include
│ ├── src
│ ├── test
│ └── example
├── apps
│ └── hello
│ ├── etc
│ └── src
└── scripts
部署后的布局为:
- include是库的头文件,lib是库,etc是配置文件,bin是程序和启动脚本等。
├── include
├── lib
├── etc
└── bin
2. 定义全局编译选项
如果只改变单个target的编译选项,用add_definitions()就可以。如果是改变针对所有的target,更好的办法是设置CMake的变量:CMAKE_C_FLAGS和CMAKE_CXX_FLAGS,它们分别用于gcc和g++编译。
# BuildType.cmake
set (CMAKE_C_FLAGS "-Wall -std=c++11 -pthread")
set (CMAKE_C_FLAGS_DEBUG "-g -ggdb")
set (CMAKE_C_FLAGS_RELEASE "-O2")
set (CMAKE_CXX_FLAGS "-Wall -std=c++11 -pthread")
set (CMAKE_CXX_FLAGS_DEBUG "-g -ggdb")
set (CMAKE_CXX_FLAGS_RELEASE "-O2")
3. 交叉编译
在嵌入式开发中几乎总是要使用交叉编译,而且还可能需要编译到不同的目标平台。切换到新的目标平台时,整个编译环境,包括编译工具链,系统库等,都会改变。
可以用CMake选项CMAKE_TOOLCHAIN_FILE指定一个文件,在这个文件中指定编译环境。
cmake -DCMAKE_TOOLCHAIN_FILE=ArmToolchain.cmake .
如下是一个CMAKE_TOOLCHAIN_FILE的例子。
# 只有指定这个变量,CMake才认为是在进行交叉编译
set (CMAKE_SYSTEM_NAME Linux)
# 指定编译器
set (CMAKE_C_COMPILER /opt/arm/bin/arm-none-linux-gnueabi-gcc)
set (CMAKE_CXX_COMPILER /opt/arm/bin/arm-none-linux-gnueabi-g++)
# 指定目标环境的根目录
set (CMAKE_FIND_ROOT_PATH /opt/arm)
# FIND_PROGRAM()使用宿主机的程序,而不是目标环境的
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# FIND_LIBRARY()使用目标环境的库
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# FIND_PATH()和FIND_FILE() 使用目标环境的库
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
4. 编译开发者自己的库
编译库最小的要求是指出需要哪些源文件,还有头文件目录。这样就能make得到库了。
include_directories (../include .)
add_library (arithm SHARED Arithm.cpp)
如果要支持make install或CPack打包,则还需要指出库的安装目录。这里将arithm库安装在lib下。
install (TARGETS arithm
DESTINATION lib)
直接在CMake语句中列出头文件目录,源代码文件或库的做法,不是很好看。更好的办法是用set()定义一组变量代表它们。
5. 编译第三方软件
ExternalProject_Add()用于引入外部软件包,它来自CMake的ExternalProject模块。它创建一个定制的target,并完成构建的一系列步骤:下载 - 更新 - 配置 - 编译 - 安装 - 测试。
如下是编译Google Test的例子。Google Test本身用CMake编译。
- URL项指出了gtest-release-1.8.0.tar.gz的地址,这里已经把它下载到本地源代码目录下,这是避免下载,节省时间的通常做法。
- PREFIX项指出了编译输出目录的位置;
- CMAKE_COMMAND项指定配置工具CMake,CMAKE_ARGS项是CMake的参数;
- BUILD_COMMAND项指定编译工具make,INSTALL_COMMAND项指定安装工具make install;
- gtest-release-1.8.0.tar.gz将在编译目录下展开,并用in-source build的方式编译。所以BUILD_IN_SOURCE项的值是1。
# 引入ExternalProject模块
include (ExternalProject)
ExternalProject_Add (gtest
PREFIX 3th-party/gtest
URL ${CMAKE_SOURCE_DIR}/3th-party/gtest/src/gtest-release-1.8.0.tar.gz
CMAKE_COMMAND cmake
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/3th-party/gtest
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_IN_SOURCE 1)
如果编译成功,就可以在${CMAKE_BINARY_DIR}/3th-party/gtest下找到GoogleTest的头文件和库了。
另外一个例子是ProtoBuf。ProtoBuf按照老式的configure方式配置。
- 主要的不同是用CONFIGURE_COMMAND代替CMAKE_COMMAND;
- 如果是交叉编译,需要手工提供configure的参数,而不是直接传递${CMAKE_TOOLCHAIN_FILE};
if (TARGET_ARCH STREQUAL "ABC")
set (PB_CMD_ARGS_CROSS --host=arm-linux-gnueabi)
endif()
set (PB_CMD_ARGS --with-protoc=/usr/bin/protoc
--disable-static
--enable-shared)
ExternalProject_Add (protobuf
PREFIX 3th-party/protobuf
URL ${CMAKE_SOURCE_DIR}/3th-party/protobuf/src/protobuf-2.4.1.tar.gz
CONFIGURE_COMMAND ./configure ${PB_CMD_ARGS_CROSS} ${PB_CMD_ARGS} --prefix=${CMAKE_BINARY_DIR}/3th-party/protobuf
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_IN_SOURCE 1)
如下是给make install准备的脚本,将protobuf安装在lib目录下。注意并没有给GoogleTest的安装脚本,因为GoogleTest不需要安装到目标系统中。
install (DIRECTORY ${PROTOBUF_DIR}/include/google/protobuf/
DESTINATION include/protobuf)
install (DIRECTORY ${PROTOBUF_DIR}/lib/
DESTINATION lib)
6. 引用库
编译好库之后,下一步是让其他库或程序方便地引用它。一个好的做法,是定义一组变量表示库的头文件目录和库。
开发者自己的库是以CMAKE_SOURCE_DIR为基础的。
set (MODULE_ROOT_DIR ${CMAKE_SOURCE_DIR})
set (COMMON_DIR ${MODULE_ROOT_DIR}/common)
set (ARITHM_LIBRARY arithm)
set (ARITHM_DIR ${COMMON_DIR}/arithm)
set (ARITHM_INCLUDE_DIR ${ARITHM_DIR}/include)
引用第三方库与引用用户自己的库类似,只是它以CMAKE_BINARY_DIR为基础,因为第三方库是在CMAKE_BINARY_DIR中展开源代码的。
set (EXTERN_MODULE_ROOT_DIR ${CMAKE_BINARY_DIR})
set (3TH_PARTY_DIR ${EXTERN_MODULE_ROOT_DIR}/3th-party)
set (GTEST_DIR ${3TH_PARTY_DIR}/gtest)
set (GTEST_INCLUDE_DIR ${GTEST_DIR}/include)
set (GTEST_LIBRARY ${GTEST_DIR}/lib/libgtest.a)
set (PROTOBUF_DIR ${3TH_PARTY_DIR}/protobuf)
set (PROTOBUF_INCLUDE_DIR ${PROTOBUF_DIR}/include)
set (PROTOBUF_LIBRARY ${PROTOBUF_DIR}/lib/libprotobuf.so)
7. 编译可执行程序
如下编译可执行程序,这里
- 使用set()定义了一组变量让脚本看起来更整齐;
- 使用了前面定义的库变量ARITHM_INCLUDE_DIR和ARITHM_LIBRARY。
set (TARGET hello)
set (INC_DIRS
${ARITHM_INCLUDE_DIR}
. )
set (LIBS
${ARITHM_LIBRARY} )
set (SRCS
Hello.cpp )
include_directories (${INC_DIRS})
add_executable (${TARGET} ${SRCS})
target_link_libraries (${TARGET} ${LIBS})
如下是给make install准备的脚本,将demo安装在bin目录下:
install (TARGETS ${TARGET}
DESTINATION bin)
8. 安装配置文件
很多程序可以依赖配置文件提供初始设置。一般把程序的配置文件放在一个目录下,如etc,让make install安装整个目录:
install (DIRECTORY etc/
DESTINATION etc)
9. 安装启动脚本
软件包一般会需要一个启动脚本和一个停止脚本,把它们放在scripts目录下。需要make install安装它们,安装到bin目录下。
set (SCRIPT
start.sh
stop.sh)
install (PROGRAMS ${SCRIPT}
DESTINATION bin)
10. CPack打包
CPack模块是CMake提供的工具,用于配置生成各种安装包,包括二进制安装包和源码安装包。
- InstallRequiredSystemLibraries模块是CPack所依赖的,所以要先导入;
- CPack变量CPACK_PACKAGE_VERSION_XXX指出安装包的版本信息,用户要给出它们的值;
- 安装包的名字格式为<pkg_name>.<version_major>.<version_minor>.<version_patch>.tar.gz,比如demo.1.22.3333.tar.gz。
include (InstallRequiredSystemLibraries)
# 安装包名字,如果不指定,则使用project()指定的名字
set (CPACK_PACKAGE_NAME demo)
# 版本信息
set (CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}")
include (CPack)
可以在一个C++头文件Version.hpp指定版本信息。这样各个模块可以用#include直接引用,并在启动时打印这些信息;同时CMake也可以从文件解析版本信息,并传递给CPack。
// Version.hpp
#define DEMO_VERSION_MAJOR 1
#define DEMO_VERSION_MINOR 0
#define DEMO_VERSION_PATCH 0
用file()从Version.hpp读入版本信息,并用string()解析出将要传递给CPack的变量。
file (READ Version.hpp VERSION_STR)
string (REGEX REPLACE ".*#define +VERSION_MAJOR +([0-9]+).*" "\\1" VERSION_MAJOR "${VERSION_STR}")
string (REGEX REPLACE ".*#define +VERSION_MINOR +([0-9]+).*" "\\1" VERSION_MINOR "${VERSION_STR}")
string (REGEX REPLACE ".*#define +VERSION_PATCH +([0-9]+).*" "\\1" VERSION_PATCH "${VERSION_STR}")
使用如下命令生成二进制安装包。
cpack -C CPackConfig.cmake
相关链接
CMake 常用法
用CMake构建中等规模的工程
在CMake工程中使用NDK独立工具链 (一)
在CMake工程中使用NDK独立工具链 (二)
参考资料
CMake 入门实战
http://www.hahack.com/codes/cmake/
CMake交叉编译配置
https://blog.csdn.net/bytxl/article/details/50635788
ExternalProject
https://cmake.org/cmake/help/v3.0/module/ExternalProject.html
网友评论