已合并
feat: 重构 CI 配置解析,生成按算子独立的构建命令 #1989
feat: 重构 CI 配置解析,生成按算子独立的构建命令 #1989
已合并
liulinxiang创建于 3月30日
4 个文件变更+569-308
Mbuild.sh+12-71
@@ -948,16 +948,14 @@ checkopts() {
948 948 
949 check_param949 check_param
950 set_create_libs950 set_create_libs
951- parse_changed_files
952 set_ut_mode951 set_ut_mode
953 check_group_compile_config952 check_group_compile_config
953+ if [[ "$CI_MODE" == "TRUE" ]]; then
954+ run_ci_mode
955+ fi
954}956}
955 957 
956-parse_changed_files() {958+run_ci_mode() {
957- if [[ -z "$CHANGED_FILES" ]]; then
958- return
959- fi
960- 
961 if [[ "$CHANGED_FILES" != /* ]]; then959 if [[ "$CHANGED_FILES" != /* ]]; then
962 CHANGED_FILES=$PWD/$CHANGED_FILES960 CHANGED_FILES=$PWD/$CHANGED_FILES
963 fi961 fi
@@ -968,73 +966,16 @@ parse_changed_files() {
968 cat $CHANGED_FILES966 cat $CHANGED_FILES
969 echo $dotted_line967 echo $dotted_line
970 968 
971- COMPILED_OPS=$(python3 scripts/ci/parse_changed_ops.py $CHANGED_FILES "$ENABLE_EXPERIMENTAL")969+ # 直接调用 gen_ci_cmd.py 生成命令并执行
972- echo "related ops "$COMPILED_OPS970+ local resolve_cmd="python3 scripts/ci/gen_ci_cmd.py -f $CHANGED_FILES --exec --experimental=${ENABLE_EXPERIMENTAL} --pkg=${ENABLE_PACKAGE} --run_example=${ENABLE_RUN_EXAMPLE}"
973- 971+ if [[ -n "$CANN_3RD_LIB_PATH" && "$CANN_3RD_LIB_PATH" != "${BASE_PATH}/third_party" ]]; then
974- local DEFAULT_OP_SET=FALSE972+ resolve_cmd="$resolve_cmd --cann_3rd_lib_path=$CANN_3RD_LIB_PATH"
975- if [[ -z $COMPILED_OPS ]]; then
976- if [[ "$ENABLE_EXPERIMENTAL" == "TRUE" ]]; then
977- COMPILED_OPS='acos'
978- else
979- COMPILED_OPS='is_finite'
980- fi
981- DEFAULT_OP_SET=TRUE
982- echo "No ops changed found, set op $COMPILED_OPS as default."
983 fi973 fi
974+ $resolve_cmd
975+ local ret=$?
984 976 
985- if [[ "$ENABLE_PACKAGE" == "TRUE" ]]; then977+ # 执行完成后退出,返回 gen_ci_cmd.py 的退出码
986- return978+ exit $ret
987- fi
988- 
989- local script_ret=$(python3 scripts/ci/parse_changed_files.py $CHANGED_FILES "$ENABLE_EXPERIMENTAL")
990- IFS='&&' read -r related_ut soc_info <<<"$script_ret"
991- echo "related ut "$related_ut
992- echo "related soc_info "$soc_info
993- 
994- COMPUTE_UNIT=$soc_info
995- 
996- if [[ "$related_ut" == "set()" ]]; then
997- # 默认算子时,固定触发 op_api UT 和 op_kernel UT
998- if [[ "$DEFAULT_OP_SET" == "TRUE" ]]; then
999- echo "Default op $COMPILED_OPS set, trigger op_api UT and op_kernel UT"
1000- OP_API_UT=TRUE
1001- OP_KERNEL_UT=TRUE
1002- ENABLE_CUSTOM=TRUE
1003- ENABLE_TEST=TRUE
1004- COMPUTE_UNIT="ascend910b"
1005- return
1006- fi
1007- 
1008- ENABLE_TEST=FALSE
1009- echo "no ut matched! no need to run!"
1010- echo "---------------- CANN build finished ----------------"
1011- return
1012- else
1013- ENABLE_TEST=TRUE
1014- fi
1015- 
1016- if [[ "$related_ut" =~ "ALL_UT" ]]; then
1017- echo "ALL UT is triggered!"
1018- return
1019- fi
1020- if [[ ("$related_ut" =~ "OP_HOST_UT" || "$related_ut" =~ "OP_GRAPH_UT") && "$OP_HOST" == "TRUE" ]]; then
1021- echo "OP_HOST_UT is triggered!"
1022- OP_HOST_UT=TRUE
1023- OP_KERNEL_UT=TRUE
1024- OP_KERNEL=TRUE
1025- OP_GRAPH=TRUE
1026- ENABLE_CUSTOM=TRUE
1027- fi
1028- if [[ "$related_ut" =~ "OP_API_UT" && "$OP_API" == "TRUE" ]]; then
1029- echo "OP_API_UT is triggered!"
1030- OP_API_UT=TRUE
1031- ENABLE_CUSTOM=TRUE
1032- fi
1033- if [[ "$related_ut" =~ "OP_KERNEL_UT" && "$OP_KERNEL" == "TRUE" ]]; then
1034- echo "OP_KERNEL_UT is triggered!"
1035- OP_KERNEL_UT=TRUE
1036- ENABLE_CUSTOM=TRUE
1037- fi
1038}979}
1039 980 
1040custom_cmake_args() {981custom_cmake_args() {
@@ -0,0 +1,557 @@
1+#!/usr/bin/env python3
2+# -*- coding: utf-8 -*-
3+# ----------------------------------------------------------------------------
4+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
6+# CANN Open Software License Agreement Version 2.0 (the "License").
7+# Please refer to the License for details. You may not use this file except in compliance with the License.
8+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
9+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
10+# See LICENSE in the root of the software repository for the full text of the License.
11+# ----------------------------------------------------------------------------
12+"""
13+根据 CI 变更文件生成构建命令
14+ 
15+用法:
16+ # UT 测试模式(默认)
17+ python3 gen_ci_cmd.py -f changes.txt # 打印命令
18+ python3 gen_ci_cmd.py -f changes.txt --exec # 直接执行
19+ python3 gen_ci_cmd.py -f changes.txt --experimental=TRUE # 指定 experimental 算子构建
20+ 
21+ # CI 出包模式
22+ python3 gen_ci_cmd.py -f changes.txt --pkg=TRUE # 生成出包命令
23+ python3 gen_ci_cmd.py -f changes.txt --pkg=TRUE --experimental=TRUE # 出包 experimental 算子
24+ 
25+ # 示例运行模式
26+ python3 gen_ci_cmd.py -f changes.txt --run_example=TRUE # 每个算子运行示例
27+ 
28+CI用法
29+ # UT
30+ ## experimental
31+ bash build.sh -f pr_filelist.txt --experimental -u --cann_3rd_lib_path=/home/jenkins/opensource -j16
32+ ## 非experimental
33+ bash build.sh -u -f pr_filelist.txt --cann_3rd_lib_path=/home/jenkins/opensource -j16
34+ 
35+ # 编译
36+ ## experimental
37+ bash build.sh -f pr_filelist.txt --experimental --cann_3rd_lib_path=/home/jenkins/opensource -j16
38+ ## 非experimental
39+ bash build.sh --pkg --jit --cann_3rd_lib_path=/home/jenkins/opensource -j16
40+ 
41+ #examples
42+ ## experimental
43+ bash build.sh -f pr_filelist.txt --experimental --run_example
44+ ## 非experimental
45+ bash build.sh -f pr_filelist.txt --run_example
46+ 
47+"""
48+import argparse
49+import os
50+import re
51+import subprocess
52+import sys
53+ 
54+# ============================================================
55+# 配置常量
56+# ============================================================
57+DEFAULT_SOC = 'ascend910b'
58+DEFAULT_EXP_OP = 'acos'
59+DEFAULT_NORMAL_OP = 'is_finite'
60+DEFAULT_UTS = {'ophost', 'opapi', 'opkernel', 'opgraph'}
61+ 
62+# 规则定义:pattern 匹配路径,提取对应信息
63+RULES = [
64+ # experimental 算子
65+ {'pattern': r'experimental/(?:math|conversion|random)/([^/]+)', 'type': 'exp_ops'},
66+ # 普通算子
67+ {'pattern': r'^(?:math|conversion|random)/([^/]+)', 'type': 'ops'},
68+]
69+ 
70+# 默认SOC(用于pkg和run_example命令的SOC过滤)
71+DEFAULT_FILTER_SOC = 'ascend910b'
72+ 
73+ 
74+def check_op_supports_soc(op_name, soc, is_experimental=False):
75+ """检查算子是否支持指定的SOC
76+ 
77+ 检查方法:查找算子的 op_host/*def.cpp 文件,
78+ 检查是否包含 AddConfig("{soc}") 字符串
79+ 
80+ Args:
81+ op_name: 算子名称
82+ soc: SOC名称,如 'ascend910b'
83+ is_experimental: 是否为 experimental 算子
84+ 
85+ Returns:
86+ bool: 是否支持该SOC
87+ """
88+ prefixes = ['math/', 'conversion/', 'random/']
89+ if is_experimental:
90+ prefixes = ['experimental/math/', 'experimental/conversion/', 'experimental/random/']
91+ 
92+ for prefix in prefixes:
93+ op_host_dir = os.path.join(prefix, op_name, 'op_host')
94+ if not os.path.isdir(op_host_dir):
95+ continue
96+ 
97+ for filename in os.listdir(op_host_dir):
98+ if filename.endswith('_def.cpp'):
99+ def_file = os.path.join(op_host_dir, filename)
100+ try:
101+ with open(def_file, 'r', encoding='utf-8') as f:
102+ content = f.read()
103+ if f'AddConfig("{soc}"' in content:
104+ return True
105+ except (IOError, OSError) as e:
106+ # 文件读取失败,继续检查下一个文件
107+ continue
108+ 
109+ return False
110+ 
111+ 
112+def filter_ops_by_soc_support(ops, soc, is_experimental=False):
113+ """过滤出支持指定SOC的算子
114+ 
115+ Args:
116+ ops: 算子集合
117+ soc: 目标SOC
118+ is_experimental: 是否为 experimental 算子
119+ 
120+ Returns:
121+ set: 支持该SOC的算子集合
122+ """
123+ supported_ops = set()
124+ for op in ops:
125+ if check_op_supports_soc(op, soc, is_experimental):
126+ supported_ops.add(op)
127+ return supported_ops
128+ 
129+ 
130+def read_file_lines(filepath):
131+ """读取文件并返回非空、非注释行列表
132+ 
133+ Args:
134+ filepath: 文件路径
135+ 
136+ Returns:
137+ list: 行列表,文件不存在返回空列表
138+ """
139+ try:
140+ with open(filepath, 'r', encoding='utf-8') as f:
141+ return [line.strip() for line in f if line.strip() and not line.strip().startswith('#')]
142+ except (FileNotFoundError, PermissionError, UnicodeDecodeError):
143+ return []
144+ 
145+ 
146+def parse_changed_files(filepath):
147+ """解析变更文件,提取算子
148+ 
149+ Returns:
150+ dict: {'exp_ops': set(), 'normal_ops': set()},文件不存在返回空集合字典
151+ """
152+ exp_ops, normal_ops = set(), set()
153+ 
154+ files = read_file_lines(filepath)
155+ 
156+ for file_path in files:
157+ if file_path.endswith('.md'):
158+ continue
159+ 
160+ for rule in RULES:
161+ m = re.search(rule['pattern'], file_path)
162+ if not m:
163+ continue
164+ 
165+ rule_type = rule['type']
166+ if rule_type == 'exp_ops':
167+ exp_ops.add(m.group(1))
168+ elif rule_type == 'ops':
169+ normal_ops.add(m.group(1))
170+ 
171+ return {'exp_ops': exp_ops, 'normal_ops': normal_ops}
172+ 
173+ 
174+def get_op_ut_types(op_name, files, is_experimental):
175+ """获取单个算子的 UT 类型和 SOC
176+ 
177+ Returns:
178+ tuple: (uts, socs)
179+ - uts: UT 类型集合,如 {'ophost', 'opapi'}
180+ - socs: 该算子检测到的 SOC 集合(通过 arch35 目录),如 {'ascend950'}
181+ """
182+ uts = set()
183+ socs = set()
184+ 
185+ # 算子可能的路径前缀
186+ prefixes = ['math/', 'conversion/', 'random/']
187+ if is_experimental:
188+ prefixes = ['experimental/math/', 'experimental/conversion/', 'experimental/random/']
189+ 
190+ for f in files:
191+ # 检查文件是否属于该算子
192+ matched = False
193+ for prefix in prefixes:
194+ if f.startswith(f'{prefix}{op_name}/'):
195+ matched = True
196+ break
197+ 
198+ if matched:
199+ # 检测 UT 类型
200+ if '/op_host/' in f or '/tests/ut/op_host/' in f:
201+ uts.add('ophost')
202+ if '/op_api/' in f or '/tests/ut/op_api/' in f:
203+ uts.add('opapi')
204+ if '/op_kernel/' in f or '/tests/ut/op_kernel/' in f:
205+ uts.add('opkernel')
206+ if '/op_graph/' in f or '/tests/ut/op_graph/' in f:
207+ uts.add('opgraph')
208+ 
209+ # 检测该算子的 SOC(通过 arch35 目录)
210+ if '/arch35/' in f:
211+ socs.add('ascend950')
212+ 
213+ return uts, socs
214+ 
215+ 
216+def make_command(op, uts, soc, cann_3rd_lib_path=None, is_experimental=False):
217+ """生成单个算子的构建命令"""
218+ cmd_parts = ['bash build.sh', '-u', '-j16']
219+ 
220+ if is_experimental:
221+ cmd_parts.append('--experimental')
222+ 
223+ cmd_parts.append(f"--ops={op}")
224+ 
225+ # 使用检测到的 UT 类型,无则使用默认全部
226+ for ut in sorted(uts if uts else DEFAULT_UTS):
227+ cmd_parts.append(f"--{ut}")
228+ 
229+ cmd_parts.append(f"--soc={soc}")
230+ 
231+ if cann_3rd_lib_path:
232+ cmd_parts.append(f"--cann_3rd_lib_path={cann_3rd_lib_path}")
233+ 
234+ return ' '.join(cmd_parts)
235+ 
236+ 
237+def make_merged_command(merged_ops, ut_type, soc, cann_3rd_lib_path=None, is_experimental=False):
238+ """生成合并后的构建命令(多个算子合并到一条命令)
239+ 合并策略:按 (SOC, UT类型) 分组合并算子,减少命令数量
240+ 
241+ 合并规则:
242+ - opapi/opgraph: 只跑默认 SOC (ascend910b),所有算子合并为一条命令
243+ - ophost/opkernel: 按 SOC 分组(有 arch35 变更时跑多个 SOC),同一 SOC 的算子合并
244+ """
245+ cmd_parts = ['bash build.sh', '-u', '-j16']
246+ 
247+ if is_experimental:
248+ cmd_parts.append('--experimental')
249+ 
250+ cmd_parts.append(f"--ops={merged_ops}")
251+ cmd_parts.append(f"--{ut_type}")
252+ cmd_parts.append(f"--soc={soc}")
253+ 
254+ if cann_3rd_lib_path:
255+ cmd_parts.append(f"--cann_3rd_lib_path={cann_3rd_lib_path}")
256+ 
257+ return ' '.join(cmd_parts)
258+ 
259+ 
260+def make_run_example_command(op_name, mode, is_experimental=False):
261+ """生成算子示例运行命令
262+ 
263+ 命令格式:bash build.sh [--experimental] --run_example $opname $mode cust [--vendor_name=experimental]
264+ 
265+ Args:
266+ op_name: 算子名称
267+ mode: 运行模式,'eager''graph'
268+ is_experimental: 是否为 experimental 算子
269+ """
270+ if is_experimental:
271+ cmd_parts = [
272+ 'bash build.sh', '--experimental', '--run_example',
273+ op_name, mode, 'cust', '--vendor_name=experimental'
274+ ]
275+ else:
276+ cmd_parts = [
277+ 'bash build.sh', '--run_example', op_name, mode, 'cust'
278+ ]
279+ return ' '.join(cmd_parts)
280+ 
281+ 
282+def build_ut_commands(filepath, experimental=False, cann_3rd_lib_path=None):
283+ """构建 UT 命令列表
284+ 
285+ 按算子维度检测 SOC(arch35),每个算子只跑它实际支持的 SOC。
286+ 
287+ Args:
288+ filepath: 变更文件路径
289+ experimental: 是否为 experimental 算子
290+ cann_3rd_lib_path: 第三方库路径
291+ """
292+ parsed = parse_changed_files(filepath)
293+ files = read_file_lines(filepath)
294+ 
295+ exp_ops = parsed['exp_ops']
296+ normal_ops = parsed['normal_ops']
297+ 
298+ # 根据参数确定跑哪类算子
299+ ops = exp_ops if experimental else normal_ops
300+ default_op = DEFAULT_EXP_OP if experimental else DEFAULT_NORMAL_OP
301+ 
302+ # 无变更,用默认命令
303+ if not ops:
304+ return [make_command(default_op, DEFAULT_UTS, DEFAULT_SOC, cann_3rd_lib_path, experimental)]
305+ 
306+ # 以下为 UT 命令生成逻辑
307+ # 分组收集:{(soc, ut_type): set(op_names)}
308+ # 按算子维度检测 SOC,每个算子只跑它实际支持的 SOC
309+ groups = {}
310+ 
311+ for op in ops:
312+ # 按算子维度获取 UT 类型和 SOC
313+ uts, op_socs = get_op_ut_types(op, files, experimental)
314+ 
315+ # 该算子的 SOC 列表:默认 + 该算子检测到的 arch35
316+ op_socs_to_run = {DEFAULT_SOC} | op_socs
317+ 
318+ # opapi/opgraph 不区分 SOC,只跑默认 SOC,合并所有算子
319+ if 'opapi' in uts:
320+ key = (DEFAULT_SOC, 'opapi')
321+ groups.setdefault(key, set()).add(op)
322+ if 'opgraph' in uts:
323+ key = (DEFAULT_SOC, 'opgraph')
324+ groups.setdefault(key, set()).add(op)
325+ 
326+ # ophost/opkernel 按该算子检测到的 SOC 分组
327+ host_kernel_uts = uts - {'opapi', 'opgraph'}
328+ for ut in host_kernel_uts:
329+ for soc in op_socs_to_run:
330+ key = (soc, ut)
331+ groups.setdefault(key, set()).add(op)
332+ 
333+ # 生成合并后的命令
334+ commands = []
335+ for (soc, ut_type), op_names in sorted(groups.items()):
336+ # 将算子列表合并为逗号分隔的字符串
337+ merged_ops = ','.join(sorted(op_names))
338+ cmd = make_merged_command(merged_ops, ut_type, soc, cann_3rd_lib_path, experimental)
339+ commands.append(cmd)
340+ 
341+ return list(dict.fromkeys(commands)) # 去重保序
342+ 
343+ 
344+def check_op_examples(op_name, is_experimental):
345+ """检查算子 examples 目录下的测试文件类型
346+ 
347+ Args:
348+ op_name: 算子名称
349+ is_experimental: 是否为 experimental 算子
350+ 
351+ Returns:
352+ dict: {'has_eager': bool, 'has_graph': bool}
353+ """
354+ # 算子可能的路径前缀
355+ prefixes = ['math/', 'conversion/', 'random/']
356+ if is_experimental:
357+ prefixes = ['experimental/math/', 'experimental/conversion/', 'experimental/random/']
358+ 
359+ result = {'has_eager': False, 'has_graph': False}
360+ 
361+ for prefix in prefixes:
362+ examples_dir = os.path.join(prefix, op_name, 'examples')
363+ if not os.path.isdir(examples_dir):
364+ continue
365+ 
366+ # 检查目录下的文件
367+ try:
368+ for filename in os.listdir(examples_dir):
369+ if filename.startswith('test_aclnn') and filename.endswith('.cpp'):
370+ result['has_eager'] = True
371+ if filename.startswith('test_geir') and filename.endswith('.cpp'):
372+ result['has_graph'] = True
373+ except OSError:
374+ continue
375+ 
376+ return result
377+ 
378+ 
379+def build_example_commands(filepath, experimental=False):
380+ """构建 run_example 命令列表
381+ 
382+ 根据算子 examples 目录下的测试文件类型生成命令:
383+ - 存在 test_aclnn*.cpp → 生成 eager 命令
384+ - 存在 test_geir*.cpp → 生成 graph 命令
385+ 
386+ Args:
387+ filepath: 变更文件路径
388+ experimental: 是否为 experimental 算子
389+ 
390+ 示例:
391+ bash build.sh --run_example add_n eager cust
392+ bash build.sh --run_example add_n graph cust
393+ """
394+ parsed = parse_changed_files(filepath)
395+ 
396+ exp_ops = parsed['exp_ops']
397+ normal_ops = parsed['normal_ops']
398+ 
399+ # 根据参数确定跑哪类算子
400+ ops = exp_ops if experimental else normal_ops
401+ default_op = DEFAULT_EXP_OP if experimental else DEFAULT_NORMAL_OP
402+ 
403+ # 过滤不支持 DEFAULT_FILTER_SOC 的算子
404+ ops = filter_ops_by_soc_support(ops, DEFAULT_FILTER_SOC, experimental)
405+ 
406+ # 如果没有检测到算子或过滤后为空,使用默认算子
407+ if not ops:
408+ ops = {default_op}
409+ 
410+ # 生成 run_example 命令
411+ commands = []
412+ for op in sorted(ops):
413+ # 检查算子的 examples 目录
414+ example_check = check_op_examples(op, experimental)
415+ 
416+ # 根据存在的测试文件类型生成对应命令
417+ if example_check['has_eager']:
418+ commands.append(make_run_example_command(op, 'eager', experimental))
419+ # TODO: 后续放开 graph 命令生成
420+ # if example_check['has_graph']:
421+ # commands.append(make_run_example_command(op, 'graph', experimental))
422+ 
423+ return commands
424+ 
425+ 
426+def make_package_command(merged_ops, cann_3rd_lib_path=None, is_experimental=False):
427+ """生成出包构建命令
428+ 
429+ 命令格式:bash build.sh --pkg -j16 --ops=op1,op2 --vendor_name=experimental/custom
430+ 
431+ 生成的包名:
432+ experimental=FALSE: cann-ops-math-custom_linux-x86_64.run
433+ experimental=TRUE: cann-ops-math-experimental_linux-x86_64.run
434+ 
435+ Args:
436+ merged_ops: 合并后的算子列表字符串,如 "op1,op2,op3"
437+ cann_3rd_lib_path: 可选的第三方库路径
438+ is_experimental: 是否为 experimental 算子
439+ """
440+ cmd_parts = ['bash build.sh', '--pkg', '-j16']
441+ 
442+ if is_experimental:
443+ cmd_parts.append('--experimental')
444+ cmd_parts.append('--vendor_name=experimental')
445+ else:
446+ cmd_parts.append('--vendor_name=custom')
447+ 
448+ cmd_parts.append(f"--ops={merged_ops}")
449+ 
450+ if cann_3rd_lib_path:
451+ cmd_parts.append(f"--cann_3rd_lib_path={cann_3rd_lib_path}")
452+ 
453+ return ' '.join(cmd_parts)
454+ 
455+ 
456+def build_package_commands(filepath, experimental=False, cann_3rd_lib_path=None):
457+ """构建出包命令列表
458+ 
459+ 出包模式特点:
460+ - 不区分 UT 类型 和 SOC
461+ - 所有算子合并到一条命令
462+ 
463+ Args:
464+ filepath: 变更文件路径
465+ experimental: 是否为 experimental 算子
466+ cann_3rd_lib_path: 第三方库路径
467+ 
468+ 示例:
469+ bash build.sh --pkg -j16 --ops=op1,op2,op3
470+ """
471+ parsed = parse_changed_files(filepath)
472+ 
473+ exp_ops = parsed['exp_ops']
474+ normal_ops = parsed['normal_ops']
475+ 
476+ # 根据参数确定跑哪类算子
477+ ops = exp_ops if experimental else normal_ops
478+ default_op = DEFAULT_EXP_OP if experimental else DEFAULT_NORMAL_OP
479+ 
480+ # 过滤不支持 DEFAULT_FILTER_SOC 的算子
481+ ops = filter_ops_by_soc_support(ops, DEFAULT_FILTER_SOC, experimental)
482+ 
483+ # 无变更或过滤后为空,用默认命令
484+ if not ops:
485+ return [make_package_command(default_op, cann_3rd_lib_path, experimental)]
486+ 
487+ # 所有算子合并到一条命令
488+ merged_ops = ','.join(sorted(ops))
489+ cmd = make_package_command(merged_ops, cann_3rd_lib_path, experimental)
490+ 
491+ return [cmd]
492+ 
493+ 
494+def print_commands(commands):
495+ """打印生成的命令列表"""
496+ print(f"生成 {len(commands)} 条命令:", flush=True)
497+ for cmd in commands:
498+ print(f" {cmd}", flush=True)
499+ print(flush=True)
500+ 
501+ 
502+def execute_commands(commands, mode='ut'):
503+ """执行命令列表并根据模式打印结果
504+ 
505+ Args:
506+ commands: 命令列表
507+ mode: 执行模式,可选 'ut', 'pkg', 'example'
508+ 
509+ Returns:
510+ int: 0 表示全部成功,1 表示有命令失败
511+ """
512+ for cmd in commands:
513+ print(f"执行: {cmd}", flush=True)
514+ result = subprocess.run(cmd, shell=True)
515+ if result.returncode != 0:
516+ print(f"run {mode} fail: {cmd} (返回码: {result.returncode})", flush=True)
517+ return 1
518+ print(f"命令成功: {cmd}", flush=True)
519+ return 0
520+ 
521+ 
522+def main():
523+ parser = argparse.ArgumentParser(description='根据 CI 变更文件生成构建命令')
524+ parser.add_argument('-f', '--file', required=True, help='变更文件列表 必选参数')
525+ parser.add_argument('--exec', action='store_true', help='直接执行生成的命令 可选参数')
526+ parser.add_argument('--experimental', choices=['TRUE', 'FALSE'], default='FALSE',
527+ help='可选参数 默认FALSE TRUE表示跑experimental目录下的用例 检测不到算子跑默认experimental目录下的acos算子 '
528+ 'FALSE表示跑基本算子的用例 检查不到算子跑math目录下的is_finite 是否指定 experimental 算子构建 (TRUE/FALSE)')
529+ parser.add_argument('--pkg', choices=['TRUE', 'FALSE'], default='FALSE',
530+ help='是否生成出包命令 (TRUE/FALSE) 可选参数 默认是FALSE TRUE表示对涉及变更的算子打自定义算子包 '
531+ 'FALSE 不打包 只跑UT')
532+ parser.add_argument('--run_example', choices=['TRUE', 'FALSE'], default='FALSE',
533+ help='是否生成 run_example 命令 (TRUE/FALSE) 可选参数 默认FALSE TRUE表示每个算子运行示例')
534+ parser.add_argument('--cann_3rd_lib_path', help='可选参数 CANN third party lib path')
535+ args = parser.parse_args()
536+ 
537+ # 根据模式选择不同的命令生成函数
538+ if args.run_example == 'TRUE':
539+ commands = build_example_commands(args.file, args.experimental == 'TRUE')
540+ mode = 'example'
541+ elif args.pkg == 'TRUE':
542+ commands = build_package_commands(args.file, args.experimental == 'TRUE', args.cann_3rd_lib_path)
543+ mode = 'pkg'
544+ else:
545+ commands = build_ut_commands(args.file, args.experimental == 'TRUE', args.cann_3rd_lib_path)
546+ mode = 'ut'
547+ 
548+ # 打印生成的命令
549+ print_commands(commands)
550+ 
551+ # 执行命令
552+ if args.exec:
553+ sys.exit(execute_commands(commands, mode))
554+ 
555+ 
556+if __name__ == '__main__':
557+ main()
@@ -1,145 +0,0 @@
1-# Copyright (c) 2025 Huawei Technologies Co., Ltd.
2-# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
3-# CANN Open Software License Agreement Version 2.0 (the "License").
4-# Please refer to the License for details. You may not use this file except in compliance with the License.
5-# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
6-# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7-# See LICENSE in the root of the software repository for the full text of the License.
8- 
9-import os
10-import sys
11-import re
12-import logging
13- 
14-OP_API_UT = "OP_API_UT"
15-OP_HOST_UT = "OP_HOST_UT"
16-OP_GRAPH_UT = "OP_GRAPH_UT"
17-OP_KERNEL_UT = "OP_KERNEL_UT"
18-ALL_UT = "ALL_UT"
19- 
20-NEW_OPS_PATH = [
21- "math",
22- "conversion",
23- "random"
24- # 添加更多算子路径
25-]
26-NEW_EXPERIMENTAL_OPS_PATH = [
27- "experimental/math",
28- "experimental/conversion",
29- "experimental/random"
30- # 添加更多算子路径
31-]
32-COMM_FILES = [
33- "tests",
34- "common"
35- # 添加更多算子路径
36-]
37-SOC_MAPPING = {
38- "arch35": "ascend950"
39-}
40- 
41- 
42-class FileChangeInfo:
43- def __init__(self, op_api_changed_files=None, op_host_changed_files=None, op_graph_changed_files=None,
44- op_kernel_changed_files=None, comm_changed_files=None, soc_info=None):
45- self.op_api_changed_files = [] if op_api_changed_files is None else op_api_changed_files
46- self.op_host_changed_files = [] if op_host_changed_files is None else op_host_changed_files
47- self.op_graph_changed_files = [] if op_graph_changed_files is None else op_graph_changed_files
48- self.op_kernel_changed_files = [] if op_kernel_changed_files is None else op_kernel_changed_files
49- self.comm_changed_files = [] if comm_changed_files is None else comm_changed_files
50- self.soc_info = set() if soc_info is None else soc_info
51- 
52- 
53-def get_file_change_info_from_ci(changed_file_info_from_ci, ops_path):
54- """
55- get file change info from ci, ci will write `git diff > /or_filelist.txt`
56- :param changed_file_info_from_ci: git diff result file from ci
57- :return: None or FileChangeInf
58- """
59- or_file_path = os.path.realpath(changed_file_info_from_ci)
60- if not os.path.exists(or_file_path):
61- logging.error("[ERROR] change file is not exist, can not get file change info in this pull request.")
62- return None
63- with open(or_file_path) as or_f:
64- lines = or_f.readlines()
65- op_api_changed_files = []
66- op_host_changed_files = []
67- op_graph_changed_files = []
68- op_kernel_changed_files = []
69- comm_changed_files = []
70- soc_info = set()
71- host_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/op_host/.*\.(cc|cpp|h)$")
72- api_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/op_api/.*\.(cc|cpp|h)$")
73- kernel_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/op_kernel/.*\.(cc|cpp|h)$")
74- graph_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/op_graph/.*\.(cc|cpp|h)$")
75- host_test_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/tests/ut/op_host/.*\.(cc|cpp|txt)$")
76- api_test_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/tests/ut/op_api/.*\.(cc|cpp|txt|py)$")
77- graph_test_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/tests/ut/op_graph/.*\.(cc|cpp|txt)$")
78- kernel_test_pattern = re.compile(rf"({'|'.join(ops_path)})/.*/tests/ut/op_kernel/.*\.(cc|cpp|txt)$")
79- comm_files_pattern = re.compile(rf"^({'|'.join(COMM_FILES)})")
80- soc_pattern = re.compile(rf"({'|'.join(re.escape(key) for key in SOC_MAPPING)})")
81- 
82- for line in lines:
83- line = line.strip()
84- ext = os.path.splitext(line)[-1].lower()
85- if ext in (".md",):
86- continue
87- if not os.path.exists(line):
88- continue
89- if api_pattern.match(line) or api_test_pattern.match(line):
90- op_api_changed_files.append(line)
91- elif host_pattern.match(line) or host_test_pattern.match(line):
92- op_host_changed_files.append(line)
93- elif kernel_pattern.match(line) or kernel_test_pattern.match(line):
94- op_kernel_changed_files.append(line)
95- elif graph_pattern.match(line) or graph_test_pattern.match(line):
96- op_graph_changed_files.append(line)
97- elif comm_files_pattern.match(line):
98- comm_changed_files.append(line)
99- soc_match = soc_pattern.search(line)
100- if soc_match:
101- matched_key = soc_match.group(1)
102- soc_info.add(SOC_MAPPING[matched_key])
103- 
104- return FileChangeInfo(op_host_changed_files=op_host_changed_files,
105- op_api_changed_files=op_api_changed_files,
106- op_graph_changed_files=op_graph_changed_files,
107- op_kernel_changed_files=op_kernel_changed_files,
108- comm_changed_files=comm_changed_files,
109- soc_info=soc_info)
110- 
111- 
112-def get_change_relate_ut_dir_list(changed_file_info_from_ci, is_experimental):
113- if is_experimental == "TRUE":
114- ops_path = NEW_EXPERIMENTAL_OPS_PATH
115- else:
116- ops_path = NEW_OPS_PATH
117- file_change_info = get_file_change_info_from_ci(changed_file_info_from_ci, ops_path)
118- if not file_change_info:
119- logging.info("[INFO] not found file change info, run all c++.")
120- return None
121- 
122- def _get_relate_ut_list_by_file_change():
123- relate_ut = set()
124- if len(file_change_info.op_host_changed_files) > 0:
125- relate_ut.add(OP_HOST_UT)
126- if len(file_change_info.op_api_changed_files) > 0:
127- relate_ut.add(OP_API_UT)
128- if len(file_change_info.op_graph_changed_files) > 0:
129- relate_ut.add(OP_GRAPH_UT)
130- if len(file_change_info.op_kernel_changed_files) > 0:
131- relate_ut.add(OP_KERNEL_UT)
132- if len(file_change_info.comm_changed_files) > 0:
133- relate_ut.add(ALL_UT)
134- return relate_ut
135- 
136- try:
137- relate_uts = _get_relate_ut_list_by_file_change()
138- except BaseException as e:
139- logging.error(e.args)
140- return None
141- return f'{str(relate_uts)}&{",".join(file_change_info.soc_info)}'
142- 
143- 
144-if __name__ == '__main__':
145- print(get_change_relate_ut_dir_list(sys.argv[1], sys.argv[2]))
@@ -1,92 +0,0 @@
1-# Copyright (c) 2025 Huawei Technologies Co., Ltd.
2-# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
3-# CANN Open Software License Agreement Version 2.0 (the "License").
4-# Please refer to the License for details. You may not use this file except in compliance with the License.
5-# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
6-# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7-# See LICENSE in the root of the software repository for the full text of the License.
8- 
9-import os
10-import sys
11-import re
12-import logging
13- 
14-NEW_OPS_PATH = [
15- "math",
16- "conversion",
17- "random"
18- # 添加更多算子路径
19-]
20- 
21- 
22-class OperatorChangeInfo:
23- def __init__(self, changed_operators=None, operator_file_map=None):
24- self.changed_operators = [] if changed_operators is None else changed_operators
25- self.operator_file_map = {} if operator_file_map is None else operator_file_map
26- 
27- 
28-def extract_operator_name(file_path, is_experimental):
29- clean_path = file_path.lstrip('/')
30- path_parts = clean_path.split('/')
31- default_name = ''
32- operator_name = ''
33- domain = ''
34- if is_experimental == "TRUE":
35- if len(path_parts) >= 3:
36- domain = path_parts[1]
37- operator_name = path_parts[2]
38- if operator_name == "common" or not os.path.exists(f'experimental/{domain}/{operator_name}'):
39- return default_name
40- else:
41- if len(path_parts) >= 2:
42- domain = path_parts[0]
43- operator_name = path_parts[1]
44- if operator_name == "common" or not os.path.exists(f'{domain}/{operator_name}'):
45- return default_name
46- if domain in NEW_OPS_PATH:
47- return operator_name
48- return default_name
49- 
50- 
51-def get_operator_info_from_ci(changed_file_info_from_ci, is_experimental):
52- """
53- get operator change info from ci, ci will write `git diff > /or_filelist.txt`
54- :param changed_file_info_from_ci: git diff result file from ci
55- :return: None or OperatorChangeInf
56- """
57- or_file_path = os.path.realpath(changed_file_info_from_ci)
58- if not os.path.exists(or_file_path):
59- logging.error("[ERROR] change file is not exist, can not get file change info in this pull request.")
60- return None
61- with open(or_file_path) as or_f:
62- lines = or_f.readlines()
63- changed_operators = set()
64- operator_file_map = {}
65- 
66- for line in lines:
67- line = line.strip()
68- ext = os.path.splitext(line)[-1].lower()
69- if ext in (".md",):
70- continue
71- operator_name = extract_operator_name(line, is_experimental)
72- if not operator_name:
73- continue
74- changed_operators.add(operator_name)
75- if operator_name not in operator_file_map:
76- operator_file_map[operator_name] = []
77- operator_file_map[operator_name].append(line)
78- 
79- return OperatorChangeInfo(changed_operators=list(changed_operators), operator_file_map=operator_file_map)
80- 
81- 
82-def get_change_ops_list(changed_file_info_from_ci, is_experimental):
83- ops_change_info = get_operator_info_from_ci(changed_file_info_from_ci, is_experimental)
84- if not ops_change_info:
85- logging.info("[INFO] not found ops change info, run all c++.")
86- return None
87- 
88- return ";".join(ops_change_info.changed_operators)
89- 
90- 
91-if __name__ == '__main__':
92- print(get_change_ops_list(sys.argv[1], sys.argv[2]))