# -----------------------------------------------------------------------------------------------------------
# Copyright (c) 2026 Huawei Technologies Co., Ltd.
# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
# CANN Open Software License Agreement Version 2.0 (the "License").
# Please refer to the License for details. You may not use this file except in compliance with the License.
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
# See LICENSE in the root of the software repository for the full text of the License.
# -----------------------------------------------------------------------------------------------------------

get_filename_component(CANN_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)

if(NOT PROJECT_SOURCE_DIR)
    # 单仓编译
    set(TOPLEVEL_PROJECT ON)
else()
    # 多仓联编
    set(TOPLEVEL_PROJECT OFF)
endif()

function(__cann_print_summary)
    if(ENABLE_CCACHE)
        if(CCACHE_PROGRAM)
            set(CCACHE_FOUND_MSG "Found ccache, ")
        else()
            set(CCACHE_FOUND_MSG "Ccache not found, ")
        endif()
    else()
        set(CCACHE_FOUND_MSG "")
    endif()

    message(STATUS "Initialization summary: ${CCACHE_FOUND_MSG}TARGET_ARCH: ${TARGET_ARCH}")
endfunction()

function(__cann_get_target_arch)
    string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ARCH_LOW)
    if (ARCH_LOW MATCHES "x86_64|amd64")
        set(TARGET_ARCH x86_64 PARENT_SCOPE)
    elseif (ARCH_LOW MATCHES "aarch64|arm64|arm")
        set(TARGET_ARCH aarch64 PARENT_SCOPE)
    else ()
        message(WARNING "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
        set(TARGET_ARCH PARENT_SCOPE)
    endif()
endfunction()

# 添加target公共编译和链接选项
macro(add_cann_target_options)
    include(${CANN_CMAKE_DIR}/intf_pub/intf_pub_linux.cmake)
endmacro()

# 设置cann工程公共参数
macro(init_cann_project)
    # 联合构建时,init函数可能被调用多次,保证第一次调用时生效,忽略后续调用
    if(NOT CANN_PROJECT_INITED)
        cmake_parse_arguments(CANN "PREPEND_MODULE_PATH" "PRODUCT_SIDE" "" ${ARGN})

        if(CANN_PRODUCT_SIDE)
            set(PRODUCT_SIDE "${CANN_PRODUCT_SIDE}")
        endif()

        if(POLICY CMP0135)
            cmake_policy(SET CMP0135 NEW)
        endif()

        include(CMakePrintHelpers)

        set(CANN_BINARY_COMPONENTS)
        set(CANN_BINARY_COMPONENTS_ALL FALSE)

        set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
        set(CMAKE_CXX_STANDARD 17)
        set(CMAKE_CXX_STANDARD_REQUIRED ON)
        set(CMAKE_CXX_EXTENSIONS OFF)

        if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
                set(CMAKE_CXX_COMPILE_OBJECT
                    "<CMAKE_CXX_COMPILER> <DEFINES> -D__FILE__='\"$(notdir $(abspath <SOURCE>))\"' -Wno-builtin-macro-redefined <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>"
                )
                set(CMAKE_C_COMPILE_OBJECT
                    "<CMAKE_C_COMPILER> <DEFINES> -D__FILE__='\"$(notdir $(abspath <SOURCE>))\"' -Wno-builtin-macro-redefined <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>"
                )
        endif()

        option(ENABLE_CCACHE "Enable ccache" TRUE)
        if(ENABLE_CCACHE)
            find_program(CCACHE_PROGRAM ccache)
            if (CCACHE_PROGRAM)
                set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
                set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
            endif()
        endif()

        if(TOPLEVEL_PROJECT OR ENABLE_UNIFIED_BUILD)
            # install时不添加OPTIONAL选项,以保证打包产物完整
            set(INSTALL_OPTIONAL)
        endif()

        set(TARGET_SYSTEM_NAME Linux)

        set(INSTALL_LIBRARY_DIR lib)
        set(INSTALL_RUNTIME_DIR bin)
        set(INSTALL_INCLUDE_DIR include)
        set(INSTALL_CONFIG_DIR cmake)

        # 组件使用ASCEND_INSTALL_PATH或ASCEND_CANN_PACKAGE_PATH作为cann包安装路径
        if(NOT ASCEND_INSTALL_PATH AND ASCEND_CANN_PACKAGE_PATH)
            set(ASCEND_INSTALL_PATH "${ASCEND_CANN_PACKAGE_PATH}")
        endif()

        # Init third_party default path witch not set in services.
        if(NOT CANN_3RD_LIB_PATH)
            set(CANN_3RD_LIB_PATH ${CMAKE_SOURCE_DIR}/third_party)
            message("set third_party default path: ${CANN_3RD_LIB_PATH}.")
        endif()

        __cann_get_target_arch()

        if(CANN_PREPEND_MODULE_PATH)
            list(PREPEND CMAKE_MODULE_PATH "${CANN_CMAKE_DIR}/modules")
            list(PREPEND CMAKE_PREFIX_PATH "${ASCEND_INSTALL_PATH}")
        endif()

        __cann_print_summary()
        set(CANN_PROJECT_INITED TRUE)

        unset(CANN_PREPEND_MODULE_PATH)
        unset(CANN_PRODUCT_SIDE)
    endif()
endmacro()

# 添加device侧工程
function(add_cann_device_project component)
    # 多仓联编时跳过device工程
    if(NOT TOPLEVEL_PROJECT)
        return()
    endif()

    # ENABLE_BUILD_DEVICE为FALSE时跳过device工程
    if(DEFINED ENABLE_BUILD_DEVICE AND NOT ENABLE_BUILD_DEVICE)
        return()
    endif()

    set(EP_CMAKE_ARGS)
    if(ASCEND_CANN_PACKAGE_PATH)
        list(APPEND EP_CMAKE_ARGS "-D" "ASCEND_CANN_PACKAGE_PATH=${ASCEND_CANN_PACKAGE_PATH}")
    else()
        list(APPEND EP_CMAKE_ARGS "-D" "ASCEND_INSTALL_PATH=${ASCEND_INSTALL_PATH}")
    endif()
    if(HI_PYTHON)
        list(APPEND EP_CMAKE_ARGS "-D" "HI_PYTHON=${HI_PYTHON}")
    endif()

    include(ExternalProject)
    ExternalProject_Add(cann_device
        SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake/device
        BINARY_DIR ${CMAKE_BINARY_DIR}/device_build
        CMAKE_ARGS
            ${EP_CMAKE_ARGS}
            -D TOOLCHAIN_DIR=${ASCEND_INSTALL_PATH}/toolkit/toolchain/hcc
            -D CMAKE_TOOLCHAIN_FILE=${CANN_CMAKE_DIR}/toolchain/aarch64-hcc-toolchain.cmake
            -D CANN_3RD_LIB_PATH=${CANN_3RD_LIB_PATH}
            -D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
            -D ENABLE_SIGN=${ENABLE_SIGN}
            -D CUSTOM_SIGN_SCRIPT=${CUSTOM_SIGN_SCRIPT}
            -D VERSION_INFO=${VERSION_INFO}
            -D ENABLE_OPEN_SRC=TRUE
            -D BUILD_OPEN_PROJECT=TRUE
        INSTALL_COMMAND ${CMAKE_CPACK_COMMAND}
        BUILD_ALWAYS TRUE
    )
    install(FILES
        ${CMAKE_BINARY_DIR}/device_build/device-${component}.tar.gz
        DESTINATION .
        COMPONENT ${component}
    )
endfunction()

# 包装 find_package,在联编/统一构建模式下跳过
macro(find_cann_package)
    if(TOPLEVEL_PROJECT OR "${ARGV0}" IN_LIST CANN_BINARY_COMPONENTS OR CANN_BINARY_COMPONENTS_ALL)
        find_package(${ARGN})
    elseif(ENABLE_UNIFIED_BUILD AND "${ARGV0}" STREQUAL "ASC")
        include(${CANN_TOP_DIR}/${CANN_PACKAGE_DIR_asc-devkit}/cmake/asc/asc_modules/FindASC.cmake)
        list(APPEND CMAKE_MODULE_PATH "${CANN_TOP_DIR}/${CANN_PACKAGE_DIR_asc-devkit}/cmake/asc/asc_modules")
    endif()
endmacro()

# 添加三方库
macro(add_cann_third_party name)
    if(TOPLEVEL_PROJECT OR ENABLE_UNIFIED_BUILD)
        include(${CANN_CMAKE_DIR}/third_party/${name}.cmake)
    endif()
endmacro()

# 通过相对父目录方式添加子目录
function(add_cann_subdirectories_relative base_dir)
    foreach(source_dir ${ARGN})
        file(RELATIVE_PATH relative_dir "${base_dir}" "${source_dir}")
        add_subdirectory("${source_dir}" "${relative_dir}")
    endforeach()
endfunction()

# 设置打包配置
# component: 组件名
# NO_COMPONENT_INSTALL: 不带--component参数安装
# ENABLE_DEVICE: 是否解压device-${component}.tar.gz
# COMPUTE_UNIT: 芯片型号
# SHARE_INFO_NAME: 如果子包在share/info目录下的名字与component不一致,则需要设置
function(set_cann_cpack_config component)
    if(NOT TOPLEVEL_PROJECT AND NOT ENABLE_UNIFIED_BUILD)
        return()
    endif()

    if(ENABLE_UNIFIED_BUILD)
        if(NOT component IN_LIST CANN_PACKAGES)
            return()
        endif()
    endif()

    cmake_parse_arguments(CANN "NO_COMPONENT_INSTALL;NO_CLEAN" "ENABLE_DEVICE;COMPUTE_UNIT;SHARE_INFO_NAME;OUTPUT" "" ${ARGN})

    if(ENABLE_UNIFIED_BUILD)
        if(component IN_LIST DEVICE_CANN_PACKAGES AND SUPERBUILD_ENABLE_DEVICE)
            set(CANN_ENABLE_DEVICE TRUE)
        else()
            set(CANN_ENABLE_DEVICE FALSE)
        endif()
    endif()

    add_cann_third_party(makeself-fetch)

    if(CANN_COMPUTE_UNIT)
        set(CPACK_SOC "${CANN_COMPUTE_UNIT}")
    endif()

    if(CANN_SHARE_INFO_NAME)
        set(CPACK_PACKAGE_PARAM_NAME "${CANN_SHARE_INFO_NAME}")
    else()
        set(CPACK_PACKAGE_PARAM_NAME "${component}")
    endif()

    if(CANN_OUTPUT)
        if(ENABLE_UNIFIED_BUILD)
            set(CPACK_CMAKE_INSTALL_PREFIX "${CANN_CMAKE_DIR}/build_out")
        else()
            set(CPACK_CMAKE_INSTALL_PREFIX "${CANN_OUTPUT}")
        endif()
    else()
        set(CPACK_CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
    endif()

    set(CPACK_PACKAGE_NAME "${component}")
    set(CPACK_PACKAGE_VERSION "${CANN_VERSION_${component}_VERSION}")
    set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}")

    if(NOT CANN_NO_COMPONENT_INSTALL)
        set(CPACK_CANN_INSTALL_COMPONENT "${component}")
    endif()
    if(CANN_NO_CLEAN)
        set(CPACK_CANN_NO_CLEAN True)
    endif()
    set(CPACK_COMPONENTS_ALL "${component}")
    set(CPACK_CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
    set(CPACK_CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}")
    set(CPACK_ENABLE_DEVICE "${CANN_ENABLE_DEVICE}")
    set(CPACK_GENERATOR External)
    set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CANN_CMAKE_DIR}/scripts/package/makeself.cmake")
    set(CPACK_EXTERNAL_ENABLE_STAGING TRUE)
    set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
    set(CPACK_MAKESELF_PATH "${MAKESELF_PATH}")
    set(CPACK_BUILD_MODE "RUN_COPY")
    set(CPACK_TARGET_ARCH "${TARGET_ARCH}")
    include(CPack)
endfunction()

# 设置子工程打包
function(set_cann_subprj_package)
    cmake_parse_arguments(CANN "SUPERBUILD" "" "" ${ARGN})

    if(NOT TOPLEVEL_PROJECT AND NOT CANN_SUPERBUILD)
        return()
    endif()

    if(CANN_SUPERBUILD)
        set(CPACK_COMPONENTS_ALL "${CANN_PACKAGES}")
    else()
        get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
        list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")
    endif()
    set(CPACK_GENERATOR TGZ)
    set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
    set(CPACK_ARCHIVE_FILE_NAME "${PRODUCT_SIDE}")
    include(CPack)
endfunction()

macro(__cann_replace_cur_major_minor_ver)
    string(REPLACE CUR_MAJOR_MINOR_VER "${CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_VERSION_MAJOR_MINOR}" depend "${depend}")
endmacro()

# 设置包和版本号
function(set_cann_package name)
    cmake_parse_arguments(VERSION "" "VERSION" "" ${ARGN})
    set(VERSION "${VERSION_VERSION}")
    if(NOT name)
        message(FATAL_ERROR "The name parameter is not set in set_cann_package.")
    endif()
    if(NOT VERSION)
        message(FATAL_ERROR "The VERSION parameter is not set in set_cann_package(${name}).")
    endif()
    string(REGEX MATCH "^([0-9]+\\.[0-9]+)" VERSION_MAJOR_MINOR "${VERSION}")
    list(APPEND CANN_VERSION_PACKAGES "${name}")
    set(CANN_VERSION_PACKAGES "${CANN_VERSION_PACKAGES}" PARENT_SCOPE)
    set(CANN_VERSION_CURRENT_PACKAGE "${name}" PARENT_SCOPE)
    set(CANN_VERSION_${name}_VERSION "${VERSION}" PARENT_SCOPE)
    set(CANN_VERSION_${name}_VERSION_MAJOR_MINOR "${VERSION_MAJOR_MINOR}" PARENT_SCOPE)
    set(CANN_VERSION_${name}_BUILD_DEPS PARENT_SCOPE)
    set(CANN_VERSION_${name}_RUN_DEPS PARENT_SCOPE)
endfunction()

# 设置构建依赖
function(set_cann_build_dependencies pkg_name depend)
    if(NOT CANN_VERSION_CURRENT_PACKAGE)
        message(FATAL_ERROR "The set_cann_package must be invoked first.")
    endif()
    if(NOT pkg_name)
        message(FATAL_ERROR "The pkg_name parameter is not set in set_cann_build_dependencies.")
    endif()
    if(NOT depend)
        message(FATAL_ERROR "The depend parameter is not set in set_cann_build_dependencies.")
    endif()
    __cann_replace_cur_major_minor_ver()
    list(APPEND CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_BUILD_DEPS "${pkg_name}" "${depend}")
    set(CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_BUILD_DEPS "${CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_BUILD_DEPS}" PARENT_SCOPE)
endfunction()

# 设置运行依赖
function(set_cann_run_dependencies pkg_name depend)
    if(NOT CANN_VERSION_CURRENT_PACKAGE)
        message(FATAL_ERROR "The set_cann_package must be invoked first.")
    endif()
    if(NOT pkg_name)
        message(FATAL_ERROR "The pkg_name parameter is not set in set_cann_run_dependencies.")
    endif()
    if(NOT depend)
        message(FATAL_ERROR "The depend parameter is not set in set_cann_run_dependencies.")
    endif()
    __cann_replace_cur_major_minor_ver()
    list(APPEND CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_RUN_DEPS "${pkg_name}" "${depend}")
    set(CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_RUN_DEPS "${CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_RUN_DEPS}" PARENT_SCOPE)
endfunction()

# 检查构建依赖
function(check_cann_pkg_build_deps pkg_name)
    if(NOT TOPLEVEL_PROJECT)
        return()
    endif()
    execute_process(
        COMMAND python3 ${CANN_CMAKE_DIR}/scripts/version/check_build_dependencies.py "${ASCEND_INSTALL_PATH}" ${CANN_VERSION_${pkg_name}_BUILD_DEPS}
        RESULT_VARIABLE result
    )
    if(result)
        message(FATAL_ERROR "Check ${pkg_name} build dependencies failed!")
    endif()
endfunction()

# 添加生成version.info的目标
# 目标名格式为:version_${包名}_info
function(add_cann_version_info_targets)
    foreach(pkg_name ${CANN_VERSION_PACKAGES})
        add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/version.${pkg_name}.info
            COMMAND python3 ${CANN_CMAKE_DIR}/scripts/version/generate_version_info.py --output ${CMAKE_BINARY_DIR}/version.${pkg_name}.info
                    "${CANN_VERSION_${pkg_name}_VERSION}" ${CANN_VERSION_${pkg_name}_RUN_DEPS}
            DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake ${CANN_CMAKE_DIR}/scripts/version/generate_version_info.py
            VERBATIM
        )
        add_custom_target(version_${pkg_name}_info ALL DEPENDS ${CMAKE_BINARY_DIR}/version.${pkg_name}.info)
    endforeach()
