CMake初探篇3-库安装生成
CMake初探篇3-库调用与安装
- CMake初探篇3-库调用与安装
- 1. 通用用法
- 1. 1查看当前构建的构建器
- 1.2 检测操作系统
- 2. 函数与宏
- 2.1 无参函数
- 2.2 固定有参函数
- 2.3 可选参数
- 3. 可执行程序如何引用库
- 3.1 非项目内部库 + 不含导入文件
- 3.2 项目内部库 + 不含导入文件
- 3.3 非项目内部库 + 导入文件
- 3.3.1 install命令
- 3.3.2 CPack程序
- 3.4 项目内部库 + Cmake导入文件
- 4. 完整的Demo流程
- 5. 参考链接
- 1. 通用用法
犹如草芥,灿若星河.

1. 通用用法
1. 1查看当前构建的构建器
查看当前构建的构建器,以及如何切换。下面是参看当前构建器的具体方法:
- 定位缓存文件:打开你的CMake项目构建目录(通常是
build或cmake-build开头的文件夹)。 - 查找生成器变量:用文本编辑器打开
CMakeCache.txt文件。 - 搜索关键字:在文件中搜索
CMAKE_GENERATOR:这一行。它后面显示的值就是当前项目配置所使用的构建器。
1.2 使用命令查看
不同系统支持的构建器不同。要查看你的CMake版本支持的所有构建器,可以运行:
cmake --help
切换构建类型
# 为Debug版本创建并配置一个目录
mkdir build-debug && cd build-debug
cmake .. -DCMAKE_BUILD_TYPE=Debug# 为Release版本创建并配置另一个目录
mkdir build-release && cd build-release
cmake .. -DCMAKE_BUILD_TYPE=Release
1.2 检测操作系统
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")message(STATUS "Configuring on/for Linux")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")message(STATUS "Configuring on/for macOS")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")message(STATUS "Configuring on/for Windows")
elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX")message(STATUS "Configuring on/for IBM AIX")
else()message(STATUS "Configuring on/for ${CMAKE_SYSTEM_NAME}")
endif()
2. 函数与宏
2.1 无参函数
无参函数主要用于显示一些常用的系统信息。例如:
function(show_system_info)# 显示系统基本信息message("=====================================")message("CMake 版本: ${CMAKE_VERSION}")message("生成器: ${CMAKE_GENERATOR}")message("系统: ${CMAKE_SYSTEM_NAME}")message("处理器: ${CMAKE_SYSTEM_PROCESSOR}")message("构建类型: ${CMAKE_BUILD_TYPE}")message("源目录: ${CMAKE_SOURCE_DIR}")message("可执行目录: ${CMAKE_BINARY_DIR}")message("=====================================")
endfunction()# 调用
show_system_info()
2.2 固定有参函数
一般用于辅助和简化逻辑
function(create_executable target_name source_file)# 参数验证if(NOT target_name)message(FATAL_ERROR "必须指定目标名称")endif()if(NOT source_file)message(FATAL_ERROR "必须指定源文件")endif()# 检查文件是否存在if(NOT EXISTS ${source_file})message(WARNING "源文件不存在: ${source_file}")endif()# 创建可执行文件add_executable(${target_name} ${source_file})message(STATUS "创建可执行文件: ${target_name} (源文件: ${source_file})")
endfunction()# 使用
create_executable(my_app src/main.cpp)
该情况主要用于利用参数输入来输出一些和输入相关的
2.3 可选参数
场景特点:参数多且变化大,不同用户有不同需求
# 最简单的完整示例
function(simple_example)# 定义参数类型set(options VERBOSE QUIET)set(oneValueArgs MODE)set(multiValueArgs FILES)# 解析cmake_parse_arguments(ARG # 前缀"${options}" # 选项"${oneValueArgs}" # 单值"${multiValueArgs}" # 多值${ARGN} # 输入参数)# 使用参数if(ARG_VERBOSE)message("详细模式")elseif(ARG_QUIET)message("安静模式")endif()if(ARG_MODE)message("模式: ${ARG_MODE}")endif()if(ARG_FILES)message("文件列表: ${ARG_FILES}")endif()
endfunction()
# 各种调用方式
simple_example() # 无参数
simple_example(VERBOSE MODE debug FILES a.txt b.txt)
simple_example(QUIET)
simple_example(FILES main.cpp util.cpp config.cpp)
详细模式
模式: debug
文件列表: a.txt;b.txt安静模式文件列表: main.cpp;util.cpp;config.cpp
3. 可执行程序如何引用库
按照个人个人理解,在CMake中,可执行调用库有下面方法。
假设当前的项目目录结构这样:
当前可执行程序为最外层定义的CMakeLists.txt,假设为A,库为TestLib1。
Project
|--[TestLib1]
|----CMakeLists.txt
|----xxx.cpp
|--CMakeLists.txt
|--main.cpp
3.1 非项目内部库 + 不含导入文件
可执行程序A的 CMakeLists
# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.15)
project (DemoHakuon)add_executable(lesson1 main.cpp)# 设置 DLL 输出到可执行文件目录
set_target_properties(lesson1 PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)# 设置库的搜索路径(如果 TestLib1 在某个目录下),可以指定包含库的头文件。
target_link_libraries(lesson1 PRIVATE "D:/build/TestLib1/libTestLib1.dll.a"
)
我们可以在build目录下,新建一个TestLib1目录,将需要使用的动态库放在里面。在可执行程序使用库函数时,手动自定包含的库头文件即可。进行构建生成后,可执行程序可以正常编译通过。
构建时,库不能和可执行程序在同一目录,测试发现如果没有TestLib1目录隔离库和生成的可执行程序,构建会报错。
该方法和C++加载动态库的操作方法一样,唯一区别可能在于上面的注意点:具体原因暂时不清楚。如果在可执行程序中包含头文件,则属于隐式加载。如果不包含头文件,改用定义函数对象用于接受需要调用的库接口,则属于动态加载。
3.2 项目内部库 + 不含导入文件
如果库文件本来属于项目中的一部分,那么就可以直接使用下面方法来进行构建生成。
# 可执行程序A的 CMakeLists
# 包含子模块, TestLib1
add_subdirectory(TestLib1)# 设置库的搜索路径(如果 TestLib1 在某个目录下)
target_link_libraries(lesson1 PRIVATE TestLib1
)
使用 add_subdirectory 添加内部库时,会记录:
- 源文件位置:
TestLib1/目录下的.cpp文件 - 输出位置:
build/TestLib1/目录下的库文件 - 链接信息:库名称、类型、依赖项
3.3 非项目内部库 + 导入文件
该方法使用 find_package 来找到指定的库,与其他的三方库或者系统自带的库(例如Qt中的Core/Widget库)一样,可以直接使用。要想做到,必须生成能够直接引用的文件。
3.3.1 install命令
cmake的install是一组命令,有多个功能
install(FILES),install(DIRECTORY)这两个都是拷贝用的,一个拷贝文件,一个拷贝目录
install(TARGETS)&install(EXPORT)这两个命令是要配合一起用,用来生成cmake目标文件,cmake目标文件描述了整个工程所有的依赖内容,最终生成{LibName}Targets.cmake和{LibName}Targets-debug.cmake文件。
install(TARGETS)&install(EXPORT)这两个命令是要配合一起用,用来生成cmake目标文件,cmake目标文件描述了整个工程所有的依赖内容,最终生成{LibName}Targets.cmake和{LibName}Targets-debug.cmake文件
- 导出文件和生成目标对象
# 安装目标文件
install(# 指定要安装的目标名称(通常是在 add_library() 或 add_executable() 中定义的目标)TARGETS ${PROJECT_NAME} # 将目标导出到名为 "${PROJECT_NAME}TTargets" 对象中,还没有生成文件EXPORT ${PROJECT_NAME}Targets# 安装公共头文件到 include/项目名/ 目录, 需要设置这个属性PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}# 安装运行时文件/共享库/静态库(.exe, .dll)到 bin/ lib/目录RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)# 安装生成后的位置
install|-[bin]|-xxx.dll|-[include]|-[aaa]|-aaa.h|-aaa_global.h|-[lib]|-xxx.lib
上面这段代码的作用是,将指定的include文件,生成的库文件放在install目录下面,如果install默认没有指定,则是系统默认目录。
2.将目标对象为文件
将一个配置的目标属性(库文件路径/头文件包含路径/库目录)写入到本地文件,后续直接读取文件既可以获取目标相关的属性。
install(# 引用前面 install(TARGETS ... EXPORT ...) 中定义的导出集EXPORT ${PROJECT_NAME}Targets# 指定生成的 CMake 文件名称FILE ${PROJECT_NAME}Targets.cmake# 添加命名空间到目标名称NAMESPACE ${PROJECT_NAME}::# 指定安装位置, 这样 find_package() 会自动搜索这个路径DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)
3.生成配置文件config
主要目的:让其他项目能够通过 find_package(YourPackage) 找到并正确使用你的库
- 创建包配置文件:生成
${PROJECT_NAME}Config.cmake文件 - 导出目标:让使用者可以直接
target_link_libraries(target PRIVATE YourPackage::YourPackage) - 传递依赖:自动处理依赖关系、包含目录、编译选项等
- 版本管理:支持版本检查和兼容性验证
cmake的find_package函数不是加载${LIB_NAME}Targets.cmake,而是加载{LIB_NAME}Config.cmake文件,{LIB_NAME}Config.cmake文件的生成,需要用到configure_package_config_file函数,一般还会配合write_basic_package_version_file,这两个函数在CMakePackageConfigHelpers里面,需要先引入
include(CMakePackageConfigHelpers)
# 生成配置文件
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in""${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)
在库源码CMakeLists.txt下面创建一个 ${PROJECT_NAME}Config.cmake.in文件,文件内容大致可以简化为下面代码,
然后生成文件在可执行目录下的${PROJECT_NAME}Config.cmake,安装到${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}目录下。
INSTALL_DESTINATION CMAKE_INSTALL_LIBDIR/cmake/${PROJECT_NAME}
这是最重要的作用!
INSTALL_DESTINATION告诉configure_package_config_file函数:
- 你的配置文件最终会安装到哪里
- 基于这个信息,函数会智能地生成相对路径**
# ${PROJECT_NAME}Config.cmake.in
# @PACKAGE_INIT@ 必须放在开头
@PACKAGE_INIT@# 设置版本变量(可选但推荐), set(MyMath_VERSION 2.3.1)
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)# 包含目标文件(核心), 一般值得是 lib\cmake\MyMath
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")# 提供传统变量(向后兼容),set(MyMath_LIBRARIES MyMath::MyMath)
set(@PROJECT_NAME@_LIBRARIES @PROJECT_NAME@::@PROJECT_NAME@)# 检查必需组件(如果有)
check_required_components(@PROJECT_NAME@)
4.编写版本配置信息和安装到指定目录
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"VERSION ${PROJECT_VERSION}COMPATIBILITY SameMajorVersion
)# 安装 config 文件
install(FILES"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake""${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)
最后install的结构为:
bin|-LibA.dll
include|-LibA|-LibA.h|-...所有头文件都在此文件夹下
lib|-LibA.lib|-cmake|-LibA|-LibAConfig.cmake|-LibAConfigVersion.cmake|-LibATargets.cmake|-LibATargets-debug.cmake
3.3.2 CPack程序
CPack 主要是为应用程序(特别是桌面应用程序)的安装包而设计的,但它也可以用于库的打包。由于库的分发通常有更专业的方式(包管理器),而应用程序需要用户友好的安装体验,所以你会看到 CPack 在应用程序打包中应用更广泛。
如果你在开发一个桌面应用、命令行工具或需要安装的系统服务,CPack 是一个非常合适的选择。如果你只是在开发一个库,CPack 可以用于生成二进制分发包,但可能不是最优选择。
# 或者指定具体安装路径, 简单指定可执行程序打包后库内容
install(TARGETS DemoHakuon RUNTIME DESTINATION bin # .exe 文件LIBRARY DESTINATION lib # .dll 文件ARCHIVE DESTINATION lib/static # .lib 文件
)# ============ 必须添加的 CPack 配置 ============
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") # 包名称
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") # 版本号
set(CPACK_PACKAGE_VENDOR "MyCompany") # 厂商(可选)
set(CPACK_GENERATOR "ZIP") # 包格式
include(CPack) # 包含 CPack 模块
# ================================================
3.4 项目内部库 + Cmake导入文件
可知直接通过add_subdirectory后者find_package来找到内部库均可以。
4. 完整的Demo流程
顶层CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.15)# 项目信息
project (DemoHakuon VERSION 1.0.0)# 添加 math 子目录
add_executable(DemoHakuon main.cpp)# 设置 DLL 输出到可执行文件目录
set_target_properties(DemoHakuon PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)# 设置安装前缀(可选)
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/install" CACHE PATH "安装目录")# 查找库
find_package(TestLib1 REQUIRED)
# 设置库的搜索路径(如果 TestLib1 在某个目录下)
target_link_libraries(DemoHakuon PRIVATE TestLib1::TestLib1
)# 或者指定具体安装路径
install(TARGETS DemoHakuon RUNTIME DESTINATION bin # .exe 文件LIBRARY DESTINATION lib # .dll 文件ARCHIVE DESTINATION lib/static # .lib 文件
)
# ============ 必须添加的 CPack 配置 ============
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") # 包名称
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") # 版本号
set(CPACK_PACKAGE_VENDOR "MyCompany") # 厂商(可选)
set(CPACK_GENERATOR "ZIP") # 包格式
include(CPack) # 包含 CPack 模块
# ================================================
库下面的CMakeLists.txt编写
# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.15)# 项目信息
project (TestLib1
VERSION 1.0.0)function(collect_files_and_group OUT_VAR ROOT_DIR EXT GROUP_NAME)set(LOCAL_LIST "")file(GLOB_RECURSE FILES RELATIVE "${ROOT_DIR}" "${ROOT_DIR}/*${EXT}")foreach(F IN LISTS FILES)set(FULL_PATH "${ROOT_DIR}/${F}")# 仅处理文件if(NOT IS_DIRECTORY "${FULL_PATH}")# 添加到结果列表list(APPEND LOCAL_LIST "${FULL_PATH}")# 构建虚拟目录:GROUP_NAME + 相对子目录get_filename_component(DIR_PART "${F}" DIRECTORY)if(DIR_PART)string(REPLACE "/" "\\" GROUP_PATH "${DIR_PART}\\${GROUP_NAME}")else()set(GROUP_PATH "${GROUP_NAME}")endif()source_group("${GROUP_PATH}" FILES "${FULL_PATH}")endif()endforeach()set(${OUT_VAR} "${LOCAL_LIST}" PARENT_SCOPE)
endfunction()collect_files_and_group(HEADERS "${CMAKE_CURRENT_SOURCE_DIR}" ".h" "Include")
collect_files_and_group(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}" ".cpp" "Src")add_library(TestLib1 SHARED ${HEADERS} ${SOURCES})# ----------------------------------------------
# 设置目标属性
# ----------------------------------------------
target_include_directories(${PROJECT_NAME}PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)set_target_properties(${PROJECT_NAME} PROPERTIESDEBUG_POSTFIX "d"PUBLIC_HEADER "${HEADERS}"
)# 声明宏定义
target_compile_definitions(TestLib1 PRIVATE mylibrary_EXPORTS)include(GNUInstallDirs)
include(CMakePackageConfigHelpers)# 安装目标文件
install(TARGETS ${PROJECT_NAME}EXPORT ${PROJECT_NAME}TargetsPUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)# 导出目标信息
install(EXPORT ${PROJECT_NAME}TargetsFILE ${PROJECT_NAME}Targets.cmakeNAMESPACE ${PROJECT_NAME}::DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)# 生成配置文件
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in""${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"VERSION ${PROJECT_VERSION}COMPATIBILITY SameMajorVersion
)# 安装 config 文件
install(FILES"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake""${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)
find_package所需要的编写的配置文件TestLib1Config.cmake.in
# @PACKAGE_INIT@ 必须放在开头
@PACKAGE_INIT@# 设置版本变量(可选但推荐), set(MyMath_VERSION 2.3.1)
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)# 包含目标文件(核心), 一般值得是 lib\cmake\MyMath
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")# 提供传统变量(向后兼容),set(MyMath_LIBRARIES MyMath::MyMath)
set(@PROJECT_NAME@_LIBRARIES @PROJECT_NAME@::@PROJECT_NAME@)# 检查必需组件(如果有)
check_required_components(@PROJECT_NAME@)
5. 参考链接
Modern-Cmake
本文来自博客园,作者:Hakuon,转载请注明原文链接:https://www.cnblogs.com/Hakuon/p/19324294