Files
build_infra/0_cmake_library/core.cmake
T
2026-06-22 17:48:51 +08:00

596 lines
21 KiB
CMake

include_guard(GLOBAL)
include(CMakePackageConfigHelpers)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fsanitize=address" HAS_ASAN)
include(${CMAKE_CURRENT_LIST_DIR}/core_file_operation.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/core_string_operation.cmake)
# TODO: 库设计思路
# list 操作 https://cmake.org/cmake/help/v3.27/command/list.html#list
# string 操作 https://cmake.org/cmake/help/v3.27/command/string.html#string
# property文档: https://cmake.org/cmake/help/v3.31/manual/cmake-commands.7.html
# Initial definition
# https://cmake.org/cmake/help/v3.31/command/define_property.html
# 在target上的属性
# https://cmake.org/cmake/help/v3.31/manual/cmake-properties.7.html#target-properties
#macro(__append_prop target prop)
# set(_cur_temp_list_ "${ARGN}")
# get_property(_temp_list_ TARGET ${target} PROPERTY ${prop})
# list(APPEND _temp_list_ ${_cur_temp_list_})
# set_property(TARGET ${target} PROPERTY ${prop} ${_temp_list_})
#endmacro()
macro(__append_prop target prop)
set(_cur_temp_list_ "${ARGN}")
# 取已有属性值(如果没设置过,避免报错)
get_property(_temp_list_ TARGET ${target} PROPERTY ${prop} SET)
if(_temp_list_)
get_property(_temp_list_ TARGET ${target} PROPERTY ${prop})
else()
set(_temp_list_ "")
endif()
# 追加新值
list(APPEND _temp_list_ ${_cur_temp_list_})
# 去重
list(REMOVE_DUPLICATES _temp_list_)
# 写回 target 属性
set_property(TARGET ${target} PROPERTY ${prop} ${_temp_list_})
endmacro()
# 自定义函数:向 ELF 文件添加自定义段并嵌入自定义内容
function(_add_custom_section_to_elf_with_temp_file target_name section_name content)
# 创建一个临时文件
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/temp)
set(TEMP_METADATA_FILE "${CMAKE_BINARY_DIR}/temp/${target_name}_${section_name}_metadata.txt")
# 将自定义内容写入临时文件
file(WRITE ${TEMP_METADATA_FILE} "${content}")
# 确保 objcopy 命令可用
if (NOT CMAKE_OBJCOPY)
find_program(OBJCOPY objcopy)
if (NOT OBJCOPY)
message(FATAL_ERROR "objcopy not found!")
endif ()
else ()
set(OBJCOPY ${CMAKE_OBJCOPY})
endif ()
get_target_property(EXE_PATH ${target_name} _default_name_)
# message("${target_name} EXE_PATH ${EXE_PATH}")
# 使用 add_custom_command 延迟执行,确保在目标构建之后执行
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${OBJCOPY} --add-section ${section_name}=${TEMP_METADATA_FILE} ${EXE_PATH}
COMMENT "Adding ${section_name} section to ${target_name}"
)
endfunction()
function(_add_metadata_win target_name)
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND CMAKE_GENERATOR MATCHES "Ninja")
return()
endif()
set(FILE_VERSION "1,0,0,0") # 文件版本
set(PRODUCT_VERSION "1,0,0,0") # 产品版本
set(COMPANY_NAME "wang-yong-chen")
set(FILE_DESCRIPTION "${G_Architecture_Name}") #描述
set(INTERNAL_NAME "INTERNAL_NAME")
set(COPYRIGHT "wang-yong-chen") #版权
set(ORIGINAL_FILENAME "${target_name}_${G_Architecture_Name}") #原始文件名
set(PRODUCT_NAME "${target_name}")
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/temp)
set(TEMP_METADATA_FILE "${CMAKE_BINARY_DIR}/temp/${target_name}_${section_name}_metadata.rc")
configure_file(${CMAKE_SOURCE_DIR}/0_cmake_library/rc.in ${TEMP_METADATA_FILE})
# 有了这个所以不需要链接空文件占位了
target_sources(${target_name} PRIVATE "${TEMP_METADATA_FILE}")
endfunction()
macro(__core_load_base_prop)
get_property(_type_ TARGET ${_name_} PROPERTY _type)
get_property(_rely_domain_ TARGET ${_name_} PROPERTY _rely_domain_)
get_property(_include_domain_ TARGET ${_name_} PROPERTY _include_domain_)
endmacro()
function(_create _type_ _name_)
set(_rely_domain_)
set(_include_domain_)
if (_type_ MATCHES "sys")
set(_rely_domain_ PUBLIC)
set(_include_domain_ PUBLIC)
add_executable(${_name_})
set_target_properties(${_name_} PROPERTIES SUFFIX ".sys")
_target_isolate_global(${_name_})
endif ()
if (_type_ MATCHES "sys_lib")
set(_rely_domain_ PUBLIC)
set(_include_domain_ PUBLIC)
endif ()
if (_type_ MATCHES "custom")
set(_rely_domain_ PUBLIC)
set(_include_domain_ PUBLIC)
add_custom_target(${_name_})
endif ()
if (_type_ MATCHES "object")
set(_rely_domain_ PRIVATE)
set(_include_domain_ PRIVATE)
add_library(${_name_} OBJECT )
endif ()
if (_type_ MATCHES "interface")
set(_rely_domain_ INTERFACE)
set(_include_domain_ INTERFACE)
add_library(${_name_} INTERFACE)
endif ()
if (_type_ MATCHES "exe")
set(_rely_domain_ PRIVATE)
set(_include_domain_ PRIVATE)
add_executable(${_name_})
target_compile_definitions(${_name_} PRIVATE "${_name_}_build_exe")
if (MSVC)
set_property(TARGET ${_name_} PROPERTY _default_name_ "${_name_}.exe")
else ()
set_property(TARGET ${_name_} PROPERTY _default_name_ "${_name_}")
endif ()
endif ()
if (_type_ MATCHES "lib")
set(_rely_domain_ PRIVATE)
set(_include_domain_ PRIVATE)
add_library(${_name_} STATIC)
target_compile_definitions(${_name_} PRIVATE "${_name_}_build_lib")
if (MSVC)
set_property(TARGET ${_name_} PROPERTY _default_name_ "${_name_}.lib")
else ()
set_property(TARGET ${_name_} PROPERTY _default_name_ "lib${_name_}.a")
endif ()
endif ()
if (_type_ MATCHES "dll")
set(_rely_domain_ PRIVATE)
set(_include_domain_ PRIVATE)
add_library(${_name_} SHARED ${empty_file})
target_compile_definitions(${_name_} PRIVATE "${_name_}_build_dll")
if (MSVC)
set_property(TARGET ${_name_} PROPERTY _default_name_ "${_name_}.dll")
else ()
set_property(TARGET ${_name_} PROPERTY _default_name_ "lib${_name_}.so")
endif ()
endif ()
set_property(TARGET ${_name_} PROPERTY _id ${_name_})
set_property(TARGET ${_name_} PROPERTY _type ${_type_})
set_property(TARGET ${_name_} PROPERTY _rely_domain_ ${_rely_domain_})
set_property(TARGET ${_name_} PROPERTY _include_domain_ ${_include_domain_})
if (
_type_ MATCHES "exe" OR
_type_ MATCHES "lib" OR
_type_ MATCHES "dll"
)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(content "")
string(APPEND content "[arch]:${G_Architecture_Name} ")
string(APPEND content "[author]: wang-yong-chen\n")
_add_custom_section_to_elf_with_temp_file(${_name_} info "${content}")
else ()
_add_metadata_win(${_name_})
endif ()
_attach_target(${_name_} "")
else()
# 其他类型不能使用下面的选项处理
return()
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(
${_name_} ${_rely_domain_}
-fvisibility=hidden
-fvisibility-inlines-hidden
-Wall
-Wextra
-Wpedantic
-Wshadow
-Wconversion
-Wsign-conversion
)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
get_directory_property(current_compile_options COMPILE_OPTIONS)
target_compile_options(
${_name_} ${_rely_domain_}
/wd4819
/utf-8
/Zc:preprocessor /DNOMINMAX /D_USE_MATH_DEFINES /EHsc /bigobj
)
#add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
if (${CMAKE_BUILD_TYPE} STREQUAL "Release")
# /PROFILE 是 Microsoft Visual Studio 中的一个链接选项,用于生成可用于性能分析的可执行文件。
# 性能分析最好不用debug
target_link_options(${_name_} ${_rely_domain_} /PROFILE)
else()
# target_link_options(${_name_} ${_rely_domain_} DbgHelp)
endif ()
#不要加这句话 Release 在很多时候是
#set(global_public_compile_definition ${CMAKE_BUILD_TYPE})
target_compile_options(${_name_} ${_rely_domain_})
# target_compile_options(${_name_} ${_rely_domain_} /EHc-) # 可能会重复添加也没有关系
# 默认启用 C++ 异常和 SEH(结构化异常处理)
#/EHsc:默认启用 C++ 异常 和 结构化异常处理(SEH)。适用于大多数 C++ 项目,这种模式会让 C++ 异常和 SEH 异常都得到处理。您可以将这一行保留,并根据需要修改。
#/EHs:启用 结构化异常处理(SEH),但禁用 C++ 异常处理。这种设置通常用于低级编程,例如与 Windows API 或 COM 接口打交道时。将这一行解注释来启用此选项。
#/EHa:启用所有异常(包括 C++ 异常 和 SEH 异常)。这种设置适用于需要处理所有类型异常的情况。
#/EHc-:禁用 C++ 异常(即禁用 throw 和 catch),但 SEH 仍然启用。这会完全禁用 C++ 异常的支持,适用于对性能有高要求,且不依赖 C++ 异常的场景。
else ()
# 这样不可以继承
# set_target_properties(${_name_} PROPERTIES
# BUILD_RPATH "$ORIGIN:$ORIGIN/../lib:$ORIGIN/lib"
# )
# target_link_options(${_name_} ${_rely_domain_} "-Wl,-rpath,$ORIGIN:$ORIGIN/../lib:$ORIGIN/lib")
# target_link_options(${_name_} ${_rely_domain_} "-fno-exceptions")
# -fsanitize=address 和 -g 其实是两个独立的编译选项:
# -fsanitize=address:启用 AddressSanitizer(内存检测)
# -g:生成调试信息(这样报错时能看到源码行号)
# 它们可以分开用,但建议一起用!
target_compile_options(
${_name_} ${_rely_domain_}
-Wall -Wextra -Werror=return-type
)
set(USE_RUNPATH 1)
if (${USE_RUNPATH})
target_link_options(${_name_} ${_rely_domain_}
-Wl,--enable-new-dtags
)
else()
target_link_options(${_name_} ${_rely_domain_}
-Wl,--disable-new-dtags
)
endif()
if (HAS_ASAN)
#message(STATUS "支持 AddressSanitizer")
target_compile_options(${_name_} ${_rely_domain_} -fsanitize=address)
target_link_options(${_name_} ${_rely_domain_} -fsanitize=address)
else ()
#message(STATUS "不支持 AddressSanitizer")
endif ()
endif ()
endfunction()
function(_depend_Ex _name_ _cur_dependency_list_var_name_)
__check_none_extr_params(${ARGC} "${ARGN}" "${ARGV}")
__append_prop(${_name_} _dependency_list_ ${${_cur_dependency_list_var_name_}})
get_property(_rely_domain_ TARGET ${_name_} PROPERTY _rely_domain_)
foreach (_need_by_ ${${_cur_dependency_list_var_name_}})
__append_prop(${_need_by_} _need_by_list_ ${_name_})
# message("======== ${_name_} rely ${_need_by_}")
target_link_libraries(${_name_} ${_rely_domain_}
"${_need_by_}"
)
endforeach ()
endfunction()
# 刷新一些自己重新解释的属性 看看这个是否有用· TODO: https://cmake.org/cmake/help/v3.31/command/define_property.html#command:define_property
function(_prase_update _name_)
set(has_qt 0)
# 自身是否标记 use_qt
get_property(_has_qt_prop TARGET ${_name_} PROPERTY _use_qt_ SET)
if(_has_qt_prop)
get_property(_qt_value TARGET ${_name_} PROPERTY _use_qt_)
if(_qt_value)
set(has_qt 1)
endif()
endif()
# 如果自身没 Qt,就检查依赖
if(NOT has_qt)
get_property(_cur_dependency_list TARGET ${_name_} PROPERTY _dependency_list_)
foreach (_need_by_ ${_cur_dependency_list})
get_property(_qt_flag TARGET ${_need_by_} PROPERTY _use_qt_ SET)
if(_qt_flag)
get_property(_qt_value TARGET ${_need_by_} PROPERTY _use_qt_)
if(_qt_value)
set(has_qt 1)
break()
endif()
endif()
endforeach()
endif()
# 如果确定需要 Qt,则开启自动 moc/uic/rcc
if(has_qt)
set_target_properties(${_name_} PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)
endif()
endfunction()
macro(__glob _suffix_)
if (_ext_ STREQUAL ".${_suffix_}")
set(_cur_abs_list_name_ _abs_${_name_}_${_base_name_}_${_suffix_})
set(_cur_rel_list_name_ _rel_${_name_}_${_base_name_}_${_suffix_})
list(APPEND ${_cur_abs_list_name_} ${_abs_})
file(RELATIVE_PATH _rel_ ${_base_path_} ${_abs_})
list(APPEND ${_cur_rel_list_name_} ${_rel_})
list(APPEND _srcs_ ${_abs_})
continue()
endif ()
endmacro()
macro(__att _suffix_)
# set(_cur_abs_list_name_ _abs_${_name_}_${_base_name_}_${_suffix_})
# set(_cur_rel_list_name_ _rel_${_name_}_${_base_name_}_${_suffix_})
# set_property(TARGET ${_name_} PROPERTY ${_suffix_}_${_base_name_} ${${_cur_abs_list_name_}})
# set_property(TARGET ${_name_} PROPERTY rel_${_suffix_}_${_base_name_} ${${_cur_rel_list_name_}})
endmacro()
function(_attach_include_dir_Ex _name_ file_list_var_name)
__append_prop(${_name_} _attach_include_list_ ${${file_list_var_name}})
set(_includes_ ${${file_list_var_name}})
__core_load_base_prop()
if (_type_ MATCHES "interface")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
elseif (_type_ MATCHES "object")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
elseif (_type_ MATCHES "custom")
elseif (_type_ MATCHES "lib")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
elseif (_type_ MATCHES "dll")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
elseif (_type_ MATCHES "exe")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
elseif (_type_ MATCHES "sys")
target_include_directories(${_name_} ${_include_domain_} "${_includes_}")
endif ()
endfunction()
function(_attach_source_file_Ex _name_ file_list_var_name)
__append_prop(${_name_} _attach_file_list_ ${${file_list_var_name}})
__core_load_base_prop()
set(_srcs_ ${${file_list_var_name}})
if (_type_ MATCHES "interface")
target_sources(${_name_} INTERFACE "${_srcs_}")
elseif (_type_ MATCHES "object")
target_sources(${_name_} PRIVATE "${_srcs_}")
elseif (_type_ MATCHES "custom")
elseif (_type_ MATCHES "lib")
target_sources(${_name_} PRIVATE "${_srcs_}")
elseif (_type_ MATCHES "dll")
target_sources(${_name_} PRIVATE "${_srcs_}")
elseif (_type_ MATCHES "exe")
target_sources(${_name_} PRIVATE "${_srcs_}")
elseif (_type_ MATCHES "sys")
target_sources(${_name_} PRIVATE "${_srcs_}")
endif ()
endfunction()
function(_attach_source_dir_Ex _name_ _list_var_)
__check_none_extr_params(${ARGC} "${ARGN}" "${ARGV}")
__append_prop(${_name_} _attach_source_list_ ${${_list_var_}})
set(_base_path_)
set(_srcs_)
set(_rels_)
__core_load_base_prop()
set(build_path_list)
foreach (_base_path_ ${${_list_var_}})# 检查路径是否是目录
if(NOT IS_DIRECTORY "${_base_path_}")
message(WARNING "The path '${_base_path_}' is not a valid directory.")
endif()
get_filename_component(_base_name_ "${_base_path_}" NAME)
file(GLOB_RECURSE _files_ ${_base_path_}/*.*)
foreach (_abs_ ${_files_})
get_filename_component(_ext_ ${_abs_} EXT)
# string(TOLOWER "${_ext_}" _ext_) # 可选:转小写以统一判断
__glob(h)
__glob(cpp)
__glob(c)
__glob(hpp)
__glob(qrc)
__glob(qml)
__glob(cc)
__glob(ui)
__glob(ipp)
endforeach ()
__att(h)
__att(cpp)
__att(c)
__att(hpp)
__att(qrc)
__att(qml)
__att(cc)
__att(ui)
__att(ipp)
endforeach ()
if(NOT _srcs_)
message(FATAL_ERROR "${_name_} empty ${_list_var_}")
endif()
if (_type_ MATCHES "interface")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
elseif (_type_ MATCHES "object")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
elseif (_type_ MATCHES "custom")
elseif (_type_ MATCHES "lib")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
elseif (_type_ MATCHES "dll")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
elseif (_type_ MATCHES "exe")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
elseif (_type_ MATCHES "sys")
target_sources(${_name_} ${_rely_domain_} "${_srcs_}")
endif ()
endfunction()
# 遍历如果是目录就添加源码 并且上一级 include
#function(_attach_Ex _name_ _cur_attach_include_list_var_name_)
# set(source_dir_list)
# set(source_file_list)
# set(include_dir_list)
# foreach(_base_path_ ${${_cur_attach_include_list_var_name_}})
# if(EXISTS "${_base_path_}")
# #message(STATUS "存在: ${_base_path_}")
# if(IS_DIRECTORY "${_base_path_}")
# #message(STATUS "是目录")
# list(APPEND source_dir_list ${_base_path_})
# get_filename_component(include_dir "${_base_path_}/../" REALPATH)
# list(APPEND include_dir_list ${include_dir})
# else()
# #message(STATUS "是文件")
# list(APPEND source_file_list ${build_path})
# endif()
# else()
# message(STATUS "不存在: ${_base_path_}")
# endif()
# endforeach()
# _attach_source_file_Ex(${_name_} source_file_list)
# _attach_source_dir_Ex(${_name_} source_dir_list)
# _attach_include_dir_Ex(${_name_} include_dir_list)
#endfunction()
# 用于遍历所有目标及其依赖一起安装
function(__bfs_traverse_targets _prop_name_ _init_target_list_ _ret_name_)
set(_visited_targets_ "") # 存储已经访问过的目标
set(_queue_ ${${_init_target_list_}}) # 初始化队列,将根目标放入队列
while (_queue_)
list(POP_FRONT _queue_ _current_target_)
if ("${_current_target_}" IN_LIST _visited_targets_)
continue()
endif ()
list(APPEND _visited_targets_ ${_current_target_})
get_property(_deps_ TARGET ${_current_target_} PROPERTY ${_prop_name_})
if (_deps_)
foreach (_dep_ IN LISTS _deps_)
if (NOT "${_dep_}" IN_LIST _visited_targets_)
list(APPEND _queue_ ${_dep_})
endif ()
endforeach ()
endif ()
endwhile ()
list(REMOVE_DUPLICATES _visited_targets_)
set(${_ret_name_} ${_visited_targets_} PARENT_SCOPE)
endfunction()
# 核心的三个函数
function(_depend _name_)
set(_temp_list_ "${ARGN}")
_depend_Ex(${_name_} _temp_list_)
endfunction()
#function(_attach _name_)
# set(_temp_list_ "${ARGN}")
# _attach_Ex(${_name_} _temp_list_)
#endfunction()
# 不能传入目录 传目录是空文件夹
function(_attach_post_build_file_Ex _name_ _file_list_var_name_)
add_custom_command(
TARGET ${_name_}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${${_file_list_var_name_}}
${CMAKE_CURRENT_BINARY_DIR}
COMMENT "拷贝配置文件到目标目录"
)
endfunction()
function(_attach_post_build_dir_Ex _name_ _dir_list_var_name_)
add_custom_command(
TARGET ${_name_}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${${_file_list_var_name_}}
${CMAKE_CURRENT_BINARY_DIR}
COMMENT "拷贝配置文件到目标目录"
)
endfunction()
function(_attach_post_build_file _name_)
set(_temp_list_ "${ARGN}")
_attach_post_build_file_Ex(${_name_} _temp_list_)
endfunction()
function(_attach_post_build_dir _name_)
set(_temp_list_ "${ARGN}")
_attach_post_build_dir_Ex(${_name_} _temp_list_)
endfunction()
function(_attach_include_dir _name_)
set(_temp_list_ "${ARGN}")
_attach_include_dir_Ex(${_name_} _temp_list_)
endfunction()
function(_attach_source_file _name_)
set(_temp_list_ "${ARGN}")
_attach_source_file_Ex(${_name_} _temp_list_)
endfunction()
function(_attach_source_dir _name_)
set(_temp_list_ "${ARGN}")
_attach_source_dir_Ex(${_name_} _temp_list_)
endfunction()
# 此目录为 _attach_file 安装的时候相对的目录
function(_set_main_dir _name_ _main_dir_)
set_property(TARGET ${_name_} PROPERTY _main_dir_ "${_main_dir_}")
endfunction()
function(_attach_test_Ex target_list_var_name)
if (NOT GTest_FOUND)
find_package(GTest)
endif()
if (NOT GTest_FOUND)
add_subdirectory(${CMAKE_SOURCE_DIR}/3rdParty/GTest)
endif()
foreach(target ${${target_list_var_name}})
get_property(_rely_domain_ TARGET ${target} PROPERTY _rely_domain_)
target_link_libraries(${target} ${_rely_domain_} GTest::gtest GTest::gmock)
target_compile_definitions(${target} ${_rely_domain_} _USE_GTEST)
endforeach()
endfunction()
function(_attach_test)
set(_temp_list_ "${ARGN}")
_attach_test_Ex(_temp_list_)
endfunction()