CMake初探篇3-库安装生成

CMake初探篇3-库调用与安装


犹如草芥,灿若星河.

33

1. 通用用法

1. 1查看当前构建的构建器

查看当前构建的构建器,以及如何切换。下面是参看当前构建器的具体方法:

  1. 定位缓存文件:打开你的CMake项目构建目录(通常是buildcmake-build开头的文件夹)。
  2. 查找生成器变量:用文本编辑器打开 CMakeCache.txt 文件。
  3. 搜索关键字:在文件中搜索 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中,可执行调用库有下面方法。

flowchart TD A[程序启动] --> B[加载库] B --> C{是否为项目内置库} C -->|是| D{是否含有导入文件} C -->|否| E{是否含有导入文件} D -->|是| F[find_package或add_subdirectory] D -->|否| G[add_subdirectory] E -->|是| H[find_package] E -->|否| I[类似C++加载库-不推荐]

假设当前的项目目录结构这样:

当前可执行程序为最外层定义的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库)一样,可以直接使用。要想做到,必须生成能够直接引用的文件。

flowchart TD A[库开发者阶段] --> B[使用者阶段] subgraph A [库开发步骤] A1[准备库项目结构] A2[编写库CMakeLists.txt] A3[配置阶段生成导出文件] A4[安装到指定目录] end subgraph B [使用步骤] B1[设置查找路径] B2[调用find_package] B3[自动导入目标] B4[链接使用] end

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文件

  1. 导出文件和生成目标对象
# 安装目标文件
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) 找到并正确使用你的库

  1. 创建包配置文件:生成 ${PROJECT_NAME}Config.cmake 文件
  2. 导出目标:让使用者可以直接 target_link_libraries(target PRIVATE YourPackage::YourPackage)
  3. 传递依赖:自动处理依赖关系、包含目录、编译选项等
  4. 版本管理:支持版本检查和兼容性验证

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. 参考链接

Modern-Cmake

posted on 2025-12-09 06:46  Hakuon  阅读(3)  评论(0)    收藏  举报