初始化Mirai项目结构,包括核心组件和测试框架。

This commit is contained in:
2026-01-03 00:44:06 +08:00
commit 0533ef053e
20 changed files with 2138 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.idea
/cmake-build-*

27
CMakeLists.txt Normal file
View File

@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.25)
# ================================================================================================
# vcpkg 工具链集成
# 必须在 project() 之前设置
# ================================================================================================
if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file")
endif()
project(mirai_project)
include(cmake/retrieve_files.cmake)
include(cmake/detect_os.cmake)
include(cmake/config_macos.cmake)
include(cmake/mirai_utils.cmake)
include(cmake/project_cpp_standard.cmake)
setup_project_options(
STANDARD 23
INTERFACE_TARGET mirai_project_options
)
add_subdirectory(src)
add_subdirectory(example)
add_subdirectory(tests)

View File

@@ -0,0 +1,108 @@
# ================================================================================================
# MIRAI Framework - 编译器选项配置模块
# 描述: 配置编译器特定选项、警告级别和优化设置
# ================================================================================================
# ================================================================================================
# 配置编译器选项
# ================================================================================================
function(configure_compiler_options)
# 使用生成器表达式为多配置生成器如Visual Studio正确配置选项
if(MSVC)
add_compile_options(
/wd5054 # 禁用 C5054: 枚举之间的 & 运算符弃用警告
/wd4324 # 禁用 C4324: 由于对齐说明符,结构被填充警告
)
# Debug配置特定设置
add_compile_options(
$<$<CONFIG:Debug>:/Zi> # 调试信息
$<$<CONFIG:Debug>:/Od> # 禁用优化
$<$<CONFIG:Debug>:/RTC1> # 运行时检查
$<$<CONFIG:Debug>:/W4> # 严格警告
)
add_compile_definitions(
$<$<CONFIG:Debug>:MIRAI_DEBUG_BUILD>
)
# Release配置特定设置
add_compile_options(
$<$<CONFIG:Release>:/O2> # 最大优化
$<$<CONFIG:Release>:/Ob2> # 内联展开
$<$<CONFIG:Release>:/Zi> # 调试信息用于调试Release版本
)
add_compile_definitions(
$<$<CONFIG:Release>:MIRAI_RELEASE_BUILD>
$<$<CONFIG:Release>:NDEBUG>
)
# RelWithDebInfo配置
add_compile_options(
$<$<CONFIG:RelWithDebInfo>:/O2>
$<$<CONFIG:RelWithDebInfo>:/Ob1>
$<$<CONFIG:RelWithDebInfo>:/Zi>
)
add_compile_definitions(
$<$<CONFIG:RelWithDebInfo>:MIRAI_RELEASE_BUILD>
$<$<CONFIG:RelWithDebInfo>:NDEBUG>
)
# MinSizeRel配置
add_compile_options(
$<$<CONFIG:MinSizeRel>:/O1>
$<$<CONFIG:MinSizeRel>:/Ob1>
$<$<CONFIG:MinSizeRel>:/Zi>
)
add_compile_definitions(
$<$<CONFIG:MinSizeRel>:MIRAI_RELEASE_BUILD>
$<$<CONFIG:MinSizeRel>:NDEBUG>
)
add_compile_definitions(
_ENABLE_EXTENDED_ALIGNED_STORAGE
)
message(STATUS "MSVC编译器选项已配置支持多配置生成器")
else()
# 非MSVC编译器的传统配置
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_definitions(MIRAI_DEBUG_BUILD)
add_compile_options(-g -O0 -fno-omit-frame-pointer)
add_compile_options(-Wall -Wextra -Wpedantic)
message(STATUS "Debug模式: 启用调试符号,禁用优化,启用严格警告")
else()
add_compile_definitions(MIRAI_RELEASE_BUILD NDEBUG)
add_compile_options(-O3 -DNDEBUG -march=native)
message(STATUS "Release模式: 启用最高级别优化")
endif()
# 启用更好的诊断信息
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options(-fdiagnostics-color=always)
add_compile_options(-ftemplate-backtrace-limit=0)
message(STATUS "启用彩色诊断和完整模板回溯")
endif()
endif()
if (MSVC)
add_compile_definitions(MIRAI_MSVC=1)
add_compile_definitions(MIRAI_GCC=0)
add_compile_definitions(MIRAI_CLANG=0)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_definitions(MIRAI_MSVC=0)
add_compile_definitions(MIRAI_GCC=1)
add_compile_definitions(MIRAI_CLANG=0)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_definitions(MIRAI_MSVC=0)
add_compile_definitions(MIRAI_GCC=0)
add_compile_definitions(MIRAI_CLANG=1)
endif()
endfunction()
# ================================================================================================
# 应用编译器配置
# ================================================================================================
function(apply_compiler_configuration)
configure_compiler_options()
message(STATUS "编译器配置完成")
endfunction()

41
cmake/config_macos.cmake Normal file
View File

@@ -0,0 +1,41 @@
# 如果是Macos
if (APPLE)
# 获取 Homebrew 安装的 libomp 路径
execute_process(
COMMAND brew --prefix libomp
OUTPUT_VARIABLE HOMEBREW_LIBOMP_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 设置 OpenMP 路径变量
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PATH}/include")
set(OpenMP_CXX_LIB_NAMES "omp")
set(OpenMP_omp_LIBRARY "${HOMEBREW_LIBOMP_PATH}/lib/libomp.dylib")
enable_language(OBJC OBJCXX)
# ============================================================================================
# Apple Accelerate框架支持 (包含AMX向量指令集)
# ============================================================================================
# 检测是否为Apple Silicon (ARM64)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
message(STATUS "Detected Apple Silicon (${CMAKE_SYSTEM_PROCESSOR})")
# 查找Accelerate框架macOS系统自带
find_library(ACCELERATE_FRAMEWORK Accelerate)
if(ACCELERATE_FRAMEWORK)
message(STATUS "Found Accelerate framework: ${ACCELERATE_FRAMEWORK}")
# 设置全局变量供其他模块使用
set(APPLE_ACCELERATE_LIBRARY ${ACCELERATE_FRAMEWORK} PARENT_SCOPE)
set(HAS_ACCELERATE TRUE PARENT_SCOPE)
# 启用Accelerate编译选项
add_compile_definitions(MIRAI_ENABLE_ACCELERATE)
else()
message(WARNING "Accelerate framework not found on macOS")
set(HAS_ACCELERATE FALSE PARENT_SCOPE)
endif()
else()
message(STATUS "Not Apple Silicon, Accelerate framework disabled")
set(HAS_ACCELERATE FALSE PARENT_SCOPE)
endif()
endif ()

205
cmake/detect_os.cmake Normal file
View File

@@ -0,0 +1,205 @@
# =============================================================================
# 顶层平台检测变量
# 这些变量可在整个项目中使用,用于条件编译和依赖选择
# =============================================================================
# 桌面平台检测
set(MIRAI_DESKTOP_PLATFORM OFF)
if(WIN32 OR (UNIX AND NOT ANDROID AND NOT IOS))
set(MIRAI_DESKTOP_PLATFORM ON)
endif()
# 移动平台检测
set(MIRAI_MOBILE_PLATFORM OFF)
if(ANDROID OR IOS)
set(MIRAI_MOBILE_PLATFORM ON)
endif()
# 各平台标志
set(MIRAI_PLATFORM_WINDOWS OFF)
set(MIRAI_PLATFORM_LINUX OFF)
set(MIRAI_PLATFORM_MACOS OFF)
set(MIRAI_PLATFORM_ANDROID OFF)
set(MIRAI_PLATFORM_IOS OFF)
if(WIN32)
set(MIRAI_PLATFORM_WINDOWS ON)
elseif(ANDROID)
set(MIRAI_PLATFORM_ANDROID ON)
elseif(IOS)
set(MIRAI_PLATFORM_IOS ON)
elseif(APPLE)
set(MIRAI_PLATFORM_MACOS ON)
elseif(UNIX)
set(MIRAI_PLATFORM_LINUX ON)
endif()
# =============================================================================
# 平台定义函数(为目标添加详细的平台宏)
# =============================================================================
# 定义一个函数,为指定的目标添加操作系统和架构相关的预处理器定义
function(add_os_definitions target)
# 检查 target 参数是否提供
if(NOT target)
message(FATAL_ERROR "函数 add_os_definitions 需要一个 target 参数。")
return()
endif()
# --- 阶段 1: 确定宏的值 ---
# 初始化所有平台、架构和特性宏的值为 0
set(mirai_def_windows 0)
set(mirai_def_macos 0)
set(mirai_def_linux 0)
set(mirai_def_freebsd 0)
set(mirai_def_ios 0)
set(mirai_def_android 0)
set(mirai_def_cygwin 0)
set(mirai_def_unix 0)
set(mirai_def_posix 0)
set(mirai_def_mobile 0)
set(mirai_def_arch_64bit 0)
set(mirai_def_arch_32bit 0)
set(mirai_def_x86 0)
set(mirai_def_arm 0)
set(mirai_def_riscv 0)
set(mirai_def_apple 0) # 用于 iOS 和 macOS 的通用定义
set(mirai_def_debug 0) # 当前是否处于调试模式
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(mirai_def_debug 1)
endif ()
# -- 操作系统检测与赋值 --
# 注意检测顺序:优先检测更具体的平台
if(CYGWIN)
# Cygwin 环境比较特殊,它在 Windows 上模拟 Unix
set(mirai_def_windows 1) # 基础是 Windows
set(mirai_def_cygwin 1) # 明确是 Cygwin
set(mirai_def_unix 1) # 提供 Unix API
set(mirai_def_posix 1) # 提供 POSIX API
# message(STATUS "检测到 **Cygwin** 环境 (运行于 Windows)")
elseif(WIN32)
# 非 Cygwin 的 Windows 环境 (MSVC, MinGW, etc.)
set(mirai_def_windows 1)
# message(STATUS "检测到 **Windows** 操作系统 (非 Cygwin)")
elseif(ANDROID)
# Android 平台 (通常需要特定工具链设置 ANDROID 变量)
set(mirai_def_android 1)
set(mirai_def_unix 1) # Android NDK 基于 Unix
set(mirai_def_posix 1) # NDK 提供 POSIX API
set(mirai_def_mobile 1) # 移动平台
# message(STATUS "检测到 **Android** 操作系统")
elseif(IOS)
# iOS 平台 (通常需要特定工具链设置 IOS 变量)
# 需要在 APPLE 之前判断,因为 iOS 下 APPLE 也为 TRUE
set(mirai_def_ios 1)
set(mirai_def_unix 1) # iOS (Darwin) 基于 Unix
set(mirai_def_posix 1) # 提供 POSIX API
set(mirai_def_mobile 1) # 移动平台
set(mirai_def_apple 1) # iOS 是 Apple 生态的一部分
# message(STATUS "检测到 **iOS** 操作系统")
elseif(APPLE)
# 此时排除了 iOS确定是 macOS
set(mirai_def_macos 1)
set(mirai_def_unix 1) # macOS (Darwin) 基于 Unix
set(mirai_def_posix 1) # 提供 POSIX API
set(mirai_def_apple 1) # macOS 是 Apple 生态的一部分
message(STATUS "检测到 **macOS** 操作系统")
elseif(UNIX)
# 此时排除了 Apple, Android, Cygwin 的其他 Unix-like 系统
set(mirai_def_unix 1)
set(mirai_def_posix 1)
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(mirai_def_linux 1)
# message(STATUS "检测到 **Linux** 操作系统")
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
set(mirai_def_freebsd 1)
# message(STATUS "检测到 **FreeBSD** 操作系统")
else()
message(WARNING "检测到未知的 类Unix 操作系统: ${CMAKE_SYSTEM_NAME}")
endif()
else()
message(WARNING "检测到未知的操作系统: ${CMAKE_SYSTEM_NAME}")
endif()
# -- 架构检测与赋值 --
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(mirai_def_arch_64bit 1)
set(mirai_def_arch_32bit 0) # 明确设置为 0
# message(STATUS "检测到 **64-bit** 架构")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(mirai_def_arch_64bit 0) # 明确设置为 0
set(mirai_def_arch_32bit 1)
# message(STATUS "检测到 **32-bit** 架构")
else()
# 对于未知或未定义的指针大小,两者都保持 0
message(WARNING "无法明确检测到 32-bit 或 64-bit 架构 (CMAKE_SIZEOF_VOID_P = ${CMAKE_SIZEOF_VOID_P})。将两者都设置为 0。")
endif()
# 检测特定架构类型
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86|i386|i486|i586|i686|i786|x86_64|AMD64")
set(mirai_def_x86 1)
# message(STATUS "检测到 **x86/x64** 架构")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch64|ARM64")
set(mirai_def_arm 1)
# message(STATUS "检测到 **ARM** 架构")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv|riscv64|riscv32")
set(mirai_def_riscv 1)
# message(STATUS "检测到 **RISC-V** 架构")
endif()
# --- 阶段 2: 组装定义列表 ---
set(definitions_list "") # 初始化空列表
# 添加平台定义
list(APPEND definitions_list "MIRAI_PLATFORM_WINDOWS=${mirai_def_windows}")
list(APPEND definitions_list "MIRAI_PLATFORM_MACOS=${mirai_def_macos}")
list(APPEND definitions_list "MIRAI_PLATFORM_LINUX=${mirai_def_linux}")
list(APPEND definitions_list "MIRAI_PLATFORM_FREEBSD=${mirai_def_freebsd}")
list(APPEND definitions_list "MIRAI_PLATFORM_IOS=${mirai_def_ios}")
list(APPEND definitions_list "MIRAI_PLATFORM_ANDROID=${mirai_def_android}")
list(APPEND definitions_list "MIRAI_PLATFORM_CYGWIN=${mirai_def_cygwin}")
list(APPEND definitions_list "MIRAI_PLATFORM_APPLE=${mirai_def_apple}") # 用于 iOS 和 macOS 的通用定义
# 添加架构定义
list(APPEND definitions_list "MIRAI_PLATFORM_ARCH_64BIT=${mirai_def_arch_64bit}")
list(APPEND definitions_list "MIRAI_PLATFORM_ARCH_32BIT=${mirai_def_arch_32bit}")
# 添加特性定义
list(APPEND definitions_list "MIRAI_PLATFORM_UNIX=${mirai_def_unix}")
list(APPEND definitions_list "MIRAI_PLATFORM_POSIX=${mirai_def_posix}")
list(APPEND definitions_list "MIRAI_PLATFORM_IS_MOBILE=${mirai_def_mobile}")
list(APPEND definitions_list "MIRAI_DEBUG=${mirai_def_debug}") # 当前是否处于调试模式
list(APPEND definitions_list "MIRAI_PLATFORM_X86=${mirai_def_x86}")
list(APPEND definitions_list "MIRAI_PLATFORM_ARM=${mirai_def_arm}")
list(APPEND definitions_list "MIRAI_PLATFORM_RISCV=${mirai_def_riscv}")
# --- 阶段 3: 应用所有定义 ---
# **关键:使用一次调用将所有定义添加到目标**
if(definitions_list) # 确保列表非空
get_target_property(target_type ${target} TYPE)
if(target_type STREQUAL "INTERFACE_LIBRARY")
target_compile_definitions(${target} INTERFACE ${definitions_list})
else()
target_compile_definitions(${target} PUBLIC ${definitions_list})
endif()
endif()
# 函数作用域结束时mirai_def_* 变量会自动销毁,无需显式 unset
endfunction()
# --- 使用示例 ---
# project(MyProject)
# add_executable(my_app main.c)
#
# # 调用函数为 my_app 添加平台定义
# add_os_definitions(my_app)
#
# # 你也可以为库调用
# # add_library(my_lib STATIC my_lib.c)
# # add_os_definitions(my_lib)

41
cmake/mingw_dll.cmake Normal file
View File

@@ -0,0 +1,41 @@
function(auto_copy_mingw_dll target)
if(MINGW)
# 获取MinGW目录
get_filename_component(MINGW_DIR ${CMAKE_CXX_COMPILER} PATH)
# 根据你的环境调整DLL列表
set(MINGW_DLLS
"libstdc++-6.dll"
# "libgcc_s_dw2-1.dll"
"libgcc_s_seh-1.dll"
"libwinpthread-1.dll"
"libbz2-1.dll"
"libbrotlidec.dll"
"libbrotlicommon.dll"
"libharfbuzz-0.dll"
"libpng16-16.dll"
"zlib1.dll"
"libglib-2.0-0.dll"
"libfreetype-6.dll"
"libgraphite2.dll"
"libintl-8.dll"
"libpcre2-8-0.dll"
"libiconv-2.dll"
)
foreach(DLL ${MINGW_DLLS})
# 检查文件是否存在
if(EXISTS "${MINGW_DIR}/${DLL}")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${MINGW_DIR}/${DLL}"
"$<TARGET_FILE_DIR:${target}>"
COMMENT "Copying ${DLL} to output directory"
VERBATIM)
else()
message(WARNING "DLL not found: ${MINGW_DIR}/${DLL}")
endif()
endforeach()
endif()
endfunction()

44
cmake/mirai_utils.cmake Normal file
View File

@@ -0,0 +1,44 @@
# 定义一个函数来配置项目的默认设置
# 这包括设置输出目录和项目根目录变量
function(configure_project_defaults)
# 检查是否在顶层 CMakeLists.txt 中调用 (可选但推荐)
# 确保 CMAKE_SOURCE_DIR 和 CMAKE_CURRENT_SOURCE_DIR 相同
if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
message(WARNING "configure_project_defaults() 应该在项目的根 CMakeLists.txt 中调用。")
# 如果您确实需要在子目录中设置不同的根目录,请调整此逻辑
endif()
# --- 配置输出目录 ---
# 使用 CMAKE_BINARY_DIR 作为基础构建目录
# ${CMAKE_BINARY_DIR} 指向您配置 CMake 时指定的构建目录
# 例如,在 CLion 中通常是 cmake-build-debug 或 cmake-build-release
# 如果手动运行 cmake ..,它就是您运行 cmake 命令的目录
# **设置可执行文件输出路径**:
# 对于单配置生成器 (如 Makefiles, Ninja), 可执行文件将位于 <build>/bin/
# 对于多配置生成器 (如 Visual Studio, Xcode), CMake 通常会自动在此路径下附加配置名称
# (例如 <build>/bin/Debug/, <build>/bin/Release/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Directory for runtime executables")
message(STATUS "运行时输出目录设置为: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
# **设置库文件输出路径 (共享库和静态库)**:
# 规则同上,库文件将位于 <build>/lib/ 或 <build>/lib/<Config>/
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for shared libraries")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for static libraries")
message(STATUS "库输出目录设置为: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "存档输出目录设置为: ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}")
# --- 提示 ---
# 这种全局设置输出目录的方法对于中小型项目是常见的。
# 对于更复杂的项目或需要更细粒度控制的情况可以考虑为每个目标target单独设置输出目录属性
# 例如:
# add_executable(my_app main.cpp)
# set_target_properties(my_app PROPERTIES
# RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/executables"
# )
# add_library(my_lib STATIC my_lib.cpp)
# set_target_properties(my_lib PROPERTIES
# ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_libs"
# )
endfunction()

