
草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. 环境检测构建系统的第一道防线1.1 平台与体系架构检测1.2 编译器探测与选项配置1.3 头文件与结构体检测二. 变量定义CMake 的核心数据机制2.1 普通变量局部配置2.2 缓存变量用户可配置选项2.3 列表变量批量处理数据2.4 环境变量系统级配置三. 功能开启选项灵活控制构建内容3.1 option 命令基本用法3.2 选项的条件执行四. 集成单元测试从手动到自动4.1 基础 CTest 测试流程4.2 构建后自动运行测试五. 完整安装流程从库文件到配置文件5.1 安装头文件5.2 安装 pkg-config 配置文件5.3 安装库文件与 CMake 配置文件六. 实战使用 JsonCpp 库结尾前言在 C 开发中JsonCpp 几乎是处理 JSON 数据的标配库。但大多数开发者只是停留在 “复制粘贴 CMakeLists.txt 模板” 的阶段当遇到跨平台编译错误、链接问题或自定义构建需求时往往束手无策。JsonCpp 的 CMake 构建系统是现代 CMake 实践的绝佳范本它完整覆盖了环境检测、变量管理、功能开关、单元测试、多类型库构建、跨平台发布等所有核心场景。本文将逐行拆解 JsonCpp 1.9.7 版本的 CMake 源码带你从底层理解每个命令的作用和设计思路真正掌握可复用的 CMake 工程化技巧。一. 环境检测构建系统的第一道防线CMake 的核心价值在于跨平台而配置阶段的环境检测是实现跨平台的基础。JsonCpp 在构建开始前会全面探测目标系统的编译器、头文件、函数和系统特性并根据探测结果动态调整构建逻辑。1.1 平台与体系架构检测CMake 在配置阶段会自动探测目标系统信息并将结果存入内置变量。JsonCpp 利用这些变量区分不同平台的构建逻辑核心变量含义JsonCpp 中的用途CMAKE_SYSTEM_NAME目标系统名 (Windows/Linux/Darwin)区分 Windows 和类 Unix 系统的内存清零实现CMAKE_SYSTEM_PROCESSOR目标 CPU 架构 (x86_64/armv8)针对不同架构优化编译选项CMAKE_CROSSCOMPILING是否交叉编译为交叉编译环境调整测试运行方式源码解读JsonCpp 中的安全内存清零逻辑完全依赖环境检测结果// json_value.cpp 中的条件编译#ifdefined(HAVE_MEMSET_S)memset_s(p,n*sizeof(T),0,n*sizeof(T));#elifdefined(_WIN32)RtlSecureZeroMemory(p,n*sizeof(T));#elsestd::fill_n(reinterpret_castvolatileunsignedchar*(p),n,0);#endif对应的 CMake 检测代码# 检查系统是否存在memset_s函数 include(CheckFunctionExists) check_function_exists(memset_s HAVE_MEMSET_S) # 若存在则定义宏供源码条件编译使用 if(HAVE_MEMSET_S) add_definitions(-DHAVE_MEMSET_S1) endif()1.2 编译器探测与选项配置不同编译器的语法和选项差异巨大JsonCpp 会根据编译器 ID 自动添加合适的编译警告和特性if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang/AppleClang编译器选项 add_compile_options(-Wall -Wconversion -Wshadow) if (JSONCPP_WITH_WARNING_AS_ERROR) add_compile_options(-Werrorconversion) endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL GNU) # GCC编译器选项 add_compile_options(-Wall -Wconversion -Wshadow -Wextra) if (JSONCPP_WITH_STRICT_ISO) add_compile_options(-Wpedantic) # 严格ISO C标准 endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL MSVC) # MSVC编译器抑制安全警告 add_definitions( -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_WIN32_WINNT0x601 # Windows 7及以上 -D_WINSOCK_DEPRECATED_NO_WARNINGS ) endif()关键设计警告级别分层基础警告默认开启严格警告和警告转错误通过选项控制兼容性优先对不同编译器使用各自支持的选项避免无效参数平台适配MSVC 下专门处理微软特有的安全警告和系统版本宏1.3 头文件与结构体检测JsonCpp 会检测系统是否提供完整的本地化功能支持并根据检测结果决定是否启用相关代码# 检查头文件是否存在 check_include_file(clocale HAVE_CLOCALE) check_include_file(sys/types.h HAVE_SYS_TYPES_H) # 检查结构体大小 check_sizeof(lconv HAVE_LCONV_SIZE) # 检查结构体成员是否存在 check_struct_has_member(lconv decimal_point clocale HAVE_DECIMAL_POINT) # 若缺少必要的本地化支持输出警告并禁用相关功能 if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT)) message(WARNING Locale functionality is not supported) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) add_compile_definitions(JSONCPP_NO_LOCALE_SUPPORT) else() add_definitions(-DJSONCPP_NO_LOCALE_SUPPORT) endif() endif()二. 变量定义CMake 的核心数据机制CMake 中所有变量本质都是字符串但可以被解释为列表、布尔值等不同类型。JsonCpp 展示了 CMake 变量的四种主要使用方式。2.1 普通变量局部配置普通变量作用域为当前目录及其子目录用于存储临时配置值# 设置C标准 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) # 不使用编译器扩展 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 强制要求指定标准 # 设置输出目录 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH Archive output dir.) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH Library output dir.) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH Executable output dir.)2.2 缓存变量用户可配置选项缓存变量会被保存到CMakeCache.txt文件中在多次 CMake 运行间保持不变是用户自定义构建的主要入口# 设置默认构建类型为Release if (NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING 选择构建类型可选值为: None Debug Release RelWithDebInfo MinSizeRel Coverage.) endif()使用方式用户可以通过命令行参数覆盖缓存变量cmake..-DCMAKE_BUILD_TYPEDebug-DCMAKE_INSTALL_PREFIX/opt/jsoncpp2.3 列表变量批量处理数据列表是用分号分隔的字符串常用于批量处理文件和目标# 定义示例程序列表 set(EXAMPLES readFromString readFromStream stringWrite streamWrite) # 遍历列表为每个示例创建可执行文件 foreach(example ${EXAMPLES}) add_executable(${example} ${example}/${example}.cpp) target_include_directories(${example} PUBLIC ${CMAKE_SOURCE_DIR}/include) target_link_libraries(${example} jsoncpp_lib) endforeach()2.4 环境变量系统级配置CMake 通过$ENV{VAR}语法访问系统环境变量主要用于安装路径的拼接# 安装时考虑DESTDIR环境变量用于打包 if (EXISTS $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/cmake/jsoncpp/jsoncpp-targets.cmake) # 处理旧版本配置文件 endif()三. 功能开启选项灵活控制构建内容option命令是 CMake 提供的布尔开关用于控制是否构建某个功能模块。JsonCpp 定义了丰富的选项让用户可以按需裁剪构建内容。3.1 option 命令基本用法# 语法option(变量名 帮助文本 [默认值]) option(JSONCPP_WITH_CMAKE_PACKAGE Generate and install cmake package files ON) option(JSONCPP_WITH_EXAMPLE Compile JsonCpp example OFF) option(JSONCPP_WITH_TESTS Compile and run JsonCpp test executables ON) option(BUILD_SHARED_LIBS Build jsoncpp_lib as a shared library. ON) option(BUILD_STATIC_LIBS Build jsoncpp_lib as a static library. ON) option(BUILD_OBJECT_LIBS Build jsoncpp_lib as a object library. ON)核心特性默认值不指定则为OFF优先级命令行-D参数 缓存变量 option 默认值全局可见option 创建的是缓存变量整个工程都可以访问3.2 选项的条件执行通过if()判断选项的值决定是否执行对应的构建逻辑# 只有启用测试选项时才添加测试子目录 if(JSONCPP_WITH_TESTS) enable_testing() include(CTest) add_subdirectory(jsontestrunner) add_subdirectory(test_lib_json) endif() # 根据是否构建共享库决定链接哪个库 if(BUILD_SHARED_LIBS) target_link_libraries(jsontestrunner_exe jsoncpp_lib) else() target_link_libraries(jsontestrunner_exe jsoncpp_static) endif()四. 集成单元测试从手动到自动JsonCpp 展示了两种主流的 CMake 测试方式手动 CTest 测试和构建后自动测试有效解决了 “忘记运行测试就发布” 的问题。4.1 基础 CTest 测试流程标准 CTest 测试分为三步include(CTest)启用测试功能add_test()定义测试用例执行ctest命令运行测试# 创建测试可执行文件 add_executable(jsoncpp_test jsontest.cpp fuzz.cpp main.cpp) # 添加测试用例 add_test( NAME jsoncpp_test COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $TARGET_FILE:jsoncpp_test )运行方式mkdirbuildcdbuild cmake..makectest# 手动运行所有测试4.2 构建后自动运行测试JsonCpp 的一个优秀实践是将测试提前到构建阶段自动运行确保每次构建都经过测试验证option(JSONCPP_WITH_POST_BUILD_UNITTEST Automatically run unit-tests as a post build step ON) if(JSONCPP_WITH_POST_BUILD_UNITTEST) # 为jsoncpp_test目标添加构建后命令 add_custom_command( TARGET jsoncpp_test POST_BUILD # 在目标构建成功后执行 COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $TARGET_FILE:jsoncpp_test COMMENT Running unit tests... ) endif()优势强制测试只要构建成功测试就会自动运行快速反馈测试失败会直接终止构建流程CI 友好无需额外配置 CI 步骤测试结果直接体现在构建日志中五. 完整安装流程从库文件到配置文件一个优秀的 C 库不仅要能编译成功还要能方便地被其他项目使用。JsonCpp 的安装流程完整覆盖了头文件、库文件、pkg-config 配置、CMake 配置四个部分是现代 CMake 库发布的标准范本。5.1 安装头文件头文件是库的公开接口必须随库一起安装# 使用file(GLOB)查找所有头文件 file(GLOB INCLUDE_FILES json/*.h) # 安装头文件到系统标准包含目录 install(FILES ${INCLUDE_FILES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/json )注意事项CMAKE_INSTALL_INCLUDEDIR由GNUInstallDirs模块定义默认值为include头文件会被安装到${CMAKE_INSTALL_PREFIX}/include/json目录安装后用户可以通过#include json/json.h引用头文件5.2 安装 pkg-config 配置文件pkg-config 是类 Unix 系统上广泛使用的库管理工具JsonCpp 会自动生成并安装.pc文件第一步编写模板文件jsoncpp.pc.inprefixCMAKE_INSTALL_PREFIX exec_prefixCMAKE_INSTALL_PREFIX libdirlibdir_for_pc_file includedirincludedir_for_pc_file Name: jsoncpp Description: A C library for interacting with JSON Version: PROJECT_VERSION URL: https://github.com/open-source-parsers/jsoncpp Libs: -L${libdir} -ljsoncpp Cflags: -I${includedir}第二步配置生成实际的.pc文件# 拼接路径 include(JoinPaths) join_paths(libdir_for_pc_file \${exec_prefix} ${CMAKE_INSTALL_LIBDIR}) join_paths(includedir_for_pc_file \${prefix} ${CMAKE_INSTALL_INCLUDEDIR}) # 替换模板中的变量 configure_file( pkg-config/jsoncpp.pc.in pkg-config/jsoncpp.pc ONLY # 只替换VAR格式的变量 ) # 安装生成的.pc文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkg-config/jsoncpp.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig )5.3 安装库文件与 CMake 配置文件这是安装流程中最核心也最容易混淆的部分可以用一句话概括先装库本身再出使用说明书。第一步安装库文件并加入导出集# 定义要安装的目标列表 list(APPEND CMAKE_TARGETS jsoncpp_lib jsoncpp_static jsoncpp_object) # 安装目标文件并加入名为jsoncpp的导出集 install(TARGETS ${CMAKE_TARGETS} EXPORT jsoncpp # 关键将目标加入导出集 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # 可执行文件 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # 共享库 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # 静态库 OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} # 对象库 )第二步安装导出集生成 “说明书”# 安装导出集生成jsoncpp-targets.cmake文件 install(EXPORT jsoncpp DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp FILE jsoncpp-targets.cmake NAMESPACE JsonCpp:: # 为导出目标添加命名空间 )第三步生成并安装 CMake 包配置文件include(CMakePackageConfigHelpers) # 生成包配置文件 configure_package_config_file( jsoncppConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp ) # 生成版本配置文件 write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion # 主版本号兼容 ) # 安装配置文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp )核心原理install(TARGETS EXPORT)复制库文件到安装目录并记录目标信息到导出集install(EXPORT)将导出集转换为 CMake 文件描述目标的路径和属性configure_package_config_file生成供find_package使用的配置文件最终用户通过find_package(jsoncpp CONFIG)即可找到并使用库六. 实战使用 JsonCpp 库完成安装后其他项目可以通过三种方式使用 JsonCpp 库分别对应不同的使用场景。方式 1pkg-config 方式类 Unix 系统# 查看编译和链接参数pkg-config--cflags--libsjsoncpp# 直接编译g main.cpp-stdc11$(pkg-config--cflags--libsjsoncpp)方式 2CMake PkgConfig 模块cmake_minimum_required(VERSION 3.18) project(JsoncppDemo) # 引入PkgConfig模块 find_package(PkgConfig REQUIRED) # 查找jsoncpp并生成导入目标 pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp) # 添加可执行文件并链接 add_executable(main main.cpp) target_link_libraries(main PRIVATE PkgConfig::JSONCPP)方式 3CMake Config 模式推荐cmake_minimum_required(VERSION 3.18) project(JsoncppDemo) # 直接查找CMake配置文件 find_package(jsoncpp CONFIG REQUIRED) # 添加可执行文件并链接 add_executable(main main.cpp) target_link_libraries(main PRIVATE JsonCpp::JsonCpp) # 也可以选择链接静态库或动态库 # target_link_libraries(main PRIVATE jsoncpp_static) # target_link_libraries(main PRIVATE jsoncpp_lib)结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语JsonCpp 的 CMake 构建系统虽然只有几百行代码但浓缩了现代 CMake 工程化的所有核心思想。它没有使用任何黑魔法全部基于 CMake 标准命令却实现了高度灵活和可扩展的构建流程。掌握了 JsonCpp 的 CMake 实践你不仅能解决日常开发中遇到的 JsonCpp 编译问题更能将这些技巧应用到自己的项目中打造出专业、健壮、跨平台的 C 库构建系统。在下一篇文章中我们将继续拆解另一个经典开源库 Curl 的 CMake 构建系统看看它在处理复杂依赖和多平台支持方面有哪些独到之处。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど