CMake 常用示例

2016-10-16 language c/cpp

整理常用的示例。

平台依赖

主要是用于检查平台的头文件、函数等是否支持。

CHECK_FUNCTION_EXISTS(backtrace HAVE_BACKTRACE)
CHECK_LIBRARY_EXISTS(pthread pthread_setname_np "" HAVE_PTHREAD_SETNAME_NP)

头文件检查

#----- 检查单个头文件
CHECK_INCLUDE_FILE("regex.h"                   HAVE_REGEX_H)
#----- 检查多个头文件
CHECK_INCLUDE_FILES("sys/prctl.h;sys/others.h" HAVE_SYS_PRCTL_H)

添加子目录

通过 ADD_SUBDIRECTORY 可以添加并构建子目录。

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

对应各个参数为:

  • source_dir 必选参数,指定子目录,需要包含 CMakeLists.txt 和代码文件,可以是绝对或者相对路径。
  • binary_dir 可选参数,指定输出文件目录,默认与 source_dir 相同。
  • EXCLUDE_FROM_ALL 可选参数,默认不构建子目录,除非显示指定或者有其它目标依赖该对象,例如通过 target_link_libraries() 指定依赖。

编译参数

可以通过 ADD_COMPILE_OPTIONS(-Wall -Werror) 添加全局编译参数,或者针对某个可执行文件添加,例如 TARGET_COMPILE_OPTIONS(exec PRIVATE -O1)

如果针对的是 CCPP 的区别,那么就可以直接更新全局参数变量。

SET(CMAKE_C_FLAGS "-fPIE -msse4.2 -mpclmul ${CMAKE_C_FLAGS}")
SET(CMAKE_CXX_FLAGS "-fPIE -msse4.2 -mpclmul ${CMAKE_CXX_FLAGS}")

或者命令行 cmake -DCMAKE_C_FLAGS="-fPIE" .. 指定。其中,编译器参数的格式为 CMAKE_<LANG>_FLAGS_<CONFIG>,比较常用的一种场景,是根据编译器设置不同的参数。

