最近出来创业,没法用鹅厂的构建工具了,只能钻研一下开源的build工具,发觉cmake至少用来构建一个中小型项目还是挺方便的,通过写一些辅助脚本,也可以具备一定的自动化能力。
cmake支持外部编译,即在源码包外额外创建一个build目录,好处是不会污染整个源码目录,比较优雅。
$ mkdir build
$ cd build
$ cmake ..
$ make
基础功能
简单示例
先给一个CMakeLists.txt
的例子
PROJECT(app)
ADD_EXECUTABLE(myapp
main.cc
classA.cc
)
TARGET_LINK_LIBRARIES(myapp
sqlite my_ilb
)
ADD_SUBDIRECTORY(lib)
# lib/CMakeLists.txt
ADD_LIBRARY(my_lib
my_lib.c
)
常用命令
隐式变量
<project_name>_SOURCE_DIR
工程代码路径,基本等同于CMAKE_SOURCE_DIR
和PROJECT_SOURCE_DIR
<project_name>_BINARY_DIR
编译目标路径,基本等同于CMAKE_BINARY_DIR
和PROJECT_BINARY_DIR
指定编译目标路径
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
指定安装路径:
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr .
或者安装时指定路径也可以:
make install DESTDIR=/tmp/t2/usr
生成库文件
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
比如,同时生成动态和静态库:
ADD_LIBRARY(hello ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}) # 注意这里使用hello_static为了和动态库不重名
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") # 这里额外修改生成的libhello_static.a变为libhello.a
设置INC和LIB搜索路径(即make的-I
和-L
参数)
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
LINK_DIRECTORIES(directory1 directory2 ...)
也可以用环境变量 CMAKE_INCLUDE_PATH
和 CMAKE_LIBRARY_PATH
。
链接库(动态库可以加.so,静态库加.a标识):
TARGET_LINK_LIBRARIES(target library1 library2 ...)
遍历一个目录下所有的源代码文件,并将文件列表存储在一个变量中,这个指令临时被用来自动构建源文件列表:
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
编译信息
Debug模式
cmake -DCMAKE_BUILD_TYPE=Debug
make VERBOSE=1
或者
cmake -DCMAKE_VERBOSE_MAKEFILE=ON .
make
或者,减少一点输出:
cmake -DCMAKE_RULE_MESSAGES=OFF -DCMAKE_VERBOSE_MAKEFILE=ON .
make --no-print-directory
导入依赖包
和Python、Java开发不同,C++开发一大痛点是没有很好的包管理器,如果想引入一个依赖的外部库做法总是很山寨,cmake这里倒是提供了不错的解决方案。分两步走:一是提供了Module机制,用于描述一个依赖包;二是提供了ExternalProject_Add
可以方便的导入外部依赖包。
Module描述
以常用库GFlags为例,可以自定义 FindGFlags.cmake
来描述GFlags这个库的头文件和库的路径信息。
1)寻找头文件,如果找到会设置路径到GFLAGS_INCLUDE_DIR
变量
find_path(GFLAGS_INCLUDE_DIR
gflags/gflags.h
HINTS
/opt/local/include
/usr/local/include
/usr/include
${GFLAGS_ROOT_DIR}/src
)
2)寻找库文件,如果找到会设置路径到GFLAGS_LIBRARY
变量
find_library(GFLAGS_LIBRARY
NAMES gflags
HINTS
/usr
/usr/local
PATH_SUFFIXES
x86_64-linux-gnu
i386-linux-gnu
lib64
lib)
3)导入辅助函数find_package_handle_standard_args
,功能是判断上面的两个变量GFLAGS_INCLUDE_DIR
、GFLAGS_LIBRARY
是否有值。如果都有值的话,会设置GFLAGS_FOUND
供后续调用使用(这种调用哪怕传入的name是小写gflags也会变为大写,另外一种FOUND_VAR
的调用方式会保持原来的case)。
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(gflags
DEFAULT_MSG
GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY)
4)如果已经安装了gflags,可以设置头文件和库文件的路径供后续使用。
if (GFLAGS_FOUND)
set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY})
set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR})
string(REGEX REPLACE "/libgflags.so" "" GFLAGS_LIBRARY_DIR ${GFLAGS_LIBRARIES})
include_directories(${GFLAGS_INCLUDE_DIR})
link_directories(${GFLAGS_LIBRARY_DIR})
mark_as_advanced(GFLAGS_LIBRARIES GFLAGS_INCLUDE_DIRS)
endif(GFLAGS_FOUND)
自动安装依赖包
结合Module和find_libray
函数,可以查询本地是否的确安装了相应的包:
set(gflags_RELEASE 2.1.2)
find_package(GFlags)
if (NOT GFLAGS_FOUND)
message (STATUS " gflags library has not been found.")
message (STATUS " gflags will be downloaded and built automatically ")
message (STATUS " when doing 'make'. ")
如果本地没有安装可以通过ExternalProject_Add
获取外部资源,包括url/git/svn/cvs等,自动下载并编译安装。
目标路径这里只设置PREFIX
即可,其他可以使用默认路径:
TMP_DIR = <prefix>/tmp
STAMP_DIR = <prefix>/src/<name>-stamp
DOWNLOAD_DIR = <prefix>/src
SOURCE_DIR = <prefix> /src/<name>
BINARY_DIR = <prefix>/src/<name>-build
INSTALL_DIR = <prefix>
注意CMAKE_ARGS
可以自定义一些编译选项,否则会使用全局设置的编译选项。
建议把UPDATE_COMMAND
显式设置为空,否则如果是svn/cvs这种代码仓库,每次会去update代码。
同时我们也不需要安装,可以把INSTALL_COMMAND
设置为空。
ExternalProject_Add(
gflags-${gflags_RELEASE} # 建议这里的name加上版本号,只在这里使用,不暴露出去用
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/gflags-${gflags_RELEASE}
GIT_REPOSITORY https://github.com/gflags/gflags.git
GIT_TAG v${gflags_RELEASE}
CMAKE_ARGS -DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=ON -DBUILD_gflags_nothreads_LIB=OFF -DCMAKE_CX X_COMPILER=${CMAKE_CXX_COMPILER}
BUILD_COMMAND make
UPDATE_COMMAND ""
PATCH_COMMAND ""
INSTALL_DIR /path/to/install
INSTALL_COMMAND make install
)
可以获取外部项目的一些属性,如下把目标路径和源码路径保存到binary_dir
和source_dir
变量里。
ExternalProject_Get_Property(gflags-${gflags_RELEASE} binary_dir)
ExternalProject_Get_Property(gflags-${gflags_RELEASE} source_dir)
ExternalProject_Get_Property(gflags-${gflags_RELEASE} install_dir) # 其实就是 INSTALL_DIR
利用上面取得的路径,设置最终gflags的头文件路径和库文件路径。可以供外部调用。
注意这里的路径不同代码项目一般是不同的,比如gflags的头文件用${binary_dir}/include
,而glog的头文件位置是在${binary_dir}/src
。
CACHE PATH参数表示该参数会被cache住(比如第一次从命令行设置了该参数,第二次命令行调用没带该参数,该参数也能生效)。
set(GFLAGS_INCLUDE_DIRS ${binary_dir}/include CACHE PATH "Local Gflags headers")
set(GFLAGS_LIBRARY_PATH ${binary_dir}/lib )
set(GFLAGS_BUILD_DIR ${binary_dir}) # to compile glog
我们拿到编译完后的信息之后,可以新建一个library的名字供外部使用,这里取名gflags(去掉了版本号)。
由于编译已经完成了,只要导入编译完的库就好了。
说明:
add_library(... IMPORTED)
,表示只是导入一个已经存在的库。set_target_properties(... PROPERTIES IMPORTED_LOCATION ...)
,设置该库的具体路径。add_dependencies(...)
该导入库依赖之前的外部库。
set(GFLAGS_LIBRARIES gflags)
add_library(gflags UNKNOWN IMPORTED)
set_target_properties(gflags PROPERTIES IMPORTED_LOCATION ${GFLAGS_LIBRARY_PATH}/libgflags.a)
add_dependencies(gflags gflags-${gflags_RELEASE})
引入头文件路径和库文件路径。
# file(GLOB GFLAGS_SHARED_LIBRARIES "${binary_dir}/libgflags${CMAKE_SHARED_LIBRARY_SUFFIX}*")
include_directories(${GFLAGS_INCLUDE_DIRS})
link_directories(${GFLAGS_LIBRARY_PATH})
endif(NOT GFLAGS_FOUND)
导入外部依赖
示例:glog不同于gflags,它是用的autotools工具链,并且还依赖gflags,所以需要做一些额外处理。
这里需要额外创建一个定制的configure_with_gflags
文件,用来配置它所依赖的gflags参数。
file(WRITE ${GLOG_CONFIGURE_TMP}
"#!/bin/sh
export CPPFLAGS=-I${GFLAGS_INCLUDE_DIRS}
export LDFLAGS=-L${GFLAGS_LIBRARY_PATH}
export LIBS=-lgflags
${GLOG_SOURCE_DIR}/configure --with-gflags=${GFLAGS_BUILD_DIR}")
这里给configure_with_gflags
文件添加指向属性。
file(COPY ${GLOG_CONFIGURE_TMP}
DESTINATION ${GLOG_PREFIX}
FILE_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
TODO:感觉上面的步骤也可以用ExternalProject_Add_Step
完成?
添加额外的CONFIGURE_COMMAND
参数,表明该外部项目采用autotools工具链。
ExternalProject_Add(
glog-${glog_RELEASE}
DEPENDS gflags
PREFIX ${GLOG_PREFIX}
GIT_REPOSITORY https://github.com/google/glog.git
GIT_TAG v${glog_RELEASE}
CONFIGURE_COMMAND ${GLOG_CONFIGURE} --prefix=<INSTALL_DIR>
BUILD_COMMAND make
UPDATE_COMMAND ""
PATCH_COMMAND ""
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(glog-${glog_RELEASE} binary_dir)
ExternalProject_Get_Property(glog-${glog_RELEASE} source_dir)
# set(GLOG_INCLUDE_DIRS ${binary_dir}/src CACHE PATH "Local glog headers")
set(GLOG_INCLUDE_DIRS ${binary_dir}/src CACHE PATH "Local glog headers")
set(GLOG_LIBRARY_PATH ${binary_dir}/.libs )
如果需要在编译完再做一些额外的工作(这里是需要把log_severity.h
函数复制到相应位置),可以调用如下函数。
注意设置DEPENDEES <build>
,表示该步骤依赖之前的某步build。
【注】如果依赖前面的build必须用这种方式,才能正确的处理依赖关系。比如,如果直接调用file(COPY...)
命令肯定不行。
# WORKAROUND log_severity.h is missing
ExternalProject_Add_Step(
glog-${glog_RELEASE} workaround
COMMAND cp ${source_dir}/src/glog/log_severity.h ${GLOG_INCLUDE_DIRS}/glog
DEPENDEES build
)