View File

@@ -0,0 +1,118 @@
# 文件: cmake/CompilerSetup.cmake
# ==============================================================================
# 函数setup_project_options
# 描述:配置项目级的 C++ 标准、编译器警告、定义和依赖。
# 此函数遵循现代 CMake 实践,将所有配置封装到一个 INTERFACE 库中。
#
# 参数:
# standard - (必选) C++ 标准版本 (例如 17, 20, 23)。
# INTERFACE_TARGET - (必选) 用于接收创建的 INTERFACE 库名称的变量名。
#
# 用法:
# include(cmake/CompilerSetup.cmake)
# setup_project_options(
# STANDARD 20
# INTERFACE_TARGET my_project_options
# )
# # ... 定义你的可执行文件或库
# add_executable(my_app main.cpp)
# # ... 将配置应用到目标上
# target_link_libraries(my_app PRIVATE ${my_project_options})
# ==============================================================================
function(setup_project_options)
# --- 参数解析 ---
set(options "") # 无单值选项
set(oneValueArgs STANDARD INTERFACE_TARGET) # 定义接收单个值的参数
set(multiValueArgs "") # 无多值选项
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# --- 参数验证 ---
if(NOT ARG_STANDARD OR NOT ARG_INTERFACE_TARGET)
message(FATAL_ERROR "setup_project_options 必须提供 STANDARD 和 INTERFACE_TARGET 参数。")
endif()
set(VALID_STANDARDS 11 14 17 20 23)
list(FIND VALID_STANDARDS ${ARG_STANDARD} _standard_index)
if(_standard_index EQUAL -1)
message(FATAL_ERROR "不支持的 C++ 标准: ${ARG_STANDARD}。有效值: ${VALID_STANDARDS}")
endif()
# --- 创建 INTERFACE 库 ---
# 这是现代 CMake 的核心:创建一个虚拟目标来承载所有配置属性。
add_library(${ARG_INTERFACE_TARGET} INTERFACE)
message(STATUS "创建配置接口库: ${ARG_INTERFACE_TARGET}")
# --- 设置 C++ 标准 (应用到接口库) ---
target_compile_features(${ARG_INTERFACE_TARGET} INTERFACE cxx_std_${ARG_STANDARD})
# --- 设置通用编译定义和选项 ---
# 使用 target_compile_definitions 和 target_compile_options并指定 INTERFACE
# 这样任何链接到此库的目标都会继承这些属性。
# --- 平台特定设置 ---
if(WIN32)
target_compile_definitions(${ARG_INTERFACE_TARGET} INTERFACE UNICODE _UNICODE)
message(STATUS "为 Windows 添加 UNICODE 定义")
endif()
# --- 编译器特定设置 ---
if(MSVC)
# MSVC 特定选项
target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE
/W4 # 更高警告等级
# /WX # 将警告视为错误 (可选,但推荐)
/EHsc
/utf-8 # 源码和执行字符集设为 UTF-8
/Zc:__cplusplus # 修正 __cplusplus 宏
/wd4100 # 禁用警告: 未使用的形参
/wd4996 # 禁用警告: 使用了被标记为否决的函数
)
message(STATUS "为 MSVC 添加特定编译选项")
else() # GCC / Clang / AppleClang
# 通用于 GCC 和 Clang 的选项
target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE
-Wall
-Wextra
-Wpedantic # 更加严格的警告
-Werror # 将所有警告视为错误 (可选,但推荐)
-Wno-unused-parameter
)
# C++17 及以上标准的额外警告
if(${ARG_STANDARD} GREATER_EQUAL 17)
target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE
-Wshadow
-Wnon-virtual-dtor
)
endif()
# 【核心修复】区分处理 AppleClang 和标准 Clang/GCC
# AppleClang 不支持 -finput-charset/-fexec-charset并默认源码为 UTF-8
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE
-finput-charset=UTF-8
-fexec-charset=UTF-8
)
message(STATUS "为 GCC/Clang 添加 UTF-8 字符集选项")
else()
message(STATUS "检测到 AppleClang源码假定为 UTF-8跳过字符集选项")
endif()
message(STATUS "为 GCC/Clang 添加特定编译选项")
endif()
# --- MinGW 特定设置 ---
if(MINGW)
# 为 C++17 及以上的 <filesystem> 支持添加链接库
if(${ARG_STANDARD} GREATER_EQUAL 17)
# 使用 target_link_libraries这才是正确的方式
target_link_libraries(${ARG_INTERFACE_TARGET} INTERFACE -lstdc++fs)
message(STATUS "为 MinGW C++${ARG_STANDARD} 添加 libstdc++fs 依赖 (用于 <filesystem>)")
endif()
endif()
# --- 将 INTERFACE 库的名称返回给调用者 ---
set(${ARG_INTERFACE_TARGET} ${ARG_INTERFACE_TARGET} PARENT_SCOPE)
message(STATUS "C++${ARG_STANDARD} 项目配置完成,请链接到 ${ARG_INTERFACE_TARGET} 目标。")
endfunction()

617
cmake/retrieve_files.cmake Normal file
View File

