CMake项目中如何按目录结构分离显示Header和Source文件
🚀 CMake项目中如何让VS2010按目录结构分离显示Header和Source文件(含注释详解)
🔧 适用版本:CMake ≥ 2.8,Visual Studio 2010及以上
💡 前言
我们在使用 CMake 为 Visual Studio 2010 生成工程文件时,默认所有源文件都会被“扁平化”放在项目里,这对大型项目来说非常不利 —— 阅读混乱、模块划分不清晰。
如何实现 按目录结构显示 + .cpp 和 .h 文件分离在“Source Files”和“Header Files”文件夹中 呢?答案就是 —— source_group()!
📁 示例目录结构(带子目录)
为了说明方便,我们使用如下项目结构:
MyProject/
├── CMakeLists.txt # 顶层 CMake 文件
├── src/
│ ├── main.cpp
│ ├── app/
│ │ ├── logic.cpp
│ │ └── logic.h
│ └── util/
│ ├── helper.cpp
│ └── helper.h
我们的目标是在 Visual Studio 2010 工程中生成如下分组结构:
MyApp
├── Header Files
│ ├── app
│ │ └── logic.h
│ └── util
│ └── helper.h
├── Source Files
│ ├── app
│ │ └── logic.cpp
│ └── util
│ └── helper.cpp
添加 src 子目录下的 CMakeLists.txt
add_subdirectory(src)
🧠 关键语法解析 + src/CMakeLists.txt 完整代码
递归获取当前目录下所有头文件(.h 和 .hpp)
file(GLOB_RECURSE HEADER_FILES"${CMAKE_CURRENT_SOURCE_DIR}/*.h""${CMAKE_CURRENT_SOURCE_DIR}/*.hpp"
)
递归获取当前目录下所有源文件(.cpp 和 .c)
file(GLOB_RECURSE SOURCE_FILES"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp""${CMAKE_CURRENT_SOURCE_DIR}/*.c"
)
遍历头文件列表,为每个文件设置 source_group
foreach(file ${HEADER_FILES})# 获取该文件相对于当前目录的路径(如 app/logic.h)file(RELATIVE_PATH rel_path "${CMAKE_CURRENT_SOURCE_DIR}" "${file}")# 提取路径部分(如 app),用于后续分组get_filename_component(dir_path "${rel_path}" PATH)# 将路径中的斜杠 '/' 替换为 Windows 风格的 '\\'string(REPLACE "/" "\\" group_path "${dir_path}")# 设置 VS 工程中过滤器(即虚拟目录)为 "Header Files\app" 这类结构source_group("Header Files\\${group_path}" FILES "${file}")
endforeach()
同理,遍历源文件列表,为每个源文件设置 source_group
foreach(file ${SOURCE_FILES})file(RELATIVE_PATH rel_path "${CMAKE_CURRENT_SOURCE_DIR}" "${file}")get_filename_component(dir_path "${rel_path}" PATH)string(REPLACE "/" "\\" group_path "${dir_path}")source_group("Source Files\\${group_path}" FILES "${file}")
endforeach()
创建一个可执行程序 MyApp,并包含所有源文件和头文件
add_executable(MyApp ${SOURCE_FILES} ${HEADER_FILES})
🔍 各关键 CMake 命令解释
命令 含义
file(GLOB_RECURSE ...) 递归查找所有匹配的文件(如所有 .cpp, .h)
file(RELATIVE_PATH) 将文件路径转为相对路径(用于构造 VS 分组)
get_filename_component(... PATH) 获取路径中的目录部分(不含文件名)
string(REPLACE "/" "\\" ...) 把路径从 Linux 风格改为 Windows 风格,VS 识别 \\ 目录结构
source_group(...) 为 Visual Studio 指定工程结构的“过滤器”(类似于文件夹)
add_executable(...) 构建目标程序,并包含所有指定源文件
✅ 效果验证(Visual Studio 2010 中)
打开你生成的 .sln 工程,进入“解决方案资源管理器”,你会看到:
Header Files → 子目录 → 头文件
Source Files → 子目录 → 源文件
文件并不会混在一起,层级结构清晰明了!
🧩 常见问题排查
问题 解决方法
.cpp 和 .h 没分开 是否为它们分别调用了 source_group(),路径是否用双斜线?
分组没生效 source_group 只影响已被 add_executable()/add_library() 引用的文件
分组只有顶层,不是多级 RELATIVE_PATH 的基准路径要选对;替换路径斜线时必须用 \
工程结构没更新 清理 CMake 缓存,重新 Configure & Generate 一遍
📦 进阶建议(支持多模块库、静态库)
你也可以将此逻辑封装成函数,比如:
# 功能:自动将源文件和头文件按目录结构分组到 VS 的过滤器中
# 参数:
# target_name:目标名称(用作变量前缀)
# base_dir :文件扫描根目录function(assign_source_filters target_name base_dir)# 扫描头文件和源文件file(GLOB_RECURSE HEADERS "${base_dir}/*.h" "${base_dir}/*.hpp")file(GLOB_RECURSE SOURCES "${base_dir}/*.cpp" "${base_dir}/*.c")# 分组头文件foreach(file IN LISTS HEADERS)file(RELATIVE_PATH rel "${base_dir}" "${file}")get_filename_component(path "${rel}" PATH)string(REPLACE "/" "\\" group "${path}")source_group("Header Files\\${group}" FILES "${file}")endforeach()# 分组源文件foreach(file IN LISTS SOURCES)file(RELATIVE_PATH rel "${base_dir}" "${file}")get_filename_component(path "${rel}" PATH)string(REPLACE "/" "\\" group "${path}")source_group("Source Files\\${group}" FILES "${file}")endforeach()# 导出变量以便 add_executable/add_library 使用set(${target_name}_HEADERS ${HEADERS} PARENT_SCOPE)set(${target_name}_SOURCES ${SOURCES} PARENT_SCOPE)
endfunction()
🎯 使用方式:
# 调用函数,传入目标名和源文件根目录
assign_source_filters(MyApp "${CMAKE_CURRENT_SOURCE_DIR}")
✅ 实现方式(只保留最后一级目录)
# 递归查找 src 下所有头文件
file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")set(INCLUDE_DIRS "")
foreach(header IN LISTS HEADERS)# 获取头文件所在目录get_filename_component(full_dir "${header}" DIRECTORY)# 提取最后一级目录名get_filename_component(last_dir "${full_dir}" NAME)
if(NOT "${A}" STREQUAL "${B}")# 只添加 app / util,不含 srclist(APPEND INCLUDE_DIRS "${last_dir}")
endif()endforeach()# 去重
list(REMOVE_DUPLICATES INCLUDE_DIRS)# 添加为相对路径(相对于 ${CMAKE_CURRENT_SOURCE_DIR})
# 假设 app/util 都在 src 目录下
foreach(dir IN LISTS INCLUDE_DIRS)list(APPEND FINAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/src/${dir}")
endforeach()# 加入到 VS 工程 include path
target_include_directories(MyApp PRIVATE ${FINAL_INCLUDE_DIRS})
🏁 总结
在 VS2010 中想要实现“源文件”和“头文件”分组,并保留子目录结构,不是简单使用 GLOB_RECURSE 就能做到的,而是需要配合 source_group()、路径处理等一整套方法。通过本文,你可以轻松让 CMake 生成的 VS 项目结构清晰明了,提升开发效率!