已合并
新增 pre-push 本地编译门禁脚本,将 CI 质量门禁左移到开发者本地,在 git push 时自动触发构建与 UT 检查。 #8563
chenqi317创建于 23 天前
新增 pre-push 本地编译门禁脚本,将 CI 质量门禁左移到开发者本地,在 git push 时自动触发构建与 UT 检查。 #8563
已合并
chenqi317创建于 23 天前
3 个文件变更+494-3
@@ -76,3 +76,19 @@ repos:
76 types: [file]76 types: [file]
77 stages: [pre-commit]77 stages: [pre-commit]
78 verbose: true78 verbose: true
79+ 
80+ # pre-push 本地编译门禁(构建 + UT)
81+ # 安装:pre-commit install --hook-type pre-push
82+ # 旁路:git push --no-verify / SKIP=pre-push-build git push / PRE_PUSH_BUILD=0 git push
83+ # 默认即快速增量模式(有漏检风险,合入前需跑完整模式)
84+ # 完整对齐模式:PRE_PUSH_FULL=1 git push(100% 复刻 CI 门禁语义)
85+ - repo: local
86+ hooks:
87+ - id: pre-push-build
88+ name: Pre-Push Build & UT Gate
89+ entry: bash scripts/ci/pre_push_build.sh
90+ language: system
91+ pass_filenames: false
92+ require_serial: true
93+ stages: [pre-push]
atomgit-bot
atomgit-botatomgit-bot23 天前

🔴 Critical

变更行 .pre-commit-config.yaml 第 92 行:stages: [pre-push]

pre-commit 框架中,pre-push hook type 对应的 stage 名称为 push,而非 pre-push。有效的 stage 名称仅有 commitmerge-commitpushmanual。配置 stages: [pre-push] 是一个无效值,框架会忽略此项,导致该 hook 在 git push 时永远不会被触发。

失效模式:开发者执行 git push 时,pre-push 构建门禁完全静默跳过,无论是否安装了 hook。这将导致 CI 质量左移完全失效,开发者可能在本地未发现构建/UT 错误直接推送,失去本 PR 的全部价值。

建议:将 stages: [pre-push] 改为 pre-commit 框架规定的正确 stage 名称 push