@@ -0,0 +1,617 @@
#[=======================================================================[
:
platform: 平台标识符 (windows|linux|mac|mobile|desktop)
is_match:
#]=======================================================================]
function(is_current_platform platform is_match)
# 设置默认值为TRUE用于未知平台
set(matches FALSE)
if(platform STREQUAL "windows")
if(WIN32 OR CYGWIN)
set(matches TRUE)
endif()
elseif(platform STREQUAL "linux")
if(UNIX AND NOT APPLE)
set(matches TRUE)
endif()
elseif(platform STREQUAL "macos")
if(APPLE AND NOT IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "ios")
if(IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "android")
if(ANDROID)
set(matches TRUE)
endif()
# 添加对unix平台的支持
elseif(platform STREQUAL "unix")
if(UNIX)
set(matches TRUE)
endif()
elseif(platform STREQUAL "mobile")
if(ANDROID OR IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "desktop")
if(WIN32 OR (UNIX AND NOT APPLE) OR (APPLE AND NOT IOS))
set(matches TRUE)
endif()
elseif(platform STREQUAL "web")
if(EMSCRIPTEN)
set(matches TRUE)
endif()
else()
# 未知平台标识,默认匹配
set(matches TRUE)
endif()
set(${is_match} ${matches} PARENT_SCOPE)
endfunction()
#[=======================================================================[
:
path:
extension:
out_files:
out_cmake_files: () .cmake
#]=======================================================================]
function(retrieve_files_custom path extension out_files)
# 1. 参数验证
if(NOT IS_DIRECTORY "${path}")
message(WARNING "错误:目录 '${path}' 不存在")
return()
endif()
# message(STATUS "正在检索目录: ${path}")
# 2. 构建文件匹配模式
set(file_patterns "")
foreach(ext IN LISTS extension)
list(APPEND file_patterns "${path}/*.${ext}")
endforeach()
# 3. 递归查找所有匹配的文件
file(GLOB_RECURSE found_files
RELATIVE ${path}
CONFIGURE_DEPENDS ${file_patterns}
)
# 4. 处理找到的文件
set(filtered_files "")
set(cmake_files_to_include "")
foreach(current_file IN LISTS found_files)
# 4.1 获取文件所在目录
get_filename_component(file_dir "${current_file}" DIRECTORY)
string(REPLACE "/" ";" dir_components "${file_dir}")
# 4.2 检查平台兼容性
set(should_skip_file FALSE)
set(found_platform_dir FALSE)
foreach(dir_name IN LISTS dir_components)
# 检查是否是平台相关目录
if(dir_name MATCHES "^(windows|linux|macos|ios|android|unix|mobile|desktop|web)$")
set(found_platform_dir TRUE)
is_current_platform(${dir_name} platform_matches)
if(NOT platform_matches)
set(should_skip_file TRUE)
break()
endif()
endif()
endforeach()
# 如果文件需要跳过,继续处理下一个文件
if(should_skip_file)
continue()
endif()
# 4.3 添加符合条件的文件
list(APPEND filtered_files "${current_file}")
# 4.3.1 自动 include .cmake 文件
get_filename_component(file_extension "${current_file}" LAST_EXT)
if(file_extension STREQUAL ".cmake")
get_filename_component(cmake_file_abs "${current_file}" ABSOLUTE)
list(APPEND cmake_files_to_include "${cmake_file_abs}")
endif()
# 4.4 设置IDE文件分组
# 计算相对路径作为分组名称
get_filename_component(root_abs_path "${path}" ABSOLUTE)
get_filename_component(file_dir_abs_path "${file_dir}" ABSOLUTE)
file(RELATIVE_PATH group_path "${root_abs_path}" "${file_dir_abs_path}")
# 处理根目录的特殊情况
if(group_path STREQUAL ".")
set(group_name "")
else()
string(REPLACE "/" "\\" group_name "${group_path}")
endif()
# 创建IDE分组
source_group("${group_name}" FILES "${current_file}")
endforeach()
# 5. 设置输出变量
set(${out_files} ${filtered_files} PARENT_SCOPE)
# 将找到的 .cmake 文件传递给父作用域
set(RETRIEVED_CMAKE_FILES ${cmake_files_to_include} PARENT_SCOPE)
endfunction()
#[=======================================================================[
便
#]=======================================================================]
function(retrieve_files path out_files)
# 设置基础文件类型
set(file_extensions
"h" # 头文件
"hpp" # C++头文件
"ini" # 配置文件
"cpp" # C++源文件
"c" # C源文件
"cc"
"ixx" # C++20模块文件
"cmake" # CMake脚本文件
)
# 针对Mac平台添加额外文件类型
if(APPLE)
list(APPEND file_extensions "mm") # Objective-C++源文件
endif()
# 执行文件检索
set(temp_files "")
retrieve_files_custom(${path} "${file_extensions}" temp_files)
# 合并结果到输出变量
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
# 将 .cmake 文件列表传递到父作用域
set(RETRIEVED_CMAKE_FILES ${RETRIEVED_CMAKE_FILES} PARENT_SCOPE)
endfunction()
#[=======================================================================[
ProtogRPC
:
TARGET_NAME: () -
PROTO_PATH: () - .proto
OUTPUT_PATH: () - ${CMAKE_CURRENT_BINARY_DIR}/generated
GRPC_ENABLED: () - gRPCTRUE
PROTO_IMPORT_DIRS:() - proto
EXPORT_MACRO: () - Windows DLL
:
compile_proto_files(
TARGET_NAME my_proto_lib
PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/protos
OUTPUT_PATH ${CMAKE_BINARY_DIR}/proto_gen
GRPC_ENABLED TRUE
PROTO_IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/protos
)
#]=======================================================================]
function(compile_proto_files)
# 定义预期的参数
set(options GRPC_ENABLED)
set(oneValueArgs TARGET_NAME PROTO_PATH OUTPUT_PATH EXPORT_MACRO)
set(multiValueArgs PROTO_IMPORT_DIRS)
# 解析传递给函数的参数
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 参数验证
if(NOT ARG_TARGET_NAME)
message(FATAL_ERROR "**compile_proto_files**: **缺少必需参数** **TARGET_NAME**.")
endif()
if(NOT ARG_PROTO_PATH)
message(FATAL_ERROR "**compile_proto_files**: **缺少必需参数** **PROTO_PATH**.")
endif()
# 设置默认值
if(NOT DEFINED ARG_GRPC_ENABLED)
set(ARG_GRPC_ENABLED TRUE)
endif()
if(NOT ARG_OUTPUT_PATH)
set(ARG_OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/generated")
endif()
# 查找Protobuf和gRPC
find_package(Protobuf CONFIG REQUIRED)
if(ARG_GRPC_ENABLED)
find_package(gRPC QUIET)
if(NOT gRPC_FOUND)
# 如果找不到gRPC包尝试手动查找
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
if(NOT GRPC_CPP_PLUGIN)
message(FATAL_ERROR "**compile_proto_files**: **找不到gRPC C++插件**. 请确保已安装gRPC.")
endif()
else()
set(GRPC_CPP_PLUGIN $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
endif()
endif()
# 创建输出目录
file(MAKE_DIRECTORY ${ARG_OUTPUT_PATH})
get_filename_component(PROTO_PATH "${ARG_PROTO_PATH}" ABSOLUTE)
# 递归查找所有.proto文件
file(GLOB_RECURSE PROTO_FILES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
CONFIGURE_DEPENDS
"${PROTO_PATH}/*.proto"
)
if(NOT PROTO_FILES)
message(WARNING "**compile_proto_files**: 在 '${PROTO_PATH}' 中未找到任何.proto文件")
return()
endif()
message(STATUS "找到 ${CMAKE_CURRENT_SOURCE_DIR} Proto文件: ${PROTO_FILES}")
# 准备生成的文件列表
set(PROTO_SRCS)
set(PROTO_HDRS)
set(GRPC_SRCS)
set(GRPC_HDRS)
# 构建导入路径参数
set(PROTO_IMPORT_ARGS)
list(APPEND PROTO_IMPORT_ARGS "-I${PROTO_PATH}")
foreach(IMPORT_DIR ${ARG_PROTO_IMPORT_DIRS})
list(APPEND PROTO_IMPORT_ARGS "-I${IMPORT_DIR}")
endforeach()
# 添加一个自定义命令用于生成前清空目标文件夹中的pb.cc和pb.h文件
add_custom_command(
OUTPUT ${ARG_OUTPUT_PATH}/.cleaned
COMMAND ${CMAKE_COMMAND} -E rm -f ${ARG_OUTPUT_PATH}/*.pb.cc ${ARG_OUTPUT_PATH}/*.pb.h ${ARG_OUTPUT_PATH}/*.grpc.pb.cc ${ARG_OUTPUT_PATH}/*.grpc.pb.h
COMMAND ${CMAKE_COMMAND} -E touch ${ARG_OUTPUT_PATH}/.cleaned
COMMENT "清理旧的生成文件: ${ARG_OUTPUT_PATH}"
VERBATIM
)
# 为每个proto文件生成代码
foreach(PROTO_FILE ${PROTO_FILES})
# 获取proto文件的绝对路径
get_filename_component(PROTO_FILE_ABS "${PROTO_FILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_filename_component(PROTO_NAME_WE "${PROTO_FILE}" NAME_WE)
get_filename_component(PROTO_DIR "${PROTO_FILE}" DIRECTORY)
# 计算相对路径以保持目录结构
if(PROTO_DIR)
file(RELATIVE_PATH REL_DIR "${PROTO_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/${PROTO_DIR}")
set(OUTPUT_SUBDIR "${ARG_OUTPUT_PATH}/${REL_DIR}")
else()
set(OUTPUT_SUBDIR "${ARG_OUTPUT_PATH}")
endif()
# 创建输出子目录
file(MAKE_DIRECTORY ${OUTPUT_SUBDIR})
# 生成的文件路径
set(PROTO_SRC "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.pb.cc")
set(PROTO_HDR "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.pb.h")
list(APPEND PROTO_SRCS ${PROTO_SRC})
list(APPEND PROTO_HDRS ${PROTO_HDR})
# 基础protobuf生成命令
set(PROTOC_ARGS
${PROTO_IMPORT_ARGS}
"--cpp_out=${ARG_OUTPUT_PATH}"
"${PROTO_FILE_ABS}"
)
if(ARG_GRPC_ENABLED)
set(GRPC_SRC "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.grpc.pb.cc")
set(GRPC_HDR "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.grpc.pb.h")
list(APPEND GRPC_SRCS ${GRPC_SRC})
list(APPEND GRPC_HDRS ${GRPC_HDR})
# 添加自定义命令生成protobuf和gRPC代码
add_custom_command(
OUTPUT ${PROTO_SRC} ${PROTO_HDR} ${GRPC_SRC} ${GRPC_HDR}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
${PROTOC_ARGS}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
${PROTO_IMPORT_ARGS}
"--grpc_out=${ARG_OUTPUT_PATH}"
"--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}"
"${PROTO_FILE_ABS}"
DEPENDS ${PROTO_FILE_ABS}
COMMENT "生成Protobuf和gRPC代码: ${PROTO_FILE}"
VERBATIM
)
else()
# 只生成protobuf代码
add_custom_command(
OUTPUT ${PROTO_SRC} ${PROTO_HDR}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
${PROTOC_ARGS}
DEPENDS ${PROTO_FILE_ABS}
COMMENT "生成Protobuf代码: ${PROTO_FILE}"
VERBATIM
)
endif()
endforeach()
# 创建库目标
add_library(${ARG_TARGET_NAME} STATIC
${PROTO_SRCS}
${PROTO_HDRS}
${GRPC_SRCS}
${GRPC_HDRS}
)
# 设置包含目录
target_include_directories(${ARG_TARGET_NAME}
PUBLIC
$<BUILD_INTERFACE:${ARG_OUTPUT_PATH}>
$<INSTALL_INTERFACE:include>
)
# 链接必要的库
target_link_libraries(${ARG_TARGET_NAME}
PUBLIC
protobuf::libprotobuf
)
if(ARG_GRPC_ENABLED)
if(gRPC_FOUND)
target_link_libraries(${ARG_TARGET_NAME}
PUBLIC
gRPC::grpc++
gRPC::grpc++_reflection
)
else()
# 手动查找并链接gRPC库
find_library(GRPC_LIBRARY grpc++)
find_library(GRPC_REFLECTION_LIBRARY grpc++_reflection)
if(GRPC_LIBRARY AND GRPC_REFLECTION_LIBRARY)
target_link_libraries(${ARG_TARGET_NAME}
PUBLIC
${GRPC_LIBRARY}
${GRPC_REFLECTION_LIBRARY}
)
else()
message(WARNING "**compile_proto_files**: 无法找到gRPC库请手动链接")
endif()
endif()
endif()
# 设置导出宏(如果提供)
if(ARG_EXPORT_MACRO)
target_compile_definitions(${ARG_TARGET_NAME}
PRIVATE ${ARG_EXPORT_MACRO}_EXPORTS
INTERFACE ${ARG_EXPORT_MACRO}_IMPORTS
)
endif()
# 设置C++标准
target_compile_features(${ARG_TARGET_NAME} PUBLIC cxx_std_11)
# IDE文件分组
source_group("Proto Files" FILES ${PROTO_FILES})
source_group("Generated Files\\Protobuf" FILES ${PROTO_SRCS} ${PROTO_HDRS})
if(ARG_GRPC_ENABLED)
source_group("Generated Files\\gRPC" FILES ${GRPC_SRCS} ${GRPC_HDRS})
endif()
# 输出信息
message(STATUS "创建Proto库目标: ${ARG_TARGET_NAME}")
message(STATUS " Proto文件数量: ${CMAKE_CURRENT_SOURCE_DIR} list length: ${PROTO_FILES}")
message(STATUS " 输出目录: ${ARG_OUTPUT_PATH}")
message(STATUS " gRPC支持: ${ARG_GRPC_ENABLED}")
endfunction()
#[=======================================================================[
# 用于添加资源文件并在编译后复制到最终可执行文件所在目录
# 注意:此函数依赖于 CMAKE_RUNTIME_OUTPUT_DIRECTORY 或 EXECUTABLE_OUTPUT_PATH
# 变量的设置,以确定可执行文件的输出目录。请确保在项目中设置了其中之一。
#
# 参数:
# TARGET_NAME: (必需) - 关联的目标 (库或可执行文件) 的名称。
# 资源复制命令将在 TARGET_NAME 构建后执行。
# RESOURCE_FILES: (必需) - 一个或多个要复制的资源文件的路径列表 (相对或绝对)
# OUTPUT_SUBDIR: (可选) - 相对于可执行文件输出目录的子目录路径 (例如 "assets")
#
# 例子:
# # 确保设置了可执行文件输出目录 (通常在顶层 CMakeLists.txt)
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#
# # 添加库
# add_library(my_lib STATIC src/my_lib.cpp)
#
# # 添加资源到 my_lib但复制到最终可执行文件的输出目录下的 'config' 子目录
# add_resource_file(
# TARGET_NAME my_lib
# RESOURCE_FILES config/settings.json config/defaults.ini
# OUTPUT_SUBDIR config
# )
#
# # 添加可执行文件
# add_executable(my_app main.cpp)
# target_link_libraries(my_app PRIVATE my_lib)
#
# # 添加 my_app 的资源,复制到可执行文件输出目录的根目录
# add_resource_file(
# TARGET_NAME my_app
# RESOURCE_FILES assets/icon.png
# )
#]=======================================================================]
function(add_resource_file)
# 定义预期的参数
set(options "") # 无布尔选项
set(oneValueArgs TARGET_NAME OUTPUT_SUBDIR)
set(multiValueArgs RESOURCE_FILES)
# 解析传递给函数的参数
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# --- 参数验证 ---
if(NOT ARG_TARGET_NAME)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **TARGET_NAME**.")
endif()
if(NOT ARG_RESOURCE_FILES)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **RESOURCE_FILES**.")
endif()
if(NOT TARGET ${ARG_TARGET_NAME})
message(WARNING "**add_resource_file**: 目标 '${ARG_TARGET_NAME}' (尚)不存在。请确保在调用 add_executable/add_library('${ARG_TARGET_NAME}') 之后调用此函数。")
# 即使目标尚不存在仍然尝试配置命令。CMake通常能处理好依赖关系。
endif()
# --- 确定最终可执行文件的目标基础目录 ---
set(DESTINATION_BASE "")
if(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(DESTINATION_BASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
elseif(DEFINED EXECUTABLE_OUTPUT_PATH AND EXECUTABLE_OUTPUT_PATH)
# EXECUTABLE_OUTPUT_PATH 是旧变量,但也检查一下
set(DESTINATION_BASE "${EXECUTABLE_OUTPUT_PATH}")
else()
# 如果是多配置生成器(如 Visual Studio, Xcode需要考虑配置类型
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
# 对于多配置,没有单一的顶级运行时目录变量。
# 可以考虑使用 $<OUTPUT_DIRECTORY> 配合一个已知的可执行文件名,但这会使函数复杂化。
# 最好的做法是要求用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
# 或者我们直接报错,强制用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY
message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。对于多配置生成器,可能需要设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> 变量。")
else()
# 对于单配置生成器(如 Makefiles, Ninja可以默认到 CMAKE_BINARY_DIR
set(DESTINATION_BASE "${CMAKE_BINARY_DIR}")
message(WARNING "**add_resource_file**: **未设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY**。默认将资源复制到 CMAKE_BINARY_DIR ('${CMAKE_BINARY_DIR}')。强烈建议设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY 以获得可预测的行为。")
endif()
# message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。")
endif()
# 处理子目录
set(DESTINATION_DIR "${DESTINATION_BASE}") # 默认目标目录
if(ARG_OUTPUT_SUBDIR)
# 清理子目录路径字符串
string(STRIP "${ARG_OUTPUT_SUBDIR}" _subdir)
if(IS_ABSOLUTE "${_subdir}")
message(FATAL_ERROR "**add_resource_file**: **OUTPUT_SUBDIR** ('${ARG_OUTPUT_SUBDIR}') **必须是相对路径**。")
else()
# 移除可能存在的前导/后导斜杠,以便干净地拼接路径
string(REGEX REPLACE "^[/\\\\]+" "" _subdir "${_subdir}")
string(REGEX REPLACE "[/\\\\]+$" "" _subdir "${_subdir}")
if(_subdir) # 仅当子目录清理后非空时才追加
set(DESTINATION_DIR "${DESTINATION_BASE}/${_subdir}")
endif()
endif()
endif()
# --- 准备源文件路径 ---
set(ABS_RESOURCE_FILES "")
foreach(RESOURCE_FILE ${ARG_RESOURCE_FILES})
get_filename_component(RESOURCE_FILE_REALPATH "${RESOURCE_FILE}" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
# if(IS_ABSOLUTE "${RESOURCE_FILE}")
# # 如果已经是绝对路径,直接使用
# list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE}")
# else()
# # 如果是相对路径,相对于当前源目录进行解析
# list(APPEND ABS_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE}")
# endif()
list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE_REALPATH}")
# 检查文件是否存在 (在配置时发出警告,有助于早期发现错误)
# list(GET ABS_RESOURCE_FILES -1 _current_abs_file) # 获取刚才添加的绝对路径
if(NOT EXISTS "${RESOURCE_FILE_REALPATH}")
message(WARNING "**add_resource_file**: **资源文件** '${RESOURCE_FILE}' (解析为 '${RESOURCE_FILE_REALPATH}') **在配置时不存在**。")
endif()
endforeach()
# --- 添加自定义命令 ---
# 使用 add_custom_command 在目标构建完成后执行复制操作
if(ABS_RESOURCE_FILES) # 确保有文件需要复制
# 注意DESTINATION_DIR 可能包含特定于配置的路径(例如,如果 CMAKE_RUNTIME_OUTPUT_DIRECTORY
# 设置为 ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin
# add_custom_command 的 COMMAND 参数在构建时执行,此时这些变量/生成器表达式已解析。
add_custom_command(
TARGET ${ARG_TARGET_NAME}
POST_BUILD # 指定在目标构建之后执行
# 步骤 1: 确保目标目录存在 (copy_if_different 不会创建目录)
COMMAND ${CMAKE_COMMAND} -E make_directory "${DESTINATION_DIR}"
# 步骤 2: 复制文件
COMMAND ${CMAKE_COMMAND} -E copy_if_different # 使用CMake内置命令复制仅当文件不同时
${ABS_RESOURCE_FILES} # 要复制的源文件列表(绝对路径)
"${DESTINATION_DIR}" # 最终可执行文件所在的目标目录 (带引号以处理空格)
COMMENT "为 ${ARG_TARGET_NAME} 将资源复制到可执行文件目录: ${DESTINATION_DIR}..." # 构建时显示的注释
VERBATIM # 确保参数(尤其是路径和生成器表达式)被正确处理
)
else()
message(WARNING "**add_resource_file**: 没有有效的资源文件提供给目标 '${ARG_TARGET_NAME}'。")
endif()
# --- 可选: 将资源文件添加到 IDE 项目结构中 ---
if(ABS_RESOURCE_FILES)
set(_source_group_name "Resource Files") # 基础组名
if(ARG_OUTPUT_SUBDIR)
# 使用与目标目录结构匹配的组名
string(STRIP "${ARG_OUTPUT_SUBDIR}" _clean_subdir)
string(REPLACE "\\" "/" _clean_subdir "${_clean_subdir}") # 统一使用正斜杠
string(REGEX REPLACE "^[/]+" "" _clean_subdir "${_clean_subdir}")
string(REGEX REPLACE "[/]+$" "" _clean_subdir "${_clean_subdir}")
if(_clean_subdir)
set(_source_group_name "Resource Files/${_clean_subdir}")
endif()
endif()
# 使用 source_group 将文件添加到 IDE 的指定组下
source_group(${_source_group_name} FILES ${ABS_RESOURCE_FILES})
endif()
endfunction()
function(simple_executable)
set(source_files "")
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} source_files)
add_executable(${PROJECT_NAME} ${source_files})
# 自动 include 检索到的 .cmake 文件
if(RETRIEVED_CMAKE_FILES)
foreach(cmake_file IN LISTS RETRIEVED_CMAKE_FILES)
message(STATUS "自动 include CMake 文件: ${cmake_file}")
include("${cmake_file}")
endforeach()
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
message(STATUS "创建可执行文件目标: ${PROJECT_NAME},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
add_os_definitions(${PROJECT_NAME})
endfunction()
function(simple_library library_type)
set(source_files "")
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} source_files)
add_library(${PROJECT_NAME} ${library_type} ${source_files})
# 自动 include 检索到的 .cmake 文件
if(RETRIEVED_CMAKE_FILES)
foreach(cmake_file IN LISTS RETRIEVED_CMAKE_FILES)
message(STATUS "自动 include CMake 文件: ${cmake_file}")
include("${cmake_file}")
endforeach()
endif()
if(library_type STREQUAL "INTERFACE")
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(${PROJECT_NAME} INTERFACE mirai_project_options)
else()
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
endif()
message(STATUS "创建库目标: ${PROJECT_NAME},类型: ${library_type},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
add_os_definitions(${PROJECT_NAME})
endfunction()

515
cmake/shader_compile.cmake Normal file
View File

@@ -0,0 +1,515 @@
# ============================================================================
# MIRAI 着色器编译 CMake 模块
# ============================================================================
# 提供自动化 GLSL 着色器编译和代码生成功能
# 使用 glslangValidator 或 glslc (shaderc) 编译 GLSL 到 SPIR-V
#
# 主要函数:
# add_glsl_shader_library() - 创建 GLSL 着色器库目标
# compile_glsl_shader() - 编译单个 GLSL 着色器
#
# 辅助函数:
# find_python_with_jinja2() - 查找带 Jinja2 的 Python
# find_glsl_compiler() - 查找 GLSL 编译器
# ============================================================================
include_guard(GLOBAL)
# ============================================================================
# find_python_with_jinja2 - 查找带 Jinja2 模块的 Python
# ============================================================================
function(find_python_with_jinja2 OUT_PYTHON_EXECUTABLE)
# 首先查找 Python
find_package(Python3 COMPONENTS Interpreter QUIET)
if(NOT Python3_FOUND)
message(FATAL_ERROR "Python3 not found. Please install Python 3.")
endif()
# 检查 Jinja2 是否已安装
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import jinja2; print(jinja2.__version__)"
RESULT_VARIABLE JINJA2_CHECK_RESULT
OUTPUT_VARIABLE JINJA2_VERSION
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT JINJA2_CHECK_RESULT EQUAL 0)
message(FATAL_ERROR
"Python Jinja2 module not found.\n"
"Please install it with: ${Python3_EXECUTABLE} -m pip install jinja2"
)
endif()
message(STATUS "Found Python with Jinja2: ${Python3_EXECUTABLE} (Jinja2 ${JINJA2_VERSION})")
set(${OUT_PYTHON_EXECUTABLE} "${Python3_EXECUTABLE}" PARENT_SCOPE)
endfunction()
# ============================================================================
# find_glsl_compiler - 查找 GLSL 编译器 (glslangValidator 或 glslc)
# ============================================================================
function(find_glsl_compiler OUT_COMPILER OUT_COMPILER_TYPE)
# 首先尝试查找 glslc (shaderc)
find_program(GLSLC_EXECUTABLE glslc
HINTS
$ENV{VULKAN_SDK}/Bin
$ENV{VULKAN_SDK}/bin
${Vulkan_GLSLC_EXECUTABLE}
)
if(GLSLC_EXECUTABLE)
message(STATUS "Found GLSL compiler: ${GLSLC_EXECUTABLE} (glslc)")
set(${OUT_COMPILER} "${GLSLC_EXECUTABLE}" PARENT_SCOPE)
set(${OUT_COMPILER_TYPE} "glslc" PARENT_SCOPE)
return()
endif()
# 尝试查找 glslangValidator
find_program(GLSLANG_VALIDATOR_EXECUTABLE glslangValidator
HINTS
$ENV{VULKAN_SDK}/Bin
$ENV{VULKAN_SDK}/bin
${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}
)
if(GLSLANG_VALIDATOR_EXECUTABLE)
message(STATUS "Found GLSL compiler: ${GLSLANG_VALIDATOR_EXECUTABLE} (glslangValidator)")
set(${OUT_COMPILER} "${GLSLANG_VALIDATOR_EXECUTABLE}" PARENT_SCOPE)
set(${OUT_COMPILER_TYPE} "glslangValidator" PARENT_SCOPE)
return()
endif()
message(FATAL_ERROR
"No GLSL compiler found. Please install Vulkan SDK or shaderc.\n"
"Expected: glslc or glslangValidator in PATH or VULKAN_SDK"
)
endfunction()
# ============================================================================
# 内部变量设置
# ============================================================================
# 着色器编译器目标名称
set(MIRAI_SHADER_COMPILER_TARGET "mirai_shader_compile")
# 代码生成脚本路径
set(MIRAI_SHADER_GENERATOR_SCRIPT "${CMAKE_SOURCE_DIR}/tools/generate_shader_bindings.py")
# 模板目录
set(MIRAI_SHADER_TEMPLATE_DIR "${CMAKE_SOURCE_DIR}/tools/templates")
# ============================================================================
# _mirai_ensure_shader_compiler - 内部函数,确保着色器编译器可用
# ============================================================================
function(_mirai_ensure_shader_compiler)
# 检查编译器目标是否存在
if(NOT TARGET ${MIRAI_SHADER_COMPILER_TARGET})
message(FATAL_ERROR
"Shader compiler target '${MIRAI_SHADER_COMPILER_TARGET}' not found.\n"
"Make sure add_subdirectory(tools/shader_compile) is called before using add_glsl_shader_library()."
)
endif()
endfunction()
# ============================================================================
# _get_shader_stage_from_extension - 从文件扩展名获取着色器阶段
# ============================================================================
function(_get_shader_stage_from_extension FILE_PATH OUT_STAGE)
get_filename_component(EXT "${FILE_PATH}" EXT)
string(TOLOWER "${EXT}" EXT_LOWER)
if(EXT_LOWER STREQUAL ".vert")
set(${OUT_STAGE} "vert" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".frag")
set(${OUT_STAGE} "frag" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".comp")
set(${OUT_STAGE} "comp" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".geom")
set(${OUT_STAGE} "geom" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".tesc")
set(${OUT_STAGE} "tesc" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".tese")
set(${OUT_STAGE} "tese" PARENT_SCOPE)
else()
set(${OUT_STAGE} "" PARENT_SCOPE)
endif()
endfunction()
# ============================================================================
# compile_glsl_shader - 编译单个 GLSL 着色器
# ============================================================================
#
# 用法:
# compile_glsl_shader(
# INPUT shader.vert
# OUTPUT shader.vert.spv
# STAGE vert
# DEFINES DEBUG FEATURE_X
# INCLUDE_DIRS include/
# )
#
# 参数:
# INPUT - 输入 GLSL 文件路径(必需)
# OUTPUT - 输出 SPIR-V 文件路径(必需)
# STAGE - 着色器阶段 (vert/frag/comp/geom/tesc/tese)(可选,自动检测)
# DEFINES - 预处理器定义(可选)
# INCLUDE_DIRS - include 目录(可选)
#
function(compile_glsl_shader)
cmake_parse_arguments(SHADER "" "INPUT;OUTPUT;STAGE" "DEFINES;INCLUDE_DIRS" ${ARGN})
# 验证必需参数
if(NOT SHADER_INPUT)
message(FATAL_ERROR "compile_glsl_shader: INPUT is required")
endif()
if(NOT SHADER_OUTPUT)
message(FATAL_ERROR "compile_glsl_shader: OUTPUT is required")
endif()
# 自动检测着色器阶段
if(NOT SHADER_STAGE)
_get_shader_stage_from_extension("${SHADER_INPUT}" SHADER_STAGE)
if(NOT SHADER_STAGE)
message(FATAL_ERROR "compile_glsl_shader: Cannot determine shader stage from extension. Please specify STAGE.")
endif()
endif()
# 查找 GLSL 编译器
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
# 构建编译命令参数
set(COMPILE_ARGS "")
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
# glslc 参数
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
# 添加 include 目录
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
# 添加宏定义
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
else()
# glslangValidator 参数
list(APPEND COMPILE_ARGS "-V") # 生成 SPIR-V
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
# 添加 include 目录
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
# 添加宏定义
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
endif()
# 创建编译命令
add_custom_command(
OUTPUT "${SHADER_OUTPUT}"
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
DEPENDS "${SHADER_INPUT}"
COMMENT "Compiling GLSL shader: ${SHADER_INPUT}"
VERBATIM
)
endfunction()
# ============================================================================
# add_glsl_shader_library - 添加 GLSL 着色器库到现有目标
# ============================================================================
#
# 将 GLSL 着色器编译和代码生成附加到现有的编译目标上
#
# 用法:
# # 方案1: 自动搜索着色器
# add_glsl_shader_library(TARGET mirai_shader
# SHADER_PATH shaders/glsl
# INCLUDE_DIRS shaders/glsl/common
# )
#
# # 方案2: 指定具体着色器文件
# add_glsl_shader_library(TARGET mirai_shader
# SHADERS shaders/ui/rect.vert shaders/ui/rect.frag
# INCLUDE_DIRS shaders/common
# )
#
# 参数:
# TARGET - 现有的编译目标(必需)
# SHADERS - 具体的着色器文件列表(可选)
# SHADER_PATH - 着色器搜索目录,相对于 CMAKE_CURRENT_SOURCE_DIR可选
# INCLUDE_DIRS - 着色器 include 路径(可选)
# DEFINES - 预处理器定义(可选)
# RECURSIVE - 是否递归搜索子目录(可选,默认 OFF
#
function(add_glsl_shader_library)
# 解析参数
set(options RECURSIVE)
set(oneValueArgs TARGET SHADER_PATH)
set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES)
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 验证必需参数
if(NOT SHADER_TARGET)
message(FATAL_ERROR "add_glsl_shader_library: TARGET is required")
endif()
# 验证目标存在
if(NOT TARGET ${SHADER_TARGET})
message(FATAL_ERROR "add_glsl_shader_library: Target '${SHADER_TARGET}' does not exist")
endif()
# 收集着色器文件
set(ALL_SHADERS "${SHADER_SHADERS}")
if(SHADER_SHADER_PATH)
set(SHADER_EXTENSIONS "vert;frag;comp;geom;tesc;tese")
if(SHADER_RECURSIVE)
foreach(EXT ${SHADER_EXTENSIONS})
file(GLOB_RECURSE FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
endforeach()
else()
foreach(EXT ${SHADER_EXTENSIONS})
file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
endforeach()
endif()
endif()
# 去重
if(ALL_SHADERS)
list(REMOVE_DUPLICATES ALL_SHADERS)
endif()
# 设置输出目录
set(SHADER_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(SHADER_INTERMEDIATE_DIR "${CMAKE_CURRENT_BINARY_DIR}/shader_intermediate")
file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}")
file(MAKE_DIRECTORY "${SHADER_INTERMEDIATE_DIR}")
# 确保编译器可用
_mirai_ensure_shader_compiler()
# 查找 Python with Jinja2
find_python_with_jinja2(PYTHON_EXECUTABLE)
# 查找 GLSL 编译器
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
# 构建 include 路径参数
set(INCLUDE_ARGS "")
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
if(IS_ABSOLUTE "${INC_DIR}")
list(APPEND INCLUDE_ARGS "${INC_DIR}")
else()
list(APPEND INCLUDE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}")
endif()
endforeach()
# 收集生成的文件
set(ALL_GENERATED_HEADERS "")
set(ALL_SPV_FILES "")
# 按着色器名称分组(去掉扩展名后相同的文件归为一组)
set(SHADER_GROUPS "")
foreach(SHADER_SOURCE ${ALL_SHADERS})
get_filename_component(SHADER_NAME_WE "${SHADER_SOURCE}" NAME_WE)
get_filename_component(SHADER_DIR "${SHADER_SOURCE}" DIRECTORY)
# 创建唯一的组标识符
string(REPLACE "/" "_" DIR_SAFE "${SHADER_DIR}")
set(GROUP_ID "${DIR_SAFE}_${SHADER_NAME_WE}")
# 添加到组列表
list(APPEND SHADER_GROUPS "${GROUP_ID}")
list(APPEND ${GROUP_ID}_SOURCES "${SHADER_SOURCE}")
set(${GROUP_ID}_NAME "${SHADER_NAME_WE}")
set(${GROUP_ID}_DIR "${SHADER_DIR}")
endforeach()
# 去重组列表
if(SHADER_GROUPS)
list(REMOVE_DUPLICATES SHADER_GROUPS)
endif()
# 处理每个着色器组
foreach(GROUP_ID ${SHADER_GROUPS})
set(GROUP_SOURCES "${${GROUP_ID}_SOURCES}")
set(GROUP_NAME "${${GROUP_ID}_NAME}")
set(GROUP_DIR "${${GROUP_ID}_DIR}")
# 每个着色器组的输出目录
set(SPIRV_OUTPUT_DIR "${SHADER_INTERMEDIATE_DIR}/${GROUP_NAME}")
set(GENERATED_HPP "${SHADER_OUTPUT_DIR}/${GROUP_NAME}_bindings.hpp")
file(MAKE_DIRECTORY "${SPIRV_OUTPUT_DIR}")
set(GROUP_SPV_FILES "")
# 编译组内的每个着色器
foreach(SHADER_SOURCE ${GROUP_SOURCES})
get_filename_component(SHADER_FILENAME "${SHADER_SOURCE}" NAME)
_get_shader_stage_from_extension("${SHADER_SOURCE}" SHADER_STAGE)
if(NOT SHADER_STAGE)
message(WARNING "Skipping unknown shader type: ${SHADER_SOURCE}")
continue()
endif()
set(SPV_OUTPUT "${SPIRV_OUTPUT_DIR}/${SHADER_FILENAME}.spv")
# 构建编译命令参数
set(COMPILE_ARGS "")
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
foreach(INC_DIR ${INCLUDE_ARGS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
else()
list(APPEND COMPILE_ARGS "-V")
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
foreach(INC_DIR ${INCLUDE_ARGS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
endif()
# 创建编译命令
add_custom_command(
OUTPUT "${SPV_OUTPUT}"
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
DEPENDS "${SHADER_SOURCE}"
COMMENT "Compiling GLSL: ${SHADER_FILENAME}"
VERBATIM
)
list(APPEND GROUP_SPV_FILES "${SPV_OUTPUT}")
list(APPEND ALL_SPV_FILES "${SPV_OUTPUT}")
endforeach()
# 使用 mirai_shader_compile 工具生成反射和绑定
if(GROUP_SPV_FILES)
# 创建标记文件表示编译完成
set(COMPILED_MARKER "${SPIRV_OUTPUT_DIR}/.compiled")
add_custom_command(
OUTPUT "${COMPILED_MARKER}"
COMMAND ${CMAKE_COMMAND} -E touch "${COMPILED_MARKER}"
DEPENDS ${GROUP_SPV_FILES}
COMMENT "GLSL shaders compiled: ${GROUP_NAME}"
VERBATIM
)
# 生成绑定头文件
add_custom_command(
OUTPUT "${GENERATED_HPP}"
COMMAND $<TARGET_FILE:${MIRAI_SHADER_COMPILER_TARGET}>
--dir "${SPIRV_OUTPUT_DIR}"
--output "${GENERATED_HPP}"
--name "${GROUP_NAME}"
DEPENDS "${COMPILED_MARKER}" ${MIRAI_SHADER_COMPILER_TARGET}
COMMENT "Generating bindings: ${GROUP_NAME}"
VERBATIM
)
list(APPEND ALL_GENERATED_HEADERS "${GENERATED_HPP}")
endif()
endforeach()
# 附加到目标:添加生成目录到 include 路径
target_include_directories(${SHADER_TARGET} PUBLIC
$<BUILD_INTERFACE:${SHADER_OUTPUT_DIR}>
)
# 添加编译依赖
if(ALL_GENERATED_HEADERS)
add_custom_target(${SHADER_TARGET}_shaders DEPENDS ${ALL_GENERATED_HEADERS})
add_dependencies(${SHADER_TARGET} ${SHADER_TARGET}_shaders)
endif()
# 导出变量
set(${SHADER_TARGET}_SHADER_HEADERS ${ALL_GENERATED_HEADERS} PARENT_SCOPE)
set(${SHADER_TARGET}_SHADER_OUTPUT_DIR ${SHADER_OUTPUT_DIR} PARENT_SCOPE)
set(${SHADER_TARGET}_SPV_FILES ${ALL_SPV_FILES} PARENT_SCOPE)
message(STATUS "Added GLSL shaders to target: ${SHADER_TARGET}")
message(STATUS " Output: ${SHADER_OUTPUT_DIR}")
if(ALL_SHADERS)
list(LENGTH ALL_SHADERS SHADER_COUNT)
message(STATUS " Shaders: ${SHADER_COUNT} files")
endif()
endfunction()
# ============================================================================
# add_shader_library - 兼容性别名(调用 add_glsl_shader_library
# ============================================================================
#
# 为了向后兼容,保留 add_shader_library 函数名
# 现在默认使用 GLSL 编译
#
function(add_shader_library)
# 解析参数
set(options RECURSIVE)
set(oneValueArgs TARGET SHADER_PATH)
set(multiValueArgs SHADERS REF_PATHS DEFINES)
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 转换参数并调用新函数
set(FORWARD_ARGS "TARGET" "${SHADER_TARGET}")
if(SHADER_SHADER_PATH)
list(APPEND FORWARD_ARGS "SHADER_PATH" "${SHADER_SHADER_PATH}")
endif()
if(SHADER_SHADERS)
list(APPEND FORWARD_ARGS "SHADERS" ${SHADER_SHADERS})
endif()
if(SHADER_REF_PATHS)
list(APPEND FORWARD_ARGS "INCLUDE_DIRS" ${SHADER_REF_PATHS})
endif()
if(SHADER_DEFINES)
list(APPEND FORWARD_ARGS "DEFINES" ${SHADER_DEFINES})
endif()
if(SHADER_RECURSIVE)
list(APPEND FORWARD_ARGS "RECURSIVE")
endif()
add_glsl_shader_library(${FORWARD_ARGS})
endfunction()

5
example/CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
project(mirai_example)
simple_executable()
target_link_libraries(${PROJECT_NAME} PUBLIC mirai)

3
example/main.cpp Normal file
View File

@@ -0,0 +1,3 @@
int main(int argc, char* argv[]) {
return 0;
}

25
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
project(mirai)
find_package(Vulkan REQUIRED)
find_package(SDL3 CONFIG REQUIRED)
find_package(harfbuzz CONFIG REQUIRED)
find_package(Freetype CONFIG REQUIRED)
find_package(yoga CONFIG REQUIRED)
find_package(Eigen3 CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(Stb REQUIRED)
simple_library(STATIC)
target_link_libraries(${PROJECT_NAME} PUBLIC
Vulkan::Vulkan
SDL3::SDL3
Freetype::Freetype
harfbuzz::harfbuzz
yoga::yogacore
Eigen3::Eigen
spdlog::spdlog
fmt::fmt
)
target_link_directories(${PROJECT_NAME} PUBLIC ${Stb_INCLUDE_DIR})

10
src/core/object.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "object.h"
namespace mirai {
std::atomic<object_id> object::next_id_{1};
object::~object() = default;
object::object() : id_(next_id_.fetch_add(1, std::memory_order_relaxed)) {
}
}

228
src/core/object.h Normal file
View File

@@ -0,0 +1,228 @@
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include "types.h"
namespace mirai {
using object_id = u64;
constexpr object_id invalid_object_id = 0;
struct type_info {
std::string_view name;
size_type hash;
const type_info* parent;
[[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept {
return hash == other.hash;
}
[[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept {
return !(*this == other);
}
[[nodiscard]] constexpr bool is_derived_from(const type_info& base) const noexcept {
auto current = this;
while (current) {
if (*current == base) {
return true;
}
current = current->parent;
}
return false;
}
};
consteval auto compile_time_hash(std::string_view str) noexcept {
size_type hash = 14695981039346656037ULL;
for (char c : str) {
hash ^= static_cast<size_type>(c);
hash *= 1099511628211ULL;
}
return hash;
}
template <typename T>
consteval auto get_type_name() noexcept {
#if defined(__clang__)
constexpr std::string_view prefix = "[T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
constexpr std::string_view prefix = "with T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
constexpr std::string_view prefix = "get_type_name<";
constexpr std::string_view suffix = ">(void)";
constexpr std::string_view function = __FUNCSIG__;
#else
return typeid(T).name();
#endif
const auto start = function.find(prefix) + prefix.size();
const auto end = function.rfind(suffix);
return function.substr(start, end - start);
}
template<typename T>
[[nodiscard]] constexpr const auto& get_type_info() noexcept {
static type_info info{
get_type_name<T>(),
compile_time_hash(get_type_name<T>()),
nullptr
};
return info;
}
template<typename T, typename Parent>
[[nodiscard]] constexpr const auto& get_type_info_with_parent() noexcept {
if constexpr (std::is_same_v<Parent, void>) {
static type_info info{
get_type_name<T>(),
compile_time_hash(get_type_name<T>()),
nullptr
};
return info;
} else {
static type_info info{
get_type_name<T>(),
compile_time_hash(get_type_name<T>()),
&get_type_info_with_parent<Parent, typename Parent::parent_t>()
};
return info;
}
}
class object : public std::enable_shared_from_this<object> {
public:
using ptr = std::shared_ptr<object>;
using weak_ptr = std::weak_ptr<object>;
using parent_t = void;
virtual ~object();
object(const object&) = delete;
object& operator=(const object&) = delete;
object(object&&) = delete;
object& operator=(object&&) = delete;
[[nodiscard]] constexpr virtual auto get_type() const noexcept -> const type_info& {
return get_type_info<object>();
}
[[nodiscard]] constexpr virtual auto get_parent_type() const noexcept -> const type_info* {
return nullptr;
}
[[nodiscard]] constexpr auto type_name() const noexcept {
return get_type().name;
}
template<typename T> requires std::derived_from<T, object>
[[nodiscard]] constexpr auto is() const noexcept {
return get_type().is_derived_from(get_type_info<T>());
}
template<typename T> requires std::derived_from<T, object>
[[nodiscard]] auto as() noexcept -> T* {
if (is<T>()) {
return static_cast<T*>(this);
}
return nullptr;
}
template<typename T> requires std::derived_from<T, object>
[[nodiscard]] auto as() const noexcept -> const T* {
if (is<T>()) {
return static_cast<const T*>(this);
}
return nullptr;
}
public:
#if MIRAI_DEBUG
void set_debug_name(std::string name) {
debug_name_ = std::move(name);
}
[[nodiscard]] const std::string& get_debug_name() const noexcept {
return debug_name_;
}
private:
std::string debug_name_;
#endif
public:
[[nodiscard]] auto id() const noexcept {
return id_;
}
template<typename T = object> requires std::derived_from<T, object>
[[nodiscard]] auto shared_from_this_as() noexcept -> std::shared_ptr<T> {
return std::dynamic_pointer_cast<T>(shared_from_this());
}
template<typename T = object> requires std::derived_from<T, object>
[[nodiscard]] auto shared_from_this_as() const noexcept -> std::shared_ptr<const T> {
return std::dynamic_pointer_cast<const T>(shared_from_this());
}
template<typename T = object> requires std::derived_from<T, object>
[[nodiscard]] auto weak_from_this_as() noexcept -> std::weak_ptr<T> {
return std::weak_ptr<T>(shared_from_this_as<T>());
}
template<typename T = object> requires std::derived_from<T, object>
[[nodiscard]] auto weak_from_this_as() const noexcept -> std::weak_ptr<const T> {
return std::weak_ptr<const T>(shared_from_this_as<T>());
}
protected:
object();
virtual void on_created() {}
virtual void on_destroying() {}
private:
object_id id_;
static std::atomic<object_id> next_id_;
friend class object_registry;
};
template<typename T>
struct object_deleter {
void operator()(T* ptr) {
if (ptr) {
ptr->on_destroying();
delete ptr;
}
}
};
#define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \
public: \
using parent_t = parent_class; \
[[nodiscard]] constexpr virtual const mirai::type_info& get_type() const noexcept override { \
return mirai::get_type_info_with_parent<class_name, parent_t>(); \
} \
[[nodiscard]] constexpr virtual const mirai::type_info* get_parent_type() const noexcept override { \
return &mirai::get_type_info<parent_t>(); \
} \
friend struct mirai::object_factory; \
template<typename T_> \
friend struct mirai::object_deleter; \
friend class object_registry;
struct object_factory {
template<typename T, typename ...Args> requires std::derived_from<T, object>
static auto create(Args&&... args) {
auto ptr = std::shared_ptr<T>(new T(std::forward<Args>(args)...), object_deleter<T>{});
ptr->on_created();
return ptr;
}
};
template<typename T, typename ...Args> requires std::derived_from<T, object>
auto make_obj(Args&&... args) {
return object_factory::create<T>(std::forward<Args>(args)...);
}
}

27
src/core/types.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <cwchar>
#include <span>
namespace mirai {
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using f32 = float;
using f64 = double;
using size_type = std::size_t;
using index_type = std::ptrdiff_t;
using ptrdiff_type = std::ptrdiff_t;
using uintptr_type = std::uintptr_t;
using intptr_type = std::intptr_t;
using byte_type = std::byte;
using byte_span = std::span<byte_type>;
using const_byte_span = std::span<const byte_type>;
}

8
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
project(mirai_tests)
enable_testing()
find_package(GTest CONFIG REQUIRED)
simple_executable()
target_link_libraries(${PROJECT_NAME} PRIVATE mirai GTest::gtest GTest::gtest_main)
add_test(AllTestsInMain ${PROJECT_NAME})

25
tests/main.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <gtest/gtest.h>
#include "object_test.h"
TEST(test_object, derived) {
auto test_obj = mirai::make_obj<test_class1>();
auto test_obj2 = mirai::make_obj<test_class2>();
// 检查类型信息
// test_obj应该继承自mirai::object
EXPECT_TRUE(test_obj->is<mirai::object>());
EXPECT_TRUE(test_obj2->is<mirai::object>());
// test_obj应该是test_class1类型但不是test_class2类型
EXPECT_TRUE(test_obj->is<test_class1>());
EXPECT_FALSE(test_obj->is<test_class2>());
// test_obj2应该是test_class2类型也是test_class1类型
EXPECT_TRUE(test_obj2->is<test_class2>());
EXPECT_TRUE(test_obj2->is<test_class1>());
// test_obj的类型名称检查
EXPECT_EQ(test_obj->type_name(), "class test_class1");
EXPECT_EQ(test_obj2->type_name(), "class test_class2");
}

11
tests/object_test.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "core/object.h"
class test_class1 : public mirai::object {
MIRAI_OBJECT_TYPE_INFO(test_class1, mirai::object)
};
class test_class2 : public test_class1 {
MIRAI_OBJECT_TYPE_INFO(test_class2, test_class1)
};

78
vcpkg.json Normal file
View File

@@ -0,0 +1,78 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "mirai",
"version": "0.1.0",
"description": "Modern C++23 GUI Framework based on Vulkan 1.3",
"homepage": "https://github.com/mirai-framework/mirai",
"license": "MIT",
"supports": "windows | linux | osx",
"builtin-baseline": "5422eb983d4ca8bc4851ac9771d6e74554efc5c8",
"dependencies": [
{
"name": "sdl3",
"version>=": "3.2.28"
},
{
"name": "spirv-cross",
"version>=": "1.3.296.0"
},
{
"name": "nlohmann-json",
"version>=": "3.12.0#1"
},
{
"name": "stb",
"version>=": "2024-07-29#1"
},
{
"name": "freetype",
"version>=": "2.13.3",
"features": ["bzip2", "png", "zlib"]
},
{
"name": "harfbuzz",
"version>=": "12.2.0",
"features": ["freetype"]
},
{
"name": "yoga",
"version>=": "3.2.1"
},
{
"name": "eigen3",
"version>=": "3.4.1"
},
{
"name": "spdlog",
"version>=": "1.16.0"
},
{
"name": "fmt",
"version>=": "12.1.0"
},
{
"name": "gtest",
"version>=": "1.17.0#2"
}
],
"overrides": [],
"features": {
"tests": {
"description": "Build unit tests",
"dependencies": [
{
"name": "gtest",
"version>=": "1.17.0#2"
}
]
},
"examples": {
"description": "Build example applications",
"dependencies": []
},
"docs": {
"description": "Build documentation",
"dependencies": []
}
}
}