| @@ -1,10 +1,10 @@ |
| #!/bin/sh | #!/bin/sh |
| # ----------------------------------------------------------------------------------------------------------- | # ----------------------------------------------------------------------------------------------------------- |
| # Copyright (c) 2026 Huawei Technologies Co., Ltd. | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| # CANN Open Software License Agreement Version 2.0 (the "License"). | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| # Please refer to the License for details. You may not use this file except in compliance with the License. | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| # See LICENSE in the root of the software repository for the full text of the License. | # See LICENSE in the root of the software repository for the full text of the License. |
| # ----------------------------------------------------------------------------------------------------------- | # ----------------------------------------------------------------------------------------------------------- |
| @@ -48,15 +48,49 @@ if [ -z "$_PYTHON" ]; then |
| fi | fi |
| | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -# 1. Ensure oat-py is installed | +# 1. Ensure oat-py>=1.0.2 is installed (serialized via flock to prevent parallel pip conflicts) |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -_OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") | +_OAT_MIN="1.0.2" |
| +_check_oat_version() { |
| + "$_PYTHON" -c " |
| +import importlib.util, sys |
| +if not importlib.util.find_spec('oat'): |
| + print('missing'); sys.exit() |
| +try: |
| + from importlib.metadata import version |
| + v = version('oat-py') |
| + parts_have = [int(x) for x in v.split('.')[:3]] |
| + parts_need = [int(x) for x in '${_OAT_MIN}'.split('.')[:3]] |
| + print('ok' if parts_have >= parts_need else 'old') |
| +except Exception: |
| + print('ok') # cannot determine version, assume ok |
| +" 2>/dev/null || echo "missing" |
| +} |
| + |
| +_OAT_OK=$(_check_oat_version) |
| if [ "$_OAT_OK" != "ok" ]; then | if [ "$_OAT_OK" != "ok" ]; then |
| - echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..." | + if [ "$_OAT_OK" = "old" ]; then |
| - "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1" | + echo "[OAT] oat-py is outdated. Upgrading to oat-py>=${_OAT_MIN} ..." |
| - _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") | + else |
| + echo "[OAT] oat-py not found. Installing oat-py>=${_OAT_MIN} ..." |
| + fi |
| + _LOCK_FILE="/tmp/oat_pip_install.lock" |
| + # flock ensures only one concurrent invocation runs pip install at a time; |
| + # re-check inside the lock so processes that waited skip redundant installs |
| + if command -v flock >/dev/null 2>&1; then |
| + ( |
| + flock -w 120 9 |
| + _RECHECK=$(_check_oat_version) |
| + if [ "$_RECHECK" != "ok" ]; then |
| + "$_PYTHON" -m pip install --quiet "oat-py>=${_OAT_MIN}" |
| + fi |
| + ) 9>"$_LOCK_FILE" |
| + else |
| + "$_PYTHON" -m pip install --quiet "oat-py>=${_OAT_MIN}" |
| + fi |
| + _OAT_OK=$(_check_oat_version) |
| if [ "$_OAT_OK" != "ok" ]; then | if [ "$_OAT_OK" != "ok" ]; then |
| - echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1" | + echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=${_OAT_MIN}" |
| echo "[OAT] Skipping OAT check, continuing commit..." | echo "[OAT] Skipping OAT check, continuing commit..." |
| exit 0 | exit 0 |
| fi | fi |
| @@ -68,10 +102,99 @@ fi |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) | REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) |
| REPO_NAME=$(basename "$REPO_ROOT") | REPO_NAME=$(basename "$REPO_ROOT") |
| -OAT_REPORT_DIR="$REPO_ROOT/oat_reports" | +OAT_REPORT_DIR="${TMPDIR:-/tmp}/oat_reports_$$" |
| +OAT_RESULT_DIR="$REPO_ROOT/oat_reports" |
| | |
| -echo "[OAT] Running OAT scan (Python Edition) — INCREMENTAL MODE" | +# --------------------------------------------------------------------------- |
| +# 2a. PR-range deduplication (pure git, no CI env vars required) |
| +# |
| +# Strategy: |
| +# 1. Find the merge-base between HEAD and the upstream branch (origin/master |
| +# or similar). If found, this is a feature-branch context ??collect ALL |
| +# files changed since the branch diverged (full PR diff). |
| +# 2. Key a done-marker on the HEAD SHA. The first invocation runs the scan; |
| +# every subsequent invocation for the same HEAD exits 0 immediately. |
| +# This eliminates redundant scans when CI calls the hook once per commit. |
| +# 3. If no merge-base is found (e.g. committing directly on master) fall back |
| +# to scanning only the currently staged files, with no done-marker. |
| +# --------------------------------------------------------------------------- |
| +_PR_MERGE_BASE="" |
| +_DONE_MARKER="" |
| +_HEAD_SHA=$(git rev-parse HEAD 2>/dev/null | cut -c1-12 || true) |
| + |
| +if [ -n "$_HEAD_SHA" ]; then |
| + # Try to find merge-base with a known upstream branch |
| + if [ -n "${PRE_COMMIT_FROM_REF:-}" ]; then |
| + # pre-commit --from-ref/--to-ref: use the provided base directly |
| + _PR_MERGE_BASE=$(git merge-base "$PRE_COMMIT_FROM_REF" HEAD 2>/dev/null || true) |
| + fi |
| + if [ -z "$_PR_MERGE_BASE" ]; then |
| + # Search all remotes (origin, upstream, etc.) for common trunk branch names. |
| + # This handles fork setups where origin=fork and upstream=main-repo. |
| + _CANDIDATE_BASE="" |
| + _CANDIDATE_DIST=0 |
| + for _b in $(git for-each-ref --format='%(refname:short)' \ |
| + 'refs/remotes/*/master' 'refs/remotes/*/main' \ |
| + 'refs/remotes/*/develop' 'refs/remotes/*/dev' 2>/dev/null); do |
| + _mb=$(git merge-base HEAD "$_b" 2>/dev/null || true) |
| + [ -z "$_mb" ] && continue |
| + # Skip if merge-base is HEAD itself (branch is ahead of or equal to HEAD) |
| + [ "$_mb" = "$(git rev-parse HEAD 2>/dev/null)" ] && continue |
| + # Pick the candidate whose merge-base is furthest from HEAD |
| + # (most commits since divergence = most likely true PR base) |
| + _dist=$(git rev-list --count "$_mb"..HEAD 2>/dev/null || echo 0) |
| + if [ -z "$_CANDIDATE_BASE" ] || [ "$_dist" -gt "$_CANDIDATE_DIST" ]; then |
| + _CANDIDATE_BASE="$_mb" |
| + _CANDIDATE_DIST="$_dist" |
| + fi |
| + done |
| + _PR_MERGE_BASE="$_CANDIDATE_BASE" |
| + fi |
| + |
| + # Only use PR-range mode when HEAD has diverged from the upstream base |
| + if [ -n "$_PR_MERGE_BASE" ] && [ "$_PR_MERGE_BASE" != "$(git rev-parse HEAD 2>/dev/null)" ]; then |
| + mkdir -p "$OAT_RESULT_DIR" |
| + _DONE_MARKER="$OAT_RESULT_DIR/.done_${_HEAD_SHA}" |
| + if [ -f "$_DONE_MARKER" ]; then |
| + echo "[OAT] [SKIP] Already scanned HEAD=${_HEAD_SHA}. Skipping duplicate invocation." |
| + exit 0 |
| + fi |
| + else |
| + # HEAD == merge-base: on the upstream branch itself, no PR range |
| + _PR_MERGE_BASE="" |
| + fi |
| +fi |
| + |
| +# --------------------------------------------------------------------------- |
| +# 2b. Deduplicate parallel invocations within the same pre-commit run. |
| +# Only the first instance that acquires the lock actually runs the scan; |
| +# all others wait then exit 0. |
| +# --------------------------------------------------------------------------- |
| +if command -v flock >/dev/null 2>&1; then |
| + _OAT_SCAN_LOCK="${TMPDIR:-/tmp}/oat_scan_$(echo "$REPO_ROOT" | tr '/\\: ' '____').lock" |
| + exec 9>"$_OAT_SCAN_LOCK" |
| + if ! flock -n 9; then |
| + echo "[OAT] Another OAT scan instance is running. Waiting..." |
| + flock -w 120 9 |
| + # Re-check done marker: if the scanning instance completed normally, skip. |
| + # If it exited abnormally (no marker), fall through and run the scan ourselves. |
| + if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then |
| + echo "[OAT] Scan already completed by another instance. Skipping." |
| + exit 0 |
| + fi |
| + echo "[OAT] Previous instance did not complete. Proceeding with scan..." |
| + # Fall through to run the scan below |
| + fi |
| + # This instance now owns the lock and will run the scan. |
| +fi |
| + |
| +echo "[OAT] Running OAT scan (Python Edition) ??INCREMENTAL MODE" |
| echo "[OAT] Project: $REPO_NAME" | echo "[OAT] Project: $REPO_NAME" |
| +if [ -n "$_PR_MERGE_BASE" ]; then |
| + echo "[OAT] Mode: PR range ??scanning all files changed since merge-base" |
| +else |
| + echo "[OAT] Mode: staged files ??scanning only currently staged files" |
| +fi |
| | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| # 3. Collect staged files | # 3. Collect staged files |
| @@ -93,15 +216,19 @@ if [ $# -gt 0 ]; then |
| FILE_LIST="$FILE_LIST,$_abs" | FILE_LIST="$FILE_LIST,$_abs" |
| fi | fi |
| done | done |
| -else | +elif [ -n "$_PR_MERGE_BASE" ]; then |
| - _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) | + # PR range mode: collect ALL files changed since the branch diverged |
| + _STAGED=$(git diff --name-only --diff-filter=ACM "$_PR_MERGE_BASE" HEAD \ |
| + 2>/dev/null || true) |
| if [ -z "$_STAGED" ]; then | if [ -z "$_STAGED" ]; then |
| - echo "[OAT] No staged files to check. Skipping." | + echo "[OAT] No files changed in PR range. Skipping." |
| + [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true |
| exit 0 | exit 0 |
| fi | fi |
| FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ') | FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ') |
| FILE_LIST="" | FILE_LIST="" |
| - for _f in $_STAGED; do | + while IFS= read -r _f; do |
| + [ -z "$_f" ] && continue |
| case "$_f" in | case "$_f" in |
| /*) _abs="$_f" ;; | /*) _abs="$_f" ;; |
| *) _abs="$REPO_ROOT/$_f" ;; | *) _abs="$REPO_ROOT/$_f" ;; |
| @@ -112,7 +239,33 @@ else |
| else | else |
| FILE_LIST="$FILE_LIST,$_abs" | FILE_LIST="$FILE_LIST,$_abs" |
| fi | fi |
| - done | + done <<EOF |
| +$_STAGED |
| +EOF |
| +else |
| + # Staged files mode: on upstream branch directly, scan only staged files |
| + _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) |
| + if [ -z "$_STAGED" ]; then |
| + echo "[OAT] No staged files to check. Skipping." |
| + exit 0 |
| + fi |
| + FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ') |
| + FILE_LIST="" |
| + while IFS= read -r _f; do |
| + [ -z "$_f" ] && continue |
| + case "$_f" in |
| + /*) _abs="$_f" ;; |
| + *) _abs="$REPO_ROOT/$_f" ;; |
| + esac |
| + [ -f "$_abs" ] || continue |
| + if [ -z "$FILE_LIST" ]; then |
| + FILE_LIST="$_abs" |
| + else |
| + FILE_LIST="$FILE_LIST,$_abs" |
| + fi |
| + done <<EOF |
| +$_STAGED |
| +EOF |
| fi | fi |
| | |
| if [ -z "$FILE_LIST" ]; then | if [ -z "$FILE_LIST" ]; then |
| @@ -123,36 +276,40 @@ fi |
| echo "[OAT] Checking $FILE_COUNT staged file(s)..." | echo "[OAT] Checking $FILE_COUNT staged file(s)..." |
| | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -# 4. Ensure oat_reports/ exists and is in .gitignore | +# 4. Ensure report directories exist |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| mkdir -p "$OAT_REPORT_DIR" | mkdir -p "$OAT_REPORT_DIR" |
| - | +mkdir -p "$OAT_RESULT_DIR" |
| -_GITIGNORE="$REPO_ROOT/.gitignore" | |
| -for _entry in "oat_reports" "log"; do | |
| - if ! grep -qE "^${_entry}/?$" "$_GITIGNORE" 2>/dev/null; then | |
| - printf "\n%s/\n" "$_entry" >> "$_GITIGNORE" 2>/dev/null || true | |
| - echo "[OAT] Added ${_entry}/ to .gitignore" | |
| - fi | |
| -done | |
| | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -# 5. Build oat command — use OAT.xml if present in repo root | +# 5. Resolve OAT.xml path |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -_OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST" | |
| - | |
| _OAT_XML="$REPO_ROOT/OAT.xml" | _OAT_XML="$REPO_ROOT/OAT.xml" |
| +if [ ! -f "$_OAT_XML" ] && [ -f "$REPO_ROOT/scripts/OAT.xml" ]; then |
| + _OAT_XML="$REPO_ROOT/scripts/OAT.xml" |
| +fi |
| + |
| +# _OAT_CMD is kept for display purposes only (shown in error messages). |
| +# Actual execution uses direct argument passing below to avoid eval word-splitting |
| +# and command-injection risks when paths or filenames contain special characters. |
| if [ -f "$_OAT_XML" ]; then | if [ -f "$_OAT_XML" ]; then |
| - _OAT_CMD="$_OAT_CMD -oatconfig $_OAT_XML" | + _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\"" |
| +else |
| + _OAT_CMD="\"$_PYTHON\" -m oat -mode s -s \"$REPO_ROOT\" -r \"$OAT_REPORT_DIR\" -n \"$REPO_NAME\" -w 1 -f \"$FILE_LIST\"" |
| fi | fi |
| | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -# 6. Run oat scan | +# 6. Run oat scan (direct argument passing � no eval) |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| echo "" | echo "" |
| echo "[OAT] Running compliance scan..." | echo "[OAT] Running compliance scan..." |
| | |
| set +e | set +e |
| -eval "$_OAT_CMD" >/dev/null 2>&1 | +if [ -f "$_OAT_XML" ]; then |
| + "$_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 |
| +else |
| + "$_PYTHON" -m oat -mode s -s "$REPO_ROOT" -r "$OAT_REPORT_DIR" -n "$REPO_NAME" -w 1 -f "$FILE_LIST" >/dev/null 2>&1 |
| +fi |
| _OAT_RC=$? | _OAT_RC=$? |
| set -e | set -e |
| | |
| @@ -170,7 +327,7 @@ fi |
| # Only: Invalid File Type + License Header Invalid (no copyright) | # Only: Invalid File Type + License Header Invalid (no copyright) |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt" | REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt" |
| -RESULT_FILE="$OAT_REPORT_DIR/result.txt" | +RESULT_FILE="$OAT_RESULT_DIR/result.txt" |
| | |
| # Section headers used as stop-boundaries when extracting sections | # Section headers used as stop-boundaries when extracting sections |
| _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:" | _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() { |
| | |
| if [ ! -f "$REPORT_FILE" ]; then | if [ ! -f "$REPORT_FILE" ]; then |
| if [ "$_OAT_RC" -eq 0 ]; then | if [ "$_OAT_RC" -eq 0 ]; then |
| + # oat exited cleanly with no report: all staged files were filtered out |
| echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." |
| + [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true |
| + rm -rf "$OAT_REPORT_DIR" |
| exit 0 | exit 0 |
| else | else |
| - echo "[OAT] [WARNING] Report not found: $REPORT_FILE" | + # oat returned exit code 1 but produced no report �?possible disk issue |
| + # or oat internal error. Do not silently pass; block the commit. |
| + echo "" |
| + echo "[OAT] [ERROR] oat exited with code $_OAT_RC but no report was generated." |
| + echo "[OAT] This may indicate a disk error or an oat internal bug." |
| + echo "[OAT] To investigate, run manually:" |
| + echo " $_OAT_CMD" |
| + echo "[OAT] Blocking commit to prevent silent compliance bypass." |
| + echo "" |
| + rm -rf "$OAT_REPORT_DIR" |
| exit 1 | exit 1 |
| fi | fi |
| fi | fi |
| | |
| -# Parse counts | +# Parse counts ??use || true to prevent set -e from triggering if grep finds no match |
| -_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1) | +_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true) |
| -_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1) | +_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true) |
| _INVALID_TYPE=${_INVALID_TYPE:-0} | _INVALID_TYPE=${_INVALID_TYPE:-0} |
| _LICENSE_INVALID=${_LICENSE_INVALID:-0} | _LICENSE_INVALID=${_LICENSE_INVALID:-0} |
| | |
| @@ -240,11 +409,8 @@ _SECTION_LIC=$(_extract_section "$REPORT_FILE" "License Header Invalid Total Cou |
| echo "===================================" | echo "===================================" |
| } > "$RESULT_FILE" | } > "$RESULT_FILE" |
| | |
| -# Clean up full plain report (keep only result.txt) | |
| -rm -f "$REPORT_FILE" | |
| - | |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| -# 8. Block commit if issues found | +# 8. Block commit if issues found; always clean up temp dir |
| # --------------------------------------------------------------------------- | # --------------------------------------------------------------------------- |
| TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID )) | TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID )) |
| | |
| @@ -258,12 +424,15 @@ if [ "$TOTAL_ISSUES" -gt 0 ]; then |
| echo " - Invalid File Type: $_INVALID_TYPE" | echo " - Invalid File Type: $_INVALID_TYPE" |
| echo " - License Header Invalid: $_LICENSE_INVALID" | echo " - License Header Invalid: $_LICENSE_INVALID" |
| echo "" | echo "" |
| - echo "[OAT] Details:" | + echo "[OAT] Details (also saved to: $RESULT_FILE):" |
| - echo " cat $RESULT_FILE" | + echo "---" |
| + cat "$RESULT_FILE" |
| + echo "---" |
| echo "" | echo "" |
| echo "Fix the issues and recommit, or skip with:" | echo "Fix the issues and recommit, or skip with:" |
| echo " git commit --no-verify" | echo " git commit --no-verify" |
| echo "" | echo "" |
| + rm -rf "$OAT_REPORT_DIR" |
| exit 1 | exit 1 |
| fi | fi |
| | |
| @@ -271,4 +440,10 @@ echo "" |
| echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." |
| echo "[OAT] Summary: cat $RESULT_FILE" | echo "[OAT] Summary: cat $RESULT_FILE" |
| echo "" | echo "" |
| +# Mark scan as done for CI range mode (prevents redundant re-runs on same PR head) |
| +if [ -n "$_DONE_MARKER" ]; then |
| + touch "$_DONE_MARKER" 2>/dev/null || true |
| + echo "[OAT] Done-marker created: $_DONE_MARKER" |
| +fi |
| +rm -rf "$OAT_REPORT_DIR" |
| exit 0 | exit 0 |
| |