endfunction()

# 将 cc 源文件的相对路径,转换为绝对路径
function(__cann_to_absolute_path origin_sources origin_source_dir output_sources)
    set(sources_list)
    foreach(source_file ${${origin_sources}})
        if(NOT IS_ABSOLUTE ${source_file} AND ${source_file} MATCHES "\\.(c|cc|cpp)$")
            list(APPEND sources_list ${${origin_source_dir}}/${source_file})
        else()
            list(APPEND sources_list ${source_file})
        endif()
    endforeach()
    set(${output_sources} ${sources_list} PARENT_SCOPE)
endfunction()

# 克隆 target 的属性到新 target,IGNORE_PROP 为需要跳过的属性名列表
#
# Usage:
#   clone_cann_target(ORIGIN ccl_kernel OUTPUT aicpu_custom)
#   clone_cann_target(ORIGIN ccl_kernel OUTPUT aicpu_custom IGNORE_PROP LINK_LIBRARIES)
#   clone_cann_target(ORIGIN ccl_kernel OUTPUT aicpu_custom IGNORE_PROP SOURCES LINK_LIBRARIES)
function(clone_cann_target)
    cmake_parse_arguments(ARG
        ""
        "ORIGIN;OUTPUT"
        "IGNORE_PROP"
        ${ARGN}
    )

    if(NOT ARG_ORIGIN OR NOT ARG_OUTPUT)
        message(FATAL_ERROR "clone_cann_target: ORIGIN or OUTPUT is required")
    endif()

    # 克隆源文件,同时将相对路径转换为绝对路径
    if(NOT "SOURCES" IN_LIST ARG_IGNORE_PROP)
        get_target_property(sourceFiles ${ARG_ORIGIN} SOURCES)
        get_target_property(sourceDir ${ARG_ORIGIN} SOURCE_DIR)
        __cann_to_absolute_path(sourceFiles sourceDir absolute_sources_files)
        target_sources(${ARG_OUTPUT} PRIVATE
            ${absolute_sources_files}
        )
    endif()

    # 克隆头文件搜索路径
    if(NOT "INCLUDE_DIRECTORIES" IN_LIST ARG_IGNORE_PROP)
        get_target_property(includeDirs ${ARG_ORIGIN} INCLUDE_DIRECTORIES)
        target_include_directories(${ARG_OUTPUT} PRIVATE
            ${includeDirs}
        )
    endif()

    # 克隆链接库
    if(NOT "LINK_LIBRARIES" IN_LIST ARG_IGNORE_PROP)
        get_target_property(linkLibs ${ARG_ORIGIN} LINK_LIBRARIES)
        target_link_libraries(${ARG_OUTPUT} PRIVATE
            ${linkLibs}
        )
    endif()

    # 克隆链接目录
    if(NOT "LINK_DIRECTORIES" IN_LIST ARG_IGNORE_PROP)
        get_target_property(linkDirs ${ARG_ORIGIN} LINK_DIRECTORIES)
        if(linkDirs)
            target_link_directories(${ARG_OUTPUT} PRIVATE
                ${linkDirs}
            )
        endif()
    endif()

    # 克隆宏定义
    if(NOT "COMPILE_DEFINITIONS" IN_LIST ARG_IGNORE_PROP)
        get_target_property(compileDefs ${ARG_ORIGIN} COMPILE_DEFINITIONS)
        if(compileDefs)
            target_compile_definitions(${ARG_OUTPUT} PRIVATE
                ${compileDefs}
            )
        endif()
    endif()

    # 克隆编译选项
    if(NOT "COMPILE_OPTIONS" IN_LIST ARG_IGNORE_PROP)
        get_target_property(compileOptions ${ARG_ORIGIN} COMPILE_OPTIONS)
        if(compileOptions)
            target_compile_options(${ARG_OUTPUT} PRIVATE
                ${compileOptions}
            )
        endif()
    endif()

    # 克隆链接选项
    if(NOT "LINK_OPTIONS" IN_LIST ARG_IGNORE_PROP)
        get_target_property(linkOpts ${ARG_ORIGIN} LINK_OPTIONS)
        if(linkOpts)
            target_link_options(${ARG_OUTPUT} PRIVATE
                ${linkOpts}
            )
        endif()
    endif()
