From 4f0908e8d81f05160abdb3412dca2a06365f126f Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 21 Aug 2026 11:36:43 +0800 Subject: [PATCH] =?UTF-8?q?cmake=20=E9=80=9F=E5=BA=A6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 0_cmake_library/cmake/package_zip.cmake | 94 +------------ .../cmake_library/base/map/map.cmake | 10 ++ .../test/base/map/map_test.cmake | 8 ++ .../test/domain/library/library_test.cmake | 7 +- .../register_libaray/rcl_register_ex.cmake | 132 +++++++++++++----- 5 files changed, 127 insertions(+), 124 deletions(-) diff --git a/0_cmake_library/cmake/package_zip.cmake b/0_cmake_library/cmake/package_zip.cmake index 11e21a4..6a6a80f 100644 --- a/0_cmake_library/cmake/package_zip.cmake +++ b/0_cmake_library/cmake/package_zip.cmake @@ -1,83 +1,4 @@ -# 打包匹配规则: -# 1. include/exclude 中的每一项都是原生 CMake 正则表达式,不是 glob,不做任何隐式转换。 -# 2. 匹配路径始终相对于 PACKAGE_ROOT,统一使用 "/",并且不以 "/" 开头。 -# 3. 文件路径形如 "src/main.cpp",目录路径形如 "frontend/node_modules/"。 -# 4. include 只匹配文件,多条 include 之间为 OR,文件至少匹配一条才能进入包。 -# 5. exclude 同时匹配文件和目录,多条 exclude 之间为 OR。 -# 6. exclude 命中目录时直接跳过整个目录,不再枚举其内部文件。 -# 7. 是否使用 ^ 和 $ 完整匹配由调用者明确指定,实现不会自动补充锚点。 -function(_package_join_regex output) - if (ARGC EQUAL 1) - set(${output} "" PARENT_SCOPE) - return() - endif () - string(JOIN ")|(" regex ${ARGN}) - set(${output} "(${regex})" PARENT_SCOPE) -endfunction() -function(_package_collect_files output root include_regex exclude_regex) - set(package_pending_dirs ".") - set(package_files) - while (package_pending_dirs) - list(POP_BACK package_pending_dirs package_current_dir) - if (package_current_dir STREQUAL ".") - set(package_current_dir_absolute "${root}") - else () - set(package_current_dir_absolute "${root}/${package_current_dir}") - endif () - file(GLOB package_entries LIST_DIRECTORIES true RELATIVE "${root}" "${package_current_dir_absolute}/*") - foreach (package_entry IN LISTS package_entries) - string(REPLACE "\\" "/" package_entry "${package_entry}") - set(package_entry_absolute "${root}/${package_entry}") - if (IS_DIRECTORY "${package_entry_absolute}" AND NOT IS_SYMLINK "${package_entry_absolute}") - set(package_directory_path "${package_entry}/") - if (NOT exclude_regex STREQUAL "" AND "${package_directory_path}" MATCHES "${exclude_regex}") - continue() - endif () - list(APPEND package_pending_dirs "${package_entry}") - continue() - endif () - if (include_regex STREQUAL "" OR NOT "${package_entry}" MATCHES "${include_regex}") - continue() - endif () - if (NOT exclude_regex STREQUAL "" AND "${package_entry}" MATCHES "${exclude_regex}") - continue() - endif () - list(APPEND package_files "${package_entry}") - endforeach () - endwhile () - set(${output} ${package_files} PARENT_SCOPE) -endfunction() -if (PACKAGE_ZIP_RUN) - file(STRINGS "${PACKAGE_INCLUDE_FILE}" package_includes) - file(STRINGS "${PACKAGE_EXCLUDE_FILE}" package_excludes) - _package_join_regex(package_include_regex ${package_includes}) - _package_join_regex(package_exclude_regex ${package_excludes}) - _package_collect_files(package_files "${PACKAGE_ROOT}" "${package_include_regex}" "${package_exclude_regex}") - foreach (internal_file IN ITEMS "${PACKAGE_OUTPUT_FILE}" "${PACKAGE_FILE_LIST}" "${PACKAGE_INCLUDE_FILE}" "${PACKAGE_EXCLUDE_FILE}") - file(RELATIVE_PATH internal_relative "${PACKAGE_ROOT}" "${internal_file}") - string(REPLACE "\\" "/" internal_relative "${internal_relative}") - if (NOT internal_relative MATCHES "^\\.\\./") - list(REMOVE_ITEM package_files "${internal_relative}") - endif () - endforeach () - list(REMOVE_DUPLICATES package_files) - list(SORT package_files) - string(REPLACE ";" "\n" package_file_list_content "${package_files}") - file(WRITE "${PACKAGE_FILE_LIST}" "${package_file_list_content}\n") - get_filename_component(package_output_dir "${PACKAGE_OUTPUT_FILE}" DIRECTORY) - file(MAKE_DIRECTORY "${package_output_dir}") - file(REMOVE "${PACKAGE_OUTPUT_FILE}") - execute_process( - COMMAND "${CMAKE_COMMAND}" -E tar cf "${PACKAGE_OUTPUT_FILE}" --format=zip "--files-from=${PACKAGE_FILE_LIST}" - WORKING_DIRECTORY "${PACKAGE_ROOT}" - RESULT_VARIABLE package_result - ) - if (NOT package_result EQUAL 0) - message(FATAL_ERROR "Failed to create ${PACKAGE_OUTPUT_FILE}") - endif () - return() -endif () -function(_add_project_zip_target target_name output_file root_directory include_list_name exclude_list_name) +function(_add_project_zip_target target_name output_file root_directory item_list_name) if (IS_ABSOLUTE "${root_directory}") get_filename_component(package_root "${root_directory}" ABSOLUTE) else () @@ -88,15 +9,12 @@ function(_add_project_zip_target target_name output_file root_directory include_ else () get_filename_component(package_output_file "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/${output_file}" ABSOLUTE) endif () - set(package_file_list "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.files") - set(package_include_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.includes") - set(package_exclude_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.excludes") - string(REPLACE ";" "\n" package_include_content "${${include_list_name}}") - string(REPLACE ";" "\n" package_exclude_content "${${exclude_list_name}}") - file(WRITE "${package_include_file}" "${package_include_content}\n") - file(WRITE "${package_exclude_file}" "${package_exclude_content}\n") + get_filename_component(package_output_dir "${package_output_file}" DIRECTORY) + file(MAKE_DIRECTORY "${package_output_dir}") + set(package_items ${${item_list_name}}) add_custom_target(${target_name} - COMMAND "${CMAKE_COMMAND}" -DPACKAGE_ZIP_RUN=ON "-DPACKAGE_ROOT=${package_root}" "-DPACKAGE_OUTPUT_FILE=${package_output_file}" "-DPACKAGE_FILE_LIST=${package_file_list}" "-DPACKAGE_INCLUDE_FILE=${package_include_file}" "-DPACKAGE_EXCLUDE_FILE=${package_exclude_file}" -P "${CMAKE_CURRENT_FUNCTION_LIST_FILE}" + COMMAND "${CMAKE_COMMAND}" -E tar cf "${package_output_file}" --format=zip ${package_items} + WORKING_DIRECTORY "${package_root}" BYPRODUCTS "${package_output_file}" COMMENT "Creating ${package_output_file}" VERBATIM diff --git a/0_cmake_library/cmake_library/base/map/map.cmake b/0_cmake_library/cmake_library/base/map/map.cmake index 0ff1000..b3b1f96 100644 --- a/0_cmake_library/cmake_library/base/map/map.cmake +++ b/0_cmake_library/cmake_library/base/map/map.cmake @@ -175,3 +175,13 @@ function(map_deserialize self text) endif() endforeach() endfunction() + +# API: map_deserialize_file( ) +# 作用: 从 map_serialize_file 生成的单个文件恢复 map。 +# 参数: self 是接收数据的 map 名称;path 是待读取的 map 文件路径。 +# 返回: 无。 +# 注意: 文件不存在或不可读属于无法恢复的配置错误,由 file(READ) 直接终止配置。 +function(map_deserialize_file self path) + file(READ "${path}" _text) + map_deserialize("${self}" "${_text}") +endfunction() diff --git a/0_cmake_library/cmake_library/test/base/map/map_test.cmake b/0_cmake_library/cmake_library/test/base/map/map_test.cmake index fbbefbf..55338cb 100644 --- a/0_cmake_library/cmake_library/test/base/map/map_test.cmake +++ b/0_cmake_library/cmake_library/test/base/map/map_test.cmake @@ -73,6 +73,14 @@ test_assert_strequal("${_value}" "one") map_get(map_test_copy second _value) test_assert_strequal("${_value}" "two") +map_deserialize_file(map_test_file_copy "${_map_serialized_file}") +map_keys(map_test_file_copy _keys) +test_assert_strequal("${_keys}" "first;second") +map_get(map_test_file_copy first _value) +test_assert_strequal("${_value}" "one") +map_get(map_test_file_copy second _value) +test_assert_strequal("${_value}" "two") + # codec 默认开启:map value 可以安全保存 CMake list 分号、序列化分隔符、百分号、反斜杠和换行。 set(_map_special "src/a.cpp;src/b.cpp|pipe%percent diff --git a/0_cmake_library/cmake_library/test/domain/library/library_test.cmake b/0_cmake_library/cmake_library/test/domain/library/library_test.cmake index 7365e32..b551fc6 100644 --- a/0_cmake_library/cmake_library/test/domain/library/library_test.cmake +++ b/0_cmake_library/cmake_library/test/domain/library/library_test.cmake @@ -456,8 +456,13 @@ library_register(library_test_auto_register util) library_depends(library_test_auto_register util core) library_register(library_test_auto_register app) library_depends(library_test_auto_register app util) +library_get_effective_data_export_dir(library_test_auto_register _target_loader_data_dir) +library_export_dir(library_test_auto_register "${_target_loader_data_dir}") +# target loader 的权威来源是磁盘 map;清空内存字段后仍应按已导出的依赖递归加载。 +library_set(library_test_auto_register library_test_auto_register::util RESOLVED_DEPENDENCIES "") +library_set(library_test_auto_register library_test_auto_register::app RESOLVED_DEPENDENCIES "") library_dependency_order_with_depth(0 _target_dependency_order library_test_auto_register::app library_test_auto_register::app) -test_assert_strequal("${_target_dependency_order}" "library_test_auto_register::core;library_test_auto_register::util;library_test_auto_register::app") +test_assert_strequal("${_target_dependency_order}" "library_test_auto_register::app") rcl_add_dependency_action_targets_with_depth(library_test_auto_register_depth_1 1 library_test_auto_register::app library_test_auto_register::app) if(NOT TARGET pfx_library_test_auto_register_app_auto) message(FATAL_ERROR "depth 1 did not register the explicit library target") diff --git a/0_cmake_library/register_libaray/rcl_register_ex.cmake b/0_cmake_library/register_libaray/rcl_register_ex.cmake index f6d1fdb..098a1b5 100644 --- a/0_cmake_library/register_libaray/rcl_register_ex.cmake +++ b/0_cmake_library/register_libaray/rcl_register_ex.cmake @@ -383,20 +383,27 @@ endfunction() function(rcl_namespace_depends namespace_name) library_namespace_depends("${namespace_name}" ${ARGN}) endfunction() -# API: rcl_generate_registered_cmake_libraries() -# 作用: 将命名空间内全部库的全部 action 注册成 CMake custom target。 -# 参数: namespace_name 是要注册的命名空间。 -function(rcl_generate_registered_cmake_libraries namespace_name) - rcl_get_data_export_dir("${namespace_name}" _data_export_dir) - library_add_item_operation_targets("${namespace_name}" pfx "${_data_export_dir}") -endfunction() -# API: rcl_generate_registered_cmake_library() -# 作用: 将指定库的全部 action 注册成 CMake custom target。 -# 参数: library_name 必须使用 namespace::id。 -function(rcl_generate_registered_cmake_library library_name) +# INTERNAL: __rcl_read_library_map( ) +# 作用: 从磁盘读取一个库的自包含配置,不导出 namespace,也不写文件。 +# 参数: library_name 使用 namespace::id;两个输出参数分别接收临时 map ID 和配置记录的数据目录。 +# 返回: 通过 PARENT_SCOPE 返回输出;单库配置文件不存在时终止配置。 +function(__rcl_read_library_map library_name map_output data_dir_output) __rcl_split_library_name("${library_name}" _namespace_name _library_id) - rcl_get_data_export_dir("${_namespace_name}" _data_export_dir) - library_add_single_item_operation_targets("${library_name}" pfx "${_data_export_dir}") + rcl_get_data_export_dir("${_namespace_name}" _namespace_data_dir) + __library_storage_path("${library_name}" _library_storage_path) + set(_map_file "${_namespace_data_dir}/registry/nodes/${_library_storage_path}/map.txt") + if (NOT EXISTS "${_map_file}") + message(FATAL_ERROR "registered library configuration does not exist: ${_map_file}") + endif () + string(SHA256 _map_hash "rcl-disk-library:${_map_file}") + set(_map "__rcl_disk_library_${_map_hash}") + map_deserialize_file("${_map}" "${_map_file}") + map_get("${_map}" DATA_EXPORT_DIR _data_export_dir) + if ("${_data_export_dir}" STREQUAL "") + set(_data_export_dir "${_namespace_data_dir}") + endif () + set(${map_output} "${_map}" PARENT_SCOPE) + set(${data_dir_output} "${_data_export_dir}" PARENT_SCOPE) endfunction() # API GROUP: dependency environment loading # API: rcl_load_dependency_environment_with_depth( [library_names...]) @@ -463,36 +470,91 @@ function(__rcl_add_unique_target_dependencies target_name) endif () endfunction() # API GROUP: dependency target loading +# INTERNAL: __rcl_load_library_action_targets_with_depth( ) +# 作用: 从单库磁盘 map 读取直接依赖并递归注册闭包内每个库的全部 action target。 +# 参数: dependency_depth 为 0 时无限递归,为 1 时只处理当前库;其余列表用于本次递归的去重、依赖优先顺序和环检测。 +# 返回: 通过最后两个输出参数返回本次递归更新后的已访问库和依赖优先顺序。 +# 注意: 磁盘 map 是该加载路径的唯一配置来源;函数不导出、不刷新、不写入任何配置文件。 +function(__rcl_load_library_action_targets_with_depth library_name dependency_depth visited_libraries dependency_order loading_stack visited_output order_output) + list(FIND visited_libraries "${library_name}" _visited_index) + if (NOT _visited_index EQUAL -1) + set(${visited_output} "${visited_libraries}" PARENT_SCOPE) + set(${order_output} "${dependency_order}" PARENT_SCOPE) + return() + endif () + list(FIND loading_stack "${library_name}" _loading_index) + if (NOT _loading_index EQUAL -1) + list(APPEND loading_stack "${library_name}") + string(JOIN " -> " _cycle ${loading_stack}) + message(FATAL_ERROR "registered library dependency cycle: ${_cycle}") + endif () + + __rcl_read_library_map("${library_name}" _library_map _data_export_dir) + map_get("${_library_map}" RESOLVED_DEPENDENCIES _direct_dependencies) + list(APPEND loading_stack "${library_name}") + set(_loaded_direct_dependencies) + if (dependency_depth EQUAL 0 OR dependency_depth GREATER 1) + if (dependency_depth EQUAL 0) + set(_next_depth 0) + else () + math(EXPR _next_depth "${dependency_depth} - 1") + endif () + foreach (_dependency_name IN LISTS _direct_dependencies) + __rcl_load_library_action_targets_with_depth( + "${_dependency_name}" "${_next_depth}" + "${visited_libraries}" "${dependency_order}" "${loading_stack}" + visited_libraries dependency_order) + list(APPEND _loaded_direct_dependencies "${_dependency_name}") + endforeach () + endif () + + __rcl_library_action_target_name("${library_name}" auto _library_action_target) + if (NOT TARGET "${_library_action_target}") + map_get("${_library_map}" BASH_EXECUTABLE _bash_executable) + map_get("${_library_map}" BASH_SCRIPT _bash_script) + map_get("${_library_map}" TARGET_FOLDER_ROOT _target_folder_root) + action_cache_operations("${_bash_executable}" "${_bash_script}" _operations) + action_add_item_operation_targets( + pfx "${_data_export_dir}" "${_bash_executable}" "${_bash_script}" + "${library_name}" "${_operations}" "${_target_folder_root}") + endif () + if (NOT TARGET "${_library_action_target}") + message(FATAL_ERROR "registered library does not provide the auto action: ${library_name}") + endif () + + set(_direct_dependency_targets) + foreach (_dependency_name IN LISTS _loaded_direct_dependencies) + __rcl_library_action_target_name("${_dependency_name}" auto _dependency_action_target) + list(APPEND _direct_dependency_targets "${_dependency_action_target}") + endforeach () + __rcl_add_unique_target_dependencies("${_library_action_target}" ${_direct_dependency_targets}) + list(APPEND visited_libraries "${library_name}") + list(APPEND dependency_order "${library_name}") + set(${visited_output} "${visited_libraries}" PARENT_SCOPE) + set(${order_output} "${dependency_order}" PARENT_SCOPE) +endfunction() + # API: rcl_add_dependency_action_targets_with_depth( [library_names...]) -# 作用: 按指定深度解析并去重依赖库,为每个库注册单库 auto target,并把真实库依赖映射成 CMake target 依赖图。 +# 作用: 按指定深度从磁盘递归加载依赖库,为每个库注册全部 action target,并把直接库依赖映射成 CMake auto target 依赖图。 # 参数: target_name 是聚合 target;dependency_depth 为 0 时无限递归,为 1 时只包含显式传入库,其他正整数表示最大层数;ARGN 是 namespace::id 格式的起始库列表。 # 返回: 无。 -# 注意: 依赖解析复用 library_dependency_order_with_depth;这里只消费解析结果,不再使用 with_rely_auto 在 Bash 内重复递归。 function(rcl_add_dependency_action_targets_with_depth target_name dependency_depth) - library_dependency_order_with_depth("${dependency_depth}" _dependency_order ${ARGN}) + if (NOT "${dependency_depth}" MATCHES "^[0-9]+$") + message(FATAL_ERROR "dependency_depth must be a non-negative integer: ${dependency_depth}") + endif () + set(_visited_libraries) + set(_dependency_order) + foreach (_library_name IN LISTS ARGN) + __rcl_load_library_action_targets_with_depth( + "${_library_name}" "${dependency_depth}" + "${_visited_libraries}" "${_dependency_order}" "" + _visited_libraries _dependency_order) + endforeach () set(_dependency_action_targets) foreach (_library_name IN LISTS _dependency_order) - rcl_generate_registered_cmake_library("${_library_name}") __rcl_library_action_target_name("${_library_name}" auto _library_action_target) - if (NOT TARGET "${_library_action_target}") - message(FATAL_ERROR "dependency action target was not generated: ${_library_action_target}") - endif () list(APPEND _dependency_action_targets "${_library_action_target}") endforeach () - foreach (_library_name IN LISTS _dependency_order) - __rcl_split_library_name("${_library_name}" _namespace_name _library_id) - library_get("${_namespace_name}" "${_library_name}" RESOLVED_DEPENDENCIES _direct_dependencies) - __rcl_library_action_target_name("${_library_name}" auto _library_action_target) - set(_direct_dependency_targets) - foreach (_dependency_name IN LISTS _direct_dependencies) - list(FIND _dependency_order "${_dependency_name}" _dependency_index) - if (NOT _dependency_index EQUAL -1) - __rcl_library_action_target_name("${_dependency_name}" auto _dependency_action_target) - list(APPEND _direct_dependency_targets "${_dependency_action_target}") - endif () - endforeach () - __rcl_add_unique_target_dependencies("${_library_action_target}" ${_direct_dependency_targets}) - endforeach () if (NOT TARGET "${target_name}") add_custom_target("${target_name}") endif () @@ -530,4 +592,4 @@ function(rcl_log_print) string(APPEND rcl_log_output "${P}调用位置 ${E} file:///${location}\n") endforeach () message("${rcl_log_output}") -endfunction() \ No newline at end of file +endfunction()