CMake初探篇3-库安装生成
CMake初探篇3-库调用与安装
犹如草芥,灿若星河.

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 PROPERTIES
RUNTIME_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 PROPERTIES
RUNTIME_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} PROPERTIES
DEBUG_POSTFIX "d"
PUBLIC_HEADER "${HEADERS}"
)
# 声明宏定义
target_compile_definitions(TestLib1 PRIVATE mylibrary_EXPORTS)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# 安装目标文件
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
PUBLIC_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}Targets
FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${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. 参考链接
本文来自博客园,作者:Hakuon,转载请注明原文链接:https://www.cnblogs.com/Hakuon/p/19324294
浙公网安备 33010602011771号