本文对CMake中库的打包,安装,导出以及支持find_package,使其能够很简单的应用到其他的项目中进行详细的总结。
CMake打包库
假设我们的库的结构如下:
- include/ - my_library/ - header-a.hpp - header-b.hpp - config.hpp - ... - src/ - source-a.cpp - source-b.cpp - config.hpp.in - ... - CMakeLists.txt - example/ - example-a.cpp - ... - CMakeLists.txt - tool/ - tool.cpp - CMakeLists.txt - test/ - test.cpp - CMakeLists.txt - CMakeLists.txt - ...
在这个库中包含了不同的头文件和源文件,还包含一些例子,工具和单元测试模块。对于库、示例和单元测试,每个模块分别拥有自己的CMakeLists.txt
,在其中定义了编译的目标并且在子目录中包含了相关的代码。而项目的根目录的CMakeLists.txt
则定义了配置选项,并将这些子模块的加入编译中去。
库的相关配置在config.hpp.in
中被定义,然后这个文件会被CMake预处理为config_impl.hpp
,然后被config.hpp
包含到项目中去(#include "config_impl.hpp"
)。
这种方法非常重要,能够让我们对不同的CMake配置文件进行分离,比如一些不相干的配置的宏等等
项目根目录的CMakeLists.txt
文件:
cmake_minimum_required(VERSION 3.0) project(MY_LIBRARY) # define library version (update: apparently you can also do it in project()!) set(MY_LIBRARY_VERSION_MAJOR 1 CACHE STRING "major version" FORCE) set(MY_LIBRARY_VERSION_MINOR 0 CACHE STRING "minor version" FORCE) set(MY_LIBRARY_VERSION ${MY_LIBRARY_VERSION_MAJOR}.${MY_LIBRARY_VERSION_MINOR} CACHE STRING "version" FORCE) # some options option(MY_LIBRARY_USE_FANCY_NEW_CLASS "whether or not to use fancy new class" ON) option(MY_LIBRARY_DEBUG_MODE "whether or not debug mode is activated" OFF) # add subdiretories add_subdirectory(src) add_subdirectory(example) add_subdirectory(tool) add_subdirectory(test)
这个文件中有一些option
操作,这些配置选项能够讲相关配置写入到config.hpp.in
中,我们需要在config.hpp.in
定义这些选项,类似这种形式:#cmakedefine01
注意,库的版本号我们使用了
force
,这个就阻止了用户在CMakeCache.txt
中更改这个版本号
库模块的src/CMakeLists.txt
文件:
# set headers set(header_path "${MY_LIBRARY_SOURCE_DIR}/include/my_library") set(header ${header_path}/header-a.hpp ${header_path}/header-b.hpp ${header_path}/config.hpp ...) # set source files set(src source-a.cpp source-b.cpp ...) # configure config.hpp.in configure_file("config.hpp.in" "${CMAKE_CURRENT_BINARY_DIR}/config_impl.hpp") # define library target add_library(my_library ${header} ${src}) target_include_directories(my_library PUBLIC ${MY_LIBRARY_SOURCE_DIR}/include
首先,我们定义了头文件和源文件的列表,方便后续使用。
注意头文件的路径变量
header_path
,这个变量在不同的CMake子文件中是不同的,而源文件因为在同一目录中,则可以直接定义。
这个CMake文件同样能够生成config_impl.hpp
,并保存在当前定义的库生成的二进制目录中(${CMAKE_CURRENT_BINARY_DIR}
),然后被包含在config.hpp
中,最终在库被使用能够被找到。
target_include_directories
指定了这个库要用到的头文件,PUBLIC
制定的包含目录包括了include/
的子目录和当前CMake
的二进制目录(为了包含config_impl.hpp
)。
其余模块的的CMakeLists.txt
类似
到这一步,其余的用户就能够通过add_subdirtectory()
来添加库的目录,然后调用target_link_libraries(my_target PUBLIC my_library)
来链接库,并且需要设置include_directories
来包含相关的头文件,从而能够调用我们的库。
CMake安装库
我们需要安装的东西包括:头文件,可执行的工具以及已经编译好的库。这些都能够直接使用install()
命令来直接安装。当我们使用cmake install(make install)
这类命令时,会拷贝这些文件到${CMAKE_INSTALL_PREFIX}
中(Linux下默认是/usr/local
)。
首先,我们在项目的根CMakeLists.txt
中定义相关的位置和变量:
set(tool_dest "bin")
set(include_dest "include/my_library-"${MY_LIBRARY_VERSION}")
set(main_lib_dest "lib/my_library-"${MY_LIBRARY_VERSION}")
然后我们配置install()
命令:
# in tool/CMakeLists.txt install(TARGETS my_library_tool DESTINATION "${tool_dest}") # in src/CMakeLists.txt install(TARGETS my_library DESTINATION "${main_lib_dest}") install(FILES ${header} DESTINATION "${include_dest}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config_impl.hpp DESTINATION "${include_dest}")
这会将可执行工具安装到${CMAKE_INSTALL_PREFIX}/bin
上,头文件安装到${CMAKE_INSTALL_PREFIX}/include/my_library-1.0
,库安装到${CMAKE_INSTALL_PREFIX}/lib/my_library-1.0
。现在就已经满足了我们的一个目标了:不同版本的库不会产生冲突,因为版本号成为了安装路径的一部分。
对于工具
tool
,我们假设其能够具有很好的兼容性,并且将其直接放到bin/
文件夹中,这样其能够直接在终端运行,如果你有需求,你应该对这部分做一些自定义的调整。
但是目前仍然没有解决一个问题:每个编译出来的库可以拥有不同的配置。因为现在只有一个配置文件。我们当然也能通过配置不同的识别名称来区别不同的配置,就像利用不同的版本号一样,但是这对于大多数文件是不需要,因此我们不必采用这种方案。
再次忽略掉tool
,那么就只剩下两个文件需要依赖不同的配置:编译得到的库和生成的config_impl.hpp
文件。因为其中包含了对于库的一些宏的操作,因此我们需要根据配置的不同,将这两个文件放在不同的位置。
但是我们怎么去区分呢?
可以使用编译类型${CMAKE_BUILD_TYPE}
这个变量。通过指示Debug
,Release
,MinSizeRel
以及RelWithDebInfo
,来指示不同的配置选项。
我们也可以定义自己的编译类型以及相对应的一些编译选项操作。
现在我们可以在项目的根CmakeLists.txt
中添加一个新的变量了lib_dest
:
set(lib_dest ${main_lib_dest}/${CMAKE_BUILD_TYPE}")
并且需要更改config_impl.hpp
和库目标的路径,将其安装到lib_dest
中,这样对于不同的编译类型(也就是不同的配置),我们就会得到不同的config_impl.hpp
和库文件。
现在,经过这些配置,我们已经能够区别不同版本和不同配置的库,将其安装到不同的目标路径中,比如${CMAKE_INSTALL_PREFIX}/lib/my_library-1.0/Debug
。