已合并
[installation] 支持msot按需打包 #104
wanghaopeng创建于 7月6日
[installation] 支持msot按需打包 #104
已合并
wanghaopeng创建于 7月6日
4 个文件变更+161-26
MCMakeLists.txt+66-16
@@ -34,12 +34,52 @@ if(BUILD_TESTS)
34 set(BUILD_ARG test)34 set(BUILD_ARG test)
35endif()35endif()
36 36 
37-include(cmake/module/msdebug.cmake)37+# 顶层总包支持的子工具列表。排除能力按这些 CMake 模块粒度生效,
38-include(cmake/module/mskl.cmake)38+# 其中 msopgen 模块会同时产出 msopgen 与 msopst 两个 whl。
39-include(cmake/module/mskpp.cmake)39+set(MSOT_VALID_TOOLS msdebug mskl mskpp msopgen msopprof mssanitizer)
40-include(cmake/module/msopgen.cmake)40+ 
41-include(cmake/module/msopprof.cmake)41+# CMake 直接调用时也可以使用该变量;build.py 的 --exclude-tools 会透传到这里。
42-include(cmake/module/mssanitizer.cmake)42+set(MSOT_EXCLUDE_TOOLS "" CACHE STRING "Comma-separated msOT sub-tools excluded from build/package")
43+ 
44+# 将用户传入的逗号分隔字符串归一化为列表:去空、转小写、去重,并做白名单校验。
45+set(MSOT_EXCLUDED_TOOL_LIST "")
46+if(MSOT_EXCLUDE_TOOLS)
47+ string(REPLACE "," ";" MSOT_EXCLUDE_TOOL_RAW_LIST "${MSOT_EXCLUDE_TOOLS}")
48+ foreach(TOOL_NAME IN LISTS MSOT_EXCLUDE_TOOL_RAW_LIST)
49+ string(STRIP "${TOOL_NAME}" TOOL_NAME)
50+ string(TOLOWER "${TOOL_NAME}" TOOL_NAME)
51+ if(TOOL_NAME STREQUAL "")
52+ continue()
53+ endif()
54+ list(FIND MSOT_VALID_TOOLS "${TOOL_NAME}" TOOL_INDEX)
55+ if(TOOL_INDEX EQUAL -1)
56+ message(FATAL_ERROR "Unsupported MSOT_EXCLUDE_TOOLS item: ${TOOL_NAME}. Supported tools: ${MSOT_VALID_TOOLS}")
57+ endif()
58+ list(FIND MSOT_EXCLUDED_TOOL_LIST "${TOOL_NAME}" EXISTING_TOOL_INDEX)
59+ if(EXISTING_TOOL_INDEX EQUAL -1)
60+ list(APPEND MSOT_EXCLUDED_TOOL_LIST "${TOOL_NAME}")
61+ endif()
62+ endforeach()
63+endif()
64+ 
65+# 归一化后的字符串用于传给 parser,也写入配置依赖文件,保证排除项变化会重新触发打包。
66+string(JOIN "," MSOT_EXCLUDE_TOOLS_NORMALIZED ${MSOT_EXCLUDED_TOOL_LIST})
67+if(MSOT_EXCLUDE_TOOLS_NORMALIZED)
68+ message(STATUS "Exclude msOT sub-tools: ${MSOT_EXCLUDE_TOOLS_NORMALIZED}")
69+endif()
70+ 
71+# 只 include 未排除的子工具模块。被排除模块不会创建对应 package_* target,
72+# 因而既不会构建,也不会作为 package_msot 的前置依赖。
73+set(MSOT_PACKAGE_TARGETS "")
74+foreach(TOOL_NAME IN LISTS MSOT_VALID_TOOLS)
75+ list(FIND MSOT_EXCLUDED_TOOL_LIST "${TOOL_NAME}" TOOL_INDEX)
76+ if(TOOL_INDEX EQUAL -1)
77+ include(cmake/module/${TOOL_NAME}.cmake)
78+ list(APPEND MSOT_PACKAGE_TARGETS package_${TOOL_NAME})
79+ else()
80+ message(STATUS "Skip msOT sub-tool target: ${TOOL_NAME}")
81+ endif()
82+endforeach()
43 83 
44# 根据构建环境信息生成scene.info84# 根据构建环境信息生成scene.info
45string(TOLOWER "${CMAKE_SYSTEM_NAME}" SYSTEM)85string(TOLOWER "${CMAKE_SYSTEM_NAME}" SYSTEM)
@@ -73,6 +113,18 @@ set(installShell share/info/${NAME}/script/install.sh)
73set(comments "ASCEND MINDSTUDIO SANITIZER RUN PACKAGE")113set(comments "ASCEND MINDSTUDIO SANITIZER RUN PACKAGE")
74file(GLOB_RECURSE PACAKGE_SCRIPT_FILES ${ROOT_DIR}/package/**/*)114file(GLOB_RECURSE PACAKGE_SCRIPT_FILES ${ROOT_DIR}/package/**/*)
75 115 
116+# 自定义命令的 DEPENDS 需要一个可跟踪文件;排除列表变化时更新该文件,
117+# 避免 CMake 认为 package.flag 已经是最新而复用上一次的总包内容。
118+set(MSOT_PACKAGE_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/msot_package_config.txt)
119+file(WRITE "${MSOT_PACKAGE_CONFIG_FILE}" "MSOT_EXCLUDE_TOOLS=${MSOT_EXCLUDE_TOOLS_NORMALIZED}\n")
120+ 
121+# parser 负责生成 filelist.csv 并复制总包内容;这里把同一份排除列表传进去,
122+# 让最终 run 包目录也跳过对应 whl/run 文件。
123+set(MSOT_PARSER_EXCLUDE_ARGS "")
124+if(MSOT_EXCLUDE_TOOLS_NORMALIZED)
125+ list(APPEND MSOT_PARSER_EXCLUDE_ARGS --exclude_tools ${MSOT_EXCLUDE_TOOLS_NORMALIZED})
126+endif()
127+ 
76file(GLOB_RECURSE MAKESELF_TAR_FILE_GLOB ${ROOT_DIR}/thirdparty/makeself/makeself-*.tar.gz)128file(GLOB_RECURSE MAKESELF_TAR_FILE_GLOB ${ROOT_DIR}/thirdparty/makeself/makeself-*.tar.gz)
77file(GLOB_RECURSE MAKESELF_PATCH_FILE_GLOB ${ROOT_DIR}/thirdparty/makeself/makeself-*.patch)129file(GLOB_RECURSE MAKESELF_PATCH_FILE_GLOB ${ROOT_DIR}/thirdparty/makeself/makeself-*.patch)
78list(GET MAKESELF_TAR_FILE_GLOB 0 MAKESELF_TAR_FILE)130list(GET MAKESELF_TAR_FILE_GLOB 0 MAKESELF_TAR_FILE)
@@ -96,20 +148,18 @@ add_custom_command(
96# 解析打包配置文件filelist.xml,并完成打包148# 解析打包配置文件filelist.xml,并完成打包
97add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/package.flag149add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/package.flag
98 COMMAND ${CMAKE_COMMAND} -E copy "${ROOT_DIR}/package/conf/version.info" ${CMAKE_INSTALL_PREFIX}150 COMMAND ${CMAKE_COMMAND} -E copy "${ROOT_DIR}/package/conf/version.info" ${CMAKE_INSTALL_PREFIX}
151+ # 清理总包 staging 目录,防止前一次构建留下的已排除子包被 makeself 再次打入 run 包。
152+ COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_INSTALL_PREFIX}/${NAME}
99 COMMAND cd ${ROOT_DIR}/package/script/ && ${PYTHON} parser.py -x ${ROOT_DIR}/package/conf/filelist.xml153 COMMAND cd ${ROOT_DIR}/package/script/ && ${PYTHON} parser.py -x ${ROOT_DIR}/package/conf/filelist.xml
100- --delivery_path ${CMAKE_INSTALL_PREFIX} -o ${CMAKE_SYSTEM_PROCESSOR}154+ --delivery_path ${CMAKE_INSTALL_PREFIX} -o ${CMAKE_SYSTEM_PROCESSOR} ${MSOT_PARSER_EXCLUDE_ARGS}
101 COMMAND cd ${CMAKE_INSTALL_PREFIX} && ${makeselfTool} --header ${makeselfHeader} --help-header ${helpInfo} --pigz155 COMMAND cd ${CMAKE_INSTALL_PREFIX} && ${makeselfTool} --header ${makeselfHeader} --help-header ${helpInfo} --pigz
102 --complevel 4 --nocrc --nomd5 --sha256 --chown --tar-quietly ${NAME} ${packageName} ${comments} ${installShell}156 --complevel 4 --nocrc --nomd5 --sha256 --chown --tar-quietly ${NAME} ${packageName} ${comments} ${installShell}
103 COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/package.flag157 COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/package.flag
104 COMMAND chmod 750 ${CMAKE_INSTALL_PREFIX}/*.run158 COMMAND chmod 750 ${CMAKE_INSTALL_PREFIX}/*.run
105- DEPENDS ${PACAKGE_SCRIPT_FILES} ${ROOT_DIR}/package/conf/version.info ${makeselfTool} ${makeselfHeader}159+ # package.flag 依赖未排除子工具 target,确保保留的子包产物先生成,再执行总包打包。
160+ DEPENDS ${MSOT_PACKAGE_TARGETS} ${PACAKGE_SCRIPT_FILES} ${ROOT_DIR}/package/conf/version.info
161+ ${MSOT_PACKAGE_CONFIG_FILE} ${makeselfTool} ${makeselfHeader}
106 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}162 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
107)163)
108-add_custom_target(package_msot ALL DEPENDS 164+# ALL 目标也只挂未排除子工具,避免 excluded 子工具在默认 make 中被构建。
109- package_msdebug165+add_custom_target(package_msot ALL DEPENDS ${MSOT_PACKAGE_TARGETS} ${CMAKE_CURRENT_BINARY_DIR}/package.flag)
110- package_mskl
111- package_mskpp
112- package_msopgen
113- package_msopprof
114- package_mssanitizer
115- ${CMAKE_CURRENT_BINARY_DIR}/package.flag)
Mbuild.py+43-1
@@ -28,6 +28,31 @@ from pathlib import Path
28 28 
29logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')29logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
30 30 
31+# 子工具排除粒度与顶层 CMake 模块保持一致;这里做第一层白名单校验,
32+# 避免非法名字继续传到 CMake 后才失败。
33+SUPPORTED_TOOLS = ("msdebug", "mskl", "mskpp", "msopgen", "msopprof", "mssanitizer")
34+ 
35+ 
36+def normalize_excluded_tools(exclude_tools):
37+ """归一化 --exclude-tools 输入,输出稳定的 CMake 逗号分隔参数来源。"""
38+ if not exclude_tools:
39+ return []
40+ 
41+ normalized_tools = []
42+ for tool in exclude_tools.split(','):
43+ # 支持用户输入空格和大小写混用;空片段来自尾逗号或连续逗号,直接忽略。
44+ tool_name = tool.strip().lower()
45+ if not tool_name:
46+ continue
47+ if tool_name not in SUPPORTED_TOOLS:
48+ raise argparse.ArgumentTypeError(
49+ "Unsupported tool '%s'. Supported tools: %s"
50+ % (tool_name, ", ".join(SUPPORTED_TOOLS))
51+ )
52+ if tool_name not in normalized_tools:
53+ normalized_tools.append(tool_name)
54+ return normalized_tools
55+ 
31 56 
32class BuildManager:57class BuildManager:
33 """58 """
@@ -41,6 +66,7 @@ class BuildManager:
41 python build.py -r <revision> 指定依赖的内部源码仓(例如msopcom)的 Git 分支/标签/commit66 python build.py -r <revision> 指定依赖的内部源码仓(例如msopcom)的 Git 分支/标签/commit
42 python build.py -v <version> 指定构建版本号,同时覆盖 --build-version 和 --whl-version67 python build.py -v <version> 指定构建版本号,同时覆盖 --build-version 和 --whl-version
43 python build.py -e KEY=VALUE 指定额外构建选项,可多次使用68 python build.py -e KEY=VALUE 指定额外构建选项,可多次使用
69+ python build.py --exclude-tools msdebug,msopprof 排除指定子工具
44 70 
45 参数说明:71 参数说明:
46 - 参数: command : 构建动作: 为空时为全构建, local 为跳过依赖下载, test 为运行单元测试。72 - 参数: command : 构建动作: 为空时为全构建, local 为跳过依赖下载, test 为运行单元测试。
@@ -48,6 +74,7 @@ class BuildManager:
48 - 参数: -v, --version : 指定构建版本号;若设置,则同时覆盖 --build-version 和 --whl-version 的值。74 - 参数: -v, --version : 指定构建版本号;若设置,则同时覆盖 --build-version 和 --whl-version 的值。
49 - 参数: --build-version, --whl-version : 历史参数,保留用于兼容;设置了 --version 时以 --version 为准。75 - 参数: --build-version, --whl-version : 历史参数,保留用于兼容;设置了 --version 时以 --version 为准。
50 - 参数: -e, --extra : 额外构建选项,格式为 KEY=VALUE,可多次指定。76 - 参数: -e, --extra : 额外构建选项,格式为 KEY=VALUE,可多次指定。
77+ - 参数: --exclude-tools : 不构建且不打包指定子工具,逗号分隔。
51 78 
52 产物归档:79 产物归档:
53 产品构建完成后,归档到 artifacts/ 目录中。80 产品构建完成后,归档到 artifacts/ 目录中。
@@ -68,6 +95,11 @@ class BuildManager:
68 help='Build version, overrides --build-version and --whl-version if set')95 help='Build version, overrides --build-version and --whl-version if set')
69 argument_parser.add_argument('-e', '--extra', metavar='KEY=VALUE', action='append', default=[],96 argument_parser.add_argument('-e', '--extra', metavar='KEY=VALUE', action='append', default=[],
70 help='Extra build options in KEY=VALUE format, can be specified multiple times')97 help='Extra build options in KEY=VALUE format, can be specified multiple times')
98+ # build.py 是用户入口,CMake 是实际构建入口;这里先完成校验和归一化,
99+ # run 阶段只需要把稳定列表透传给 MSOT_EXCLUDE_TOOLS。
100+ argument_parser.add_argument('--exclude-tools', metavar='TOOLS', type=normalize_excluded_tools, default=[],
101+ help='Comma-separated tools excluded from build/package. Supported tools: %s'
102+ % ', '.join(SUPPORTED_TOOLS))
71 self.parsed_arguments = argument_parser.parse_args()103 self.parsed_arguments = argument_parser.parse_args()
72 104 
73 if self.parsed_arguments.version is not None:105 if self.parsed_arguments.version is not None:
@@ -77,6 +109,9 @@ class BuildManager:
77 if self.parsed_arguments.build_version is not None:109 if self.parsed_arguments.build_version is not None:
78 logging.info("--build-version: %s", self.parsed_arguments.build_version)110 logging.info("--build-version: %s", self.parsed_arguments.build_version)
79 111 
112+ if self.parsed_arguments.exclude_tools:
113+ logging.info("--exclude-tools: %s", ",".join(self.parsed_arguments.exclude_tools))
114+ 
80 def _execute_command(self, command_sequence, timeout_seconds=36000, cwd=None, env=None):115 def _execute_command(self, command_sequence, timeout_seconds=36000, cwd=None, env=None):
81 logging.info("Running: %s", " ".join(command_sequence))116 logging.info("Running: %s", " ".join(command_sequence))
82 subprocess.run(command_sequence, timeout=timeout_seconds, check=True, cwd=cwd, env=env)117 subprocess.run(command_sequence, timeout=timeout_seconds, check=True, cwd=cwd, env=env)
@@ -146,7 +181,14 @@ class BuildManager:
146 product_build_dir.mkdir(exist_ok=True)181 product_build_dir.mkdir(exist_ok=True)
147 os.chdir(product_build_dir)182 os.chdir(product_build_dir)
148 183 
149- self._execute_command(["cmake", ".."])184+ # 始终显式传递 MSOT_EXCLUDE_TOOLS,包括空值;否则 CMake 会复用
185+ # build/CMakeCache.txt 中上一次的排除列表,导致后续默认构建仍跳过旧工具。
186+ cmake_command = [
187+ "cmake",
188+ "..",
189+ "-DMSOT_EXCLUDE_TOOLS=%s" % ",".join(self.parsed_arguments.exclude_tools),
190+ ]
191+ self._execute_command(cmake_command)
atomgit-bot
atomgit-botatomgit-bot7月6日

🟡 Medium Priority

问题链:

  1. build.py 第 184-188 行:cmake_command = ["cmake", ".."] 仅在 self.parsed_arguments.exclude_tools 非空时才追加 -DMSOT_EXCLUDE_TOOLS=...
  2. build.py 第 180-182 行:构建目录 build/ 通过 mkdir(exist_ok=True) 复用,因此 CMakeCache.txt 在多次构建间持久存在。
  3. CMakeLists.txt 第 42 行:MSOT_EXCLUDE_TOOLS 定义为 CACHE 变量且无 FORCE,命令行未传入时使用缓存值。

触发条件:用户先执行 build.py --exclude-tools msdebug,再执行 build.py(不带 --exclude-tools)。

失败模式:

  • 第二次构建时 self.parsed_arguments.exclude_tools[]if 条件为 False,不追加 -DMSOT_EXCLUDE_TOOLS
  • 用户预期"不指定即不排除",实际行为与预期相反,导致产出的 run 包缺少 msdebug 子工具。

建议:将 -DMSOT_EXCLUDE_TOOLS 的追加从条件分支中提出,始终向 CMake 传递该参数。当排除列表为空时传入空字符串 -DMSOT_EXCLUDE_TOOLS= 以显式清空缓存。

改动建议
191
+ cmake_command = ["cmake", ".."]
192
+ cmake_command.append("-DMSOT_EXCLUDE_TOOLS=%s" % ",".join(self.parsed_arguments.exclude_tools))
191
193
  self._execute_command(cmake_command)
应用建议
likedislike
150 self._execute_command(["make", "-j", str(self.build_jobs)])192 self._execute_command(["make", "-j", str(self.build_jobs)])
151 193 
152 self._archive_artifacts()194 self._archive_artifacts()
Mpackage/conf/filelist.xml+9-7
@@ -29,13 +29,15 @@
29 </file_info>29 </file_info>
30 30 
31 <file_info value="package" copy_type="source" src_path="${OUTPUT}" dst_path="mindstudio-operator-tools" install_path="mindstudio-operator-tools" install_mod="550">31 <file_info value="package" copy_type="source" src_path="${OUTPUT}" dst_path="mindstudio-operator-tools" install_path="mindstudio-operator-tools" install_mod="550">
32- <file value="mskpp/mindstudio_kpp-*.whl" />32+ <!-- tool 属性用于排除工具参数过滤;未设置 tool 的公共文件始终打入总包。 -->
33- <file value="mskl/mindstudio_kl-*.whl" />33+ <!-- msopgen 同时产出 mindstudio_opgen 与 mindstudio_opst,因此两个 whl 使用同一个 tool 标记。 -->
34- <file value="msopgen/mindstudio_opgen-*.whl" />34+ <file value="mskpp/mindstudio_kpp-*.whl" tool="mskpp" />
35- <file value="msopgen/mindstudio_opst-*.whl" />35+ <file value="mskl/mindstudio_kl-*.whl" tool="mskl" />
36- <file value="msdebug/mindstudio-debugger_*.run" />36+ <file value="msopgen/mindstudio_opgen-*.whl" tool="msopgen" />
37- <file value="mssanitizer/mindstudio-sanitizer_*.run" />37+ <file value="msopgen/mindstudio_opst-*.whl" tool="msopgen" />
38- <file value="msopprof/mindstudio-opprof_*.run" />38+ <file value="msdebug/mindstudio-debugger_*.run" tool="msdebug" />
39+ <file value="mssanitizer/mindstudio-sanitizer_*.run" tool="mssanitizer" />
40+ <file value="msopprof/mindstudio-opprof_*.run" tool="msopprof" />
39 </file_info>41 </file_info>
40 42 
41</config>43</config>
Mpackage/script/parser.py+43-2
@@ -41,6 +41,9 @@ FAIL = -1
41LOG_E = "ERROR"41LOG_E = "ERROR"
42LOG_W = "WARNING"42LOG_W = "WARNING"
43LOG_I = "INFO"43LOG_I = "INFO"
44+# 与顶层 CMake 的 MSOT_VALID_TOOLS 保持一致;parser 只负责“总包内容过滤”,
45+# 构建阶段是否跳过由 CMake 控制。
46+SUPPORTED_TOOLS = ("msdebug", "mskl", "mskpp", "msopgen", "msopprof", "mssanitizer")
44 47 
45def log_print_element(log_element):48def log_print_element(log_element):
46 print("[" + log_element + "]", end=' ')49 print("[" + log_element + "]", end=' ')
@@ -62,11 +65,14 @@ class Xmlparser(object):
62 """65 """
63 功能描述: 解析xml配置文件。66 功能描述: 解析xml配置文件。
64 """67 """
65- def __init__(self, xml_file, delivery_dir, os_arch):68+ def __init__(self, xml_file, delivery_dir, os_arch, excluded_tools=None):
66 self.xml_file = xml_file69 self.xml_file = xml_file
67 self.delivery_dir = delivery_dir70 self.delivery_dir = delivery_dir
68 self.default_config = {}71 self.default_config = {}
69 self.target_env="{0}-{1}".format(os_arch, "linux")72 self.target_env="{0}-{1}".format(os_arch, "linux")
73+ # excluded_tools 只过滤带 tool 属性的文件项;公共安装脚本和基础信息不设置 tool,
74+ # 因此始终会进入总包。
75+ self.excluded_tools = set(excluded_tools or [])
70 76 
71 self._cache_dir_info = {}77 self._cache_dir_info = {}
72 self._dir_install_list = []78 self._dir_install_list = []
@@ -132,6 +138,10 @@ class Xmlparser(object):
132 for sub_item in list(item.iter())[1:]:138 for sub_item in list(item.iter())[1:]:
133 file_info = file_config.copy()139 file_info = file_config.copy()
134 file_info.update(sub_item.attrib)140 file_info.update(sub_item.attrib)
141+ # filelist.xml 中的 tool 属性表示该文件属于哪个子工具。
142+ # 命中排除列表时,既不生成 filelist.csv 记录,也不执行后续复制。
143+ if self.is_tool_excluded(file_info.get('tool')):
144+ continue
135 # 添加路径验证145 # 添加路径验证
136 self._validate_path_component(file_info.get('src_path'), 'src_path')146 self._validate_path_component(file_info.get('src_path'), 'src_path')
137 self._validate_path_component(file_info.get('value'), 'value')147 self._validate_path_component(file_info.get('value'), 'value')
@@ -152,6 +162,12 @@ class Xmlparser(object):
152 file_info['hash'] = hash_value162 file_info['hash'] = hash_value
153 self._package_content_list.append(file_info)163 self._package_content_list.append(file_info)
154 164 
165+ def is_tool_excluded(self, tool_name):
166+ """判断 XML 文件项是否属于被排除的子工具。"""
167+ if not tool_name:
168+ return False
169+ return tool_name.strip().lower() in self.excluded_tools
170+ 
155 def get_src_target(self, file_info):171 def get_src_target(self, file_info):
156 """172 """
157 获取文件的实际路径173 获取文件的实际路径
@@ -400,6 +416,26 @@ def safe_join(base_dir, user_path):
400 )416 )
401 return joined417 return joined
402 418 
419+def normalize_excluded_tools(exclude_tools):
420+ """归一化 parser 入参,保证直接调用 parser.py 时也有白名单校验。"""
421+ if not exclude_tools:
422+ return []
423+ 
424+ normalized_tools = []
425+ for tool in exclude_tools.split(','):
426+ # 兼容 CMake/build.py 传入的逗号分隔字符串;忽略空片段,保留首次出现顺序。
427+ tool_name = tool.strip().lower()
428+ if not tool_name:
429+ continue
430+ if tool_name not in SUPPORTED_TOOLS:
431+ raise ValueError(
432+ "Unsupported tool '%s'. Supported tools: %s"
433+ % (tool_name, ", ".join(SUPPORTED_TOOLS))
434+ )
435+ if tool_name not in normalized_tools:
436+ normalized_tools.append(tool_name)
437+ return normalized_tools
438+ 
403def do_copy(target_conf={}, delivery_dir='', release_dir=''):439def do_copy(target_conf={}, delivery_dir='', release_dir=''):
404 '''440 '''
405 功能描述: 根据拷贝类型来执行文件或目录拷贝441 功能描述: 根据拷贝类型来执行文件或目录拷贝
@@ -560,7 +596,9 @@ def main(args=None):
560 log_msg(LOG_E, "Input xml_file is None, please check paramters!")596 log_msg(LOG_E, "Input xml_file is None, please check paramters!")
561 return FAIL597 return FAIL
562 598 
563- xmlparser = Xmlparser(pkg_xml_file, delivery_dir, args.os_arch)599+ # args 可能来自命令行,也可能来自单测/其他脚本构造;getattr 保持旧调用兼容。
600+ excluded_tools = normalize_excluded_tools(getattr(args, 'exclude_tools', ''))
601+ xmlparser = Xmlparser(pkg_xml_file, delivery_dir, args.os_arch, excluded_tools=excluded_tools)
564 if xmlparser.parse():602 if xmlparser.parse():
565 sys.exit(FAIL)603 sys.exit(FAIL)
566 604 
@@ -600,6 +638,9 @@ def args_prase():
600 help="This parameter define the delivery path for all module." )638 help="This parameter define the delivery path for all module." )
601 parser.add_argument('-o', '--os_arch', metavar='os_arch', required=False, dest='os_arch', nargs='?', const='',639 parser.add_argument('-o', '--os_arch', metavar='os_arch', required=False, dest='os_arch', nargs='?', const='',
602 default=None, help="This parameter define the package's os_arch")640 default=None, help="This parameter define the package's os_arch")
641+ # 由顶层 CMake 传入,用于过滤 filelist.xml 中带 tool 属性的子工具产物。
642+ parser.add_argument('--exclude_tools', metavar='tools', required=False, dest='exclude_tools', nargs='?', const='',
643+ default='', help="Comma-separated tools excluded from package.")
603 return parser.parse_args()644 return parser.parse_args()
604 645 
605 646