#!/bin/bash
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")"
cd "$WORKSPACE"
if [[ "${PRE_PUSH_BUILD:-1}" == "0" ]]; then
echo "[pre-push] PRE_PUSH_BUILD=0, skipping pre-push build gate."
exit 0
fi
if [[ -n "${SKIP:-}" && "$SKIP" == *"pre-push-build"* ]]; then
echo "[pre-push] SKIP=pre-push-build, skipping pre-push build gate."
exit 0
fi
THREAD_NUM=${THREAD_NUM:-$(grep -c ^processor /proc/cpuinfo)}
BASE_BRANCH_NAME=${BASE_BRANCH_NAME:-master}
PR_FILELIST="pr_filelist.txt"
cann_installed=false
for _cann_dir in "${ASCEND_HOME_PATH:-}" "${ASCEND_TOOLKIT_HOME:-}" "/usr/local/Ascend/ascend-toolkit/latest"; do
[[ -z "$_cann_dir" ]] && continue
if [[ -f "$_cann_dir/set_env.sh" ]]; then
cann_installed=true
break
fi
done
if [[ "$cann_installed" == "false" ]]; then
echo -e "\033[31m[pre-push][WARN] 环境未检测到 CANN 包,跳过 pre-push 检查,带风险提交。\033[0m"
exit 0
fi
if [[ ! -f "$WORKSPACE/scripts/ci/local_build.sh" ]]; then
echo "[pre-push][ERROR] scripts/ci/local_build.sh not found."
exit 1
fi
log_echo() {
echo "$@" | tee -a pre_push_build.log
}
calc_changed_files() {
local target_url="https://gitcode.com/cann/ops-nn.git"
local remote_name=""
for remote in $(git remote); do
local url
url=$(git remote get-url "$remote" 2>/dev/null || true)
if [[ "$url" == *"$target_url"* || "$url" == *"${target_url%.git}"* ]]; then
remote_name="$remote"
break
fi
done
if [[ -z "$remote_name" ]]; then
if git remote | grep -q "^origin$"; then
remote_name="origin"
else
log_echo "[pre-push][ERROR] No remote found for git diff calculation."
exit 1
fi
fi
log_echo "[pre-push] Remote: $remote_name ($(git remote get-url $remote_name))"
log_echo "[pre-push] Fetching latest from $remote_name..."
git fetch "$remote_name" --quiet --prune 2>/dev/null || true
local remote_ref="${remote_name}/${BASE_BRANCH_NAME}"
if ! git rev-parse --verify "$remote_ref" >/dev/null 2>&1; then
log_echo "[pre-push][ERROR] Remote branch '$remote_ref' does not exist."
git branch -r --list "${remote_name}/*"
exit 1
fi
local merge_base target_commit
merge_base=$(git merge-base "$remote_ref" HEAD 2>/dev/null || true)
if [[ -z "$merge_base" ]]; then
log_echo "[pre-push][ERROR] Could not find merge-base with $remote_ref."
exit 1
fi
target_commit=$(git rev-parse HEAD)
local changed_files
changed_files=$(git diff --name-only "$merge_base" "$target_commit" | sort -u)
if [[ -z "$changed_files" ]]; then
log_echo "[pre-push][WARN] No changed files found. Please check whether the code has been committed."
exit 0
fi
echo "$changed_files" > "$PR_FILELIST"
local total
total=$(wc -l < "$PR_FILELIST")
log_echo "[pre-push] Changed files: $total"
log_echo "[pre-push] --- Changed File List ---"
while IFS= read -r f; do
[[ -z "$f" ]] && continue
log_echo " $f"
done < "$PR_FILELIST"
log_echo "[pre-push] ---------------------------"
}
identify_changes() {
has_kernel=false
has_host=false
has_api=false
has_graph=false
has_experimental=false
has_arch35=false
need_jit_build=false
md_only=true
kernel_ops=""
arch_list=()
while IFS= read -r f; do
[[ -z "$f" ]] && continue
case "$f" in
*/op_kernel/*) has_kernel=true; md_only=false ;;
*/op_host/*) has_host=true; md_only=false ;;
*/op_api/*) has_api=true; md_only=false ;;
*/op_graph/*) has_graph=true; md_only=false ;;
*/examples/*) md_only=false ;;
experimental/*) has_experimental=true; md_only=false ;;
*.md|*.json|*.ini|*.txt|*.yaml|*.yml|cmake/*|scripts/*) ;;
*) md_only=false ;;
esac
case "$f" in
*/arch35/*) ;;
*/op_kernel/*) ;;
*.md|*.json|*.ini|*.txt|*.yaml|*.yml|cmake/*|scripts/*) ;;
*) need_jit_build=true ;;
esac
case "$f" in
*/arch35/*) arch_list+=("ascend950"); has_arch35=true ;;
*/arch22/*) arch_list+=("ascend910b") ;;
*/arch20/*) arch_list+=("ascend310p") ;;
esac
done < "$PR_FILELIST"
if [[ ${#arch_list[@]} -gt 0 ]]; then
mapfile -t arch_list < <(printf '%s\n' "${arch_list[@]}" | sort -u)
fi
log_echo "[pre-push] --- Change Identification ---"
log_echo "[pre-push] op_kernel : $has_kernel"
log_echo "[pre-push] op_host : $has_host"
log_echo "[pre-push] op_api : $has_api"
log_echo "[pre-push] op_graph : $has_graph"
log_echo "[pre-push] experimental: $has_experimental"
log_echo "[pre-push] md_only : $md_only"
log_echo "[pre-push] need_jit_build: $need_jit_build"
if [[ ${#arch_list[@]} -gt 0 ]]; then
log_echo "[pre-push] arch : ${arch_list[*]}"
else
log_echo "[pre-push] arch : default (affects all SOC)"
fi
log_echo "[pre-push] -----------------------------"
}
print_pipeline() {
local mode
if [[ "${PRE_PUSH_FULL:-0}" == "1" ]]; then
mode="FULL"
else
mode="FAST"
fi
log_echo ""
log_echo "===================================================================="
log_echo " Pipeline Preview (mode: $mode)"
log_echo "===================================================================="
if [[ "$mode" == "FULL" ]]; then
log_echo " [1] JIT build : bash build.sh --pkg --jit -j${THREAD_NUM}"
log_echo " [2] ophost UT : bash build.sh -u --ophost -f $PR_FILELIST -j${THREAD_NUM}"
log_echo " [3] opapi UT : bash build.sh -u --opapi -f $PR_FILELIST -j${THREAD_NUM}"
if [[ "$BASE_BRANCH_NAME" == "master" ]]; then
log_echo " [4] opgraph UT : bash build.sh -u --opgraph -f $PR_FILELIST -j${THREAD_NUM}"
log_echo " [5] opkernel UT : bash scripts/ci/check_kernel_ut.sh $PR_FILELIST --no_cov"
fi
log_echo " [6] single-op pkg : bash scripts/ci/check_pkg.sh $PR_FILELIST -j${THREAD_NUM} --no_force"
log_echo " [7] ascend950 pkg : bash scripts/ci/compile_ascend950_pkg.sh $PR_FILELIST --no_force -j${THREAD_NUM}"
log_echo " [8] A2 smoke : bash scripts/ci/check_example.sh $PR_FILELIST (needs 910B)"
log_echo " [9] experimental pkg : bash scripts/ci/check_experimental_pkg.sh $PR_FILELIST"
else
local idx=1
if [[ "$need_jit_build" == "true" ]]; then
log_echo " [$idx] JIT build : bash build.sh --pkg --jit -j${THREAD_NUM}"
idx=$((idx+1))
else
log_echo " [--] JIT build : SKIP (arch35/op_kernel only)"
fi
if [[ "$has_host" == "true" ]]; then
log_echo " [$idx] ophost UT : bash build.sh -u --ophost -f $PR_FILELIST -j${THREAD_NUM}"
idx=$((idx+1))
else
log_echo " [--] ophost UT : SKIP (op_host not changed)"
fi
if [[ "$has_api" == "true" ]]; then
log_echo " [$idx] opapi UT : bash build.sh -u --opapi -f $PR_FILELIST -j${THREAD_NUM}"
idx=$((idx+1))
else
log_echo " [--] opapi UT : SKIP (op_api not changed)"
fi
if [[ "$BASE_BRANCH_NAME" == "master" ]]; then
if [[ "$has_graph" == "true" ]]; then
log_echo " [$idx] opgraph UT : bash build.sh -u --opgraph -f $PR_FILELIST -j${THREAD_NUM}"
idx=$((idx+1))
else
log_echo " [--] opgraph UT : SKIP (op_graph not changed)"
fi
if [[ "$has_kernel" == "true" ]]; then
log_echo " [$idx] opkernel UT : bash scripts/ci/check_kernel_ut.sh $PR_FILELIST --no_cov"
idx=$((idx+1))
else
log_echo " [--] opkernel UT : SKIP (op_kernel not changed)"
fi
else
log_echo " [--] opgraph/opkernel UT: SKIP (non-master branch)"
fi
if [[ "$has_arch35" == "true" ]]; then
local arch_flag="--no_force"
[[ "$has_kernel" == "false" ]] && arch_flag="-force_jit"
log_echo " [$idx] ascend950 pkg : bash scripts/ci/compile_ascend950_pkg.sh $PR_FILELIST ${arch_flag} -j${THREAD_NUM}"
idx=$((idx+1))
else
log_echo " [--] ascend950 pkg : SKIP (arch35 not changed)"
fi
log_echo " [--] single-op pkg : SKIP (fast mode)"
log_echo " [--] A2 smoke : SKIP (fast mode)"
if [[ "$has_experimental" == "true" ]]; then
log_echo " [$idx] experimental pkg : bash scripts/ci/check_experimental_pkg.sh $PR_FILELIST"
idx=$((idx+1))
else
log_echo " [--] experimental pkg : SKIP (experimental not changed)"
fi
fi
log_echo "===================================================================="
log_echo ""
}
run_cmd_silent() {
local stage_name=$1 log_file=$2 extra_grep=$3
shift 3
log_echo "[pre-push] >>> $stage_name (log: $log_file)"
[[ "$log_file" != "pre_push_build.log" ]] && : > "$log_file"
local rc=0
set +e
"$@" >> "$log_file" 2>&1
rc=$?
set -e
[[ "$log_file" != "pre_push_build.log" ]] && cat "$log_file" >> pre_push_build.log
if [[ $rc -ne 0 ]]; then
log_echo "[pre-push][FAILED] $stage_name (exit $rc), check log: $log_file"
local failed_lines
failed_lines=$(grep -E '\[ FAILED \]|FAILED TESTS|error happened|Error:|error:' "$log_file" 2>/dev/null | tail -5)
[[ -n "$failed_lines" ]] && log_echo "$failed_lines"
exit $rc
fi
if [[ -n "$extra_grep" ]] && grep -q "$extra_grep" "$log_file" 2>/dev/null; then
log_echo "[pre-push][FAILED] $stage_name: found '$extra_grep' in log: $log_file"
exit 1
fi
log_echo "[pre-push][PASSED] $stage_name"
}
clean_build() {
local dir
for dir in build build_out; do
local abs="$WORKSPACE/$dir"
if [[ -z "$WORKSPACE" || "$abs" == "/" || ! "$abs" =~ ^/[^/] ]]; then
log_echo "[pre-push][ERROR] clean_build refused: unsafe path '$abs' (WORKSPACE='$WORKSPACE')."
exit 1
fi
if [[ -d "$abs" ]]; then
rm -rf "$abs"
fi
done
}
run_full_mode() {
log_echo "[pre-push] >>> FULL MODE: delegating to local_build.sh (100% CI-aligned)"
log_echo "[pre-push] Branch: $(git rev-parse --abbrev-ref HEAD) Base: $BASE_BRANCH_NAME"
run_cmd_silent "FULL MODE (local_build.sh)" pre_push_build.log "" \
bash "$WORKSPACE/scripts/ci/local_build.sh"
log_echo "[pre-push] <<< FULL MODE: ALL PASSED"
}
run_fast_mode() {
log_echo "[pre-push] >>> FAST MODE: incremental build (default)"
log_echo "[pre-push] NOTE: Fast mode has known coverage gaps vs CI. Run full mode before merge."
log_echo ""
local branch
branch=$(git rev-parse --abbrev-ref HEAD)
export WORKSPACE="$WORKSPACE"
export ASCEND_3RD_LIB_PATH="$WORKSPACE/third_party"
export BASE_PATH="$WORKSPACE"
export BUILD_PATH="$WORKSPACE/build"
clean_build
if [[ "$need_jit_build" == "true" ]]; then
run_cmd_silent "JIT build" pre_push_build.log "" \
bash build.sh --pkg --jit -j${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH}
clean_build
fi
if [[ "$has_host" == "true" ]]; then
run_cmd_silent "ophost UT" ut_test.log "" \
bash build.sh -u --ophost -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
else
log_echo "[pre-push] op_host not changed, skipping ophost UT (fast mode)."
fi
if [[ "$has_api" == "true" ]]; then
if [[ "$has_host" == "false" && "$need_jit_build" == "false" ]]; then
run_cmd_silent "JIT build (opapi dep)" pre_push_build.log "" \
bash build.sh --pkg --jit -j${THREAD_NUM} --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH}
fi
run_cmd_silent "opapi UT" ut_test.log "" \
bash build.sh -u --opapi -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
else
log_echo "[pre-push] op_api not changed, skipping opapi UT (fast mode)."
fi
if [[ "$branch" == "master" || "$BASE_BRANCH_NAME" == "master" ]]; then
if [[ "$has_graph" == "true" ]]; then
run_cmd_silent "opgraph UT" ut_test.log "" \
bash build.sh -u --opgraph -f "$PR_FILELIST" --cann_3rd_lib_path=${ASCEND_3RD_LIB_PATH} -j${THREAD_NUM}
fi
if [[ "$has_kernel" == "true" ]]; then
run_cmd_silent "opkernel UT" ut_kernel.log "error happened" \
bash scripts/ci/check_kernel_ut.sh "$PR_FILELIST" --no_cov
fi
else
log_echo "[pre-push] Non-master branch, skipping opgraph/opkernel UT (aligned with CI)."
fi
clean_build
if [[ "$has_arch35" == "true" ]]; then
if [[ "$has_kernel" == "false" ]]; then
run_cmd_silent "ascend950 pkg" pre_push_build.log "" \
bash scripts/ci/compile_ascend950_pkg.sh "$PR_FILELIST" -force_jit -j${THREAD_NUM}
else
run_cmd_silent "ascend950 pkg" pre_push_build.log "" \
bash scripts/ci/compile_ascend950_pkg.sh "$PR_FILELIST" -j${THREAD_NUM} --no_force
fi
clean_build
fi
log_echo "[pre-push][SKIP] Single-op pkg / A2 smoke skipped in fast mode."
log_echo "[pre-push][SKIP] Run full mode (PRE_PUSH_FULL=1) to cover remaining CI stages."
if [[ "$has_experimental" == "true" ]]; then
run_cmd_silent "experimental pkg" pre_push_build.log "" \
bash scripts/ci/check_experimental_pkg.sh "$PR_FILELIST"
clean_build
log_echo "[pre-push][SKIP] experimental example skipped in fast mode."
fi
log_echo "[pre-push] <<< FAST MODE: DONE (with coverage gaps, see SKIP above)"
}
main() {
: > pre_push_build.log
log_echo "===================================================================="
log_echo " Pre-Push Build & UT Gate"
log_echo " Mode: $([[ "${PRE_PUSH_FULL:-0}" == "1" ]] && echo "FULL (CI-aligned)" || echo "FAST (incremental)")"
log_echo " Workspace: $WORKSPACE"
log_echo " Time: $(date '+%Y-%m-%d %H:%M:%S')"
log_echo "===================================================================="
calc_changed_files
identify_changes
print_pipeline
if [[ "$md_only" == "true" ]]; then
log_echo "[pre-push] Only docs/config/cmake/scripts changed, skipping all build & UT."
log_echo "[pre-push][HINT] If scripts/ci or CMakeLists were modified, CI script changes are NOT verified locally. Review manually."
exit 0
fi
if [[ "$has_experimental" == "true" && "$has_host" == "false" && "$has_kernel" == "false" \
&& "$has_api" == "false" && "$has_graph" == "false" ]]; then
log_echo "[pre-push] Only experimental changed, running check_experimental_pkg.sh only."
clean_build
export ASCEND_3RD_LIB_PATH="$WORKSPACE/third_party"
run_cmd_silent "experimental pkg" pre_push_build.log "" \
bash scripts/ci/check_experimental_pkg.sh "$PR_FILELIST"
exit 0
fi
local start_ts end_ts elapsed
start_ts=$(date +%s)
if [[ "${PRE_PUSH_FULL:-0}" == "1" ]]; then
run_full_mode
else
run_fast_mode
fi
end_ts=$(date +%s)
elapsed=$((end_ts - start_ts))
log_echo ""
log_echo "===================================================================="
log_echo " Pre-Push Gate: PASSED (elapsed ${elapsed}s)"
log_echo "===================================================================="
exit 0
}
main "$@"