CMake工程指南(三)静态库 📅 2026/7/13 8:39:00 书接上回CMake工程指南三静态库-CSDN博客目录1.2.2.6 add_subdirectory1.2.2.7 file1.2.3 CMake 内部静态库的⽣成与定位流程1.3 静态库编译-链接-安装1.3.1 单步操作1.3.2 重点命令解释1.3.2.1 export1.3.2.2 configure_package_config_file1.3.2.3 write_basic_package_version_file1.2.2.6 add_subdirectory函数作⽤添加⼦⽬录到构建树cmake会⾃动进⼊到源码树⼦⽬录执⾏位于⼦⽬录⾥的CMakeLists.txt⽂件。基本形式add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])参数解释source_dir: 通常为当前⽂件夹下的⼦⽬录的名字。【binary_dir】:cmake会在构建树⾥创建同名的⼦⽬录⽤于保存⼦⽬录的的cmake⽂件⾥⽣成的⽬标和⼆进制关键⾏为处理顺序:CMake 会⽴即处理source_dir中的 CMakeLists.txt当前⽂件的处理会暂停直到⼦⽬录处理完毕在继续处理当前⽂件add_subdirectory之后的命令。路径解析:source_dir相对路径相对于当前 CMakeLists.txt ⽂件所在⽬录。binary_dir相对路径相对于当前构建⽬录不指定则使⽤source_dir。变量作⽤域:⼦⽬录中定义的变量默认是局部的不会影响⽗⽬录。可通过set(xxx PARENT_SCOPE)将变量传递到⽗⽬录。缓存变量是全局的⼦⽬录⾥设置的缓存变量⽗⽬录也可以获取到.CMake 在当前上下⽂执⾏完add_subdirectory命令进⼊到⼦⽬录之后在开始执⾏CMakeLists.txt时会修改的内置变量变量名进⼊⼦⽬录后是否变化说明CMAKE_CURRENT_SOURCE_DIR✅ 变化变为⼦⽬录的源代码树的⽬录CMAKE_CURRENT_BINARY_DIR✅ 变化变为⼦⽬录的构建树的⽬录CMAKE_CURRENT_LIST_FILE✅ 变化变为⼦⽬录的CMakeLists.txt ⽂件全路径CMAKE_CURRENT_LIST_DIR✅ 变化变为⼦⽬录的 CMakeLists.txt ⽂件⽬录1.注意同include变量的对CMAKE_CURRENT_SOURCE_DIR 的影响的区别。2.不管是include模式还是add_subdirectory要得到相对于正在执⾏的cmake ⽂件建议使⽤CMAKE_CURRENT_LIST_FILE 作为相对路径的参考点。下⾯我们新建test_add_sub_dir内容如下Step 0 ⽬录结构如下├── CMakeLists.txt └── sub └── CMakeLists.txtStep 1 新建-CMakeLists.txtcmake_minimum_required(VERSION 3.18) # 2 设置项⽬名称 project(TestAddSubDir) # 3 打印 内置路径变量 message(STATUS from top-level CMakeLists.txt) # 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录 message(STATUS CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}) # 打印 当前正在执⾏的cmake 脚本的 完整名称 message(STATUS CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}) # 打印当前正在执⾏的cmake 脚本的 全⽬录 message(STATUS CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}) # 4 包含⼦⽬录cmake脚本 add_subdirectory(sub)Step 1 新建-sub/CMakeLists.txtmessage(STATUS from sub/CMakeLists.txt) # 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录 message(STATUS CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}) # 打印 当前正在执⾏的cmake 脚本的 完整名称 message(STATUS CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}) # 打印当前正在执⾏的cmake 脚本的 全⽬录 message(STATUS CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR})Step 2 运⾏cmake1 可以看到add_subdirectory 之后src⾥的变量⾃动添加了app ⼦⽬录。2 ⼦⽬录cmake ⽂件处理完成之后才会回到main⾥继续执⾏可以C语⾔ 函数调⽤流程类似。图解1.2.2.7 file函数作⽤查看⽬录下的所有⽂件如果匹配规则则添加⽂件名字到⽂件列表中是否需要递归需要显⽰指定。基本形式file(GLOB|GLOB_RECURSE variable [LIST_DIRECTORIEStrue|false][RELATIVE path] [CONFIGURE_DEPENDS] globbing-expressions...)参数解释参数含义GLOB匹配当前⽬录下的⽂件不递归⼦⽬录GLOB_RECURSE递归匹配当前⽬录及其所有⼦⽬录下的⽂件。out-var收集到的⽂件列表变量globbing-expr通配符表达式如 *.cpp、src/**/*.h。1.2.3CMake 内部静态库的⽣成与定位流程在上⾯的⼆进制调⽤本项⽬内部的静态库中函数例⼦⼤家有没有这样的问题⼆进制是如何定位到静态库的地址的我刚开始⼀直会有这样的疑惑但那会限于⾃⾝知识体系的单薄没有找到合适的答案⽹上有好多版本但是都和cmake源代码对应不起来官⽅⽂档也没有提及这个过程。在多年后的今天写这个课件时我仔细阅读了cmake的源代码现在和⼤家⼀起分享下这个答案。在cmake⾥当你添加⼀个⽬标⽐如静态库后cmake会在配置阶段⾃动跟踪静态库的输出位置然后在⽣成阶段会把这个输出地址通过-L参数传递给链接器链接器就可以找到项⽬内部的静态库整个过程的产物就是可视化的makefile。⼀切尽在makefile⾥。下⾯就详细展开这个步骤第⼀步⽬标⽣成当你写下add_library(MyMath STATIC add.cpp sub.cpp)这⼀⾏命令时cmake内部会在全局的Targets 容器中注册⼀个名为 MyMath 的cmTarget⽬标。第⼆步⽬标信息存储每个cmTargetInternals会存储⽬标的名称类型源⽂件列表输出地址等属性。当你使⽤LIBRARY_OUTPUT_DIRECTORY等属性修改⽬标输出地址的时候cmake也会更新最终的输出地址。第三步⽣成器阶段-定位静态库路径CMake 配置阶段结束后进⼊⽣成阶段cmLocalGenerator、cmGlobalGenerator,⽣成器会遍历所有⽬标cmTarget根据⽬标的属性如 ARCHIVE_OUTPUT_DIRECTORY和平台规则推导出静态库的实际输出路径。⽐如build/libmylib.a。第四步链接命令的⽣成⽣成器会为每⼀个⽬标⽣成链接规则其中包括⽬标⽂件的输出路径最终会⽣成⼀下 makefile指令g main.o -o myexe build/libmylib.a 这样链接器g/ld就能找到并链接libmylib.a。上⾯都是理论过程下⾯我们通过例⼦来验证下这个过程过程中使⽤到了配置⽣成阶段的⼀个⽣成器表达式⽤于获取⽬标的输出路径$TARGET_FILE:MyMath。cmake-generator-expressions(7) — CMake 4.4.0 Documentation是 CMake 中⼀种特殊的语法⽤于在⽣成阶段即CMake ⽣成构建系统⽂件时动态计算和展开内容。以 $... 的形式出现常⽤于 target 属性、编译选项、链接参数、install 规则计算。Step 0修改⽂件-app/CMakeLists.txt# 1 搜集⽂件列表 file(GLOB SRC_LISTS *.cpp) # 2 添加构建⽬标 add_executable(main ${SRC_LISTS}) # 3 添加依赖库列表 target_link_libraries(main PRIVATE MyMath) # 4 设置输出路径 set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) # 5 打印静态库的输出路径 add_custom_command( TARGET main POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo $TARGET_FILE:MyMath COMMENT 获取静态库的输出路径 )Step 1运⾏cmakecdbuildrm-fr .cmake ../Step 2编译链接make [ 60%] Built target MyMath [ 80%] Linking CXX executable ../bin/main 获取静态库的输出路径 /home/bit/workspace/CMakeClass/my_math/build/lib/libMyMath.a [100%] Built target mainStep 3查看makefile⽂件cmake 会根据⽬标的输出路径⽣成实际的链接规则cat app/CMakeFiles/main.dir/link.txt /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.4.sdk -mmacosx-version-min15.3 -Wl,-search_paths_first -Wl,- headerpad_max_install_names CMakeFiles/main.dir/main.cpp.o -o ../bin/main ../lib/libMyMath.a1.3 静态库编译-链接-安装上⼀节我们⽤⼀个简单的加法和减法数学库演⽰了如何⽣成并链接项⽬内部的⼀个静态库。这节我们还是⽤同样的例⼦和⼤家⼀起演⽰下如何发布我们⽣成的静态库到linux下的标准路径以供其他软件引⽤从⽽达到真正代码复⽤的⽬的。在下⼀节我们将演⽰如果使⽤cmake的find_package函数查找我们发布的静态库并在其他项⽬⾥引⽤。1.3.1 单步操作Step 0⽬录结构├── CMakeLists.txt └── my_lib ├── CMakeLists.txt ├── Config.cmake.in ├── include │ └── math.h └── src ├── add.cpp └── sub.cppStep 1新建⽂件-CMakeLists.txtcmake_minimum_required(VERSION 3.18) project(InstallMyMath LANGUAGES CXX ) add_subdirectory(my_lib)Step 2新建⽂件-my_lib/CMakeLists.txt# 1 收集源代码 file(GLOB SRC_LISTS src/*.cpp) # 2 添加构建⽬标 add_library(MyMath STATIC ${SRC_LISTS}) # 3 设置库的使⽤要求也就是下游消费者必须包含的头⽂件搜索路径 target_include_directories(MyMath INTERFACE $INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR} # include ) # 4 设置库的默认输出路径 set_target_properties(MyMath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # 5 安装我们的静态库 include(GNUInstallDirs) install(TARGETS MyMath EXPORT MyMathTargets DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib ) # 6 安装头⽂件 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math # /usr/local/include/math/math.h FILES_MATCHING PATTERN *.h ) # 7 安装导出⽬标集合 到 构建树 export(EXPORT MyMathTargets FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake ) # 8 安装导出⽬标集合 到 安装树 install(EXPORT MyMathTargets FILE MyMathTargets.cmake NAMESPACE MyMath:: # MyMath::MyMath DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath # /usr/local/lib/cmake/MyMath/MyMathTargets.cmake ) # 9 ⽣成find_package 需要的 配置⽂件 include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake INSTALL_DESTINATION lib/cmake/MyMath ) # 10 安装配置⽂件到cmake 标准的安装路径 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake DESTINATION lib/cmake/MyMath )Step 3新建⽂件-Config.cmake.inPACKAGE_INITinclude(${CMAKE_CURRENT_LIST_DIR}/MyMathTargets.cmake)Step 4新建⽂件-math.hintadd(inta,intb);intsub(inta,intb);Step 5新建⽂件-add.cppint add(int x, int y){return x y;}Step 6新建⽂件-sub.cppint sub(int x, int y) { return x - y; }Step 7运⾏cmakemkdirbuild cdbuildcmake ../Step 8编译链接cmake --build . [ 33%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/add.cpp.o [ 66%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/del.cpp.o [100%] Linking CXX static library libMyMath.a [100%] Built target MyMathStep 9安装sudo cmake --install . -- Install configuration: -- Installing: /usr/local/lib/libMyMath.a -- Up-to-date: /usr/local/include -- Up-to-date: /usr/local/include/math.h -- Installing: /usr/local/lib/cmake/MyMath/MyMathTargets.cmake -- Installing: /usr/local/lib/cmake/MyMath/MyMathTargets-noconfig.cmake -- Installing: /usr/local/lib/cmake/MyMath/MyMathConfig.cmake -- Installing: /usr/local/lib/cmake/MyMath/MyMathConfigVersion.cmake这样我们就能在/usr/local/lib/cmake这个路径下面找到生成的三个文件图解最后我们测试这个MyMath库我们先创建一个test_MyMath测试文件里面一共就只有两个文件CMakeLists.txt,main.cppCMakeLists.txt文件如下cmake_minimum_required(VERSION 3.18) project(TestMyMath) add_executable(main main.cpp) # 查找MyMath数学库 find_package(MyMath CONFIG REQUIRED) # 声明依赖 target_link_libraries(main PRIVATE MyMath::MyMath)main.cpp如下#include iostream #include math/math.h int main() { std::coutadd(3,4)std::endl; std::coutsub(3,4)std::endl; return 0; }最后我们看看详细构建过程g -v -x c -E /dev/null查看 g 的编译环境信息并让预处理器运行一次从而查看 C 头文件搜索路径等配置。每个参数解析g调用 GNU C 编译器。-v显示编译过程中的详细信息例如gcc/g 版本、编译配置、使用的内部命令、默认搜索路径-x c指定输入文件类型。-E只进行预处理不进行编译和链接。/dev/nullLinux 的黑洞文件。特点写进去的数据全部丢弃。给 g 一个空的 C 文件让它执行预处理流程。因为没有代码所以不会输出大量内容但是-v会打印环境信息。你可以把它理解成让 g 假装编译一个空的 C 文件但是不要真正编译只把自己的配置环境全部告诉我。cailoVM-0-17-ubuntu:~/cmakegit/test_MyMath/build$ g -v -x c -E /dev/null Using built-in specs. COLLECT_GCCg OFFLOAD_TARGET_NAMESnvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversionUbuntu 11.4.0-1ubuntu1~22.04.2 --with-bugurlfile:///usr/share/doc/gcc-11/README.Bugs --enable-languagesc,ada,c,go,brig,d,fortran,objc,obj-c,m2 --prefix/usr --with-gcc-major-version-only --program-suffix-11 --program-prefixx86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir/usr/lib --without-included-gettext --enable-threadsposix --libdir/usr/lib --enable-nls --enable-bootstrap --enable-clocalegnu --enable-libstdcxx-debug --enable-libstdcxx-timeyes --with-default-libstdcxx-abinew --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checkingrelease --with-target-system-zlibauto --enable-objc-gcauto --enable-multiarch --disable-werror --enable-cet --with-arch-32i686 --with-abim64 --with-multilib-listm32,m64,mx32 --enable-multilib --with-tunegeneric --enable-offload-targetsnvptx-none/build/gcc-11-2Y5pKs/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa/build/gcc-11-2Y5pKs/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checkingrelease --buildx86_64-linux-gnu --hostx86_64-linux-gnu --targetx86_64-linux-gnu --with-build-configbootstrap-lto-lean --enable-link-serialization2 Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04.2) COLLECT_GCC_OPTIONS-v -E -shared-libgcc -mtunegeneric -marchx86-64 /usr/lib/gcc/x86_64-linux-gnu/11/cc1plus -E -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE /dev/null -mtunegeneric -marchx86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -dumpbase null ignoring duplicate directory /usr/include/x86_64-linux-gnu/c/11 ignoring nonexistent directory /usr/local/include/x86_64-linux-gnu ignoring nonexistent directory /usr/lib/gcc/x86_64-linux-gnu/11/include-fixed ignoring nonexistent directory /usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include #include ... search starts here: #include ... search starts here: /usr/include/c/11 /usr/include/x86_64-linux-gnu/c/11 /usr/include/c/11/backward /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/include End of search list. # 0 /dev/null # 0 built-in # 0 command-line # 1 /usr/include/stdc-predef.h 1 3 4 # 0 command-line 2 # 1 /dev/null COMPILER_PATH/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS-v -E -shared-libgcc -mtunegeneric -marchx86-64 cailoVM-0-17-ubuntu:~/cmakegit/test_MyMath/build$1.3.2 重点命令解释1.3.2.1 export函数作⽤将项⽬中的⽬标如可执⾏⽂件、库及其相关属性导出到⼀个⽂件中以便在其他项⽬中使⽤。基本形式export(EXPORT export_name FILE file_name [NAMESPACE namespace] [DESTINATION destination] [OPTIONAL] [CONFIGURATIONS config1 [config2...]] [INCLUDE_INSTALL_DIRS])参数解释参数含义export_name导出集的名称⽤于在其他项⽬中引⽤file_name导出⽂件的名称通常为 export_nameConfig.cmakeNAMESPACE namespace可选为导出的⽬标添加命名空间避免命名冲突DESTINATION destination可选指定导出⽂件的安装⽬录CONFIGURATIONS config1 可选指定要导出的配置如 Debug、Release。INCLUDE_INSTALL_DIRS可选导出的配置⽂件中包含安装路径export() 命令会⽣成⼀个 CMake ⽂件其中包含已导出⽬标的信息如位置、依赖项、编译选项。其他项⽬可以通过 find_package() 加载此配置⽂件从⽽使⽤你项⽬中的⽬标。通常和install 配合使⽤ install 定义需要导出的导出组export 将导出组中的所有⽬标导出到配置⽂件。与 install(EXPORT) 命令的区别•export(EXPORT)⽣成临时配置⽂件仅⽤于从构建⽬录中导出⽬标。•install(EXPORT ...)⽣成正式配置⽂件并安装到系统⽤于从安装⽬录导出⽬标。1.3.2.2configure_package_config_file函数作⽤根据模板⽂件⽣成⾃定义包配置⽂件可以被其他项⽬使⽤来查找和配置你的包。基本形式configure_package_config_file( input output [COPYONLY] [INSTALL_DESTINATION dir] [PATH_VARS var1 [var2...]] [NO_SET_AND_CHECK_MACRO] [NO_CHECK_REQUIRED_COMPONENTS_MACRO] [NO_MESSAGE] )参数解释参数含义input模板⽂件的路径通常是⼀个 CMake 脚本⽂件其中包含了⼀些变量和逻辑⽤于⽣成最终的配置⽂件output⽣成的配置⽂件的路径COPYONLY可选如果指定将直接复制模板⽂件⽽不进⾏任何配置INSTALL_DESTINATION dir可选指定⽣成的配置⽂件的安装⽬录CONFIGURATIONS config1 可选指定要导出的配置如 Debug、ReleaseNO_SET_AND_CHECK_MACRO可选如果指定将不⽣成 set_and_check 宏⽤于设置变量并验证其路径是否存在NO_CHECK_REQUIRED_COMPONENTS_MACRO可选如果指定将不⽣成 check_required_components 宏⽤于检查包的所有必需组件是否都已找到并正确配置。1.3.2.3write_basic_package_version_file函数作⽤⽣成⼀个包含版本信息和兼容性检查的包版本⽂件使得其他项⽬能够轻松地使⽤你的包并确保版本兼容性。基本形式write_basic_package_version_file( file VERSION version [COMPATIBILITY compatibility] [ARCH_INDEPENDENT] )参数解释参数含义file⽣成的版本⽂件的路径VERSION version包的版本号格式通常为 major.minor.patchCOMPATIBILITY compatibility可选AnyNewerVersion任何更新的版本都兼容 SameMajorVersion只有主版本号相同的版本才兼容 ExactVersion只有完全相同的版本才兼容ARCH_INDEPENDENT可选如果指定版本⽂件将不包含架构相关的信息