# -----------------------------------------------------------------------------------------------------------
# Copyright (c) 2025 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.
# -----------------------------------------------------------------------------------------------------------
# 定义 es_gen_esb_serial job pool
# 用于串行化 gen_esb 及其依赖的构建,避免 ar 和 ld 的文件竞态
set_property(GLOBAL PROPERTY JOB_POOLS es_gen_esb_serial=1)
# 在函数外部获取当前 cmake 文件的路径(函数内部 CMAKE_CURRENT_LIST_FILE 会指向调用者)
# 每次 include 时都重新设置,确保路径正确
get_filename_component(_ADD_ES_LIBRARY_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
message(STATUS "[add_es_library] Module loaded from: ${_ADD_ES_LIBRARY_CMAKE_DIR}")
# ======================================================================================================================
# 内部实现函数: _add_es_library_impl (不要直接调用此函数)
#
# 功能描述:
# 为指定的算子分包生成 Eager Style (ES) API 产物
#
# 参数说明:
# ES_PACKAGE_TARGET - [必需] 对外暴露的接口库 target 名称
# OPP_PROTO_TARGET - [必需] 算子原型库的 CMake target 名称
# OUTPUT_PATH - [必需] 产物输出的根目录
# EXCLUDE_OPS - [可选] 需要排除生成的OP算子
# SKIP_WHL - [可选] 是否跳过 Python wheel 包生成
#
# 依赖要求:
# - CMake 版本: >= 3.16 (使用 CONFIGURE_DEPENDS、configure_file COPYONLY 等特性)
# - gen_esb: 支持两种环境
# 1. 社区环境: 自动从 cmake 文件路径推导 run 包中的 gen_esb 位置
# 2. 开发环境: 使用源码编译的 gen_esb target
# - OPP_PROTO_TARGET: 必须存在,且需要设置 LIBRARY_OUTPUT_DIRECTORY 属性
# - eager_style_graph_builder_base: 支持两种来源
# 1. 源码编译的 target(优先): 如果存在 target,直接使用
# 2. run 包中的库: 自动推导并查找
# - Python3 可执行文件可用
# - setuptools 和 wheel Python 包已安装
#
# 注意事项:
# 1. ES_LINKABLE_AND_ALL_TARGET 应使用小写字母和下划线,建议以 es_ 开头(如 es_math, es_nn)
# 2. OPP_PROTO_TARGET 必须存在且已设置 LIBRARY_OUTPUT_DIRECTORY 属性
# 3. 函数会自动从 OPP_PROTO_TARGET 的输出路径推导 ASCEND_OPP_PATH
# 4. 函数会自动检测 gen_esb 位置(run 包或源码编译)
# 5. 生成的 whl 包版本号默认为 1.0.0,可通过修改函数内 setup.py 模板调整
# 6. 使用每包独立文件锁确保同一包的代码生成串行,避免文件竞态
# 7. 多个 ES 包会自动添加依赖关系,确保共享依赖只构建一次
# ======================================================================================================================
# ======================================================================================================================
# add_custom_command 封装宏
#
# 统一封装代码生成命令,内部根据 COMBINED_COMMERCIAL_MODE 自动选择模式:
# 单次模式(COMBINED_COMMERCIAL_MODE=FALSE):仅执行一次 gen_esb,生成 C++ API
# 双次模式(COMBINED_COMMERCIAL_MODE=TRUE): 步骤1 代码生成 + 步骤2 历史原型库归档
#
# 三处调用点(外部 gen_esb 有依赖 / 外部 gen_esb 无依赖 / 源码 gen_esb)共用此宏,
# 仅 gen_esb 路径、LD_LIBRARY_PATH、EXCLUDE_OPS、DEPENDS、COMMENT 不同,由调用方通过参数传入。
#
# 参数:
# _gen_esb_exe gen_esb 可执行路径(字面量或生成器表达式 $<TARGET_FILE:gen_esb>)
# _lib_dir LD_LIBRARY_PATH(run 包环境传 ${ASCEND_LIB_DIR},源码环境传空字符串)
# _excl_ops 排除算子列表(INTERFACE 库传空字符串,其他传 ${ARG_EXCLUDE_OPS})
# _comment COMMENT 说明文字
#
# 调用前需设置 _COMBINED_MODE_DEPENDS:
# 有依赖时:set(_COMBINED_MODE_DEPENDS "DEPENDS;dep1;dep2")
# 无依赖时:unset(_COMBINED_MODE_DEPENDS)
# ======================================================================================================================
macro(_es_add_gen_esb_cmd _gen_esb_exe _lib_dir _excl_ops _comment)
if (COMBINED_COMMERCIAL_MODE)
set(_HIST_STAGE_DIR "${FINAL_OUTPUT_PATH}")
if ("${_AUTO_HISTORY_REGISTRY}" STREQUAL "${FINAL_OUTPUT_PATH}")
# 首次构建(历史库尚不存在于 CANN 路径):_AUTO_HISTORY_REGISTRY == ARG_OUTPUT_PATH,
set(_PREPOPULATE_STAGING "")
else ()
# 非首次构建:将 CANN 只读历史库内容合并复制到 ARG_OUTPUT_PATH
set(_PREPOPULATE_STAGING
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_AUTO_HISTORY_REGISTRY}" "${FINAL_OUTPUT_PATH}"
COMMAND chmod u+w "${FINAL_OUTPUT_PATH}/index.json"
)
endif ()
add_custom_command(
OUTPUT ${CODE_GEN_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Generating ES code (combined commercial mode) for package: ${ARG_ES_LINKABLE_AND_ALL_TARGET}"
COMMAND ${CMAKE_COMMAND} -E remove_directory ${GEN_CODE_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${GEN_CODE_DIR}
# 步骤1: 代码生成(消费历史原型库,gen_esb 自动选取窗口内历史版本对比)
COMMAND bash ${ES_LOCK_SCRIPT}
${GEN_CODE_DIR}/.gen.lock
${_gen_esb_exe}
${OPP_PROTO_PATH}
${GEN_CODE_DIR}
${MODULE_NAME}
"${_lib_dir}"
"${_excl_ops}"
""
""
${HISTORY_REGISTRY_ARG}
""
""
# 步骤2: 归档到 ARG_OUTPUT_PATH;若已有历史库则先复制并授权,gen_esb 追加新条目
${_PREPOPULATE_STAGING}
COMMAND ${CMAKE_COMMAND} -E make_directory "${_HIST_STAGE_DIR}"
COMMAND bash ${ES_LOCK_SCRIPT}
${_HIST_STAGE_DIR}/.extract.lock
${_gen_esb_exe}
${OPP_PROTO_PATH}
${_HIST_STAGE_DIR}
${MODULE_NAME}
"${_lib_dir}"
""
${EXTRACT_HISTORY_FLAG}
${RELEASE_VERSION_ARG}
""
${RELEASE_DATE_ARG}
${BRANCH_NAME_ARG}
COMMAND ${CMAKE_COMMAND} -E echo "[ES] Historical prototype library generation completed: ${FINAL_OUTPUT_PATH}"
# 步骤3: 动态生成 wrapper 文件的 include 内容
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_WRAPPER_SCRIPT}
COMMAND ${CMAKE_COMMAND} -E touch ${CODE_GEN_FLAG}
${_COMBINED_MODE_DEPENDS}
COMMENT "${_comment}"
JOB_POOL es_gen_esb_serial
VERBATIM
)
else ()
# 历史库路径有效时:代码生成后将 CANN 历史库内容合并复制到 ARG_OUTPUT_PATH
if (_AUTO_HISTORY_REGISTRY)
set(_COPY_EXISTING_HISTORY
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_AUTO_HISTORY_REGISTRY}" "${FINAL_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E echo "[ES] Existing history registry copied to output: ${FINAL_OUTPUT_PATH}"
)
else ()
set(_COPY_EXISTING_HISTORY "")
endif ()
add_custom_command(
OUTPUT ${CODE_GEN_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Generating ES code for package: ${ARG_ES_LINKABLE_AND_ALL_TARGET}"
COMMAND ${CMAKE_COMMAND} -E remove_directory ${GEN_CODE_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${GEN_CODE_DIR}
# 单次代码生成
COMMAND bash ${ES_LOCK_SCRIPT}
${GEN_CODE_DIR}/.gen.lock
${_gen_esb_exe}
${OPP_PROTO_PATH}
${GEN_CODE_DIR}
${MODULE_NAME}
"${_lib_dir}"
"${_excl_ops}"
${CODE_GEN_STEP_EXTRACT_FLAG}
${RELEASE_VERSION_ARG}
${HISTORY_REGISTRY_ARG}
${RELEASE_DATE_ARG}
${BRANCH_NAME_ARG}
# 版本重复时将现有历史库复制到输出目录
${_COPY_EXISTING_HISTORY}
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_WRAPPER_SCRIPT}
COMMAND ${CMAKE_COMMAND} -E touch ${CODE_GEN_FLAG}
${_COMBINED_MODE_DEPENDS}
COMMENT "${_comment}"
JOB_POOL es_gen_esb_serial
VERBATIM
)
endif ()
endmacro()
function(_add_es_library_impl)
# 0. 生成辅助 shell 脚本(自包含,无需外部文件)
# 在首次调用时创建 run_gen_esb_with_lock.sh 到构建目录
set(ES_LOCK_SCRIPT "${CMAKE_BINARY_DIR}/cmake/run_gen_esb_with_lock.sh")
# 将 ENABLE_ASAN 归一化为 "true" 或空字符串
# CMake if() 自动识别 TRUE/True/ON/On/on/YES/yes/1 等真值,无需逐一枚举
if(ENABLE_ASAN)
set(ENABLE_ASAN_NORMALIZED "true")
else()
set(ENABLE_ASAN_NORMALIZED "")
endif()
if (NOT EXISTS ${ES_LOCK_SCRIPT})
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/cmake")
file(WRITE ${ES_LOCK_SCRIPT} "#!/bin/bash
# Copyright (c) 2025 Huawei Technologies Co., Ltd.
# Auto-generated by generate_es_package.cmake
# ES code generation wrapper script with detailed logging and flock fallback
#
# Args: \$1=lock_file \$2=gen_esb_path \$3=ASCEND_OPP_PATH \$4=output_dir \$5=module_name \$6=LD_LIBRARY_PATH(optional) \$7=EXCLUDE_OPS(optional) \$8=MODE_ARG(optional, e.g. --es_mode=extract_history) \$9=RELEASE_VERSION(optional) \$10=HISTORY_REGISTRY_ARG(optional) \$11=RELEASE_DATE_ARG(optional) \$12=BRANCH_NAME_ARG(optional)
LOCK_FILE=\"\$1\"
GEN_ESB_EXE=\"\$2\"
OPP_PATH=\"\$3\"
OUTPUT_DIR=\"\$4\"
MODULE_NAME=\"\$5\"
LIB_PATH=\"\${6:-}\"
EXCLUDE_OPS=\"\${7:-}\"
EXTRACT_HISTORY_FLAG=\"\${8:-}\"
RELEASE_VERSION_FLAG=\"\${9:-}\"
HISTORY_REGISTRY_ARG=\"\${10:-}\"
RELEASE_DATE_ARG=\"\${11:-}\"
BRANCH_NAME_ARG=\"\${12:-}\"
# Enable pipefail to ensure exit code from gen_esb is captured through pipes
set -o pipefail
# Debug log file
DEBUG_LOG=\"\${OUTPUT_DIR}/.gen_esb_debug.log\"
mkdir -p \"\${OUTPUT_DIR}\"
log_debug() {
echo \"[\$(date '+%Y-%m-%d %H:%M:%S')] [ES-GEN] \$*\" | tee -a \"\${DEBUG_LOG}\" >&2
}
log_debug \"========== ES Code Generation Started ==========\"
log_debug \"Lock file: \${LOCK_FILE}\"
log_debug \"Gen ESB: \${GEN_ESB_EXE}\"
log_debug \"OPP path: \${OPP_PATH}\"
log_debug \"Output dir: \${OUTPUT_DIR}\"
log_debug \"Module: \${MODULE_NAME}\"
log_debug \"Lib path: \${LIB_PATH}\"
log_debug \"Exclude ops: \${EXCLUDE_OPS}\"
FLAG_FILE=\"\${OUTPUT_DIR}/generated_code.flag\"
INPROGRESS_FILE=\"\${OUTPUT_DIR}/.generating.lock\"
MAX_RETRIES=10
RETRY_COUNT=0
quick_check_if_done() {
if [ -f \"\${INPROGRESS_FILE}\" ]; then
log_debug \"[Quick Check] Another process is generating code, waiting...\"
local wait_count=0
while [ -f \"\${INPROGRESS_FILE}\" ] && [ \$wait_count -lt 20 ]; do
sleep 0.5
wait_count=\$((wait_count + 1))
done
log_debug \"[Quick Check] Wait completed (waited \${wait_count} times)\"
fi
if [ -f \"\${FLAG_FILE}\" ]; then
local has_h=0
local has_hc=0
local has_py=0
[ -f \"\${OUTPUT_DIR}/es_\${MODULE_NAME}_ops.h\" ] && has_h=1
[ -f \"\${OUTPUT_DIR}/es_\${MODULE_NAME}_ops_c.h\" ] && has_hc=1
[ -f \"\${OUTPUT_DIR}/es_\${MODULE_NAME}_ops.py\" ] && has_py=1
if [ \${has_h} -eq 1 ] && [ \${has_hc} -eq 1 ] && [ \${has_py} -eq 1 ]; then
log_debug \"[Fast Path] Code already generated, skipping\"
return 0
else
log_debug \"[Stale FLAG] Removing stale flag (h=\${has_h} hc=\${has_hc} py=\${has_py})\"
rm -f \"\${FLAG_FILE}\"
fi
fi
return 1
}
execute_gen_esb() {
if quick_check_if_done; then
return 0
fi
touch \"\${INPROGRESS_FILE}\"
trap \"rm -f '\${INPROGRESS_FILE}'\" EXIT INT TERM
log_debug \"Waiting for filesystem sync...\"
sleep 0.3
sync
while [ \$RETRY_COUNT -lt \$MAX_RETRIES ]; do
if [ ! -x \"\${GEN_ESB_EXE}\" ]; then
log_debug \"[Retry \$((RETRY_COUNT + 1))/\$MAX_RETRIES] gen_esb not executable, waiting...\"
sleep 0.3
sync
RETRY_COUNT=\$((RETRY_COUNT + 1))
continue
fi
log_debug \"[Attempt \$((RETRY_COUNT + 1))/\$MAX_RETRIES] Executing gen_esb...\"
if [ -n \"\$LIB_PATH\" ]; then
ENV_PREFIX=\"LD_LIBRARY_PATH=\${LIB_PATH}:\\\$LD_LIBRARY_PATH ASCEND_OPP_PATH=\${OPP_PATH}\"
else
ENV_PREFIX=\"ASCEND_OPP_PATH=\${OPP_PATH}\"
fi
if [[ \"${ENABLE_ASAN_NORMALIZED}\" == \"true\" ]]; then
USE_ASAN=\$(gcc -print-file-name=libasan.so)
ENV_PREFIX=\"\${ENV_PREFIX} LD_PRELOAD=\${USE_ASAN}\"
fi
log_debug \"Command: \$ENV_PREFIX \\\"\${GEN_ESB_EXE}\\\" --output_dir=\\\"\${OUTPUT_DIR}\\\" --module_name=\\\"\${MODULE_NAME}\\\" --exclude_ops=\\\"\${EXCLUDE_OPS}\\\" \${EXTRACT_HISTORY_FLAG} \${RELEASE_VERSION_FLAG} \${HISTORY_REGISTRY_ARG} \${RELEASE_DATE_ARG} \${BRANCH_NAME_ARG}\"
# Execute gen_esb directly (without env -i, with pipefail enabled)
if eval \"\$ENV_PREFIX \\\"\${GEN_ESB_EXE}\\\" --output_dir=\\\"\${OUTPUT_DIR}\\\" --module_name=\\\"\${MODULE_NAME}\\\" --exclude_ops=\\\"\${EXCLUDE_OPS}\\\" \${EXTRACT_HISTORY_FLAG} \${RELEASE_VERSION_FLAG} \${HISTORY_REGISTRY_ARG} \${RELEASE_DATE_ARG} \${BRANCH_NAME_ARG} 2>&1 | tee -a \\\"\${DEBUG_LOG}\\\"\"; then
log_debug \"[Success] gen_esb executed successfully\"
return 0
else
EXIT_CODE=\$?
# Exit code 139 = SIGSEGV (128 + 11), retry for ASan-induced failures
if [ \$EXIT_CODE -eq 139 ]; then
log_debug \"[Retriable] SIGSEGV (ASan shadow memory conflict), retrying...\"
sleep 0.5
sync
RETRY_COUNT=\$((RETRY_COUNT + 1))
elif [ \$EXIT_CODE -eq 126 ] || [ \$EXIT_CODE -eq 127 ]; then
log_debug \"[Retriable] Exit code \$EXIT_CODE, retrying...\"
sleep 0.5
sync
RETRY_COUNT=\$((RETRY_COUNT + 1))
else
log_debug \"[Non-retriable] Exit code \$EXIT_CODE\"
return \$EXIT_CODE
fi
fi
done
log_debug \"[Final Failure] Reached maximum retry count \$MAX_RETRIES\"
return 1
}
log_debug \"========== Main Logic Started ==========\"
if quick_check_if_done; then
log_debug \"Quick check passed, exiting\"
exit 0
fi
if command -v flock &> /dev/null; then
log_debug \"Using flock file locking mechanism\"
if flock -x -n 200 2>/dev/null; then
log_debug \"[flock] Acquired lock immediately\"
execute_gen_esb
EXIT_CODE=\$?
log_debug \"[flock] Execution completed, exit code: \$EXIT_CODE\"
exit \$EXIT_CODE
elif flock -x -w 180 200 2>/dev/null; then
log_debug \"[flock] Acquired lock after waiting (max 3 minutes)\"
if quick_check_if_done; then
log_debug \"[flock] Another worker already completed, skipping\"
exit 0
fi
execute_gen_esb
EXIT_CODE=\$?
log_debug \"[flock] Execution completed, exit code: \$EXIT_CODE\"
exit \$EXIT_CODE
else
log_debug \"[flock] Failed to acquire lock after long wait, entering polling mode\"
POLL_COUNT=0
MAX_POLLS=360
while [ \$POLL_COUNT -lt \$MAX_POLLS ]; do
sleep 0.5
if quick_check_if_done; then
log_debug \"[Polling] Code generation completed, exiting\"
exit 0
fi
POLL_COUNT=\$((POLL_COUNT + 1))
if [ \$((\$POLL_COUNT % 10)) -eq 0 ]; then
log_debug \"[Polling] Waiting... (\$POLL_COUNT/\$MAX_POLLS)\"
fi
done
log_debug \"[Final Failure] Polling timeout and code not generated\"
if quick_check_if_done; then
log_debug \"[Final Check] Code generated, exiting\"
exit 0
fi
log_debug \"[Final Check] Code still not generated, exiting\"
exit 1
fi 200>\"\${LOCK_FILE}\"
else
log_debug \"Warning: flock not available, using mkdir fallback mechanism\"
LOCK_DIR=\"\${OUTPUT_DIR}/.gen_esb.lock.d\"
RETRY_LOCK_COUNT=0
MAX_LOCK_RETRIES=30
while [ \$RETRY_LOCK_COUNT -lt \$MAX_LOCK_RETRIES ]; do
if mkdir \"\${LOCK_DIR}\" 2>/dev/null; then
log_debug \"[mkdir lock] Successfully acquired lock\"
trap \"rmdir '\${LOCK_DIR}' 2>/dev/null\" EXIT INT TERM
execute_gen_esb
EXIT_CODE=\$?
rmdir \"\${LOCK_DIR}\" 2>/dev/null
log_debug \"[mkdir lock] Released lock, exit code: \$EXIT_CODE\"
exit \$EXIT_CODE
else
log_debug \"[mkdir lock] Lock occupied, waiting... (attempt \$((RETRY_LOCK_COUNT + 1))/\$MAX_LOCK_RETRIES)\"
sleep 0.5
if quick_check_if_done; then
log_debug \"[mkdir lock] Another worker already completed, skipping\"
exit 0
fi
RETRY_LOCK_COUNT=\$((RETRY_LOCK_COUNT + 1))
fi
done
log_debug \"[mkdir lock] Failed to acquire lock (timeout)\"
if quick_check_if_done; then
log_debug \"[mkdir lock] Final check passed, skipping\"
exit 0
fi
log_debug \"[mkdir lock] Final check failed, exiting\"
exit 1
fi
log_debug \"========== Script End (should not reach here) ==========\"
exit 1
")
# 设置脚本可执行权限
execute_process(COMMAND chmod +x ${ES_LOCK_SCRIPT})
message(STATUS "Generated helper script: ${ES_LOCK_SCRIPT}")
endif ()
# 1. 解析函数参数
set(options SKIP_WHL)
set(oneValueArgs ES_LINKABLE_AND_ALL_TARGET OPP_PROTO_TARGET OUTPUT_PATH EXCLUDE_OPS)
set(multiValueArgs "")
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 1.1. 只有 add_es_library_and_whl 调用时才需要检查 Python3
if (NOT ARG_SKIP_WHL)
if (NOT Python3_EXECUTABLE)
find_package(Python3 COMPONENTS Interpreter)
if (Python3_EXECUTABLE)
message(STATUS "Found Python3: ${Python3_EXECUTABLE}")
else ()
message(FATAL_ERROR "Python3 is required for wheel package generation, but not found.\n"
"Please choose one of the following options:\n"
" 1. Confirm if python3 is installed (run 'which python3' or 'python3 --version')\n"
" 2. If python3 exists but CMake cannot find it, you can define Python3_EXECUTABLE in your CMakeLists.txt:\n"
" set(Python3_EXECUTABLE /path/to/python3)\n"
" 3. If you don't need the Python wheel package, you can use 'add_es_library'")
endif ()
else ()
message(STATUS "Using existing Python3: ${Python3_EXECUTABLE}")
endif ()
endif ()
# 2. 参数校验
if (NOT ARG_ES_LINKABLE_AND_ALL_TARGET)
message(FATAL_ERROR "_add_es_library_impl: ES_LINKABLE_AND_ALL_TARGET is required")
endif ()
if (NOT ARG_OPP_PROTO_TARGET)
message(FATAL_ERROR "_add_es_library_impl: OPP_PROTO_TARGET is required")
endif ()
if (NOT ARG_OUTPUT_PATH)
message(STATUS "_add_es_library_impl: OUTPUT_PATH not specified, artifacts will remain in build directory")
else ()
message(STATUS "_add_es_library_impl: OUTPUT_PATH is ${ARG_OUTPUT_PATH}")
endif ()
if (NOT ARG_EXCLUDE_OPS)
message(STATUS "_add_es_library_impl: EXCLUDE_OPS is not provided")
else()
message(STATUS "_add_es_library_impl: EXCLUDE_OPS is ${ARG_EXCLUDE_OPS}")
endif ()
# 历史原型库相关参数
# 使用 cmake 变量(-D 传入);若未定义则从同名环境变量捕获。
# GE_ES_EXTRACT_HISTORY: bool 开关,ON 时启用历史原型库归档模式(两次 gen_esb 调用);
# 不设置或 OFF 时走纯代码生成模式(仅生成 C++ API,不归档)
# GE_ES_RELEASE_VERSION: 当前新版本号(例如 "8.0.RC1"),用于历史原型库归档
# GE_ES_RELEASE_DATE: 归档时的发布日期(可选,格式 YYYY-MM-DD,不指定则 gen_esb 使用当前日期)
# GE_ES_BRANCH_NAME: 构建分支名(可选;master 分支自动屏蔽归档参数)
#
# 历史原型库路径由函数内部从 cmake 文件路径自动推导(${CANN_INSTALL_PATH}/cann/opp/history_registry/<module>),
# 路径存在且非空时自动传 --history_registry,无需用户传参。
#
# 从环境变量兜底捕获(cmake 变量未定义时生效)
foreach(_ES_VAR GE_ES_EXTRACT_HISTORY GE_ES_RELEASE_VERSION GE_ES_RELEASE_DATE GE_ES_BRANCH_NAME)
if (NOT DEFINED ${_ES_VAR} AND DEFINED ENV{${_ES_VAR}})
set(${_ES_VAR} "$ENV{${_ES_VAR}}")
message(STATUS "[ES] Captured from environment variable: ${_ES_VAR}=${${_ES_VAR}}")
endif ()
endforeach ()
# master 分支:屏蔽全部归档参数,走纯代码生成模式;历史原型库路径仍传递(用于生成带重载 C++ API)
if (GE_ES_BRANCH_NAME STREQUAL "master")
if (GE_ES_EXTRACT_HISTORY OR GE_ES_RELEASE_VERSION OR GE_ES_RELEASE_DATE)
message(STATUS "[ES] Branch is master, ignoring GE_ES_EXTRACT_HISTORY/GE_ES_RELEASE_VERSION/GE_ES_RELEASE_DATE/GE_ES_BRANCH_NAME, using code-generation-only mode")
endif ()
set(GE_ES_EXTRACT_HISTORY OFF)
set(GE_ES_RELEASE_VERSION "")
set(GE_ES_RELEASE_DATE "")
set(GE_ES_BRANCH_NAME "")
endif ()
set(EXTRACT_HISTORY_FLAG "")
if (GE_ES_EXTRACT_HISTORY)
set(EXTRACT_HISTORY_FLAG "--es_mode=extract_history")
message(STATUS "[ES] Historical prototype library mode enabled (GE_ES_EXTRACT_HISTORY=ON), gen_esb will append --es_mode=extract_history")
endif ()
set(RELEASE_VERSION_ARG "")
if (GE_ES_RELEASE_VERSION)
set(RELEASE_VERSION_ARG "--release_version=${GE_ES_RELEASE_VERSION}")
endif ()
set(RELEASE_DATE_ARG "")
if (GE_ES_RELEASE_DATE)
set(RELEASE_DATE_ARG "--release_date=${GE_ES_RELEASE_DATE}")
endif ()
set(BRANCH_NAME_ARG "")
if (GE_ES_BRANCH_NAME)
set(BRANCH_NAME_ARG "--branch_name=${GE_ES_BRANCH_NAME}")
endif ()
# 2.0. 提前定义 BUILD_DIR(OBJECT 分支和后续逻辑需要)
set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_ES_LINKABLE_AND_ALL_TARGET}_build")
# 根据 OUTPUT_PATH 是否提供决定产物拷贝策略
if (ARG_OUTPUT_PATH)
set(DO_COPY_INSTALL TRUE)
set(INCLUDE_DIR "${ARG_OUTPUT_PATH}/include/${ARG_ES_LINKABLE_AND_ALL_TARGET}")
set(LIB_DIR "${ARG_OUTPUT_PATH}/lib64")
set(WHL_DIR "${ARG_OUTPUT_PATH}/whl")
set(FINAL_OUTPUT_PATH "${ARG_OUTPUT_PATH}")
else ()
set(DO_COPY_INSTALL FALSE)
set(FINAL_OUTPUT_PATH "${BUILD_DIR}")
endif ()
# 2.1. 检查 OPP_PROTO_TARGET 是否存在
if (NOT TARGET ${ARG_OPP_PROTO_TARGET})
message(FATAL_ERROR "_add_es_library_impl: OPP_PROTO_TARGET '${ARG_OPP_PROTO_TARGET}' is not a valid CMake target")
endif ()
# 2.2. 从 OPP_PROTO_TARGET 获取原型库输出目录
# 支持三种方式:
# 1. 常规库(SHARED/STATIC):从 LIBRARY_OUTPUT_DIRECTORY 获取
# 2. INTERFACE 库(包装):从 INTERFACE_LIBRARY_OUTPUT_DIRECTORY 获取(自定义属性)
# 3. OBJECT 库:创建临时 SHARED 库,直接输出到标准 OPP 路径
# 先检查 target 类型
get_target_property(TARGET_TYPE ${ARG_OPP_PROTO_TARGET} TYPE)
if (TARGET_TYPE STREQUAL "OBJECT_LIBRARY")
# OBJECT 库:创建临时 SHARED 库,直接输出到标准 OPP 路径
message(STATUS "add_es_package: Detected OBJECT_LIBRARY, creating temporary SHARED library")
# 创建标准路径
set(STANDARD_OPP_BASE "${BUILD_DIR}/opp_standard_path_${ARG_OPP_PROTO_TARGET}")
set(STANDARD_OPP_PROTO_DIR "${STANDARD_OPP_BASE}/op_proto/custom")
file(MAKE_DIRECTORY ${STANDARD_OPP_PROTO_DIR})
# 创建临时库名称
set(TEMP_PROTO_TARGET "${ARG_ES_LINKABLE_AND_ALL_TARGET}__temp_proto")
# 创建临时 SHARED 库包装 OBJECT 库
add_library(${TEMP_PROTO_TARGET} SHARED $<TARGET_OBJECTS:${ARG_OPP_PROTO_TARGET}>)
# 设置输出目录为标准路径,设置命名格式
set_target_properties(${TEMP_PROTO_TARGET} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${STANDARD_OPP_PROTO_DIR}"
OUTPUT_NAME "${ARG_OPP_PROTO_TARGET}_temp"
PREFIX "lib_"
SUFFIX ".so"
)
# 复制原始 OBJECT 库的链接依赖到临时库
# $<TARGET_OBJECTS:...> 会直接使用已编译的 .o 文件,不需要复制编译选项
# 但需要复制链接库
get_target_property(_OBJ_LINK_LIBS ${ARG_OPP_PROTO_TARGET} LINK_LIBRARIES)
if (_OBJ_LINK_LIBS)
target_link_libraries(${TEMP_PROTO_TARGET} PRIVATE ${_OBJ_LINK_LIBS})
endif ()
add_dependencies(${TEMP_PROTO_TARGET} ${ARG_OPP_PROTO_TARGET})
# 后续使用临时库替代原始目标,路径直接使用标准路径
set(EFFECTIVE_OPP_PROTO_TARGET ${TEMP_PROTO_TARGET})
set(OPP_PROTO_OUTPUT_DIR "${STANDARD_OPP_PROTO_DIR}")
message(STATUS "add_es_package: Created temporary SHARED library: ${TEMP_PROTO_TARGET}")
message(STATUS " - Standard output path: ${STANDARD_OPP_PROTO_DIR}")
message(STATUS " - Output name: lib_${ARG_OPP_PROTO_TARGET}_temp.so")
elseif (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
# INTERFACE 库,使用自定义属性
get_target_property(OPP_PROTO_OUTPUT_DIR ${ARG_OPP_PROTO_TARGET} INTERFACE_LIBRARY_OUTPUT_DIRECTORY)
if (NOT OPP_PROTO_OUTPUT_DIR OR OPP_PROTO_OUTPUT_DIR STREQUAL "OPP_PROTO_OUTPUT_DIR-NOTFOUND")
message(FATAL_ERROR "add_es_package: OPP_PROTO_TARGET '${ARG_OPP_PROTO_TARGET}' is an INTERFACE library\n"
"Please set custom property: set_target_properties(${ARG_OPP_PROTO_TARGET} PROPERTIES INTERFACE_LIBRARY_OUTPUT_DIRECTORY <path>)")
endif ()
message(STATUS "add_es_package: Detected INTERFACE library, using INTERFACE_LIBRARY_OUTPUT_DIRECTORY property")
set(EFFECTIVE_OPP_PROTO_TARGET ${ARG_OPP_PROTO_TARGET})
else ()
# 常规库,使用标准属性
get_target_property(OPP_PROTO_OUTPUT_DIR ${ARG_OPP_PROTO_TARGET} LIBRARY_OUTPUT_DIRECTORY)
if (NOT OPP_PROTO_OUTPUT_DIR OR OPP_PROTO_OUTPUT_DIR STREQUAL "OPP_PROTO_OUTPUT_DIR-NOTFOUND")
# 如果未设置,使用默认输出路径
if (CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(OPP_PROTO_OUTPUT_DIR "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "add_es_package: Using CMAKE_LIBRARY_OUTPUT_DIRECTORY: ${OPP_PROTO_OUTPUT_DIR}")
else ()
set(OPP_PROTO_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
message(STATUS "add_es_package: Using default output directory: ${OPP_PROTO_OUTPUT_DIR}")
endif ()
endif ()
set(EFFECTIVE_OPP_PROTO_TARGET ${ARG_OPP_PROTO_TARGET})
endif ()
message(STATUS "add_es_package: OPP_PROTO_TARGET output directory: ${OPP_PROTO_OUTPUT_DIR}")
# 2.3. 从 LIBRARY_OUTPUT_DIRECTORY 推导 ASCEND_OPP_PATH
# 需要去掉 /op_proto/custom 或 /built-in/op_proto 等后缀
set(OPP_BASE_PATH "${OPP_PROTO_OUTPUT_DIR}")
# 尝试移除常见的路径后缀(注意 built-in 是连字符)
string(REGEX REPLACE "/op_proto/custom$" "" OPP_BASE_PATH "${OPP_BASE_PATH}")
string(REGEX REPLACE "/built-in/op_proto$" "" OPP_BASE_PATH "${OPP_BASE_PATH}")
string(REGEX REPLACE "/op_proto$" "" OPP_BASE_PATH "${OPP_BASE_PATH}")
message(STATUS "add_es_package: Derived ASCEND_OPP_PATH: ${OPP_BASE_PATH}")
# 2.3.1. 如果不是标准路径,创建标准路径并拷贝原型库
set(USE_STANDARD_PATH FALSE)
set(OPP_COPY_FLAG "")
# 如果路径没有变化,说明不是标准的 OPP 目录结构
if (OPP_BASE_PATH STREQUAL OPP_PROTO_OUTPUT_DIR)
# 非标准路径,需要创建标准路径
set(USE_STANDARD_PATH TRUE)
set(STANDARD_OPP_BASE "${BUILD_DIR}/opp_standard_path_${ARG_OPP_PROTO_TARGET}")
set(STANDARD_OPP_PROTO_DIR "${STANDARD_OPP_BASE}/op_proto/custom")
file(MAKE_DIRECTORY ${STANDARD_OPP_PROTO_DIR})
# 只处理非 INTERFACE 类型的库
if (NOT TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
# 添加自定义命令拷贝原型库 .so 到标准路径
# 使用 $<TARGET_FILE:target> 获取实际的 .so 文件位置(运行时确定)
set(OPP_COPY_FLAG "${BUILD_DIR}/.opp_copied.flag")
add_custom_command(
OUTPUT ${OPP_COPY_FLAG}
COMMAND ${CMAKE_COMMAND} -E make_directory ${STANDARD_OPP_PROTO_DIR}
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${EFFECTIVE_OPP_PROTO_TARGET}> ${STANDARD_OPP_PROTO_DIR}/
COMMAND ${CMAKE_COMMAND} -E touch ${OPP_COPY_FLAG}
DEPENDS ${EFFECTIVE_OPP_PROTO_TARGET}
COMMENT "Copying OPP proto library to standard path..."
)
# 更新 OPP_BASE_PATH 为标准路径
set(OPP_BASE_PATH ${STANDARD_OPP_BASE})
message(STATUS " - Created standard OPP path: ${STANDARD_OPP_BASE}")
else ()
message(STATUS " - Skipped standard path creation (INTERFACE library)")
endif ()
endif ()
# 2.4. 自动检测 gen_esb 的位置
# 优先级: TARGET gen_esb > 从 cmake 文件路径推导 run 包中的 gen_esb
set(USE_EXTERNAL_GEN_ESB FALSE)
set(GEN_ESB_EXE "")
set(ASCEND_LIB_DIR "")
if (TARGET gen_esb)
# 开发环境/源码编译环境: 使用源码编译的 gen_esb target
set(USE_EXTERNAL_GEN_ESB FALSE)
message(STATUS "Use the source-compiled gen_esb target")
# 为 gen_esb 及其依赖添加 JOB_POOL 保护
# 防止 ar 和 ld 的文件竞态(特别是在高负载下)
if (TARGET geir_collector)
set_target_properties(geir_collector PROPERTIES
JOB_POOL_COMPILE es_gen_esb_serial
JOB_POOL_LINK es_gen_esb_serial
)
message(STATUS " - Applied JOB_POOL to geir_collector")
endif ()
if (TARGET gen_esb_impl)
set_target_properties(gen_esb_impl PROPERTIES
JOB_POOL_COMPILE es_gen_esb_serial
JOB_POOL_LINK es_gen_esb_serial
)
message(STATUS " - Applied JOB_POOL to gen_esb_impl")
endif ()
set_target_properties(gen_esb PROPERTIES
JOB_POOL_COMPILE es_gen_esb_serial
JOB_POOL_LINK es_gen_esb_serial
)
message(STATUS " - Applied JOB_POOL to gen_esb")
else ()
# gen_esb target 不存在,尝试从环境中查找
message(STATUS "_add_es_library_impl: gen_esb target not found, searching in environment...")
# 方法 1: 检查 PATH 环境变量(用户执行了 source setenv.bash 的场景)
find_program(GEN_ESB_IN_PATH gen_esb)
if (GEN_ESB_IN_PATH)
set(GEN_ESB_EXE "${GEN_ESB_IN_PATH}")
set(USE_EXTERNAL_GEN_ESB TRUE)
# 从 gen_esb 路径推导库路径
get_filename_component(BIN_DIR "${GEN_ESB_EXE}" DIRECTORY)
get_filename_component(INSTALL_BASE "${BIN_DIR}" DIRECTORY)
# 检查是否有 lib64 目录
if (EXISTS "${INSTALL_BASE}/lib64")
set(ASCEND_LIB_DIR "${INSTALL_BASE}/lib64")
endif ()
message(STATUS " Found gen_esb in PATH (environment variables configured)")
message(STATUS " - gen_esb path: ${GEN_ESB_EXE}")
message(STATUS " - library path: ${ASCEND_LIB_DIR}")
else ()
# 方法 2: 从 cmake 文件路径推导 run 包中的 gen_esb
# 如果当前文件在 /usr/local/Ascend/cann/include/ge/cmake/,则 gen_esb 在相对路径 ../../../bin/gen_esb
# 使用文件加载时缓存的路径(在函数外部已获取)
set(CMAKE_SCRIPT_DIR "${_ADD_ES_LIBRARY_CMAKE_DIR}")
message(STATUS " - gen_esb not in PATH, trying to deduce from cmake file path...")
message(STATUS " - Cached module directory: ${_ADD_ES_LIBRARY_CMAKE_DIR}")
message(STATUS " - Using script directory: ${CMAKE_SCRIPT_DIR}")
# 尝试推导 run 包的基础路径
# 假设结构: <base>/include/ge/cmake/ -> <base>/bin/gen_esb
get_filename_component(POTENTIAL_GE_DIR "${CMAKE_SCRIPT_DIR}" DIRECTORY) # 去掉 /cmake
get_filename_component(POTENTIAL_INCLUDE_DIR "${POTENTIAL_GE_DIR}" DIRECTORY) # 去掉 /ge
get_filename_component(POTENTIAL_BASE_DIR "${POTENTIAL_INCLUDE_DIR}" DIRECTORY) # 去掉 /include
# 自动检测系统架构
execute_process(
COMMAND uname -m
OUTPUT_VARIABLE UNAME_MACHINE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 根据 uname -m 结果映射到 ASCEND_ARCH
if (UNAME_MACHINE MATCHES "x86_64")
set(ASCEND_ARCH "x86_64-linux")
elseif (UNAME_MACHINE MATCHES "aarch64")
set(ASCEND_ARCH "aarch64-linux")
else ()
message(FATAL_ERROR "add_es_package: Unsupported architecture: ${UNAME_MACHINE}\n"
"Supported architectures: x86_64, aarch64")
endif ()
# 尝试多种可能的路径
set(POTENTIAL_GEN_ESB_PATHS
"${POTENTIAL_BASE_DIR}/bin/gen_esb" # 可能的路径1
"${POTENTIAL_BASE_DIR}/${ASCEND_ARCH}/bin/gen_esb" # 可能的路径2(带架构)
)
foreach (POTENTIAL_PATH ${POTENTIAL_GEN_ESB_PATHS})
if (EXISTS ${POTENTIAL_PATH})
set(GEN_ESB_EXE "${POTENTIAL_PATH}")
set(USE_EXTERNAL_GEN_ESB TRUE)
# 推导库路径
get_filename_component(BIN_DIR "${GEN_ESB_EXE}" DIRECTORY)
get_filename_component(ARCH_OR_BASE_DIR "${BIN_DIR}" DIRECTORY)
# 检查是否有 lib64 子目录
if (EXISTS "${ARCH_OR_BASE_DIR}/lib64")
set(ASCEND_LIB_DIR "${ARCH_OR_BASE_DIR}/lib64")
endif ()
message(STATUS " Found gen_esb by path deduction (derived from cmake path)")
message(STATUS " - gen_esb path: ${GEN_ESB_EXE}")
message(STATUS " - library path: ${ASCEND_LIB_DIR}")
message(STATUS " - detected architecture: ${ASCEND_ARCH} (uname -m: ${UNAME_MACHINE})")
break()
endif ()
endforeach ()
endif ()
# 如果所有方法都失败了
if (NOT USE_EXTERNAL_GEN_ESB)
message(FATAL_ERROR "add_es_package: gen_esb unavailable\n"
"Failed to locate a usable gen_esb. Tried the following:\n"
" 1. Looked for the source-built gen_esb target - not found\n"
" 2. Looked for gen_esb in the PATH environment variable - not found\n"
" 3. Derived gen_esb from the cmake file path - failed\n"
"\n"
"Please ensure:\n"
" 1. The run package is installed completely according to the installation guide\n"
" 2. 'source /usr/local/Ascend/cann/bin/setenv.bash' has been executed to configure environment variables\n"
" 3. This cmake file is included from the run package path (e.g. /usr/local/Ascend/cann/include/ge/cmake/)\n"
"\n"
"Debug info:\n"
" - current cmake script path: ${CMAKE_SCRIPT_DIR}\n"
" - derived base directory: ${POTENTIAL_BASE_DIR}\n"
" - gen_esb paths attempted: ${POTENTIAL_GEN_ESB_PATHS}")
endif ()
endif ()
# 2.5. 使用从 OPP_PROTO_TARGET 推导的原型库路径
set(OPP_PROTO_PATH ${OPP_BASE_PATH})
message(STATUS "Using proto library path (derived from OPP_PROTO_TARGET): ${OPP_PROTO_PATH}")
# 2.6. 提取 gen_esb 需要的 module name
# ES_LINKABLE_AND_ALL_TARGET 如果是 "es_math",则 MODULE_NAME 是 "math"(用于 gen_esb --module_name)
# 如果 ES_LINKABLE_AND_ALL_TARGET 不以 es_ 开头,直接使用
set(EXPORTED_TARGET "${ARG_ES_LINKABLE_AND_ALL_TARGET}")
string(REGEX REPLACE "^es_" "" MODULE_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}")
if (MODULE_NAME STREQUAL ARG_ES_LINKABLE_AND_ALL_TARGET)
# 没有 es_ 前缀,建议用户使用 es_ 前缀
message(WARNING "add_es_package: ES_LINKABLE_AND_ALL_TARGET '${ARG_ES_LINKABLE_AND_ALL_TARGET}' does not start with 'es_'\n"
" It is recommended to use the es_ prefix to follow the naming convention, for example: es_math, es_nn")
endif ()
message(STATUS "add_es_package: Module name for gen_esb: ${MODULE_NAME}")
# 2.7. 自动推导历史原型库路径
# 从 cmake 文件路径推导安装根目录,查找 ${CANN_INSTALL_PATH}/cann/opp/history_registry/${MODULE_NAME},
# 路径存在且非空时自动传 --history_registry 给 gen_esb,无需用户显式设置。
set(HISTORY_REGISTRY_ARG "")
set(_AUTO_HISTORY_REGISTRY "")
if (USE_EXTERNAL_GEN_ESB)
get_filename_component(_HIST_GE_DIR "${_ADD_ES_LIBRARY_CMAKE_DIR}" DIRECTORY) # 去掉 /cmake
get_filename_component(_HIST_INCLUDE_DIR "${_HIST_GE_DIR}" DIRECTORY) # 去掉 /ge
get_filename_component(_HIST_CANN_DIR "${_HIST_INCLUDE_DIR}" DIRECTORY) # 去掉 /include
set(_CANDIDATE "${_HIST_CANN_DIR}/opp/history_registry/${MODULE_NAME}")
if (IS_DIRECTORY "${_CANDIDATE}")
file(GLOB _HIST_CONTENTS LIST_DIRECTORIES true "${_CANDIDATE}/*")
if (_HIST_CONTENTS)
set(_AUTO_HISTORY_REGISTRY "${_CANDIDATE}")
set(HISTORY_REGISTRY_ARG "--history_registry=${_CANDIDATE}")
message(STATUS "[add_es_library] Auto-detected history registry: ${_CANDIDATE}")
else ()
message(STATUS "[add_es_library] History registry path exists but is empty, skipping: ${_CANDIDATE}")
endif ()
else ()
message(STATUS "[add_es_library] No history registry found at ${_CANDIDATE}, skipping")
endif ()
endif ()
# 版本去重:若历史原型库中已存在相同版本号
set(_DUPLICATE_VERSION_DETECTED FALSE)
if (GE_ES_EXTRACT_HISTORY AND GE_ES_RELEASE_VERSION AND _AUTO_HISTORY_REGISTRY)
set(_INDEX_JSON "${_AUTO_HISTORY_REGISTRY}/index.json")
if (EXISTS "${_INDEX_JSON}")
file(READ "${_INDEX_JSON}" _INDEX_CONTENT)
string(FIND "${_INDEX_CONTENT}" "\"${GE_ES_RELEASE_VERSION}\"" _VER_POS)
if (_VER_POS GREATER_EQUAL 0)
message(STATUS "[ES] Version ${GE_ES_RELEASE_VERSION} already exists in historical prototype library, skipping archive, still using code generation mode")
message(STATUS "[ES] Existing history registry will be copied to output: ${FINAL_OUTPUT_PATH}")
set(GE_ES_EXTRACT_HISTORY OFF)
set(EXTRACT_HISTORY_FLAG "")
set(_DUPLICATE_VERSION_DETECTED TRUE)
endif ()
endif ()
endif ()
# 只要 GE_ES_EXTRACT_HISTORY=ON 就走双次调用路径
# 有已有历史原型库 → codegen(带重载)+ extract&merge
# 无已有历史原型库(首次构建)→ codegen + extract 生成全新历史原型库,输出到 OUTPUT_PATH
set(COMBINED_COMMERCIAL_MODE FALSE)
if (GE_ES_EXTRACT_HISTORY)
set(COMBINED_COMMERCIAL_MODE TRUE)
if (_AUTO_HISTORY_REGISTRY)
message(STATUS " - [add_es_library] Combined commercial mode: "
"code gen with overload + extract & merge history registry (two gen_esb calls internally)")
else ()
set(_AUTO_HISTORY_REGISTRY "${FINAL_OUTPUT_PATH}")
message(STATUS " - [add_es_library] Combined commercial mode (first build, no existing history registry): "
"code gen + fresh history registry → ${FINAL_OUTPUT_PATH}")
endif ()
endif ()
# 代码生成步骤的 --es_mode 参数:
# 完整商发模式下,代码生成步骤不传 --es_mode=extract_history(历史原型库生成模式由第二次 gen_esb 调用完成)
if (COMBINED_COMMERCIAL_MODE)
set(CODE_GEN_STEP_EXTRACT_FLAG "")
else ()
set(CODE_GEN_STEP_EXTRACT_FLAG "${EXTRACT_HISTORY_FLAG}")
endif ()
# 2.9. 检测 eager_style_graph_builder_base 的来源
set(HAS_ES_BASE_TARGET FALSE)
set(ES_BASE_LIB "")
if (TARGET eager_style_graph_builder_base)
# 源码编译的 target
set(HAS_ES_BASE_TARGET TRUE)
set(ES_BASE_LIB eager_style_graph_builder_base)
message(STATUS "Using source-built eager_style_graph_builder_base target")
elseif (USE_EXTERNAL_GEN_ESB AND ASCEND_LIB_DIR)
# 使用 run 包环境,尝试从 run 包中查找库
message(STATUS "Trying to locate eager_style_graph_builder_base library from run package...")
# 在 run 包路径中查找库
find_library(ES_BASE_LIB_FOUND
NAMES eager_style_graph_builder_base
PATHS ${ASCEND_LIB_DIR}
NO_DEFAULT_PATH
)
if (ES_BASE_LIB_FOUND)
set(ES_BASE_LIB ${ES_BASE_LIB_FOUND})
message(STATUS " Found library in run package: ${ES_BASE_LIB}")
else ()
message(WARNING " Failed to find eager_style_graph_builder_base library in run package\n"
" Path: ${ASCEND_LIB_DIR}\n"
" Will fall back to linking by library name")
# 如果找不到,使用库名称,依赖运行时 LD_LIBRARY_PATH
set(ES_BASE_LIB eager_style_graph_builder_base)
endif ()
else ()
# 开发环境但 target 不存在
message(WARNING "eager_style_graph_builder_base target not available, will try to link by library name\n"
" Please ensure the runtime LD_LIBRARY_PATH contains the directory of this library")
set(ES_BASE_LIB eager_style_graph_builder_base)
endif ()
# 3. 定义目标名称和路径变量
# 命名规则:使用 ES_LINKABLE_AND_ALL_TARGET 直接作为库名和目录名(如 es_math -> es_math_so, es_math_a, libes_math.so, libes_math.a)
# 注意:BUILD_DIR 已在前面定义(标准路径处理需要)
set(SO_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}_so") # 内部 .so target 名称
set(A_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}_a") # 内部 .a target 名称
set(OBJ_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}_obj") # 内部 OBJECT target 名称(避免重复编译)
set(PYTHON_PKG_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}") # Python 包名(如 es_math)
set(GEN_CODE_DIR "${BUILD_DIR}/generated_code")
set(PYTHON_BUILD_DIR "${BUILD_DIR}/python_package")
message(STATUS "Configuring ES package: ${ARG_ES_LINKABLE_AND_ALL_TARGET}")
message(STATUS " - Exported target: ${EXPORTED_TARGET} (public interface)")
message(STATUS " - Internal .obj target: ${OBJ_NAME} (compile once)")
message(STATUS " - Internal .so target: ${SO_NAME}")
message(STATUS " - Internal .a target: ${A_NAME}")
message(STATUS " - Module name (for gen_esb): ${MODULE_NAME}")
message(STATUS " - OPP proto target: ${ARG_OPP_PROTO_TARGET} (type: ${TARGET_TYPE})")
message(STATUS " - OPP proto path: ${OPP_PROTO_PATH}")
if (ARG_OUTPUT_PATH)
message(STATUS " - Output path: ${ARG_OUTPUT_PATH} (artifacts will be copied)")
else ()
message(STATUS " - Output path: ${BUILD_DIR} (artifacts remain in build directory)")
endif ()
message(STATUS " - Library files: lib${ARG_ES_LINKABLE_AND_ALL_TARGET}.so, lib${ARG_ES_LINKABLE_AND_ALL_TARGET}.a")
message(STATUS " - Python package: ${PYTHON_PKG_NAME}")
# 4. 创建必要的目录
file(MAKE_DIRECTORY ${BUILD_DIR})
file(MAKE_DIRECTORY ${GEN_CODE_DIR})
file(MAKE_DIRECTORY ${PYTHON_BUILD_DIR})
if (DO_COPY_INSTALL)
file(MAKE_DIRECTORY ${INCLUDE_DIR})
file(MAKE_DIRECTORY ${LIB_DIR})
file(MAKE_DIRECTORY ${WHL_DIR})
endif ()
# 5. 创建 wrapper 文件生成脚本(单文件方案)
# 这个脚本会在代码生成完成后,动态扫描生成的 .cpp 文件并生成 include 列表
set(GENERATE_WRAPPER_SCRIPT "${BUILD_DIR}/generate_wrapper.cmake")
file(WRITE ${GENERATE_WRAPPER_SCRIPT} "# Auto-generated script for creating wrapper file
# This script scans generated .cpp files and creates a single wrapper with includes
# Generated by: generate_es_package.cmake
set(GEN_CODE_DIR \"${GEN_CODE_DIR}\")
set(WRAPPER_FILE \"${GEN_CODE_DIR}/es_${MODULE_NAME}_all_in_one.cpp\")
set(MODULE_NAME \"${MODULE_NAME}\")
set(TARGET_NAME \"${ARG_ES_LINKABLE_AND_ALL_TARGET}\")
# 扫描生成的 .cpp 文件(排除 wrapper 自身)
file(GLOB CPP_FILES
\"\${GEN_CODE_DIR}/*.cpp\"
)
# 过滤掉 wrapper 文件自己
list(FILTER CPP_FILES EXCLUDE REGEX \"es_\${MODULE_NAME}_all_in_one\\\\.cpp\")
# 排序保证稳定性
list(SORT CPP_FILES)
# 统计数量
list(LENGTH CPP_FILES NUM_OPS)
# 生成 wrapper 文件内容
set(wrapper_content \"// Auto-generated wrapper for \${TARGET_NAME} ES operators\\n\")
set(wrapper_content \"\${wrapper_content}// This file dynamically includes all generated operator implementations\\n\")
set(wrapper_content \"\${wrapper_content}//\\n\")
set(wrapper_content \"\${wrapper_content}// Build workflow (single-file mode):\\n\")
set(wrapper_content \"\${wrapper_content}// 1. gen_esb generates individual operator .cpp files\\n\")
set(wrapper_content \"\${wrapper_content}// 2. This wrapper file is regenerated with #include statements\\n\")
set(wrapper_content \"\${wrapper_content}// 3. Compiler compiles this single file (includes all implementations)\\n\")
set(wrapper_content \"\${wrapper_content}//\\n\")
set(wrapper_content \"\${wrapper_content}// Generated by: generate_es_package.cmake\\n\")
set(wrapper_content \"\${wrapper_content}// DO NOT EDIT THIS FILE MANUALLY\\n\")
set(wrapper_content \"\${wrapper_content}//\\n\")
set(wrapper_content \"\${wrapper_content}// Total operators: \${NUM_OPS}\\n\")
set(wrapper_content \"\${wrapper_content}//\\n\")
set(wrapper_content \"\${wrapper_content}\\n\")
# 添加各个算子的 include
foreach(cpp_file \${CPP_FILES})
get_filename_component(filename \${cpp_file} NAME)
set(wrapper_content \"\${wrapper_content}#include \\\"\${filename}\\\"\\n\")
endforeach()
# 写入文件
file(WRITE \"\${WRAPPER_FILE}\" \"\${wrapper_content}\")
message(STATUS \"[ES Wrapper] Generated: \${WRAPPER_FILE}\")
message(STATUS \"[ES Wrapper] Total operators included: \${NUM_OPS}\")
")
# 6. 创建初始 wrapper 文件(配置阶段占位,避免首次配置报错)
set(ALL_IN_ONE_WRAPPER "${GEN_CODE_DIR}/es_${MODULE_NAME}_all_in_one.cpp")
if (NOT EXISTS ${ALL_IN_ONE_WRAPPER})
file(WRITE ${ALL_IN_ONE_WRAPPER}
"// Placeholder wrapper for ${ARG_ES_LINKABLE_AND_ALL_TARGET}
// This file will be regenerated after code generation
//
// Generated by: generate_es_package.cmake
// DO NOT EDIT THIS FILE MANUALLY
")
endif ()
# 7. 单文件方案:只使用 wrapper 文件作为源文件
message(STATUS "ES package '${ARG_ES_LINKABLE_AND_ALL_TARGET}' using single-file compilation mode")
message(STATUS " - Wrapper file: ${ALL_IN_ONE_WRAPPER}")
message(STATUS " - Module name: ${MODULE_NAME}")
# 8. 定义代码生成命令(单文件方案)
# gen_esb 生成:
# - 聚合头文件: es_${MODULE_NAME}_ops.h, es_${MODULE_NAME}_ops_c.h, es_${MODULE_NAME}_ops.py
# - 每个算子独立的 .cpp 和 .h 文件(如 es_add.cpp, es_add.h)
# 然后运行 generate_wrapper.cmake 生成包含所有 #include 的 wrapper 文件
set(CODE_GEN_FLAG "${GEN_CODE_DIR}/generated_code.flag")
set(GEN_ESB_OUTPUT_DIR "${GEN_CODE_DIR}")
# 8.1. 准备依赖列表
# INTERFACE 库不需要构建,不添加到 DEPENDS 中
# OBJECT 库使用临时 SHARED 库,需要依赖临时库 target
set(CODE_GEN_DEPENDS "")
if (NOT TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
set(CODE_GEN_DEPENDS "${EFFECTIVE_OPP_PROTO_TARGET}")
# 如果使用了标准路径,还需要依赖拷贝完成
if (USE_STANDARD_PATH AND OPP_COPY_FLAG)
list(APPEND CODE_GEN_DEPENDS ${OPP_COPY_FLAG})
endif ()
endif ()
set(_EXCL_OPS_ARG "${ARG_EXCLUDE_OPS}")
if (USE_EXTERNAL_GEN_ESB)
# 使用 run 包的 gen_esb
if (CODE_GEN_DEPENDS)
set(_COMBINED_MODE_DEPENDS "DEPENDS;${CODE_GEN_DEPENDS}")
else ()
# INTERFACE 库,无需依赖
unset(_COMBINED_MODE_DEPENDS)
endif ()
_es_add_gen_esb_cmd(
"${GEN_ESB_EXE}" "${ASCEND_LIB_DIR}" "${_EXCL_OPS_ARG}"
"Generating ES API code for '${ARG_ES_LINKABLE_AND_ALL_TARGET}' using run package gen_esb..."
)
else ()
# 使用源码编译的 gen_esb target
# 源码环境的 OPP_PROTO_TARGET 可能是 OBJECT 库(已转换为临时 SHARED)
# 构建完整依赖列表(包含 gen_esb 和标准路径拷贝)
set(CODE_GEN_DEPENDS "$<TARGET_FILE:gen_esb>;${EFFECTIVE_OPP_PROTO_TARGET}")
if (USE_STANDARD_PATH AND OPP_COPY_FLAG)
list(APPEND CODE_GEN_DEPENDS ${OPP_COPY_FLAG})
endif ()
set(_COMBINED_MODE_DEPENDS "DEPENDS;${CODE_GEN_DEPENDS}")
_es_add_gen_esb_cmd(
"$<TARGET_FILE:gen_esb>" "" "${_EXCL_OPS_ARG}"
"Generating ES API code for '${ARG_ES_LINKABLE_AND_ALL_TARGET}' using source-built gen_esb..."
)
endif ()
# 9. 创建自定义目标触发代码生成
set(CODE_GEN_TARGET "generate_${ARG_ES_LINKABLE_AND_ALL_TARGET}_code")
add_custom_target(${CODE_GEN_TARGET} ALL
DEPENDS ${CODE_GEN_FLAG}
)
# 使用 JOB_POOL 和 USES_TERMINAL 确保串行执行
set_target_properties(${CODE_GEN_TARGET} PROPERTIES
JOB_POOL es_gen_esb_serial
USES_TERMINAL_BUILD ON
)
# 10. 创建 OBJECT 库目标(避免重复编译)
# OBJECT 库只编译源文件生成 .o 文件,不进行归档或链接
# 共享库和静态库都链接同一个 .o 文件,避免重复编译
add_library(${OBJ_NAME} OBJECT ${ALL_IN_ONE_WRAPPER})
# 为 OBJECT 库设置 POSITION_INDEPENDENT_CODE(确保生成的 .o 文件是 PIC)
# 这对于共享库链接是必需的,特别是使用 address sanitizer 时
set_target_properties(${OBJ_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
# 10.1 创建共享库目标(使用 OBJECT 库的 .o 文件)
add_library(${SO_NAME} SHARED $<TARGET_OBJECTS:${OBJ_NAME}>)
# 10.2 创建静态库目标(使用 OBJECT 库的 .o 文件)
add_library(${A_NAME} STATIC $<TARGET_OBJECTS:${OBJ_NAME}>)
# 11. 标记生成的文件属性(告诉 CMake/IDE 这是生成的文件)
set_source_files_properties(
${ALL_IN_ONE_WRAPPER}
PROPERTIES GENERATED TRUE
)
# 12. 确保代码生成完成后再编译
# 这个依赖关系确保:先执行 CODE_GEN_TARGET 生成文件,再编译
# OBJ_NAME 依赖 CODE_GEN_TARGET,SO_NAME 和 A_NAME 依赖 OBJ_NAME
add_dependencies(${OBJ_NAME} ${CODE_GEN_TARGET})
# 13. 配置编译选项(应用于 OBJECT 目标,编译选项只设置一次)
if (DEFINED AIR_COMMON_DYNAMIC_COMPILE_OPTION)
target_compile_options(${OBJ_NAME} PRIVATE ${AIR_COMMON_DYNAMIC_COMPILE_OPTION})
message(STATUS "add_es_library: Using CANN compile options: ${AIR_COMMON_DYNAMIC_COMPILE_OPTION}")
else ()
# 外部环境默认编译选项
# 使用 C++17 标准 + ABI 兼容性处理
set(ES_COMMON_COMPILE_OPTION
-fPIC
-Wall
-fstack-protector-all
-std=c++17
-D_GLIBCXX_USE_CXX11_ABI=0 # 使用旧的 ABI,确保与依赖库兼容
-O2
-Wno-free-nonheap-object # 抑制高版本的gcc12+的误报
)
target_compile_options(${OBJ_NAME} PRIVATE ${ES_COMMON_COMPILE_OPTION})
message(STATUS "add_es_library: Using default compile options: ${ES_COMMON_COMPILE_OPTION}")
endif ()
# 13.1 强制添加 ABI 兼容性定义(解决 std::string coredump 问题)
# 确保所有库使用相同的 std::string ABI 版本
if (NOT DEFINED _GLIBCXX_USE_CXX11_ABI)
target_compile_definitions(${OBJ_NAME} PRIVATE _GLIBCXX_USE_CXX11_ABI=0)
message(STATUS "add_es_library: Set _GLIBCXX_USE_CXX11_ABI=0 for ABI compatibility")
endif ()
# 13.1.1 动态库安全链接选项
target_link_options(${SO_NAME} PRIVATE
-Wl,-z,relro
-Wl,-z,now
-Wl,-z,noexecstack
# 在 Release 配置下添加 -s 选项去除符号表
$<$<CONFIG:Release>:-s>)
# 清除so中的RPATH,避免安全风险(仅对共享库)
set_target_properties(${SO_NAME} PROPERTIES
SKIP_BUILD_RPATH TRUE
SKIP_INSTALL_RPATH TRUE
)
# 13.2 准备头文件搜索路径列表
set(ES_INCLUDE_DIRS ${GEN_CODE_DIR})
# 需要拷贝时添加 INCLUDE_DIR 到头文件路径中
if (DO_COPY_INSTALL)
list(APPEND ES_INCLUDE_DIRS ${INCLUDE_DIR})
endif ()
# 如果使用外部 gen_esb(run 包环境),添加 run 包的头文件路径
if (USE_EXTERNAL_GEN_ESB)
# 从 cmake 模块文件路径推导基础路径
get_filename_component(HEADER_GE_DIR "${_ADD_ES_LIBRARY_CMAKE_DIR}" DIRECTORY) # 去掉 /cmake
get_filename_component(HEADER_INCLUDE_DIR "${HEADER_GE_DIR}" DIRECTORY) # 去掉 /ge
get_filename_component(HEADER_BASE_PATH "${HEADER_INCLUDE_DIR}" DIRECTORY) # 去掉 /include
message(STATUS "add_es_library: Deduced header base path from cmake file: ${HEADER_BASE_PATH}")
list(APPEND ES_INCLUDE_DIRS
${HEADER_BASE_PATH}/include
${HEADER_BASE_PATH}/include/ge/
${HEADER_BASE_PATH}/include/external/
)
else ()
# 源码环境:添加 ES base 库的头文件路径
if (DEFINED AIR_CODE_DIR)
list(APPEND ES_INCLUDE_DIRS
${AIR_CODE_DIR}/inc/external/ge/eager_style_graph_builder/c
${AIR_CODE_DIR}/inc/external/ge/eager_style_graph_builder/cpp
)
endif ()
endif ()
# 为 OBJECT 目标设置头文件路径(PRIVATE,仅用于编译)
target_include_directories(${OBJ_NAME} PRIVATE ${ES_INCLUDE_DIRS})
# OBJECT 库不会实际链接(只生成 .o 文件),但会继承头文件路径用于编译
if (HAS_ES_BASE_TARGET)
target_link_libraries(${OBJ_NAME} PRIVATE ${ES_BASE_LIB})
endif ()
# 如果使用 run 包的库,添加库搜索路径(仅对共享库有效)
if (USE_EXTERNAL_GEN_ESB AND ASCEND_LIB_DIR AND NOT HAS_ES_BASE_TARGET)
target_link_directories(${SO_NAME} PUBLIC ${ASCEND_LIB_DIR})
endif ()
# 14. 配置库链接(兼容性处理,同时应用于共享库和静态库)
set(REQUIRED_LIBS "")
# 检查 CANN 环境特有的库
if (TARGET metadef_headers)
list(APPEND REQUIRED_LIBS metadef_headers)
message(STATUS "add_es_library: Found metadef_headers target")
else ()
message(STATUS "add_es_library: metadef_headers target not found, skipping")
endif ()
if (TARGET c_sec)
list(APPEND REQUIRED_LIBS c_sec)
message(STATUS "add_es_library: Found c_sec target")
else ()
message(STATUS "add_es_library: c_sec target not found, skipping")
endif ()
# 设置链接库(共享库和静态库使用相同的链接库)
if (REQUIRED_LIBS)
# OBJECT 库不会实际链接(只生成 .o 文件),但会继承头文件路径用于编译
target_link_libraries(${OBJ_NAME} PRIVATE ${REQUIRED_LIBS})
target_link_libraries(${SO_NAME} PUBLIC ${ES_BASE_LIB} ${REQUIRED_LIBS})
target_link_libraries(${A_NAME} PUBLIC ${ES_BASE_LIB} ${REQUIRED_LIBS})
message(STATUS "add_es_library: Linking with CANN libraries: ${REQUIRED_LIBS}")
else ()
target_link_libraries(${SO_NAME} PUBLIC ${ES_BASE_LIB})
target_link_libraries(${A_NAME} PUBLIC ${ES_BASE_LIB})
message(STATUS "add_es_library: Using minimal configuration (no CANN libraries found)")
endif ()
# 14.1 设置共享库输出名称
# 新的命名规则:lib<ES_LINKABLE_AND_ALL_TARGET>.so (如 libes_math.so)
set_target_properties(${SO_NAME} PROPERTIES
OUTPUT_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}"
PREFIX "lib"
SUFFIX ".so"
)
# 14.2 设置静态库输出名称
# 新的命名规则:lib<ES_LINKABLE_AND_ALL_TARGET>.a (如 libes_math.a)
set_target_properties(${A_NAME} PROPERTIES
OUTPUT_NAME "${ARG_ES_LINKABLE_AND_ALL_TARGET}"
PREFIX "lib"
SUFFIX ".a"
)
# 15. 创建生成 setup.py 的辅助脚本
set(PYTHON_PKG_DIR "${PYTHON_BUILD_DIR}/${PYTHON_PKG_NAME}")
set(SETUP_PY_FILE "${PYTHON_BUILD_DIR}/setup.py")
set(CREATE_SETUP_SCRIPT "${BUILD_DIR}/create_setup.cmake")
# 写入辅助脚本用于在构建时生成 setup.py
file(WRITE ${CREATE_SETUP_SCRIPT} "# Auto-generated script for creating setup.py
file(WRITE \"${SETUP_PY_FILE}\" \"from setuptools import setup, find_packages
setup(
name='${PYTHON_PKG_NAME}',
version='1.0.0',
description='ES Generated API for ${ARG_ES_LINKABLE_AND_ALL_TARGET} operators',
author='Huawei Technologies Co., Ltd.',
packages=find_packages(),
python_requires='>=3.7',
entry_points={
'ge.es.plugins': [
'${MODULE_NAME} = ${PYTHON_PKG_NAME}:get_module',
],
},
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
],
)
\")
message(STATUS \"Created setup.py for package '${ARG_ES_LINKABLE_AND_ALL_TARGET}'\")
")
# 18. 创建 Python 包结构的命令
set(WHL_GEN_FLAG "${PYTHON_BUILD_DIR}/whl_generated.flag")
# 创建 __init__.py 内容生成脚本
set(CREATE_INIT_SCRIPT "${BUILD_DIR}/create_init.cmake")
file(WRITE ${CREATE_INIT_SCRIPT} "# Auto-generated script for creating __init__.py
file(WRITE \"${PYTHON_PKG_DIR}/__init__.py\" \"# Auto-generated ES API for ${ARG_ES_LINKABLE_AND_ALL_TARGET}
from .es_${MODULE_NAME}_ops import *
__all__ = [name for name in dir() if not name.startswith('_')]
def get_module():
import sys
return sys.modules[__name__]
\")
")
# 创建拷贝 Python 文件的脚本
set(COPY_PY_SCRIPT "${BUILD_DIR}/copy_py_files.cmake")
file(WRITE ${COPY_PY_SCRIPT} "# Auto-generated script for copying Python files
file(GLOB PY_FILES \"${GEN_CODE_DIR}/*.py\")
foreach(py_file \${PY_FILES})
get_filename_component(filename \${py_file} NAME)
# 使用 configure_file 替代 COPY_FILE (兼容 CMake 3.16)
configure_file(\${py_file} \"${PYTHON_PKG_DIR}/\${filename}\" COPYONLY)
message(STATUS \"Copied: \${filename}\")
endforeach()
")
# 检查是否跳过 wheel 包生成
if (NOT ARG_SKIP_WHL)
add_custom_command(
OUTPUT ${WHL_GEN_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Building Python wheel for package: ${PYTHON_PKG_NAME}"
# 清理旧的构建产物
COMMAND ${CMAKE_COMMAND} -E remove_directory ${PYTHON_BUILD_DIR}/${PYTHON_PKG_NAME}
COMMAND ${CMAKE_COMMAND} -E remove_directory ${PYTHON_BUILD_DIR}/build
COMMAND ${CMAKE_COMMAND} -E remove_directory ${PYTHON_BUILD_DIR}/dist
COMMAND ${CMAKE_COMMAND} -E remove_directory ${PYTHON_BUILD_DIR}/*.egg-info
# 创建 Python 包目录结构
COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_PKG_DIR}
# 拷贝生成的 Python 文件
COMMAND ${CMAKE_COMMAND} -P ${COPY_PY_SCRIPT}
# 创建 __init__.py
COMMAND ${CMAKE_COMMAND} -P ${CREATE_INIT_SCRIPT}
# 创建 setup.py
COMMAND ${CMAKE_COMMAND} -P ${CREATE_SETUP_SCRIPT}
# 构建 wheel 包 - 修复 chdir 语法
COMMAND ${CMAKE_COMMAND} -E chdir ${PYTHON_BUILD_DIR} ${Python3_EXECUTABLE} -m pip wheel . --no-deps --wheel-dir=${PYTHON_BUILD_DIR}/dist
# 标记完成
COMMAND ${CMAKE_COMMAND} -E touch ${WHL_GEN_FLAG}
DEPENDS ${CODE_GEN_TARGET}
COMMENT "Building Python wheel package for '${PYTHON_PKG_NAME}'..."
WORKING_DIRECTORY ${PYTHON_BUILD_DIR}
)
else ()
# 如果跳过 wheel 生成,创建一个空的 flag 文件
add_custom_command(
OUTPUT ${WHL_GEN_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Skipping wheel generation"
COMMAND ${CMAKE_COMMAND} -E touch ${WHL_GEN_FLAG}
DEPENDS ${CODE_GEN_TARGET}
COMMENT "Skipping Python wheel package generation for '${PYTHON_PKG_NAME}'"
)
endif ()
# 19. 创建 wheel 生成目标
set(WHL_GEN_TARGET "generate_${ARG_ES_LINKABLE_AND_ALL_TARGET}_whl")
add_custom_target(${WHL_GEN_TARGET} ALL
DEPENDS ${WHL_GEN_FLAG}
)
# 20. 拷贝逻辑(根据 DO_COPY_INSTALL 决定是否拷贝)
set(INSTALL_FLAG "${BUILD_DIR}/install.flag")
if (DO_COPY_INSTALL)
# 20.1. 创建拷贝头文件的脚本
set(COPY_HEADERS_SCRIPT "${BUILD_DIR}/copy_headers.cmake")
file(WRITE ${COPY_HEADERS_SCRIPT} "# Auto-generated script for copying headers
file(GLOB H_FILES \"${GEN_CODE_DIR}/*.h\")
foreach(h_file \${H_FILES})
get_filename_component(filename \${h_file} NAME)
# 使用 configure_file 替代 COPY_FILE (兼容 CMake 3.16)
configure_file(\${h_file} \"${INCLUDE_DIR}/\${filename}\" COPYONLY)
message(STATUS \"Copied header: \${filename}\")
endforeach()
")
# 20.2. 创建拷贝 whl 文件的脚本
set(COPY_WHL_SCRIPT "${BUILD_DIR}/copy_whl.cmake")
file(WRITE ${COPY_WHL_SCRIPT} "# Auto-generated script for copying whl files
file(GLOB WHL_FILES \"${PYTHON_BUILD_DIR}/dist/*.whl\")
foreach(whl_file \${WHL_FILES})
get_filename_component(filename \${whl_file} NAME)
# 使用 configure_file 替代 COPY_FILE (兼容 CMake 3.16)
configure_file(\${whl_file} \"${WHL_DIR}/\${filename}\" COPYONLY)
message(STATUS \"Copied wheel: \${filename}\")
endforeach()
")
# 21. 拷贝产物到 OUTPUT_PATH
add_custom_command(
OUTPUT ${INSTALL_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Installing ES package: ${ARG_ES_LINKABLE_AND_ALL_TARGET}"
# 拷贝头文件
COMMAND ${CMAKE_COMMAND} -P ${COPY_HEADERS_SCRIPT}
# 拷贝共享库
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:${SO_NAME}> ${LIB_DIR}/
# 拷贝静态库
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:${A_NAME}> ${LIB_DIR}/
# 拷贝 wheel 包
COMMAND ${CMAKE_COMMAND} -P ${COPY_WHL_SCRIPT}
# 标记完成
COMMAND ${CMAKE_COMMAND} -E touch ${INSTALL_FLAG}
DEPENDS ${SO_NAME} ${A_NAME} ${WHL_GEN_TARGET}
COMMENT "Installing ES package '${ARG_ES_LINKABLE_AND_ALL_TARGET}' to ${ARG_OUTPUT_PATH}"
VERBATIM
)
# 22. 创建安装目标(内部使用)
set(INSTALL_TARGET "install_${ARG_ES_LINKABLE_AND_ALL_TARGET}")
add_custom_target(${INSTALL_TARGET}
DEPENDS ${INSTALL_FLAG}
)
else ()
# 不需要拷贝,直接创建一个空的 flag 文件
add_custom_command(
OUTPUT ${INSTALL_FLAG}
COMMAND ${CMAKE_COMMAND} -E echo "Skipping install (OUTPUT_PATH not specified)"
COMMAND ${CMAKE_COMMAND} -E touch ${INSTALL_FLAG}
DEPENDS ${SO_NAME} ${A_NAME} ${WHL_GEN_TARGET}
COMMENT "Skipping install for '${ARG_ES_LINKABLE_AND_ALL_TARGET}' (OUTPUT_PATH not specified)"
)
# 不创建 INSTALL_TARGET,直接使用 SO_NAME + A_NAME + WHL_GEN_TARGET
set(INSTALL_TARGET "")
endif ()
# 22.5. 创建 SMART_BUILD_TARGET
# 单文件方案无需二次构建,外部使用此 target 即可触发完整构建流程
set(SMART_BUILD_TARGET "build_${ARG_ES_LINKABLE_AND_ALL_TARGET}")
if (DO_COPY_INSTALL)
add_custom_target(${SMART_BUILD_TARGET}
DEPENDS ${INSTALL_TARGET}
COMMENT "ES package '${ARG_ES_LINKABLE_AND_ALL_TARGET}' build completed (single-file mode)"
)
else ()
add_custom_target(${SMART_BUILD_TARGET}
DEPENDS ${SO_NAME} ${A_NAME} ${WHL_GEN_TARGET}
COMMENT "ES package '${ARG_ES_LINKABLE_AND_ALL_TARGET}' build completed (single-file mode, no install)"
)
endif ()
# 22.6. 依赖管理:防止多个 ES packages 并发构建共享依赖
# 如果有多个 ES packages,让后续的包依赖第一个包,确保 gen_esb 等共享依赖只构建一次
if (NOT DEFINED FIRST_ES_LINKABLE_AND_ALL_TARGET)
set(FIRST_ES_LINKABLE_AND_ALL_TARGET ${SMART_BUILD_TARGET} CACHE INTERNAL "First ES package build target")
message(STATUS "add_es_package: '${SMART_BUILD_TARGET}' is the first ES package (will be built first)")
else ()
add_dependencies(${SMART_BUILD_TARGET} ${FIRST_ES_LINKABLE_AND_ALL_TARGET})
message(STATUS "add_es_package: '${SMART_BUILD_TARGET}' depends on '${FIRST_ES_LINKABLE_AND_ALL_TARGET}' to avoid concurrent builds of shared dependencies")
endif ()
# 23. 创建对外接口库(供使用方链接)
# 单文件方案:直接链接内部 SO target(静态库用户按需直接链接)
add_library(${EXPORTED_TARGET} INTERFACE)
# INTERFACE 库依赖 SMART_BUILD_TARGET(确保外部链接时会触发构建)
add_dependencies(${EXPORTED_TARGET} ${SMART_BUILD_TARGET})
# 设置头文件搜索路径(传递给使用方)
target_include_directories(${EXPORTED_TARGET} INTERFACE ${ES_INCLUDE_DIRS})
# 如果使用 run 包的库,添加库搜索路径(传递给使用方)
if (USE_EXTERNAL_GEN_ESB AND ASCEND_LIB_DIR AND NOT HAS_ES_BASE_TARGET)
target_link_directories(${EXPORTED_TARGET} INTERFACE ${ASCEND_LIB_DIR})
endif ()
# 链接到内部 SO target 和 ES base 库(静态库用户按需直接链接)
target_link_libraries(${EXPORTED_TARGET} INTERFACE
${SO_NAME}
${ES_BASE_LIB}
)
# 24. 输出总结信息
message(STATUS "ES package '${ARG_ES_LINKABLE_AND_ALL_TARGET}' configured successfully (single-file mode):")
message(STATUS " - Exported target (for linking): ${EXPORTED_TARGET}")
message(STATUS " - Build target: ${SMART_BUILD_TARGET}")
message(STATUS " - Wrapper file: ${ALL_IN_ONE_WRAPPER}")
message(STATUS " - Internal targets:")
message(STATUS " * Code generation: ${CODE_GEN_TARGET}")
message(STATUS " * Object library: ${OBJ_NAME} (compiled once)")
message(STATUS " * Shared library: ${SO_NAME}")
message(STATUS " * Static library: ${A_NAME}")
message(STATUS " * Wheel generation: ${WHL_GEN_TARGET}")
if (DO_COPY_INSTALL)
message(STATUS " * Install: ${INSTALL_TARGET}")
else ()
message(STATUS " * Install: (skipped, OUTPUT_PATH not specified)")
endif ()
if (TARGET_TYPE STREQUAL "OBJECT_LIBRARY")
message(STATUS " * Temporary proto library: ${TEMP_PROTO_TARGET} (retained in build directory)")
endif ()
message(STATUS " - Library files: lib${ARG_ES_LINKABLE_AND_ALL_TARGET}.so, lib${ARG_ES_LINKABLE_AND_ALL_TARGET}.a")
message(STATUS " - Python package: ${PYTHON_PKG_NAME}")
if (HAS_ES_BASE_TARGET)
message(STATUS " - ES base library: eager_style_graph_builder_base (target)")
else ()
message(STATUS " - ES base library: ${ES_BASE_LIB} (from library)")
endif ()
message(STATUS "")
message(STATUS "Build mode: Single-file compilation (no reconfiguration needed)")
message(STATUS "")
message(STATUS "Quick start:")
message(STATUS " 1. Link: target_link_libraries(your_target PRIVATE ${EXPORTED_TARGET})")
message(STATUS " 2. Build: make your_target (recommended, triggers automatically)")
message(STATUS "")
endfunction()
# ======================================================================================================================
# 对外接口函数 1: add_es_library_and_whl
#
# 功能: 生成 ES API 的完整产物(C/C++ 动态库 + Python wheel 包)
#
# 参数:
# ES_LINKABLE_AND_ALL_TARGET - [必需] 对外暴露的接口库 target 名称(如 es_math)
# OPP_PROTO_TARGET - [必需] 算子原型库的 CMake target 名称(支持 SHARED/STATIC/INTERFACE/OBJECT 类型)
# OUTPUT_PATH - [可选] 产物输出的根目录。不指定时产物保留在构建目录中
# EXCLUDE_OPS - [可选] 需要排除生成的算子(如 Add,Conv2D)
#
# 使用示例:
# add_es_library_and_whl(
# ES_LINKABLE_AND_ALL_TARGET es_math
# OPP_PROTO_TARGET opgraph_math
# OUTPUT_PATH ${CMAKE_BINARY_DIR}/output
# )
#
# 生成产物:
# - C/C++ 头文件: include/es_math/*.h
# - 动态库: lib64/libes_math.so
# - Python 包: whl/es_math-1.0.0-py3-none-any.whl
# ======================================================================================================================
function(add_es_library_and_whl)
_add_es_library_impl(${ARGN})
endfunction()
# ======================================================================================================================
# 对外接口函数 2: add_es_library
#
# 功能: 只生成 ES API 的 C/C++ 动态库(不生成 Python wheel 包)
#
# 参数:
# ES_LINKABLE_AND_ALL_TARGET - [必需] 对外暴露的接口库 target 名称(如 es_math)
# OPP_PROTO_TARGET - [必需] 算子原型库的 CMake target 名称(支持 SHARED/STATIC/INTERFACE/OBJECT 类型)
# OUTPUT_PATH - [可选] 产物输出的根目录。不指定时产物保留在构建目录中
# EXCLUDE_OPS - [可选] 需要排除生成的算子(如 Add,Conv2D)
#
# 使用示例:
# add_es_library(
# ES_LINKABLE_AND_ALL_TARGET es_math
# OPP_PROTO_TARGET opgraph_math
# OUTPUT_PATH ${CMAKE_BINARY_DIR}/output
# )
#
# 生成产物:
# - C/C++ 头文件: include/es_math/*.h
# - 动态库: lib64/libes_math.so
# - ⚠️ 不生成 Python wheel 包
#
# 适用场景:
# - 纯 C/C++ 项目,不需要 Python 接口
# - 加快构建速度(跳过 wheel 打包步骤)
# ======================================================================================================================
function(add_es_library)
_add_es_library_impl(${ARGN} SKIP_WHL)
endfunction()