改动建议
93
- stages: [pre-push]
93
+ stages: [push]
应用建议
likedislike
chenqi317
22 天前 评论:
94+ verbose: true
@@ -244,12 +244,14 @@ done
244echo "related op: ${builtin_ops_name[*]}"244echo "related op: ${builtin_ops_name[*]}"
245echo "related experimental op: ${experimental_ops_name[*]}"245echo "related experimental op: ${experimental_ops_name[*]}"
246echo "need build all: ${build_all}"246echo "need build all: ${build_all}"
247- 247+echo "force_jit: ${force_jit}"
248a5_soc="ascend950"248a5_soc="ascend950"
249 249 
250-if [[ ${#builtin_ops_name[@]} -gt 0 && "$force_jit" = "false" ]]; then250+if [[ ${#builtin_ops_name[@]} -gt 0 ]]; then
251 builtin_ops_str=$(IFS=,; echo "${builtin_ops_name[*]}")251 builtin_ops_str=$(IFS=,; echo "${builtin_ops_name[*]}")
252- build_cmd="bash build.sh --pkg --ops=$builtin_ops_str --soc=$a5_soc ${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} ${force}"252+ jit_flag=""
253+ [[ "$force_jit" == "true" ]] && jit_flag="--jit"
254+ build_cmd="bash build.sh --pkg ${jit_flag} --ops=$builtin_ops_str --soc=$a5_soc ${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} ${force}"
253 run_build_command "$build_cmd"255 run_build_command "$build_cmd"
254 execute_run_file "custom"256 execute_run_file "custom"
255fi257fi
@@ -0,0 +1,473 @@
1+#!/bin/bash
范其瑞
范其瑞范其瑞22 天前

[MAJOR] 473行 Shell 脚本建议在开头顶部添加 set -euo pipefail,确保任何命令失败时脚本立即退出。当前无完整源码可见,若缺失此配置,中间步骤(编译/UT)失败可能被静默忽略,导致构建门禁形同虚设。

文件: scripts/ci/pre_push_build.sh 行: 1

likedislike
2+# -----------------------------------------------------------------------------------------------------------
3+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
4+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
5+# CANN Open Software License Agreement Version 2.0 (the "License").
6+# Please refer to the License for details. You may not use this file except in compliance with the License.
7+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
8+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
9+# See LICENSE in the root of the software repository for the full text of the License.
10+# -----------------------------------------------------------------------------------------------------------
11+# ============================================================================
12+# pre-push 本地编译门禁脚本
13+# 用途:在 git push 前触发本地构建与 UT,将门禁质量左移到本地,保障远程门禁成功率。
14+#
15+# 运行模式:
16+# 默认(快速增量模式):按变更模块精简编译范围,耗时短但有漏检风险。
17+# PRE_PUSH_FULL=1(完整对齐模式):直接调用 scripts/ci/local_build.sh,100% 复刻 CI 门禁语义。
18+#
19+# 旁路方式:
20+# git push --no-verify
21+# SKIP=pre-push-build git push
22+# PRE_PUSH_BUILD=0 git push
23+# ============================================================================
24+ 
25+set -eo pipefail
26+ 
27+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
28+WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")"
29+cd "$WORKSPACE"
30+ 
31+# ---------------------------------------------------------------------------
32+# 0. 旁路控制
33+# ---------------------------------------------------------------------------
34+if [[ "${PRE_PUSH_BUILD:-1}" == "0" ]]; then
35+ echo "[pre-push] PRE_PUSH_BUILD=0, skipping pre-push build gate."
36+ exit 0
37+fi
38+ 
39+if [[ -n "${SKIP:-}" && "$SKIP" == *"pre-push-build"* ]]; then
40+ echo "[pre-push] SKIP=pre-push-build, skipping pre-push build gate."
41+ exit 0
42+fi
43+ 
44+# ---------------------------------------------------------------------------
45+# 1. 环境自检
46+# ---------------------------------------------------------------------------
47+THREAD_NUM=${THREAD_NUM:-$(grep -c ^processor /proc/cpuinfo)}
48+BASE_BRANCH_NAME=${BASE_BRANCH_NAME:-master}
49+PR_FILELIST="pr_filelist.txt"
50+ 
51+# 检查 CANN Toolkit 环境是否安装
52+cann_installed=false
53+for _cann_dir in "${ASCEND_HOME_PATH:-}" "${ASCEND_TOOLKIT_HOME:-}" "/usr/local/Ascend/ascend-toolkit/latest"; do
54+ [[ -z "$_cann_dir" ]] && continue
55+ if [[ -f "$_cann_dir/set_env.sh" ]]; then
56+ cann_installed=true
57+ break
58+ fi
59+done
60+if [[ "$cann_installed" == "false" ]]; then
61+ echo -e "\033[31m[pre-push][WARN] 环境未检测到 CANN 包,跳过 pre-push 检查,带风险提交。\033[0m"
62+ exit 0
63+fi
64+ 
65+# 检查关键脚本是否存在
66+if [[ ! -f "$WORKSPACE/scripts/ci/local_build.sh" ]]; then
67+ echo "[pre-push][ERROR] scripts/ci/local_build.sh not found."
68+ exit 1
69+fi
70+ 
71+# 日志辅助:同时输出到终端和 pre_push_build.log
72+log_echo() {
73+ echo "$@" | tee -a pre_push_build.log
74+}
75+ 
76+# ---------------------------------------------------------------------------
77+# 2. 计算变更文件清单(复用 local_build.sh 的 git diff 逻辑)
78+# ---------------------------------------------------------------------------
79+calc_changed_files() {
80+ local target_url="https://gitcode.com/cann/ops-nn.git"
81+ local remote_name=""
82+ for remote in $(git remote); do
83+ local url
84+ url=$(git remote get-url "$remote" 2>/dev/null || true)
85+ if [[ "$url" == *"$target_url"* || "$url" == *"${target_url%.git}"* ]]; then
86+ remote_name="$remote"
87+ break
88+ fi
89+ done
90+ if [[ -z "$remote_name" ]]; then
91+ if git remote | grep -q "^origin$"; then
92+ remote_name="origin"
93+ else
94+ log_echo "[pre-push][ERROR] No remote found for git diff calculation."
95+ exit 1
96+ fi
97+ fi
98+ 
99+ log_echo "[pre-push] Remote: $remote_name ($(git remote get-url $remote_name))"
100+ log_echo "[pre-push] Fetching latest from $remote_name..."
101+ git fetch "$remote_name" --quiet --prune 2>/dev/null || true
102+ 
103+ local remote_ref="${remote_name}/${BASE_BRANCH_NAME}"
104+ if ! git rev-parse --verify "$remote_ref" >/dev/null 2>&1; then
105+ log_echo "[pre-push][ERROR] Remote branch '$remote_ref' does not exist."
106+ git branch -r --list "${remote_name}/*"
107+ exit 1
108+ fi
109+ 
110+ local merge_base target_commit
111+ merge_base=$(git merge-base "$remote_ref" HEAD 2>/dev/null || true)
112+ if [[ -z "$merge_base" ]]; then
113+ log_echo "[pre-push][ERROR] Could not find merge-base with $remote_ref."
114+ exit 1
115+ fi
116+ target_commit=$(git rev-parse HEAD)
117+ 
118+ local changed_files
119+ changed_files=$(git diff --name-only "$merge_base" "$target_commit" | sort -u)
120+ if [[ -z "$changed_files" ]]; then
121+ log_echo "[pre-push][WARN] No changed files found. Please check whether the code has been committed."
122+ exit 0
123+ fi
124+ 
125+ echo "$changed_files" > "$PR_FILELIST"
126+ local total
127+ total=$(wc -l < "$PR_FILELIST")
128+ log_echo "[pre-push] Changed files: $total"
129+ log_echo "[pre-push] --- Changed File List ---"
130+ while IFS= read -r f; do
131+ [[ -z "$f" ]] && continue
132+ log_echo " $f"
133+ done < "$PR_FILELIST"
134+ log_echo "[pre-push] ---------------------------"
135+}
136+ 
137+# ---------------------------------------------------------------------------
138+# 3. 四步识别(用于快速模式与日志展示)
139+# ---------------------------------------------------------------------------
140+identify_changes() {
141+ has_kernel=false
142+ has_host=false
143+ has_api=false
144+ has_graph=false
145+ has_experimental=false
146+ has_arch35=false
147+ need_jit_build=false
148+ md_only=true
149+ kernel_ops=""
150+ arch_list=()
151+ 
152+ while IFS= read -r f; do
153+ [[ -z "$f" ]] && continue
154+ case "$f" in
155+ */op_kernel/*) has_kernel=true; md_only=false ;;
156+ */op_host/*) has_host=true; md_only=false ;;
157+ */op_api/*) has_api=true; md_only=false ;;
158+ */op_graph/*) has_graph=true; md_only=false ;;
159+ */examples/*) md_only=false ;;
160+ experimental/*) has_experimental=true; md_only=false ;;
161+ *.md|*.json|*.ini|*.txt|*.yaml|*.yml|cmake/*|scripts/*) ;;
162+ *) md_only=false ;;
163+ esac
164+ # 目录包含 arch35 或 op_kernel 不触发 JIT,配置/脚本也不触发
165+ case "$f" in
166+ */arch35/*) ;;
167+ */op_kernel/*) ;;
168+ *.md|*.json|*.ini|*.txt|*.yaml|*.yml|cmake/*|scripts/*) ;;
169+ *) need_jit_build=true ;;
170+ esac
171+ # arch 识别
172+ case "$f" in
173+ */arch35/*) arch_list+=("ascend950"); has_arch35=true ;;
174+ */arch22/*) arch_list+=("ascend910b") ;;
175+ */arch20/*) arch_list+=("ascend310p") ;;
176+ esac
177+ done < "$PR_FILELIST"
178+ 
179+ # 去重 arch_list
180+ if [[ ${#arch_list[@]} -gt 0 ]]; then
181+ mapfile -t arch_list < <(printf '%s\n' "${arch_list[@]}" | sort -u)
182+ fi
183+ 
184+ log_echo "[pre-push] --- Change Identification ---"
185+ log_echo "[pre-push] op_kernel : $has_kernel"
186+ log_echo "[pre-push] op_host : $has_host"
187+ log_echo "[pre-push] op_api : $has_api"
188+ log_echo "[pre-push] op_graph : $has_graph"
189+ log_echo "[pre-push] experimental: $has_experimental"
190+ log_echo "[pre-push] md_only : $md_only"
191+ log_echo "[pre-push] need_jit_build: $need_jit_build"
192+ if [[ ${#arch_list[@]} -gt 0 ]]; then
193+ log_echo "[pre-push] arch : ${arch_list[*]}"
194+ else
195+ log_echo "[pre-push] arch : default (affects all SOC)"
196+ fi
197+ log_echo "[pre-push] -----------------------------"
198+}
199+ 
200+# ---------------------------------------------------------------------------
201+# 3b. 打印本次将执行的环节与命令概览
202+# ---------------------------------------------------------------------------
203+print_pipeline() {
204+ local mode
205+ if [[ "${PRE_PUSH_FULL:-0}" == "1" ]]; then
206+ mode="FULL"
207+ else
208+ mode="FAST"
209+ fi
210+ 
211+ log_echo ""
212+ log_echo "===================================================================="
213+ log_echo " Pipeline Preview (mode: $mode)"
214+ log_echo "===================================================================="
215+ 
216+ if [[ "$mode" == "FULL" ]]; then
217+ log_echo " [1] JIT build : bash build.sh --pkg --jit -j${THREAD_NUM}"
218+ log_echo " [2] ophost UT : bash build.sh -u --ophost -f $PR_FILELIST -j${THREAD_NUM}"
219+ log_echo " [3] opapi UT : bash build.sh -u --opapi -f $PR_FILELIST -j${THREAD_NUM}"
220+ if [[ "$BASE_BRANCH_NAME" == "master" ]]; then
221+ log_echo " [4] opgraph UT : bash build.sh -u --opgraph -f $PR_FILELIST -j${THREAD_NUM}"
222+ log_echo " [5] opkernel UT : bash scripts/ci/check_kernel_ut.sh $PR_FILELIST --no_cov"
223+ fi
224+ log_echo " [6] single-op pkg : bash scripts/ci/check_pkg.sh $PR_FILELIST -j${THREAD_NUM} --no_force"
225+ log_echo " [7] ascend950 pkg : bash scripts/ci/compile_ascend950_pkg.sh $PR_FILELIST --no_force -j${THREAD_NUM}"
226+ log_echo " [8] A2 smoke : bash scripts/ci/check_example.sh $PR_FILELIST (needs 910B)"
227+ log_echo " [9] experimental pkg : bash scripts/ci/check_experimental_pkg.sh $PR_FILELIST"
228+ else
229+ local idx=1
230+ if [[ "$need_jit_build" == "true" ]]; then
231+ log_echo " [$idx] JIT build : bash build.sh --pkg --jit -j${THREAD_NUM}"
232+ idx=$((idx+1))
233+ else
234+ log_echo " [--] JIT build : SKIP (arch35/op_kernel only)"
235+ fi
236+ if [[ "$has_host" == "true" ]]; then
237+ log_echo " [$idx] ophost UT : bash build.sh -u --ophost -f $PR_FILELIST -j${THREAD_NUM}"
238+ idx=$((idx+1))
239+ else
240+ log_echo " [--] ophost UT : SKIP (op_host not changed)"
241+ fi
242+ if [[ "$has_api" == "true" ]]; then
243+ log_echo " [$idx] opapi UT : bash build.sh -u --opapi -f $PR_FILELIST -j${THREAD_NUM}"
244+ idx=$((idx+1))
245+ else
246+ log_echo " [--] opapi UT : SKIP (op_api not changed)"
247+ fi
248+ if [[ "$BASE_BRANCH_NAME" == "master" ]]; then
249+ if [[ "$has_graph" == "true" ]]; then
250+ log_echo " [$idx] opgraph UT : bash build.sh -u --opgraph -f $PR_FILELIST -j${THREAD_NUM}"
251+ idx=$((idx+1))
252+ else
253+ log_echo " [--] opgraph UT : SKIP (op_graph not changed)"
254+ fi
255+ if [[ "$has_kernel" == "true" ]]; then
256+ log_echo " [$idx] opkernel UT : bash scripts/ci/check_kernel_ut.sh $PR_FILELIST --no_cov"
257+ idx=$((idx+1))
258+ else
259+ log_echo " [--] opkernel UT : SKIP (op_kernel not changed)"
260+ fi
261+ else
262+ log_echo " [--] opgraph/opkernel UT: SKIP (non-master branch)"
263+ fi
264+ if [[ "$has_arch35" == "true" ]]; then
265+ local arch_flag="--no_force"
266+ [[ "$has_kernel" == "false" ]] && arch_flag="-force_jit"
267+ log_echo " [$idx] ascend950 pkg : bash scripts/ci/compile_ascend950_pkg.sh $PR_FILELIST ${arch_flag} -j${THREAD_NUM}"
268+ idx=$((idx+1))
269+ else
270+ log_echo " [--] ascend950 pkg : SKIP (arch35 not changed)"
271+ fi
272+ log_echo " [--] single-op pkg : SKIP (fast mode)"
273+ log_echo " [--] A2 smoke : SKIP (fast mode)"
274+ if [[ "$has_experimental" == "true" ]]; then
275+ log_echo " [$idx] experimental pkg : bash scripts/ci/check_experimental_pkg.sh $PR_FILELIST"
276+ idx=$((idx+1))
277+ else
278+ log_echo " [--] experimental pkg : SKIP (experimental not changed)"
279+ fi
280+ fi
281+ log_echo "===================================================================="
282+ log_echo ""
283+}
284+ 
285+# ---------------------------------------------------------------------------
286+# 4. 静默执行:构建/UT 输出仅写入日志文件,终端只打印阶段名与 PASS/FAIL
287+# 用法: run_cmd_silent <stage_name> <log_file> <extra_grep> <cmd...>
288+# extra_grep 非空时,即使退出码为 0 也会在日志中搜索该关键字,命中即判失败
289+# ---------------------------------------------------------------------------
290+run_cmd_silent() {
291+ local stage_name=$1 log_file=$2 extra_grep=$3
292+ shift 3
293+ log_echo "[pre-push] >>> $stage_name (log: $log_file)"
294+ [[ "$log_file" != "pre_push_build.log" ]] && : > "$log_file"
295+ local rc=0
296+ set +e
297+ "$@" >> "$log_file" 2>&1
298+ rc=$?
299+ set -e
300+ [[ "$log_file" != "pre_push_build.log" ]] && cat "$log_file" >> pre_push_build.log
301+ if [[ $rc -ne 0 ]]; then
302+ log_echo "[pre-push][FAILED] $stage_name (exit $rc), check log: $log_file"
303+ local failed_lines
304+ failed_lines=$(grep -E '\[ FAILED \]|FAILED TESTS|error happened|Error:|error:' "$log_file" 2>/dev/null | tail -5)
305+ [[ -n "$failed_lines" ]] && log_echo "$failed_lines"
306+ exit $rc
307+ fi
308+ if [[ -n "$extra_grep" ]] && grep -q "$extra_grep" "$log_file" 2>/dev/null; then
309+ log_echo "[pre-push][FAILED] $stage_name: found '$extra_grep' in log: $log_file"
310+ exit 1
311+ fi
312+ log_echo "[pre-push][PASSED] $stage_name"
313+}
314+ 
315+# ---------------------------------------------------------------------------
316+# 5. 阶段间清理
317+# ---------------------------------------------------------------------------
318+clean_build() {
319+ local dir
320+ for dir in build build_out; do
321+ local abs="$WORKSPACE/$dir"
322+ if [[ -z "$WORKSPACE" || "$abs" == "/" || ! "$abs" =~ ^/[^/] ]]; then
323+ log_echo "[pre-push][ERROR] clean_build refused: unsafe path '$abs' (WORKSPACE='$WORKSPACE')."
324+ exit 1
325+ fi
326+ if [[ -d "$abs" ]]; then
327+ rm -rf "$abs"
328+ fi
329+ done
330+}
331+ 
332+# ---------------------------------------------------------------------------
333+# 6a. 完整对齐模式:直接调用 local_build.sh
334+# ---------------------------------------------------------------------------
335+run_full_mode() {
336+ log_echo "[pre-push] >>> FULL MODE: delegating to local_build.sh (100% CI-aligned)"
337+ log_echo "[pre-push] Branch: $(git rev-parse --abbrev-ref HEAD) Base: $BASE_BRANCH_NAME"
338+ run_cmd_silent "FULL MODE (local_build.sh)" pre_push_build.log "" \
339+ bash "$WORKSPACE/scripts/ci/local_build.sh"
340+ log_echo "[pre-push] <<< FULL MODE: ALL PASSED"
341+}
342+ 
343+# ---------------------------------------------------------------------------
344+# 6b. 快速增量模式:按模块精简
345+# ---------------------------------------------------------------------------
346+run_fast_mode() {
347+ log_echo "[pre-push] >>> FAST MODE: incremental build (default)"
348+ log_echo "[pre-push] NOTE: Fast mode has known coverage gaps vs CI. Run full mode before merge."
349+ log_echo ""
350+ 
351+ local branch
352+ branch=$(git rev-parse --abbrev-ref HEAD)
353+ export WORKSPACE="$WORKSPACE"
354+ export ASCEND_3RD_LIB_PATH="$WORKSPACE/third_party"
355+ export BASE_PATH="$WORKSPACE"
356+ export BUILD_PATH="$WORKSPACE/build"
357+ 
358+ clean_build
359+ 
360+ if [[ "$need_jit_build" == "true" ]]; then
361+ run_cmd_silent "JIT build" pre_push_build.log "" \
362+ bash build.sh --pkg --jit -j${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH}
363+ clean_build
364+ fi
365+ 
366+ if [[ "$has_host" == "true" ]]; then
367+ run_cmd_silent "ophost UT" ut_test.log "" \
368+ bash build.sh -u --ophost -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
369+ else
370+ log_echo "[pre-push] op_host not changed, skipping ophost UT (fast mode)."
371+ fi
372+ 
373+ if [[ "$has_api" == "true" ]]; then
374+ if [[ "$has_host" == "false" && "$need_jit_build" == "false" ]]; then
375+ run_cmd_silent "JIT build (opapi dep)" pre_push_build.log "" \
376+ bash build.sh --pkg --jit -j${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH}
377+ fi
378+ run_cmd_silent "opapi UT" ut_test.log "" \
379+ bash build.sh -u --opapi -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
380+ else
381+ log_echo "[pre-push] op_api not changed, skipping opapi UT (fast mode)."
382+ fi
383+ 
384+ if [[ "$branch" == "master" || "$BASE_BRANCH_NAME" == "master" ]]; then
385+ if [[ "$has_graph" == "true" ]]; then
386+ run_cmd_silent "opgraph UT" ut_test.log "" \
387+ bash build.sh -u --opgraph -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
388+ fi
389+ if [[ "$has_kernel" == "true" ]]; then
390+ run_cmd_silent "opkernel UT" ut_kernel.log "error happened" \
391+ bash scripts/ci/check_kernel_ut.sh "$PR_FILELIST" --no_cov
392+ fi
393+ else
394+ log_echo "[pre-push] Non-master branch, skipping opgraph/opkernel UT (aligned with CI)."
395+ fi
396+ clean_build
397+ 
398+ if [[ "$has_arch35" == "true" ]]; then
399+ if [[ "$has_kernel" == "false" ]]; then
400+ run_cmd_silent "ascend950 pkg" pre_push_build.log "" \
401+ bash scripts/ci/compile_ascend950_pkg.sh "$PR_FILELIST" -force_jit -j${THREAD_NUM}
402+ else
403+ run_cmd_silent "ascend950 pkg" pre_push_build.log "" \
404+ bash scripts/ci/compile_ascend950_pkg.sh "$PR_FILELIST" -j${THREAD_NUM} --no_force
405+ fi
406+ clean_build
407+ fi
408+ 
409+ log_echo "[pre-push][SKIP] Single-op pkg / A2 smoke skipped in fast mode."
410+ log_echo "[pre-push][SKIP] Run full mode (PRE_PUSH_FULL=1) to cover remaining CI stages."
411+ 
412+ if [[ "$has_experimental" == "true" ]]; then
413+ run_cmd_silent "experimental pkg" pre_push_build.log "" \
414+ bash scripts/ci/check_experimental_pkg.sh "$PR_FILELIST"
415+ clean_build
416+ log_echo "[pre-push][SKIP] experimental example skipped in fast mode."
417+ fi
418+ 
419+ log_echo "[pre-push] <<< FAST MODE: DONE (with coverage gaps, see SKIP above)"
420+}
421+ 
422+# ---------------------------------------------------------------------------
423+# 7. 主流程
424+# ---------------------------------------------------------------------------
425+main() {
426+ : > pre_push_build.log
427+ log_echo "===================================================================="
428+ log_echo " Pre-Push Build & UT Gate"
429+ log_echo " Mode: $([[ "${PRE_PUSH_FULL:-0}" == "1" ]] && echo "FULL (CI-aligned)" || echo "FAST (incremental)")"
430+ log_echo " Workspace: $WORKSPACE"
431+ log_echo " Time: $(date '+%Y-%m-%d %H:%M:%S')"
432+ log_echo "===================================================================="
433+ calc_changed_files
434+ identify_changes
435+ print_pipeline
436+ 
437+ # 仅文档/配置/cmake/scripts 变更时,所有流程直接跳过
438+ if [[ "$md_only" == "true" ]]; then
439+ log_echo "[pre-push] Only docs/config/cmake/scripts changed, skipping all build & UT."
440+ log_echo "[pre-push][HINT] If scripts/ci or CMakeLists were modified, CI script changes are NOT verified locally. Review manually."
441+ exit 0
442+ fi
443+ 
444+ # 仅 experimental 变更时,只触发 check_experimental_pkg.sh
445+ if [[ "$has_experimental" == "true" && "$has_host" == "false" && "$has_kernel" == "false" \
446+ && "$has_api" == "false" && "$has_graph" == "false" ]]; then
447+ log_echo "[pre-push] Only experimental changed, running check_experimental_pkg.sh only."
448+ clean_build
449+ export ASCEND_3RD_LIB_PATH="$WORKSPACE/third_party"
450+ run_cmd_silent "experimental pkg" pre_push_build.log "" \
451+ bash scripts/ci/check_experimental_pkg.sh "$PR_FILELIST"
452+ exit 0
453+ fi
454+ 
455+ local start_ts end_ts elapsed
456+ start_ts=$(date +%s)
457+ 
458+ if [[ "${PRE_PUSH_FULL:-0}" == "1" ]]; then
459+ run_full_mode
460+ else
461+ run_fast_mode
462+ fi
463+ 
464+ end_ts=$(date +%s)
465+ elapsed=$((end_ts - start_ts))
466+ log_echo ""
467+ log_echo "===================================================================="
468+ log_echo " Pre-Push Gate: PASSED (elapsed ${elapsed}s)"
469+ log_echo "===================================================================="
470+ exit 0
471+}
472+ 
473+main "$@"