已合并
compa #126
compa #126
已合并
liyonghong创建于 25 天前
7 个文件变更+569-41
@@ -0,0 +1,25 @@
1+# ------------------------------------------------------------------------------------------------------------
2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for details. You may not use this file except in compliance with the License.
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+# See LICENSE in the root of the software repository for the full text of the License.
9+# ------------------------------------------------------------------------------------------------------------
10+ 
11+# CMake lowest version requirement
12+cmake_minimum_required(VERSION 3.16.0)
13+ 
14+# Project information
15+project(Devmng_Sample)
16+ 
17+include_directories(${ASCEND_DRV_PACKAGE_PATH}/include
18+ ${BASE_DIR}/pkg_inc
19+ ${BASE_DIR}/examples)
20+ 
21+link_directories(${ASCEND_DRV_LIB_PATH})
22+ 
23+add_executable(main main.c)
24+ 
25+target_link_libraries(main ascend_hal)
@@ -0,0 +1,32 @@
1+# device manager
2+ 
3+## 描述
4+ 
5+本样例展示了如何查询设备数量信息并且开启设备间P2P功能
6+ 
7+## 支持的产品型号
8+ 
9+- Atlas A3 训练系列产品/Atlas A3 推理系列产品
10+- Atlas A2 训练系列产品/Atlas A2 推理系列产品
11+- 昇腾950PR处理器/昇腾950DT处理器
12+ 
13+## 编译运行
14+ 
15+环境安装详情以及运行详情请见example目录下的[README](../../README.md)。
16+ 
17+## CANN Driver API
18+ 
19+在该example中,涉及的关键功能点及其关键接口,如下所示:
20+ 
21+- 查询设备数量和ID信息
22+ - 调用drvGetDevNum接口查询设备数量。
23+ - 调用drvGetDevIDs接口查询设备ID列表。
24+ - 调用drvDeviceGetPhyIdByIndex进行逻辑ID和物理ID转换。
25+- P2P使能管理
26+ - 调用halDeviceCanAccessPeer接口查询Device之间是否支持数据交互。
27+ - 调用halDeviceEnableP2P接口使能当前Device与指定Device之间的数据交互。
28+ - 调用halDeviceDisableP2P接口关闭当前Device与指定Device之间的数据交互功能。
29+ 
30+## 已知issue
31+ 
32+ 暂无
@@ -0,0 +1,32 @@
1+# device manager
2+ 
3+## Description
4+ 
5+This example demonstrates how to query device count information and enable P2P functionality between devices.
6+ 
7+## Supported Product Models
8+ 
9+- Atlas A3 Training Series Products/Atlas A3 Inference Series Products
10+- Atlas A2 Training Series Products/Atlas A2 Inference Series Products
11+- Ascend 950PR Processor/Ascend 950DT Processor
12+ 
13+## Compilation and Running
14+ 
15+For environment installation details and running details, refer to the [README](../../README.md) in the examples directory.
16+ 
17+## CANN Driver API
18+ 
19+In this example, the key features and key interfaces involved are shown below:
20+ 
21+- Query device count and ID information
22+ - Call drvGetDevNum interface to query device count.
23+ - Call drvGetDevIDs interface to query device ID list.
24+ - Call drvDeviceGetPhyIdByIndex for logical ID and physical ID conversion.
25+- P2P enable management
26+ - Call halDeviceCanAccessPeer interface to query whether data interaction is supported between Devices.
27+ - Call halDeviceEnableP2P interface to enable data interaction between current Device and specified Device.
28+ - Call halDeviceDisableP2P interface to disable data interaction functionality between current Device and specified Device.
29+ 
30+## Known Issues
31+ 
32+ None
@@ -0,0 +1,113 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <sys/types.h>
12+#include <stdio.h>
13+#include <unistd.h>
14+ 
15+#include "ascend_hal.h"
16+#include "ascend_hal_error.h"
17+#include "utils.h"
18+ 
19+#define MAX_DEV_NUM 64
20+#define SOC_VERSION_SIZE 32
21+ 
22+int main(int argc, const char *argv[])
23+{
24+ int ret;
25+ int canAccessPeer = 0;
26+ unsigned int devId = 0;
27+ unsigned int devNum = 0;
28+ unsigned int phyId[2] = {0};
29+ char socVersion[SOC_VERSION_SIZE] = {0};
30+ unsigned int devArray[MAX_DEV_NUM] = {0};
31+ 
32+ LOG_INFO("Start to run device manager sample.\n");
33+ 
34+ ret = drvGetDevNum(&devNum);
35+ if (ret != 0) {
36+ LOG_ERR("Get device number failed. (ret=%d)\n", ret);
37+ return ret;
38+ }
39+ 
40+ if (devNum > MAX_DEV_NUM) {
41+ LOG_ERR("Device number is invalid. (devNum=%u)\n", devNum);
42+ return DRV_ERROR_INVALID_DEVICE;
43+ }
44+ 
45+ // Query logical device id array.
46+ ret = drvGetDevIDs(devArray, devNum);
47+ if (ret != 0) {
48+ LOG_ERR("Get device id array failed. (ret=%d)\n", ret);
49+ return ret;
50+ }
51+ 
52+ // Query device soc version.
53+ ret = halGetSocVersion(devArray[0], socVersion, SOC_VERSION_SIZE);
54+ if (ret != 0) {
55+ LOG_ERR("Get soc version failed. (ret=%d)\n", ret);
56+ return ret;
57+ }
58+ 
59+ LOG_INFO("Device soc version is %s.\n", socVersion);
60+ if (devNum <= 1) {
61+ LOG_INFO("Device number is %d, not support P2P.\n", devNum);
62+ } else {
63+ ret = drvDeviceGetPhyIdByIndex(devArray[0], &phyId[0]);
64+ if (ret != 0) {
65+ LOG_ERR("Get physical id from logical id failed. (ret=%d; dev_id=%u)\n", ret, devArray[0]);
66+ return ret;
67+ }
68+ 
69+ ret = drvDeviceGetPhyIdByIndex(devArray[1], &phyId[1]);
70+ if (ret != 0) {
71+ LOG_ERR("Get physical id from logical id failed. (ret=%d; dev_id=%u)\n", ret, devArray[1]);
72+ return ret;
73+ }
74+ 
75+ ret = halDeviceCanAccessPeer(&canAccessPeer, devArray[0], phyId[1]);
76+ if (ret != 0) {
77+ LOG_ERR("Check device data interaction failed. (ret=%d)\n", ret);
78+ return ret;
79+ }
80+ 
81+ LOG_INFO("P2P access between device%u and device%u is %d.\n", devArray[0], devArray[1], canAccessPeer);
82+ if (canAccessPeer == 1) {
83+ ret = halDeviceEnableP2P(devArray[0], phyId[1], 0);
84+ if (ret != 0) {
85+ LOG_ERR("Enable P2P failed. (ret=%d)\n", ret);
86+ return ret;
87+ }
88+ 
89+ ret = halDeviceEnableP2P(devArray[1], phyId[0], 0);
90+ if (ret != 0) {
91+ LOG_ERR("Enable P2P failed. (ret=%d)\n", ret);
92+ return ret;
93+ }
94+ 
95+ ret = halDeviceDisableP2P(devArray[0], phyId[1], 0);
96+ if (ret != 0) {
97+ LOG_ERR("Disable P2P failed. (ret=%d)\n", ret);
98+ return ret;
99+ }
100+ 
101+ ret = halDeviceDisableP2P(devArray[1], phyId[0], 0);
102+ if (ret != 0) {
103+ LOG_ERR("Disable P2P failed. (ret=%d)\n", ret);
104+ return ret;
105+ }
106+ 
107+ LOG_INFO("Enable and disable P2P between device%u and device%u successfully.\n", devArray[0], devArray[1]);
108+ }
109+ }
110+ 
111+ LOG_INFO("Run the device manager sample successfully.\n");
112+ return 0;
113+}
@@ -0,0 +1,45 @@
1+#!/bin/bash
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+DEP_INFO_FILE="/etc/ascend_install.info"
13+if [ -f "${DEP_INFO_FILE}" ]; then
14+ . ${DEP_INFO_FILE}
15+ DRV_INSTALL_PATH=""${Driver_Install_Path_Param%*/}"/driver"
16+ DRV_LIB64_COMMON_LDPATH=""${Driver_Install_Path_Param%*/}"/driver/lib64/common"
17+ DRV_LIB64_DRV_LDPATH=""${Driver_Install_Path_Param%*/}"/driver/lib64/driver"
18+ DRV_LIB64_LDPATH=""${Driver_Install_Path_Param%*/}"/driver/lib64"
19+else
20+ echo "[WARNING] Driver install path not found, please check the driver."
21+ exit 1
22+fi
23+ 
24+source $DRV_INSTALL_PATH/bin/setenv.bash
25+ 
26+BASE_DIR=$(cd "$(dirname $0)/../../.."; pwd)
27+ 
28+rm -rf build
29+mkdir -p build
30+cmake -B build \
31+ -DASCEND_DRV_PACKAGE_PATH=${DRV_INSTALL_PATH} \
32+ -DASCEND_DRV_LIB_PATH=${DRV_LIB64_DRV_LDPATH} \
33+ -DBASE_DIR=${BASE_DIR}
34+cmake --build build -j
35+cmake --install build
36+ 
37+file_path=output_msg.txt
38+./build/main | tee $file_path
39+main_ret=${PIPESTATUS[0]}
40+ 
41+if [ ${main_ret} -ne 0 ]; then
42+ exit 1
43+fi
44+ 
45+exit 0
@@ -0,0 +1,106 @@
1+#!/bin/bash
2+# ------------------------------------------------------------------------------------------------------------
3+# Copyright (c) 2025 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+set +e
13+ 
14+SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd)
15+REPO_DIR=$(cd "${SCRIPT_DIR}/.."; pwd)
16+DCMI_HEADER_PATH="${REPO_DIR}/src/custom/include/dcmi_interface_api.h"
17+DCMI_INSTALL_HEADER_PATH="/usr/local/dcmi/dcmi_interface_api.h"
18+ 
19+if [ ! -f "${DCMI_INSTALL_HEADER_PATH}" ]; then
20+ cp "${DCMI_HEADER_PATH}" "${DCMI_INSTALL_HEADER_PATH}"
21+ cp_ret=$?
22+ if [ ${cp_ret} -ne 0 ]; then
23+ echo "COPY_HEADER_FAILED: ${DCMI_HEADER_PATH} -> ${DCMI_INSTALL_HEADER_PATH} ret=${cp_ret}"
24+ exit 1
25+ fi
26+fi
27+ 
28+ 
29+RUN_TARGETS=(
30+ #"dcmi/dcmi/run.sh|all"
31+ "devmng/0_device_p2p/run.sh|"
32+ #"resmng/queue_buff_esched/run.sh|"
33+ #"resmng/svm/0_svm_memcpy/run.sh|"
34+ #"resmng/trs/0_trs_shrid/run.sh|"
35+ #"resmng/uvm/developer_demo/run.sh|"
36+)
37+ 
38+FAILED_COUNT=0
39+SUCCESS_COUNT=0
40+RESULTS=()
41+ 
42+run_one_script() {
43+ local relative_path="$1"
44+ local arg_string="$2"
45+ local script_path="${SCRIPT_DIR}/${relative_path}"
46+ local script_dir
47+ local script_name
48+ local ret
49+ 
50+ script_dir=$(dirname "${script_path}")
51+ script_name=$(basename "${script_path}")
52+ 
53+ if [ ! -f "${script_path}" ]; then
54+ echo "MISSING: ${relative_path}"
55+ RESULTS+=("FAIL ${relative_path} 127")
56+ FAILED_COUNT=$((FAILED_COUNT + 1))
57+ return 127
58+ fi
59+ 
60+ echo "======================================"
61+ echo "RUNNING: ${relative_path} ${arg_string}"
62+ echo "======================================"
63+ 
64+ if [ -n "${arg_string}" ]; then
65+ (
66+ cd "${script_dir}" && bash "./${script_name}" ${arg_string}
67+ )
68+ else
69+ (
70+ cd "${script_dir}" && bash "./${script_name}"
71+ )
72+ fi
73+ ret=$?
74+ 
75+ if [ ${ret} -eq 0 ]; then
76+ echo "RUN_SH_SUCCESS: ${relative_path}"
77+ RESULTS+=("PASS ${relative_path} 0")
78+ SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
79+ else
80+ echo "RUN_SH_FAILED: ${relative_path} ret=${ret}"
81+ RESULTS+=("FAIL ${relative_path} ${ret}")
82+ FAILED_COUNT=$((FAILED_COUNT + 1))
83+ fi
84+ 
85+ echo
86+ return ${ret}
87+}
88+ 
89+for target in "${RUN_TARGETS[@]}"; do
90+ relative_path="${target%%|*}"
91+ arg_string="${target#*|}"
92+ run_one_script "${relative_path}" "${arg_string}"
93+done
94+ 
95+echo "============== SUMMARY =============="
96+for result in "${RESULTS[@]}"; do
97+ echo "${result}"
98+done
99+echo "SUCCESS_COUNT=${SUCCESS_COUNT}"
100+echo "FAILED_COUNT=${FAILED_COUNT}"
101+ 
102+if [ ${FAILED_COUNT} -ne 0 ]; then
103+ exit 1
104+fi
105+ 
106+exit 0
@@ -1,10 +1,10 @@
1#!/bin/sh1#!/bin/sh
2# -----------------------------------------------------------------------------------------------------------2# -----------------------------------------------------------------------------------------------------------
3# Copyright (c) 2026 Huawei Technologies Co., Ltd.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 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").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.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, 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.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.9# See LICENSE in the root of the software repository for the full text of the License.
10# -----------------------------------------------------------------------------------------------------------10# -----------------------------------------------------------------------------------------------------------
@@ -48,15 +48,49 @@ if [ -z "$_PYTHON" ]; then
48fi48fi
49 49 
50# ---------------------------------------------------------------------------50# ---------------------------------------------------------------------------
51-# 1. Ensure oat-py is installed51+# 1. Ensure oat-py>=1.0.2 is installed (serialized via flock to prevent parallel pip conflicts)
52# ---------------------------------------------------------------------------52# ---------------------------------------------------------------------------
53-_OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")53+_OAT_MIN="1.0.2"
54+_check_oat_version() {
55+ "$_PYTHON" -c "
56+import importlib.util, sys
57+if not importlib.util.find_spec('oat'):
58+ print('missing'); sys.exit()
59+try:
60+ from importlib.metadata import version
61+ v = version('oat-py')
62+ parts_have = [int(x) for x in v.split('.')[:3]]
63+ parts_need = [int(x) for x in '${_OAT_MIN}'.split('.')[:3]]
64+ print('ok' if parts_have >= parts_need else 'old')
65+except Exception:
66+ print('ok') # cannot determine version, assume ok
67+" 2>/dev/null || echo "missing"
68+}
69+ 
70+_OAT_OK=$(_check_oat_version)
54if [ "$_OAT_OK" != "ok" ]; then71if [ "$_OAT_OK" != "ok" ]; then
55- echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..."72+ if [ "$_OAT_OK" = "old" ]; then
56- "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1"73+ echo "[OAT] oat-py is outdated. Upgrading to oat-py>=${_OAT_MIN} ..."
57- _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")74+ else
75+ echo "[OAT] oat-py not found. Installing oat-py>=${_OAT_MIN} ..."
76+ fi
77+ _LOCK_FILE="/tmp/oat_pip_install.lock"
78+ # flock ensures only one concurrent invocation runs pip install at a time;
79+ # re-check inside the lock so processes that waited skip redundant installs
80+ if command -v flock >/dev/null 2>&1; then
81+ (
82+ flock -w 120 9
83+ _RECHECK=$(_check_oat_version)
84+ if [ "$_RECHECK" != "ok" ]; then
85+ "$_PYTHON" -m pip install --quiet "oat-py>=${_OAT_MIN}"
86+ fi
87+ ) 9>"$_LOCK_FILE"
88+ else
89+ "$_PYTHON" -m pip install --quiet "oat-py>=${_OAT_MIN}"
90+ fi
91+ _OAT_OK=$(_check_oat_version)
58 if [ "$_OAT_OK" != "ok" ]; then92 if [ "$_OAT_OK" != "ok" ]; then
59- echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1"93+ echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=${_OAT_MIN}"
60 echo "[OAT] Skipping OAT check, continuing commit..."94 echo "[OAT] Skipping OAT check, continuing commit..."
61 exit 095 exit 0
62 fi96 fi
@@ -68,10 +102,99 @@ fi
68# ---------------------------------------------------------------------------102# ---------------------------------------------------------------------------
69REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)103REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
70REPO_NAME=$(basename "$REPO_ROOT")104REPO_NAME=$(basename "$REPO_ROOT")
71-OAT_REPORT_DIR="$REPO_ROOT/oat_reports"105+OAT_REPORT_DIR="${TMPDIR:-/tmp}/oat_reports_$$"
106+OAT_RESULT_DIR="$REPO_ROOT/oat_reports"
72 107 
73-echo "[OAT] Running OAT scan (Python Edition) — INCREMENTAL MODE"108+# ---------------------------------------------------------------------------
109+# 2a. PR-range deduplication (pure git, no CI env vars required)
110+#
111+# Strategy:
112+# 1. Find the merge-base between HEAD and the upstream branch (origin/master
113+# or similar). If found, this is a feature-branch context ??collect ALL
114+# files changed since the branch diverged (full PR diff).
115+# 2. Key a done-marker on the HEAD SHA. The first invocation runs the scan;
116+# every subsequent invocation for the same HEAD exits 0 immediately.
117+# This eliminates redundant scans when CI calls the hook once per commit.
118+# 3. If no merge-base is found (e.g. committing directly on master) fall back
119+# to scanning only the currently staged files, with no done-marker.
120+# ---------------------------------------------------------------------------
121+_PR_MERGE_BASE=""
122+_DONE_MARKER=""
123+_HEAD_SHA=$(git rev-parse HEAD 2>/dev/null | cut -c1-12 || true)
124+ 
125+if [ -n "$_HEAD_SHA" ]; then
126+ # Try to find merge-base with a known upstream branch
127+ if [ -n "${PRE_COMMIT_FROM_REF:-}" ]; then
128+ # pre-commit --from-ref/--to-ref: use the provided base directly
129+ _PR_MERGE_BASE=$(git merge-base "$PRE_COMMIT_FROM_REF" HEAD 2>/dev/null || true)
130+ fi
131+ if [ -z "$_PR_MERGE_BASE" ]; then
132+ # Search all remotes (origin, upstream, etc.) for common trunk branch names.
133+ # This handles fork setups where origin=fork and upstream=main-repo.
134+ _CANDIDATE_BASE=""
135+ _CANDIDATE_DIST=0
136+ for _b in $(git for-each-ref --format='%(refname:short)' \
137+ 'refs/remotes/*/master' 'refs/remotes/*/main' \
138+ 'refs/remotes/*/develop' 'refs/remotes/*/dev' 2>/dev/null); do
139+ _mb=$(git merge-base HEAD "$_b" 2>/dev/null || true)
140+ [ -z "$_mb" ] && continue
141+ # Skip if merge-base is HEAD itself (branch is ahead of or equal to HEAD)
142+ [ "$_mb" = "$(git rev-parse HEAD 2>/dev/null)" ] && continue
143+ # Pick the candidate whose merge-base is furthest from HEAD
144+ # (most commits since divergence = most likely true PR base)
145+ _dist=$(git rev-list --count "$_mb"..HEAD 2>/dev/null || echo 0)
146+ if [ -z "$_CANDIDATE_BASE" ] || [ "$_dist" -gt "$_CANDIDATE_DIST" ]; then
147+ _CANDIDATE_BASE="$_mb"
148+ _CANDIDATE_DIST="$_dist"
149+ fi
150+ done
151+ _PR_MERGE_BASE="$_CANDIDATE_BASE"
152+ fi
153+ 
154+ # Only use PR-range mode when HEAD has diverged from the upstream base
155+ if [ -n "$_PR_MERGE_BASE" ] && [ "$_PR_MERGE_BASE" != "$(git rev-parse HEAD 2>/dev/null)" ]; then
156+ mkdir -p "$OAT_RESULT_DIR"
157+ _DONE_MARKER="$OAT_RESULT_DIR/.done_${_HEAD_SHA}"
158+ if [ -f "$_DONE_MARKER" ]; then
159+ echo "[OAT] [SKIP] Already scanned HEAD=${_HEAD_SHA}. Skipping duplicate invocation."
160+ exit 0
161+ fi
162+ else
163+ # HEAD == merge-base: on the upstream branch itself, no PR range
164+ _PR_MERGE_BASE=""
165+ fi
166+fi
167+ 
168+# ---------------------------------------------------------------------------
169+# 2b. Deduplicate parallel invocations within the same pre-commit run.
170+# Only the first instance that acquires the lock actually runs the scan;
171+# all others wait then exit 0.
172+# ---------------------------------------------------------------------------
173+if command -v flock >/dev/null 2>&1; then
174+ _OAT_SCAN_LOCK="${TMPDIR:-/tmp}/oat_scan_$(echo "$REPO_ROOT" | tr '/\\: ' '____').lock"
175+ exec 9>"$_OAT_SCAN_LOCK"
176+ if ! flock -n 9; then
177+ echo "[OAT] Another OAT scan instance is running. Waiting..."
178+ flock -w 120 9
179+ # Re-check done marker: if the scanning instance completed normally, skip.
180+ # If it exited abnormally (no marker), fall through and run the scan ourselves.
181+ if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then
182+ echo "[OAT] Scan already completed by another instance. Skipping."
183+ exit 0
184+ fi
185+ echo "[OAT] Previous instance did not complete. Proceeding with scan..."
186+ # Fall through to run the scan below
187+ fi
188+ # This instance now owns the lock and will run the scan.
189+fi
190+ 
191+echo "[OAT] Running OAT scan (Python Edition) ??INCREMENTAL MODE"
74echo "[OAT] Project: $REPO_NAME"192echo "[OAT] Project: $REPO_NAME"
193+if [ -n "$_PR_MERGE_BASE" ]; then
194+ echo "[OAT] Mode: PR range ??scanning all files changed since merge-base"
195+else
196+ echo "[OAT] Mode: staged files ??scanning only currently staged files"
197+fi
75 198 
76# ---------------------------------------------------------------------------199# ---------------------------------------------------------------------------
77# 3. Collect staged files200# 3. Collect staged files
@@ -93,15 +216,19 @@ if [ $# -gt 0 ]; then
93 FILE_LIST="$FILE_LIST,$_abs"216 FILE_LIST="$FILE_LIST,$_abs"
94 fi217 fi
95 done218 done
96-else219+elif [ -n "$_PR_MERGE_BASE" ]; then
97- _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)220+ # PR range mode: collect ALL files changed since the branch diverged
221+ _STAGED=$(git diff --name-only --diff-filter=ACM "$_PR_MERGE_BASE" HEAD \
222+ 2>/dev/null || true)
98 if [ -z "$_STAGED" ]; then223 if [ -z "$_STAGED" ]; then
99- echo "[OAT] No staged files to check. Skipping."224+ echo "[OAT] No files changed in PR range. Skipping."
225+ [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true
100 exit 0226 exit 0
101 fi227 fi
102 FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ')228 FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ')
103 FILE_LIST=""229 FILE_LIST=""
104- for _f in $_STAGED; do230+ while IFS= read -r _f; do
231+ [ -z "$_f" ] && continue
105 case "$_f" in232 case "$_f" in
106 /*) _abs="$_f" ;;233 /*) _abs="$_f" ;;
107 *) _abs="$REPO_ROOT/$_f" ;;234 *) _abs="$REPO_ROOT/$_f" ;;
@@ -112,7 +239,33 @@ else
112 else239 else
113 FILE_LIST="$FILE_LIST,$_abs"240 FILE_LIST="$FILE_LIST,$_abs"
114 fi241 fi
115- done242+ done <<EOF
243+$_STAGED
244+EOF
245+else
246+ # Staged files mode: on upstream branch directly, scan only staged files
247+ _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)
248+ if [ -z "$_STAGED" ]; then
249+ echo "[OAT] No staged files to check. Skipping."
250+ exit 0
251+ fi
252+ FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ')
253+ FILE_LIST=""
254+ while IFS= read -r _f; do
255+ [ -z "$_f" ] && continue
256+ case "$_f" in
257+ /*) _abs="$_f" ;;
258+ *) _abs="$REPO_ROOT/$_f" ;;
259+ esac
260+ [ -f "$_abs" ] || continue
261+ if [ -z "$FILE_LIST" ]; then
262+ FILE_LIST="$_abs"
263+ else
264+ FILE_LIST="$FILE_LIST,$_abs"
265+ fi
266+ done <<EOF
267+$_STAGED
268+EOF
116fi269fi
117 270 
118if [ -z "$FILE_LIST" ]; then271if [ -z "$FILE_LIST" ]; then
@@ -123,36 +276,40 @@ fi
123echo "[OAT] Checking $FILE_COUNT staged file(s)..."276echo "[OAT] Checking $FILE_COUNT staged file(s)..."
124 277 
125# ---------------------------------------------------------------------------278# ---------------------------------------------------------------------------
126-# 4. Ensure oat_reports/ exists and is in .gitignore279+# 4. Ensure report directories exist
127# ---------------------------------------------------------------------------280# ---------------------------------------------------------------------------
128mkdir -p "$OAT_REPORT_DIR"281mkdir -p "$OAT_REPORT_DIR"
129- 282+mkdir -p "$OAT_RESULT_DIR"
130-_GITIGNORE="$REPO_ROOT/.gitignore"
131-for _entry in "oat_reports" "log"; do
132- if ! grep -qE "^${_entry}/?$" "$_GITIGNORE" 2>/dev/null; then
133- printf "\n%s/\n" "$_entry" >> "$_GITIGNORE" 2>/dev/null || true
134- echo "[OAT] Added ${_entry}/ to .gitignore"
135- fi
136-done
137 283 
138# ---------------------------------------------------------------------------284# ---------------------------------------------------------------------------
139-# 5. Build oat command — use OAT.xml if present in repo root285+# 5. Resolve OAT.xml path
140# ---------------------------------------------------------------------------286# ---------------------------------------------------------------------------
141-_OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST"
142- 
143_OAT_XML="$REPO_ROOT/OAT.xml"287_OAT_XML="$REPO_ROOT/OAT.xml"
288+if [ ! -f "$_OAT_XML" ] && [ -f "$REPO_ROOT/scripts/OAT.xml" ]; then
289+ _OAT_XML="$REPO_ROOT/scripts/OAT.xml"
290+fi
291+ 
292+# _OAT_CMD is kept for display purposes only (shown in error messages).
293+# Actual execution uses direct argument passing below to avoid eval word-splitting
294+# and command-injection risks when paths or filenames contain special characters.
144if [ -f "$_OAT_XML" ]; then295if [ -f "$_OAT_XML" ]; then
145- _OAT_CMD="$_OAT_CMD -oatconfig $_OAT_XML"296+ _OAT_CMD="\"$_PYTHON\" -m oat -mode s -s \"$REPO_ROOT\" -r \"$OAT_REPORT_DIR\" -n \"$REPO_NAME\" -w 1 -f \"$FILE_LIST\" -oatconfig \"$_OAT_XML\""
297+else
298+ _OAT_CMD="\"$_PYTHON\" -m oat -mode s -s \"$REPO_ROOT\" -r \"$OAT_REPORT_DIR\" -n \"$REPO_NAME\" -w 1 -f \"$FILE_LIST\""
146fi299fi
147 300 
148# ---------------------------------------------------------------------------301# ---------------------------------------------------------------------------
149-# 6. Run oat scan302+# 6. Run oat scan (direct argument passing � no eval)
150# ---------------------------------------------------------------------------303# ---------------------------------------------------------------------------
151echo ""304echo ""
152echo "[OAT] Running compliance scan..."305echo "[OAT] Running compliance scan..."
153 306 
154set +e307set +e
155-eval "$_OAT_CMD" >/dev/null 2>&1308+if [ -f "$_OAT_XML" ]; then
309+ "$_PYTHON" -m oat -mode s -s "$REPO_ROOT" -r "$OAT_REPORT_DIR" -n "$REPO_NAME" -w 1 -f "$FILE_LIST" -oatconfig "$_OAT_XML" >/dev/null 2>&1
310+else
311+ "$_PYTHON" -m oat -mode s -s "$REPO_ROOT" -r "$OAT_REPORT_DIR" -n "$REPO_NAME" -w 1 -f "$FILE_LIST" >/dev/null 2>&1
312+fi
156_OAT_RC=$?313_OAT_RC=$?
157set -e314set -e
158 315 
@@ -170,7 +327,7 @@ fi
170# Only: Invalid File Type + License Header Invalid (no copyright)327# Only: Invalid File Type + License Header Invalid (no copyright)
171# ---------------------------------------------------------------------------328# ---------------------------------------------------------------------------
172REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt"329REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt"
173-RESULT_FILE="$OAT_REPORT_DIR/result.txt"330+RESULT_FILE="$OAT_RESULT_DIR/result.txt"
174 331 
175# Section headers used as stop-boundaries when extracting sections332# Section headers used as stop-boundaries when extracting sections
176_ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:"333_ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:"
@@ -201,17 +358,29 @@ _extract_section() {
201 358 
202if [ ! -f "$REPORT_FILE" ]; then359if [ ! -f "$REPORT_FILE" ]; then
203 if [ "$_OAT_RC" -eq 0 ]; then360 if [ "$_OAT_RC" -eq 0 ]; then
361+ # oat exited cleanly with no report: all staged files were filtered out
204 echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."362 echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."
363+ [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true
364+ rm -rf "$OAT_REPORT_DIR"
205 exit 0365 exit 0
206 else366 else
207- echo "[OAT] [WARNING] Report not found: $REPORT_FILE"367+ # oat returned exit code 1 but produced no report �?possible disk issue
368+ # or oat internal error. Do not silently pass; block the commit.
369+ echo ""
370+ echo "[OAT] [ERROR] oat exited with code $_OAT_RC but no report was generated."
371+ echo "[OAT] This may indicate a disk error or an oat internal bug."
372+ echo "[OAT] To investigate, run manually:"
373+ echo " $_OAT_CMD"
374+ echo "[OAT] Blocking commit to prevent silent compliance bypass."
375+ echo ""
376+ rm -rf "$OAT_REPORT_DIR"
208 exit 1377 exit 1
209 fi378 fi
210fi379fi
211 380 
212-# Parse counts381+# Parse counts ??use || true to prevent set -e from triggering if grep finds no match
213-_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1)382+_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true)
214-_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1)383+_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true)
215_INVALID_TYPE=${_INVALID_TYPE:-0}384_INVALID_TYPE=${_INVALID_TYPE:-0}
216_LICENSE_INVALID=${_LICENSE_INVALID:-0}385_LICENSE_INVALID=${_LICENSE_INVALID:-0}
217 386 
@@ -240,11 +409,8 @@ _SECTION_LIC=$(_extract_section "$REPORT_FILE" "License Header Invalid Total Cou
240 echo "==================================="409 echo "==================================="
241} > "$RESULT_FILE"410} > "$RESULT_FILE"
242 411 
243-# Clean up full plain report (keep only result.txt)
244-rm -f "$REPORT_FILE"
245- 
246# ---------------------------------------------------------------------------412# ---------------------------------------------------------------------------
247-# 8. Block commit if issues found413+# 8. Block commit if issues found; always clean up temp dir
248# ---------------------------------------------------------------------------414# ---------------------------------------------------------------------------
249TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID ))415TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID ))
250 416 
@@ -258,12 +424,15 @@ if [ "$TOTAL_ISSUES" -gt 0 ]; then
258 echo " - Invalid File Type: $_INVALID_TYPE"424 echo " - Invalid File Type: $_INVALID_TYPE"
259 echo " - License Header Invalid: $_LICENSE_INVALID"425 echo " - License Header Invalid: $_LICENSE_INVALID"
260 echo ""426 echo ""
261- echo "[OAT] Details:"427+ echo "[OAT] Details (also saved to: $RESULT_FILE):"
262- echo " cat $RESULT_FILE"428+ echo "---"
429+ cat "$RESULT_FILE"
430+ echo "---"
263 echo ""431 echo ""
264 echo "Fix the issues and recommit, or skip with:"432 echo "Fix the issues and recommit, or skip with:"
265 echo " git commit --no-verify"433 echo " git commit --no-verify"
266 echo ""434 echo ""
435+ rm -rf "$OAT_REPORT_DIR"
267 exit 1436 exit 1
268fi437fi
269 438 
@@ -271,4 +440,10 @@ echo ""
271echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."440echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."
272echo "[OAT] Summary: cat $RESULT_FILE"441echo "[OAT] Summary: cat $RESULT_FILE"
273echo ""442echo ""
443+# Mark scan as done for CI range mode (prevents redundant re-runs on same PR head)
444+if [ -n "$_DONE_MARKER" ]; then
445+ touch "$_DONE_MARKER" 2>/dev/null || true
446+ echo "[OAT] Done-marker created: $_DONE_MARKER"
447+fi
448+rm -rf "$OAT_REPORT_DIR"
274exit 0449exit 0