IF(CMAKE_CXX_COMPILER_ID MATCHES GNU)
  LIST(APPEND CMAKE_C_FLAGS "-fno-rtti" "-fno-exceptions")
  LIST(APPEND CMAKE_C_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-override")
  LIST(APPEND CMAKE_C_FLAGS_RELEASE "-O3" "-Wno-unused")
ENDIF()

IF(CMAKE_CXX_COMPILER_ID MATCHES Clang)
  LIST(APPEND CMAKE_C_FLAGS "-fno-rtti" "-fno-exceptions" "-fcolor-diagnostics")
  LIST(APPEND CMAKE_C_FLAGS_DEBUG "-Wdocumentation")
  LIST(APPEND CMAKE_C_FLAGS_RELEASE "-O3" "-Wno-unused")
ENDIF()

还有就是根据某个开关配置。

SET(WITH_CONVERAGE   false   CACHE BOOL   "Enable coverage")
IF(WITH_CONVERAGE)
	SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
ENDIF()

编译时调用 cmake .. -DWITH_CONVERAGE=true 即可。

打包静态库

通过 TARGET_LINK_LIBRARIES 可以设置编译依赖的选项,不过对于静态库来说,实际不会将其打包,常规的有如下几种方式。

=====> 先将静态库拆解,再合并
ar x libfoo.a
ar x libbar.a
ar crs libfoobar.a *.o

=====> 更加简洁的命令,生成的是 Thin Archive
ar crsT libfoobar.a libfoo.a libbar.a

详细参数可以参考 编译链接,另外,上述的方法不适合 Mac 系统,需要通过如下命令合并。

libtool -static -o libfoobar.a libfoo.a libbar.a

自动生成

ADD_CUSTOM_COMMAND(OUTPUT libfoobarfat.a
    #COMMAND ar crsT libfoobarfat.a $<TARGET_FILE:foo> $<TARGET_FILE:bar>
    COMMAND ar x $<TARGET_FILE:foo>
    COMMAND ar x $<TARGET_FILE:bar>
    COMMAND ar crs libfoobarfat.a *.o
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    DEPENDS foo bar
)
ADD_CUSTOM_TARGET(_foobarfat ALL DEPENDS libfoobarfat.a)

上述会命令会依赖源静态库的路径,可以通过 $<TARGET_FILE:foo> 类似方式获取,而对于类似 FIND_LIBRARY(LIBBAR c HINT ${SEARCH_PATH}) 方式查找的库,可以直接通过 ${LIBBAR} 方式引用。

其中的 ADD_CUSTOM_TARGET 主要是生成一个依赖项,可以自动生成。另外,还可以根据平台进行适配。

导入静态库

上面只是生成了静态库,在 CMake 中会被标记为 GENERATED,用户可以单独使用,如果同时要在 CMake 中使用,那么就需要通过如下方式导入。

ADD_LIBRARY(foobarfat STATIC IMPORTED GLOBAL)
ADD_DEPENDENCIES(foobarfat _foobarfat)
SET_TARGET_PROPERTIES(foobarfat PROPERTIES
    IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/libfoobarfat.a
)

ADD_EXECUTABLE(demo src/main.c)
TARGET_LINK_LIBRARIES(demo PRIVATE foobarfat)

库使用

关于库的使用常见有如下的操作。

----- 增加库的搜索路径
LINK_DIRECTORIES(./lib)

----- 生成库,可以是动态(SHARED)或者静态库(STATIC)
SET(SRC_LIST test.c foobar.c)
ADD_LIBRARY(hello SHARED ${SRC_LIST})

----- 指定生成对象时依赖的库
TARGET_LINK_LIBRARIES(hello A B.a C.so)

----- 自定义链接选项,单独对B.a使用--whole-archive选项
TARGET_LINK_LIBRARIES(hello A -Wl,--whole-archive B.a -Wl,--no-whole-archive C.so)

在使用 ADD_LIBRARY(foo SHARED foo.c) 时,不同平台输出有所区别,例如,foo.dll(Windows)libfoo.so(Linux)libfoo.dylib(Mac)

并且各个平台下各不相同,可以通过如下方式修改前缀以及后缀:

SET_TARGET_PROPERTIES(foo PROPERTIES PREFIX "")
SET_TARGET_PROPERTIES(foo PROPERTIES SUFFIX "so")

注意,通过 ADD_LIBRARY 函数不允许同时生成静态和动态两个文件,此时需要修改某个库的生成,使用方式如下。

SET(LIB_STATIC_NAME ${PROJECT_NAME}-static)
SET(LIB_SHARED_NAME ${PROJECT_NAME}-shared)

ADD_LIBRARY(${LIB_STATIC_NAME} STATIC ${SRC_LIST})
SET_TARGET_PROPERTIES(${LIB_STATIC_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})

ADD_LIBRARY(${LIB_SHARED_NAME} SHARED ${SRC_LIST})
SET_TARGET_PROPERTIES(${LIB_SHARED_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})

安装

CMake 默认会在与源码目录相同的路径下生成二进制文件或者库文件,实际上可以通过如下方式自定义。

SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

当然,对于单个可执行文件,也可以通过 SET_TARGET_PROPERTIES() 指令设置如下的变量覆盖全局的参数。

SET_TARGET_PROPERTIES(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
SET_TARGET_PROPERTIES(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
SET_TARGET_PROPERTIES(${target} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

安装指令

在编译完成之后,可以通过 make install 进行安装,注意,此时通过 make clean 不会删除对应的文件,可以通过 cat install_manifest.txt | xargs rm 命令删除,需要提前指定安装规则。

INSTALL(TARGETS foobar DESTINATION bin)
INSTALL(FILES foobar.h DESTINATION include)

如上的配置会将 foobarfoobar.h 安装到 /usr/local/{bin,include} 目录下即可,实际上对于安装目录而言,也可以直接使用 INCLUDE(GNUInstallDirs) 指定标准的目录。

对于 TARGETS 的配置,主要有三个参数,分别为 ARCHIVELIBRARYRUNTIME ,一般会类似如下的方式编写。

INSTALL(TARGETS targets...
    ARCHIVE
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
    RUNTIME
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
    PUBLIC_HEADER
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
)

其中上述的 targets 可以指定多个,而且可以是不同的类型,如二进制、动态库、静态库、头文件等。

上述指定的 DESTINATION 一般是相对路径,可以通过 CMAKE_INSTALL_PREFIX 指定其前缀,对于 Linux 默认是 /usr/local/;另外,其前面还可以指定 DESTDIR 目录。

除了使用 TARGETS 外,还可以使用 FILES 或者 DIRECTORY ,对于 DIRECTORY 使用比较灵活,常见需要注意的关键点如下。

后缀符号

需要注意其后缀的 / 符号。

#----- 会将目录复制成为 dst/src/{subdirs and files...}
INSTALL(DIRECTORY   myproj/src DESTINATION dst)

#----- 会将目录复制成为 dst/{subdirs and files...}
INSTALL(DIRECTORY   myproj/src/ DESTINATION dst)

文件过滤

可以通过参数 FILES_MATCHING 用于指定操作档案的条件,可以使用 PATTERNREGEX 两种匹配方式,要注意 PATTERN 会比对全路径而不只是文件名。

INSTALL(DIRECTORY src/ DESTINATION include FILES_MATCHING PATTERN "*.h")

以上会把 src/ 底下所有文件后缀为 .h 的文件复制到 include 文件夹下,并且会保留原本目录树的结构。

另外,还可以在匹配条件后面通过 EXCLUDE 排除符合条件的文件或目录。

INSTALL(DIRECTORY myapp/ mylib DESTINATION myproj PATTERN ".git" EXCLUDE)