endfunction()

# 打包目标文件和普通文件
# OUTPUT - 输出文件路径
# MANIFEST - 可选,manifest文件名
# OUTPUT_TARGET - 输出目标名
# SIZE_LIMIT - 可选,大小限制(单位KB),超出则报错
# TARGETS - 目标列表
# FILES - 文件列表
# 说明:如果设置了 CANN_VERSION_CURRENT_PACKAGE,会自动生成 .ini 文件并打包
function(cann_pack_targets_and_files)
    cmake_parse_arguments(ARG
        "GEN_INI"
        "OUTPUT;MANIFEST;OUTPUT_TARGET;TAR_ROOT_DIR;SIZE_LIMIT"
        "TARGETS;FILES"
        ${ARGN}
    )

    # --- Validation ---
    if(NOT ARG_OUTPUT)
        message(FATAL_ERROR "[pack_targets_and_files] OUTPUT is required")
    endif()

    if(NOT IS_ABSOLUTE "${ARG_OUTPUT}")
        set(ARG_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${ARG_OUTPUT}")
    endif()

    if(NOT ARG_OUTPUT_TARGET)
        message(FATAL_ERROR "[pack_targets_and_files] OUTPUT_TARGET is required")
    endif()

    # Generate safe target name
    get_filename_component(tar_basename "${ARG_OUTPUT}" NAME_WE)
    string(MAKE_C_IDENTIFIER "pack_${tar_basename}" safe_name)
    set(staging_root_dir "${CMAKE_CURRENT_BINARY_DIR}/_${safe_name}_stage")

    if(ARG_TAR_ROOT_DIR)
        set(staging_dir "${staging_root_dir}/${ARG_TAR_ROOT_DIR}")
        set(tar_src ${ARG_TAR_ROOT_DIR})
    else()
        set(staging_dir "${staging_root_dir}/${ARG_TAR_ROOT_DIR}")
        set(tar_src ".")
    endif()
    
    # Generate .ini file if CANN_VERSION_CURRENT_PACKAGE is set
    set(ini_file "")
    set(ini_version "")
    if(CANN_VERSION_CURRENT_PACKAGE)
        set(pkg_version "${CANN_VERSION_${CANN_VERSION_CURRENT_PACKAGE}_VERSION}")
        if(pkg_version)
            get_filename_component(output_dir "${ARG_OUTPUT}" DIRECTORY)
            get_filename_component(output_name "${ARG_OUTPUT}" NAME)
            if(output_name MATCHES "(.+)\\.tar\\.gz$")
                set(ini_basename "${CMAKE_MATCH_1}")
            elseif(output_name MATCHES "(.+)\\.tgz$")
                set(ini_basename "${CMAKE_MATCH_1}")
            else()
                get_filename_component(ini_basename "${output_name}" NAME_WE)
            endif()
            set(ini_file "${staging_dir}/${ini_basename}.ini")
            set(ini_version "${pkg_version}")
        endif()
    endif()

    # --- Collect all source items (as generator expressions) ---
    set(src_items "")
    foreach(tgt IN LISTS ARG_TARGETS)
        if(NOT TARGET ${tgt})
            message(FATAL_ERROR "[pack_targets_and_files] Target '${tgt}' does not exist")
        endif()

        get_target_property(type ${tgt} TYPE)
        if(type MATCHES "^(EXECUTABLE|SHARED_LIBRARY|STATIC_LIBRARY)$")
            list(APPEND src_items "$<TARGET_FILE:${tgt}>")
        endif()
    endforeach()
    list(APPEND src_items ${ARG_FILES})

    if(NOT src_items)
        message(FATAL_ERROR "[pack_targets_and_files] No targets or files specified to pack")
    endif()

    set(manifest_arg "")
    if(ARG_MANIFEST)
        if("${ARG_MANIFEST}" STREQUAL "")
            message(FATAL_ERROR "[pack] MANIFEST filename cannot be empty")
        endif()
        if(IS_ABSOLUTE "${ARG_MANIFEST}")
            message(FATAL_ERROR "[pack] MANIFEST must be relative (e.g., 'sha256sums.cfg')")
        endif()
        set(manifest_arg -D_MANIFEST_FILE=${staging_dir}/${ARG_MANIFEST})
    endif()

    add_custom_command(
        OUTPUT ${staging_dir}
        COMMAND ${CMAKE_COMMAND} -E make_directory "${staging_dir}"
        VERBATIM
    )

    set(ini_output "")
    set(ini_command "")
    set(ini_depends "")
    set(ini_copy_command "")
    if(ini_file)
        get_filename_component(output_dir "${ARG_OUTPUT}" DIRECTORY)
        set(ini_output "${ini_file}")
        set(ini_output_dir "${output_dir}/${ini_basename}.ini")
        set(ini_command COMMAND python3 ${CANN_CMAKE_DIR}/scripts/version/generate_package_ini.py
            "${ini_version}" --output "${ini_file}")
        set(ini_copy_command COMMAND ${CMAKE_COMMAND} -E copy "${ini_file}" "${ini_output_dir}")
        set(ini_depends ${CANN_CMAKE_DIR}/scripts/version/generate_package_ini.py)
    endif()

    set(size_check_command "")
    if(ARG_SIZE_LIMIT AND CMAKE_BUILD_TYPE STREQUAL "Release")
        set(size_check_command COMMAND ${CMAKE_COMMAND} -D_OUTPUT_FILE=${ARG_OUTPUT} -D_SIZE_LIMIT_KB=${ARG_SIZE_LIMIT} -P ${CANN_CMAKE_DIR}/function/_check_size_limit.cmake)
    endif()

    add_custom_command(
        OUTPUT "${ARG_OUTPUT}" ${ini_output}
        ${ini_command}
        ${ini_copy_command}
        COMMAND ${CMAKE_COMMAND}
            -D _STAGING_DIR=${staging_dir}
            ${manifest_arg}
            -D "_ITEMS=$<JOIN:${src_items},;>"
            -P "${CANN_CMAKE_DIR}/function/_pack_stage.cmake"
        COMMAND tar "czf" "${ARG_OUTPUT}" ${tar_src}
                "--mode=750"
        ${size_check_command}
        WORKING_DIRECTORY ${staging_root_dir}
        DEPENDS ${ARG_TARGETS} ${staging_dir} ${ini_depends}
        COMMENT "Packing with ${ARG_OUTPUT}"
        VERBATIM
    )

    add_custom_target(${ARG_OUTPUT_TARGET} ALL DEPENDS "${ARG_OUTPUT}")
    set_target_properties(${ARG_OUTPUT_TARGET} PROPERTIES
        TARGET_FILE "${ARG_OUTPUT}"
    )
endfunction()

function(add_cann_sign_file)
    cmake_parse_arguments(
        ARG
        ""
        "OUTPUT_TARGET;INPUT;CONFIG;RESULT_VAR"
        "SCRIPT_ARGS;DEPENDS"
        ${ARGN}
    )

    # --- Validation ---
    if(DEFINED CUSTOM_SIGN_SCRIPT AND NOT CUSTOM_SIGN_SCRIPT STREQUAL "")
        set(SIGN_SCRIPT ${CUSTOM_SIGN_SCRIPT})
    else()
        set(SIGN_SCRIPT)
    endif()

    if(ENABLE_SIGN)
        set(sign_flag "true")
    else()
        set(sign_flag "false")
    endif()

    foreach(var INPUT CONFIG RESULT_VAR)
        if(NOT ARG_${var})
            message(FATAL_ERROR "[sign_file] Missing required: ${var}")
        endif()
    endforeach()

    if(NOT EXISTS "${ARG_CONFIG}")
        message(FATAL_ERROR "[sign_file] Sign config not found: ${ARG_CONFIG}")
    endif()

    # Normalize input
    if(NOT IS_ABSOLUTE "${ARG_INPUT}")
        set(ARG_INPUT "${CMAKE_CURRENT_BINARY_DIR}/${ARG_INPUT}")
    endif()

    # Auto output path: ${CMAKE_CURRENT_BINARY_DIR}/signatures
    set(signatures_dir "${CMAKE_CURRENT_BINARY_DIR}/signatures")
    get_filename_component(input_name "${ARG_INPUT}" NAME)
    set(output_sig "${signatures_dir}/${input_name}")

    if(EXISTS "${SIGN_SCRIPT}")
        get_filename_component(EXT ${SIGN_SCRIPT} EXT) # 获取文件扩展名

        if (${EXT} STREQUAL ".sh")
            set(sign_cmd bash ${SIGN_SCRIPT} ${output_sig} ${ARG_CONFIG} ${sign_flag})
        elseif(${EXT} STREQUAL ".py")
            set(add_header ${CANN_CMAKE_DIR}/scripts/sign/add_header_sign.py)
            set(sign_builder ${CANN_CMAKE_DIR}/scripts/sign/community_sign_build.py)
            message(STATUS "Detected +++VERSION_INFO:${VERSION_INFO}, CANN_CMAKE_DIR:${CANN_CMAKE_DIR}")
            set(sign_cmd python3 ${add_header} ${signatures_dir} ${sign_flag} --bios_check_cfg=${ARG_CONFIG} --sign_script=${sign_builder} --version=${VERSION_INFO})
        endif()
    else()
        set(sign_cmd )
    endif()

    # Ensure dir exists
    file(MAKE_DIRECTORY "${signatures_dir}")

    # Target name
    get_filename_component(sign_basename "${ARG_INPUT}" NAME_WE)
    string(MAKE_C_IDENTIFIER "${sign_basename}" safe_name)

    if(ARG_OUTPUT_TARGET)
        set(sign_target "${ARG_OUTPUT_TARGET}")
    else()
        set(sign_target "sign_${safe_name}")
    endif()

    add_custom_command(
        OUTPUT "${output_sig}"
        COMMAND ${CMAKE_COMMAND} -E make_directory ${signatures_dir}
        COMMAND ${CMAKE_COMMAND} -E copy ${ARG_INPUT} ${output_sig}
        COMMAND ${sign_cmd}
        DEPENDS "${ARG_INPUT}" "${SIGN_SCRIPT}" ${ARG_DEPENDS} ${ARG_CONFIG}
        COMMENT "Signing: ${ARG_INPUT} → ${output_sig}"
        VERBATIM
    )

    add_custom_target(${sign_target} ALL DEPENDS "${output_sig}")

    # Return path via RESULT_VAR
    if(ARG_RESULT_VAR)
        set(${ARG_RESULT_VAR} "${output_sig}" PARENT_SCOPE)
    endif()
endfunction()

function(__cann_generate_stub_with_output_name name output_name)
    string(FIND ${output_name} "::" temp)
    if(temp EQUAL "-1")
        set(target_plain_name ${output_name})
    else()
        string(REPLACE "::" ";" temp_list ${output_name})
        list(GET temp_list 1 target_plain_name)
    endif()

    # 多个name可以对应一个output_name
    if(NOT TARGET ${target_plain_name}_stub_tmp)
        add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/stub/${target_plain_name}.c
            COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/stub
            COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/stub/${target_plain_name}.c)
        add_library(${target_plain_name}_stub_tmp SHARED ${CMAKE_CURRENT_BINARY_DIR}/stub/${target_plain_name}.c)
        set_target_properties(${target_plain_name}_stub_tmp PROPERTIES
            WINDOWS_EXPORT_ALL_SYMBOLS TRUE
            LIBRARY_OUTPUT_NAME ${target_plain_name} 
            RUNTIME_OUTPUT_NAME ${target_plain_name}
            LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/stub
            RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/stub)
    endif()

    add_library(${name} SHARED IMPORTED GLOBAL)
    if(UNIX)
        set_target_properties(${name} PROPERTIES
            IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/stub/lib${target_plain_name}.so")
    endif()
    if(WIN32)
        set_target_properties(${name} PROPERTIES
            IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/stub/${target_plain_name}.dll"
            IMPORTED_IMPLIB "${CMAKE_CURRENT_BINARY_DIR}/stub/${target_plain_name}.lib")
    endif()
    add_dependencies(${name} ${target_plain_name}_stub_tmp)
endfunction()

# 生成打桩库
function(generate_cann_stub_library name)
    cmake_parse_arguments(CANN "" "OUTPUT_NAME" "" ${ARGN})

    if(NOT CANN_OUTPUT_NAME)
        set(CANN_OUTPUT_NAME ${name})
    endif()

    __cann_generate_stub_with_output_name(${name} ${CANN_OUTPUT_NAME})
endfunction()

# 创建导入库头文件搜索目录
function(create_imported_interface_include_directories)
    foreach(target IN LISTS ARGN)
        get_property(DIRS TARGET ${target} PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
        foreach(DIR IN LISTS DIRS)
            if(NOT EXISTS "${DIR}")
                file(MAKE_DIRECTORY "${DIR}")
            endif()
        endforeach()
    endforeach()
endfunction()

# 生成版本号头文件
function(gen_cann_version_header pkg_name)
    cmake_parse_arguments(CANN "" "VERSION" "" ${ARGN})
    if(CANN_UNPARSED_ARGUMENTS)
        # 如果有2个位置参数,将第2个位置参数作为component
        list(GET CANN_UNPARSED_ARGUMENTS 0 component)
    else()
        set(component "${pkg_name}")
    endif()
    if(NOT CANN_VERSION)
        set(CANN_VERSION "${CANN_VERSION_${component}_VERSION}")
    endif()
    set(OUTPUT_PATH "${CMAKE_BINARY_DIR}/include/version/${pkg_name}_version.h")
    add_custom_command(OUTPUT ${OUTPUT_PATH}
        COMMAND bash ${CANN_CMAKE_DIR}/scripts/package/generate_version_header.sh "${pkg_name}" "${CANN_VERSION}" --output ${OUTPUT_PATH}
    )
    add_custom_target(gen_${pkg_name}_version_header ALL DEPENDS ${OUTPUT_PATH})
    if(NOT PRODUCT_SIDE STREQUAL "device")
        install(FILES ${OUTPUT_PATH}
            DESTINATION ${TARGET_ARCH}-linux/include/version
            PERMISSIONS OWNER_READ GROUP_READ
            COMPONENT ${component}
            ${INSTALL_OPTIONAL}
        )
    endif()
    if(NOT TARGET cann_version_headers)
        add_library(cann_version_headers INTERFACE)
        target_include_directories(cann_version_headers INTERFACE
            $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
        )
    endif()
    add_dependencies(cann_version_headers gen_${pkg_name}_version_header)
endfunction()