diff --git a/.github/scripts/checkstyle-pr.sh b/.github/scripts/checkstyle-pr.sh new file mode 100644 index 00000000..8272fc45 --- /dev/null +++ b/.github/scripts/checkstyle-pr.sh @@ -0,0 +1,207 @@ +#!/bin/bash +# ============================================================ +# checkstyle-pr.sh - 增量检查(扫描整个变更文件,不过滤行号) +# 功能:对本次提交中变更的 Java 文件执行完整的 Checkstyle 检查 +# 不阻断构建,生成完整报告 +# ============================================================ + +set -e +unset GREP_OPTIONS +echo "========================================" +echo " Checkstyle 增量检查" +echo " 扫描范围:本次变更的 Java 文件(完整文件)" +echo "========================================" + +# 1. 确定目标分支 +if [ -n "$GITHUB_BASE_REF" ]; then + BASE_BRANCH="origin/$GITHUB_BASE_REF" +elif [ -n "$GITHUB_REF" ] && [ "$GITHUB_EVENT_NAME" == "push" ]; then + BASE_BRANCH="HEAD^" +else + if git rev-parse --verify origin/main >/dev/null 2>&1; then + BASE_BRANCH="origin/main" + elif git rev-parse --verify origin/develop >/dev/null 2>&1; then + BASE_BRANCH="origin/develop" + else + echo "❌ 无法确定目标分支,请设置 BASE_BRANCH 环境变量。" + exit 1 + fi + echo "🔍 本地运行模式,对比分支: $BASE_BRANCH" +fi + +# 2. 获取变更的 Java 文件 +if [ "$BASE_BRANCH" == "HEAD^" ]; then + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH" HEAD -- '*.java' 2>/dev/null || true) +else + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH"...HEAD -- '*.java' 2>/dev/null || true) +fi + +if [ -z "$CHANGED_FILES" ]; then + echo "✅ 没有 Java 文件变更,跳过检查。" + exit 0 +fi + +echo "📝 变更的 Java 文件:" +echo "$CHANGED_FILES" +echo "----------------------------------------" + +# 按模块分组,并把路径转换为 Checkstyle includes 使用的源码相对路径 +declare -A module_files +for file in $CHANGED_FILES; do + module="${file%%/*}" + if [ -z "$module" ] || [ "$module" == "$file" ]; then + echo "⚠️ 忽略根目录文件: $file" + continue + fi + rel="${file#$module/}" + case "$rel" in + src/main/java/*) + include="${rel#src/main/java/}" + ;; + src/test/java/*) + include="${rel#src/test/java/}" + ;; + *) + echo "⚠️ 跳过非源码目录 Java 文件: $file" + continue + ;; + esac + if [ -z "${module_files[$module]}" ]; then + module_files[$module]="$include" + else + module_files[$module]="${module_files[$module]},$include" + fi +done + +if [ ${#module_files[@]} -eq 0 ]; then + echo "⚠️ 没有识别到任何模块,跳过检查。" + exit 0 +fi + +echo "📝 按模块分组后的相对路径:" +for module in "${!module_files[@]}"; do + echo " $module: ${module_files[$module]}" +done +echo "----------------------------------------" + +total_violations=0 +execution_failures=0 + +# 对每个模块执行 Checkstyle +for module in "${!module_files[@]}"; do + file_list="${module_files[$module]}" + report_file="$module/target/checkstyle-result.xml" + echo "🚀 扫描模块: $module" + echo " 文件列表: $file_list" + + if [ ! -d "$module" ] || [ ! -f "$module/pom.xml" ]; then + echo "⚠️ 模块目录 $module 不存在或没有 pom.xml,跳过。" + continue + fi + + echo " - 运行 Checkstyle 检查(增量扫描)..." + echo "$file_list" + set +e + PROJECT_ROOT=$(pwd) + rm -f "$report_file" + output=$(cd "$module" && \ + echo " Current directory: $(pwd)" && \ + echo " Checking file existence:" && \ + IFS=',' read -ra includes <<< "$file_list" && \ + for include in "${includes[@]}"; do \ + if [ -f "src/main/java/$include" ]; then \ + ls -l "src/main/java/$include"; \ + elif [ -f "src/test/java/$include" ]; then \ + ls -l "src/test/java/$include"; \ + else \ + echo " ⚠️ not found: $include"; \ + fi; \ + done && \ + mvn checkstyle:check \ + -Dcheckstyle.config.location="$PROJECT_ROOT/checkstyle/code-check-checkstyle.xml" \ + -Dcheckstyle.violationSeverity=warning \ + -Dcheckstyle.outputFormat=xml \ + -Dcheckstyle.includes="$file_list" 2>&1 ) + mvn_exit=$? + if [ $mvn_exit -ne 0 ]; then + echo " ⚠️ 模块 $module 的 Checkstyle 检查失败(但继续)" + fi + set -e + echo "$output" + + # 从 XML 报告中统计违规数,比解析 Maven 日志更稳定 + if [ -f "$report_file" ]; then + count=$(grep -c -- '/dev/null || true) + else + count=0 + fi + total_violations=$((total_violations + count)) + echo " 模块 $module 违规数: $count" + + if [ $mvn_exit -ne 0 ] && [ "$count" -eq 0 ]; then + echo " ❌ 模块 $module 的 Checkstyle 执行失败,且未生成可解析的违规报告。" + execution_failures=$((execution_failures + 1)) + fi + + # (可选)生成 HTML 报告供人工查看 + echo " - 生成 HTML 报告(可选)..." + set +e + (cd "$module" && mvn checkstyle:checkstyle \ + -Dcheckstyle.config.location="$PROJECT_ROOT/checkstyle/code-check-checkstyle.xml" \ + -Dcheckstyle.includes="$file_list" \ + -Dcheckstyle.violationSeverity=warning) > /dev/null 2>&1 + set -e + echo "" +done + +# 汇总输出 +echo "----------------------------------------" +if [ $total_violations -eq 0 ]; then + echo "✅ 所有变更文件未发现违规!" +else + echo "⚠️ 总计发现 $total_violations 个违规。" + echo "" + echo "📋 违规摘要(前 30 条):" + for module in "${!module_files[@]}"; do + report_file="$module/target/checkstyle-result.xml" + if [ -f "$report_file" ] && grep -q -- '/dev/null; then + grep -- '/dev/null | head -30 | sed 's///' | \ + sed 's|line="|行号: |g; s|column="|列: |g; s|severity="|严重性: |g; s|message="|信息: |g; s|source="||g' | \ + while read -r line; do + echo " $line" + done || true + break + fi + done +fi + +# Step Summary +if [ -n "$GITHUB_STEP_SUMMARY" ]; then + { + echo "## 📋 Checkstyle 汇总报告" + echo "" + echo "| 指标 | 结果 |" + echo "|------|------|" + if [ $total_violations -eq 0 ]; then + echo "| 总违规数 | ✅ **0** |" + else + echo "| 总违规数 | ⚠️ **$total_violations** |" + fi + echo "| 执行失败模块数 | $execution_failures |" + echo "| 涉及模块 | ${!module_files[*]} |" + echo "" + echo "📥 完整报告已作为 Artifact 上传。" + } >> "$GITHUB_STEP_SUMMARY" +fi + +# 根据违规数决定退出码 +if [ $total_violations -eq 0 ] && [ $execution_failures -eq 0 ]; then + echo "✅ 检查通过,构建成功。" + exit 0 +elif [ $execution_failures -ne 0 ]; then + echo "❌ 有 $execution_failures 个模块 Checkstyle 执行失败,构建失败。" + exit 1 +else + echo "❌ 发现 $total_violations 个违规,构建失败。" + exit 1 +fi diff --git a/.github/scripts/pmd-pr.sh b/.github/scripts/pmd-pr.sh new file mode 100644 index 00000000..b766d07d --- /dev/null +++ b/.github/scripts/pmd-pr.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# ============================================================ +# pmd-pr.sh - PMD 增量扫描脚本 +# 功能:只扫描本次提交中变更且仍存在的 Java 文件 +# 报告:target/pmd-report.xml、target/pmd-report.html +# ============================================================ + +set -euo pipefail + +echo "========================================" +echo " PMD 增量扫描" +echo " 扫描范围:本次变更的 Java 文件" +echo "========================================" + +# 1. 确定目标分支 +if [ -n "${GITHUB_BASE_REF:-}" ]; then + BASE_BRANCH="origin/$GITHUB_BASE_REF" +elif [ -n "${GITHUB_REF:-}" ] && [ "${GITHUB_EVENT_NAME:-}" == "push" ]; then + BASE_BRANCH="HEAD^" +else + if git rev-parse --verify origin/main >/dev/null 2>&1; then + BASE_BRANCH="origin/main" + elif git rev-parse --verify origin/develop >/dev/null 2>&1; then + BASE_BRANCH="origin/develop" + else + echo "❌ 无法确定目标分支,请设置 BASE_BRANCH 环境变量。" + exit 1 + fi + echo "🔍 本地运行模式,对比分支: $BASE_BRANCH" +fi + +# 2. 获取变更的 Java 文件 +if [ "$BASE_BRANCH" == "HEAD^" ]; then + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH" HEAD -- '*.java' 2>/dev/null || true) +else + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH"...HEAD -- '*.java' 2>/dev/null || true) +fi + +if [ -z "$CHANGED_FILES" ]; then + echo "✅ 没有 Java 文件变更,跳过 PMD 扫描。" + exit 0 +fi + +echo "📝 变更的 Java 文件:" +echo "$CHANGED_FILES" +echo "----------------------------------------" + +# 3. 生成文件列表(绝对路径),跳过已删除文件 +PROJECT_ROOT=$(pwd) +mkdir -p target +FILE_LIST="target/pmd-changed-files.txt" +REPORT_FILE="target/pmd-report.xml" +HTML_REPORT_FILE="target/pmd-report.html" +> "$FILE_LIST" +rm -f "$REPORT_FILE" "$HTML_REPORT_FILE" + +scan_count=0 +for file in $CHANGED_FILES; do + if [ ! -f "$file" ]; then + echo "⚠️ 跳过不存在的文件: $file" + continue + fi + + echo "$PROJECT_ROOT/$file" >> "$FILE_LIST" + scan_count=$((scan_count + 1)) +done + +if [ "$scan_count" -eq 0 ]; then + echo "✅ 没有需要 PMD 扫描的现存 Java 文件。" + rm -f "$FILE_LIST" + exit 0 +fi + +echo "📄 PMD 文件列表:$FILE_LIST" +cat "$FILE_LIST" +echo "----------------------------------------" + +# 4. 准备 PMD(如果未安装) +PMD_VERSION="${PMD_VERSION:-6.55.0}" +PMD_HOME="${PMD_HOME:-target/pmd-bin-$PMD_VERSION}" +PMD_ZIP="target/pmd-bin-$PMD_VERSION.zip" +PMD_RULESETS="${PMD_RULESETS:-category/java/bestpractices.xml,category/java/codestyle.xml,category/java/design.xml,category/java/errorprone.xml,category/java/performance.xml,category/java/security.xml}" + +if [ ! -x "$PMD_HOME/bin/run.sh" ]; then + echo "⬇️ 下载 PMD $PMD_VERSION ..." + rm -rf "$PMD_HOME" "$PMD_ZIP" "target/pmd-bin-$PMD_VERSION" + curl -fsSL "https://github.com/pmd/pmd/releases/download/pmd_releases%2F${PMD_VERSION}/pmd-bin-${PMD_VERSION}.zip" -o "$PMD_ZIP" + unzip -q "$PMD_ZIP" -d target + rm -f "$PMD_ZIP" +fi + +PMD_CMD="$PMD_HOME/bin/run.sh" +chmod +x "$PMD_CMD" + +# 5. 执行 PMD 扫描(使用 filelist) +echo "🚀 执行 PMD 扫描..." +set +e +"$PMD_CMD" pmd --no-cache \ + -filelist "$FILE_LIST" \ + -f xml \ + -R "$PMD_RULESETS" \ + -r "$REPORT_FILE" +pmd_exit=$? +set -e + +# 6. 统计违规数 +if [ -f "$REPORT_FILE" ]; then + violations=$(grep -c -- '/dev/null || true) + echo "✅ PMD 报告已生成:$REPORT_FILE" +else + violations=0 + echo "⚠️ PMD 未生成报告。" +fi + +# 7. 生成 PMD HTML 可视化报告 +echo "🖼️ 生成 PMD HTML 报告..." +set +e +"$PMD_CMD" pmd --no-cache \ + -filelist "$FILE_LIST" \ + -f html \ + -R "$PMD_RULESETS" \ + -r "$HTML_REPORT_FILE" +pmd_html_exit=$? +set -e + +if [ -f "$HTML_REPORT_FILE" ]; then + echo "✅ PMD HTML 报告已生成:$HTML_REPORT_FILE" +else + echo "⚠️ PMD HTML 报告未生成。" +fi + +rm -f "$FILE_LIST" + +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + { + echo "## PMD 增量扫描" + echo "" + echo "| 指标 | 结果 |" + echo "|------|------|" + echo "| 扫描文件数 | $scan_count |" + echo "| 违规数 | $violations |" + echo "| XML 报告 | $REPORT_FILE |" + echo "| HTML 报告 | $HTML_REPORT_FILE |" + } >> "$GITHUB_STEP_SUMMARY" +fi + +if [ "$violations" -gt 0 ]; then + echo "❌ PMD 发现 $violations 个问题,构建失败。" + exit 1 +fi + +if [ "$pmd_exit" -ne 0 ]; then + echo "❌ PMD 执行失败,退出码: $pmd_exit" + exit "$pmd_exit" +fi + +if [ "$pmd_html_exit" -ne 0 ]; then + echo "❌ PMD HTML 报告生成失败,退出码: $pmd_html_exit" + exit "$pmd_html_exit" +fi + +echo "✅ PMD 未发现问题。" +exit 0 diff --git a/.github/scripts/spotbugs-incremental.sh b/.github/scripts/spotbugs-incremental.sh new file mode 100644 index 00000000..3cad4604 --- /dev/null +++ b/.github/scripts/spotbugs-incremental.sh @@ -0,0 +1,525 @@ +#!/bin/bash +# ============================================================ +# spotbugs-incremental.sh - 增量 SpotBugs 扫描 +# 功能:只分析本次变更的 src/main/java 或 src/test/java 文件对应的类 +# 报告:各模块 target/spotbugsXml.xml、target/spotbugs-reports/spotbugs.html +# ============================================================ + +set -euo pipefail + +PYTHON_BIN="${PYTHON_BIN:-}" +if [ -z "$PYTHON_BIN" ]; then + if command -v python3 >/dev/null 2>&1; then + PYTHON_BIN="python3" + elif command -v python >/dev/null 2>&1; then + PYTHON_BIN="python" + fi +fi + +generate_styled_spotbugs_html_report() { + local module_dir="$1" + local module_name="$2" + local class_list="$3" + local output_file="$module_dir/target/spotbugs-reports/spotbugs.html" + local xml_files=() + + while IFS= read -r xml_file; do + xml_files+=("$xml_file") + done < <(find "$module_dir/target" -type f \( -name 'spotbugsXml.xml' -o -name 'spotbugs*.xml' \) 2>/dev/null) + + if [ ${#xml_files[@]} -eq 0 ] || [ -z "$PYTHON_BIN" ]; then + return 1 + fi + + mkdir -p "$(dirname "$output_file")" + "$PYTHON_BIN" - "$output_file" "$module_name" "$class_list" "${xml_files[@]}" <<'PY' +import datetime +import html +import sys +import xml.etree.ElementTree as ET + +output_file = sys.argv[1] +module_name = sys.argv[2] +class_list = sys.argv[3] +xml_files = sys.argv[4:] +priority_names = { + "1": "High", + "2": "Medium", + "3": "Low", + "4": "Experimental", +} +bugs = [] + +for xml_file in xml_files: + try: + root = ET.parse(xml_file).getroot() + except ET.ParseError: + continue + + for bug in root.findall(".//BugInstance"): + source = bug.find("SourceLine") + bug_class = bug.find("Class") + long_message = bug.findtext("LongMessage") or bug.findtext("ShortMessage") or "" + location = "" + if source is not None: + location = source.get("sourcepath") or source.get("sourcefile") or "" + line = source.get("start") or source.get("startLine") or "" + if line and line != "-1": + location = f"{location}:{line}" if location else line + + bugs.append({ + "priority": priority_names.get(bug.get("priority", ""), bug.get("priority", "")), + "rank": bug.get("rank", ""), + "category": bug.get("category", ""), + "type": bug.get("type", ""), + "class": bug_class.get("classname", "") if bug_class is not None else "", + "location": location, + "message": long_message.strip(), + }) + +def esc(value): + return html.escape(str(value or ""), quote=True) + +rows = [] +for bug in bugs: + priority_class = esc(bug["priority"].lower()) + rows.append(f""" + + {esc(bug["priority"])} + {esc(bug["rank"])} + {esc(bug["category"])} + {esc(bug["type"])} + {esc(bug["class"])} + {esc(bug["location"])} + {esc(bug["message"])} + + """) + +empty_state = "" +if not bugs: + empty_state = '
No SpotBugs issues were found in the incremental scan.
' + +generated_at = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") +html_doc = f""" + + + + + SpotBugs Incremental Report - {esc(module_name)} + + + +
+

SpotBugs Incremental Report

+

Module: {esc(module_name)} · Generated: {esc(generated_at)}

+
+
+
+
Issues{len(bugs)}
+
Classes{len([c for c in class_list.split(",") if c])}
+
Sources{len(xml_files)}
+
+

Analyzed classes: {esc(class_list)}

+ {empty_state} +
+ + + + + + + + + + + + + + {''.join(rows)} + +
PriorityRankCategoryTypeClassLocationMessage
+
+
+ + +""" + +with open(output_file, "w", encoding="utf-8") as report: + report.write(html_doc) +PY +} + +echo "========================================" +echo " SpotBugs 增量扫描" +echo " 扫描范围:本次变更的主源码和测试源码 Java 类" +echo "========================================" + +# 1. 确定目标分支 +if [ -n "${GITHUB_BASE_REF:-}" ]; then + BASE_BRANCH="origin/$GITHUB_BASE_REF" +elif [ -n "${GITHUB_REF:-}" ] && [ "${GITHUB_EVENT_NAME:-}" == "push" ]; then + BASE_BRANCH="HEAD^" +else + if git rev-parse --verify origin/main >/dev/null 2>&1; then + BASE_BRANCH="origin/main" + elif git rev-parse --verify origin/develop >/dev/null 2>&1; then + BASE_BRANCH="origin/develop" + else + echo "❌ 无法确定目标分支,请设置 BASE_BRANCH 环境变量。" + exit 1 + fi + echo "🔍 本地运行模式,对比分支: $BASE_BRANCH" +fi + +# 2. 获取变更的 Java 文件 +if [ "$BASE_BRANCH" == "HEAD^" ]; then + CHANGED_JAVA=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH" HEAD -- '*.java' 2>/dev/null || true) +else + CHANGED_JAVA=$(git diff --name-only --diff-filter=ACMRT "$BASE_BRANCH"...HEAD -- '*.java' 2>/dev/null || true) +fi + +if [ -z "$CHANGED_JAVA" ]; then + echo "✅ 没有 Java 文件变更,跳过 SpotBugs 扫描。" + exit 0 +fi + +echo "📝 变更的 Java 文件:" +echo "$CHANGED_JAVA" +echo "----------------------------------------" + +# 3. 按 Maven 模块提取待分析类 +declare -A module_classes +declare -A module_include_tests +declare -A module_scopes +scan_count=0 + +for file in $CHANGED_JAVA; do + if [ ! -f "$file" ]; then + echo "⚠️ 跳过不存在的文件: $file" + continue + fi + + module="${file%%/*}" + rel="${file#$module/}" + + if [ "$module" == "$file" ] || [ ! -f "$module/pom.xml" ]; then + echo "⚠️ 跳过非模块 Java 文件: $file" + continue + fi + + source_scope="" + if [[ "$rel" == src/main/java/* ]]; then + source_scope="main" + elif [[ "$rel" == src/test/java/* ]]; then + source_scope="test" + module_include_tests[$module]="true" + else + echo "ℹ️ SpotBugs 分析编译后的 main/test class,跳过非源码目录文件: $file" + continue + fi + + pkg=$(sed -nE 's/^[[:space:]]*package[[:space:]]+([^;]+);.*/\1/p' "$file" | head -1) + if [ -z "$pkg" ]; then + echo "⚠️ 跳过未声明 package 的文件: $file" + continue + fi + + classname=$(basename "$file" .java) + fqcn="$pkg.$classname" + + if [ -z "${module_classes[$module]:-}" ]; then + module_classes[$module]="$fqcn" + else + module_classes[$module]="${module_classes[$module]},$fqcn" + fi + + if [ -z "${module_scopes[$module]:-}" ]; then + module_scopes[$module]="$source_scope" + elif [[ ",${module_scopes[$module]}," != *",$source_scope,"* ]]; then + module_scopes[$module]="${module_scopes[$module]},$source_scope" + fi + + scan_count=$((scan_count + 1)) +done + +if [ ${#module_classes[@]} -eq 0 ]; then + echo "✅ 没有需要 SpotBugs 分析的主源码或测试源码类。" + exit 0 +fi + +echo "📋 按模块分组后的类:" +for module in "${!module_classes[@]}"; do + include_tests="${module_include_tests[$module]:-false}" + echo " $module (${module_scopes[$module]}, includeTests=$include_tests): ${module_classes[$module]}" +done +echo "----------------------------------------" + +total_bugs=0 +execution_failures=0 +total_html_reports=0 +html_failures=0 + +# 4. 对每个模块执行 SpotBugs +for module in "${!module_classes[@]}"; do + class_list="${module_classes[$module]}" + include_tests="${module_include_tests[$module]:-false}" + echo "🚀 扫描模块: $module" + echo " 类列表: $class_list" + echo " 扫描测试类: $include_tests" + + rm -f "$module/target/spotbugsXml.xml" + rm -rf "$module/target/spotbugs-reports" + + set +e + if [ "$include_tests" == "true" ]; then + output=$(cd "$module" && mvn test-compile spotbugs:check \ + -DskipTests \ + -Dcheckstyle.skip=true \ + -Dpmd.skip=true \ + -Dcpd.skip=true \ + -Dspotbugs.onlyAnalyze="$class_list" \ + -Dspotbugs.includeTests=true \ + -Dspotbugs.xmlOutput=true \ + -Dspotbugs.htmlOutput=true 2>&1) + else + output=$(cd "$module" && mvn spotbugs:check \ + -Dspotbugs.onlyAnalyze="$class_list" \ + -Dspotbugs.includeTests=false \ + -Dspotbugs.xmlOutput=true \ + -Dspotbugs.htmlOutput=true 2>&1) + fi + mvn_exit=$? + set -e + echo "$output" + + if generate_styled_spotbugs_html_report "$module" "$module" "$class_list"; then + echo " HTML 可视化报告: $module/target/spotbugs-reports/spotbugs.html" + fi + + html_count=0 + while IFS= read -r html_file; do + html_count=$((html_count + 1)) + echo " HTML 报告: $html_file" + done < <(find "$module/target" -type f \( -name 'spotbugs.html' -o -name 'spotbugs*.html' \) 2>/dev/null) + + if [ "$html_count" -eq 0 ]; then + echo " - 未找到 SpotBugs HTML 报告,单独生成可视化报告..." + set +e + if [ "$include_tests" == "true" ]; then + report_output=$(cd "$module" && mvn test-compile spotbugs:spotbugs \ + -DskipTests \ + -Dcheckstyle.skip=true \ + -Dpmd.skip=true \ + -Dcpd.skip=true \ + -Dspotbugs.onlyAnalyze="$class_list" \ + -Dspotbugs.includeTests=true \ + -Dspotbugs.xmlOutput=true \ + -Dspotbugs.htmlOutput=true 2>&1) + else + report_output=$(cd "$module" && mvn spotbugs:spotbugs \ + -Dspotbugs.onlyAnalyze="$class_list" \ + -Dspotbugs.includeTests=false \ + -Dspotbugs.xmlOutput=true \ + -Dspotbugs.htmlOutput=true 2>&1) + fi + report_exit=$? + set -e + echo "$report_output" + + if generate_styled_spotbugs_html_report "$module" "$module" "$class_list"; then + echo " HTML 可视化报告: $module/target/spotbugs-reports/spotbugs.html" + fi + + html_count=0 + while IFS= read -r html_file; do + html_count=$((html_count + 1)) + echo " HTML 报告: $html_file" + done < <(find "$module/target" -type f \( -name 'spotbugs.html' -o -name 'spotbugs*.html' \) 2>/dev/null) + + if [ "$report_exit" -ne 0 ] && [ "$html_count" -eq 0 ]; then + echo " ❌ 模块 $module 的 SpotBugs HTML 报告生成失败,退出码: $report_exit" + fi + fi + + if [ "$html_count" -eq 0 ]; then + html_failures=$((html_failures + 1)) + fi + total_html_reports=$((total_html_reports + html_count)) + + bug_count=0 + while IFS= read -r report_file; do + count=$(grep -c -- '/dev/null || true) + bug_count=$((bug_count + count)) + echo " 报告: $report_file,问题数: $count" + done < <(find "$module/target" -type f \( -name 'spotbugsXml.xml' -o -name 'spotbugs*.xml' \) 2>/dev/null) + + total_bugs=$((total_bugs + bug_count)) + echo " 模块 $module SpotBugs 问题数: $bug_count" + + if [ "$mvn_exit" -ne 0 ] && [ "$bug_count" -eq 0 ]; then + echo " ❌ 模块 $module 的 SpotBugs 执行失败,且未生成可解析的问题报告。" + execution_failures=$((execution_failures + 1)) + fi + echo "" +done + +# 5. 汇总 +echo "----------------------------------------" +echo "SpotBugs 扫描类数: $scan_count" +echo "SpotBugs 问题总数: $total_bugs" +echo "SpotBugs 执行失败模块数: $execution_failures" +echo "SpotBugs HTML 报告数: $total_html_reports" +echo "SpotBugs HTML 报告失败模块数: $html_failures" + +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + { + echo "## SpotBugs 增量扫描" + echo "" + echo "| 指标 | 结果 |" + echo "|------|------|" + echo "| 扫描类数 | $scan_count |" + echo "| 问题数 | $total_bugs |" + echo "| 执行失败模块数 | $execution_failures |" + echo "| HTML 报告数 | $total_html_reports |" + echo "| HTML 报告失败模块数 | $html_failures |" + } >> "$GITHUB_STEP_SUMMARY" +fi + +if [ "$html_failures" -ne 0 ]; then + echo "❌ 有 $html_failures 个模块未生成 SpotBugs HTML 报告,构建失败。" + exit 1 +fi + +if [ "$execution_failures" -ne 0 ]; then + echo "❌ 有 $execution_failures 个模块 SpotBugs 执行失败,构建失败。" + exit 1 +fi + +if [ "$total_bugs" -ne 0 ]; then + echo "❌ SpotBugs 发现 $total_bugs 个问题,构建失败。" + exit 1 +fi + +echo "✅ SpotBugs 未发现问题。" +exit 0 diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index 74529b84..8315b5b2 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -1,37 +1,122 @@ -name: Checkstyle Code Quality - -on: - push: - branches: - - develop # 或者你想要检查的分支 - pull_request: - branches: - - develop # 你可以在 PR 时检查代码 - -jobs: - check: - runs-on: ubuntu-24.04 - - steps: - # 检出代码 - - name: Checkout code - uses: actions/checkout@v4 - - # 设置 JDK(如果是 Java 项目) - - name: Set up JDK 17.* - uses: actions/setup-java@v4 - with: - java-version: '17.*' - distribution: 'temurin' - - # 安装依赖并运行 Checkstyle(如果是 Maven 项目) - - name: Install dependencies and run Checkstyle - run: | - mvn clean package - - # 查看 Checkstyle 检查报告 - - name: Upload Checkstyle report - uses: actions/upload-artifact@v4 - with: - name: checkstyle-report - path: target/checkstyle-result.xml # 这个路径应该是 Maven 生成的检查报告路径 +name: Checkstyle Code Quality + +on: + push: + branches: + - develop + pull_request: + branches: + - develop + +jobs: + checkstyle: + runs-on: ubuntu-24.04 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 # 必须拉取完整历史,才能比较分支差异 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + + # 缓存 Maven 依赖,加速构建 + - name: Cache Maven dependencies + uses: actions/cache@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + # 新增:安装所有模块到本地仓库,解决依赖解析 + - name: Install project (for dependency resolution) + id: install + run: mvn install -DskipTests -Dmaven.test.skip=true -Dcheckstyle.skip=true -Dpmd.skip=true -Dspotbugs.skip=true -Dcpd.skip=true + + # 直接运行 Checkstyle 检查(不执行完整的 package) + - name: Run Checkstyle + id: checkstyle + run: bash .github/scripts/checkstyle-pr.sh + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + + # ==================== PMD 增量扫描 ==================== + - name: Run PMD (incremental) + if: ${{ always() && steps.install.outcome == 'success' }} + id: pmd + run: bash .github/scripts/pmd-pr.sh + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + + # ==================== SpotBugs 增量扫描 ==================== + - name: Run SpotBugs (incremental) + if: ${{ always() && steps.install.outcome == 'success' }} + id: spotbugs + run: bash .github/scripts/spotbugs-incremental.sh + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + + # ==================== 调试:列出所有报告 ==================== + - name: Debug - list all reports + if: always() + run: | + echo "当前工作目录: $(pwd)" + echo "=== 查找所有检查报告 ===" + find . -type f \( -name "checkstyle-result.xml" -o -name "pmd-report.xml" -o -name "pmd-report.html" -o -name "cpd.xml" -o -name "spotbugsXml.xml" -o -name "spotbugs*.xml" -o -name "spotbugs*.html" \) + + - name: Debug - list all checkstyle reports + if: always() + run: | + echo "当前工作目录: $(pwd)" + echo "=== 列出所有 target 目录 ===" + find . -type d -name "target" -exec echo "目录: {}" \; -exec ls -la {}/ \; + echo "=== 查找 checkstyle 文件 ===" + find . -name "checkstyle*.xml" -o -name "checkstyle*.html" | while read f; do echo "找到: $f"; done + + - name: Debug - Check HTML existence + if: always() + run: | + echo "Searching for checkstyle.html:" + find . -name "checkstyle.html" -type f + echo "Also check reports directory:" + ls -la base/target/reports/ || echo "base/target/reports not found" + + # 如果检查失败,仍然上传报告供查看 + - name: Upload Checkstyle report + if: always() # 即使失败也上传报告 + uses: actions/upload-artifact@v7 + with: + name: checkstyle-report + path: | + **/target/checkstyle-result.xml + **/target/checkstyle-checker.xml + **/target/reports/ + if-no-files-found: warn + + - name: Upload PMD report + if: always() + uses: actions/upload-artifact@v7 + with: + name: pmd-report + path: | + target/pmd-report.xml + target/pmd-report.html + if-no-files-found: warn + + - name: Upload SpotBugs report + if: always() + uses: actions/upload-artifact@v7 + with: + name: spotbugs-report + path: | + **/target/spotbugsXml.xml + **/target/spotbugs*.html + **/target/spotbugs-reports/ + if-no-files-found: warn + diff --git a/.github/workflows/pmd.yml b/.github/workflows/pmd.yml new file mode 100644 index 00000000..3ac2e69e --- /dev/null +++ b/.github/workflows/pmd.yml @@ -0,0 +1,131 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: pmd + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + schedule: + - cron: '41 12 * * 3' + +permissions: + contents: read + +jobs: + pmd-code-scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # 必须拉取完整历史,才能比较分支差异 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + - name: Run PMD on changed Java files only + id: pmd + run: | + # 1. 确定目标分支(从环境变量获取) + if [ -n "$GITHUB_BASE_REF" ]; then + BASE_BRANCH="$GITHUB_BASE_REF" + else + # 如果是 push 事件,回退到 main 或 develop + BASE_BRANCH="develop" # 或 main,根据项目调整 + fi + + # 2. 确保目标分支的远程引用存在 + git fetch origin "$BASE_BRANCH" --depth=1 || true + + # 3. 获取变更的 Java 文件(比较当前 HEAD 与目标分支) + CHANGED_FILES=$(git diff --name-only "origin/$BASE_BRANCH" HEAD | grep '\.java$' || true) + + echo "📝 变更的 Java 文件列表:" + if [ -n "$CHANGED_FILES" ]; then + echo "$CHANGED_FILES" + else + echo "(无)" + fi + echo "----------------------------------------" + + if [ -z "$CHANGED_FILES" ]; then + echo "No Java files changed, skipping PMD." + echo "violations=0" >> $GITHUB_OUTPUT + exit 0 + fi + + # 4. 生成文件列表(绝对路径) + > changed-files.txt + for file in $CHANGED_FILES; do + echo "$PWD/$file" >> changed-files.txt + done + + # 5. 下载 PMD + PMD_VERSION="6.55.0" + curl -L "https://github.com/pmd/pmd/releases/download/pmd_releases%2F${PMD_VERSION}/pmd-bin-${PMD_VERSION}.zip" -o pmd.zip + unzip -q pmd.zip + mv pmd-bin-${PMD_VERSION} pmd + PMD_CMD="$PWD/pmd/bin/run.sh" + chmod +x "$PMD_CMD" + + # 6. 扫描变更的 Java 文件 + "$PMD_CMD" pmd --no-cache \ + --file-list changed-files.txt \ + -f sarif \ + -R rulesets/java/quickstart.xml \ + -r pmd-report.sarif || true + + # 7. 统计违规数 + if [ -f pmd-report.sarif ]; then + violations=$(jq '.runs[0].results | length' pmd-report.sarif) + else + violations=0 + fi + echo "violations=$violations" >> $GITHUB_OUTPUT + + # 清理 + rm -f changed-files.txt + + + - name: Install sarif-tools and convert to HTML + run: | + pip install sarif-tools + sarif html pmd-report.sarif --output pmd-report.html + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: pmd-report.sarif + + - name: Upload SARIF file as artifact + uses: actions/upload-artifact@v7 + with: + name: pmd-sarif-report # 给工件起一个有意义的名字 + path: pmd-report.sarif + + - name: Upload HTML report + uses: actions/upload-artifact@v7 + with: + name: pmd-html-report + path: pmd-report.html + + - name: Check PMD violations + run: | + if [[ ${{ steps.pmd.outputs.violations }} -eq 0 ]]; then + echo "✅ PMD 未发现代码问题,构建通过。" + exit 0 + else + echo "❌ PMD 发现 ${{ steps.pmd.outputs.violations }} 个代码问题,构建失败。" + exit 1 + fi \ No newline at end of file diff --git a/app/src/main/java/com/tinyengine/it/test/SampleViolations.java b/app/src/main/java/com/tinyengine/it/test/SampleViolations.java new file mode 100644 index 00000000..fa7db8bc --- /dev/null +++ b/app/src/main/java/com/tinyengine/it/test/SampleViolations.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +package com.tinyengine.it.test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Sample class used to validate code quality checks. + * + * @since 2026-08-17 + */ +public class SampleViolations { + private final List messages = new ArrayList<>(); + + /** + * Adds a non-blank message. + * + * @param message the message + * @return true if the message is added + */ + public boolean addMessage(String message) { + if (message == null) { + return false; + } + + String trimmedMessage = message.trim(); + if (trimmedMessage.isEmpty()) { + return false; + } + + messages.add(trimmedMessage); + return true; + } + + /** + * Gets all collected messages. + * + * @return the collected messages + */ + public List getMessages() { + return Collections.unmodifiableList(new ArrayList<>(messages)); + } + + /** + * Gets the number of collected messages. + * + * @return the message count + */ + public int getMessageCount() { + return messages.size(); + } +} diff --git a/base/src/main/java/com/tinyengine/it/common/utils/CheckstyleValidation.java b/base/src/main/java/com/tinyengine/it/common/utils/CheckstyleValidation.java new file mode 100644 index 00000000..f9eb88cb --- /dev/null +++ b/base/src/main/java/com/tinyengine/it/common/utils/CheckstyleValidation.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +package com.tinyengine.it.common.utils; + +/** + * Validates sample values used by code quality checks. + * + * @since 2026-08-17 + */ +public class CheckstyleValidation { + private String validationName = ""; + + /** + * Updates the validation name when the input has text. + * + * @param name the validation name + * @return true if the value is accepted + */ + public boolean updateValidationName(String name) { + if (name == null) { + return false; + } + + String trimmedName = name.trim(); + if (trimmedName.isEmpty()) { + return false; + } + + validationName = trimmedName; + return true; + } + + /** + * Gets the validation name. + * + * @return the validation name + */ + public String getValidationName() { + return validationName; + } + + /** + * Checks whether the validation name has been configured. + * + * @return true if the validation name has text + */ + public boolean isConfigured() { + return !validationName.isEmpty(); + } +} diff --git a/base/src/test/java/com/tinyengine/it/common/utils/JsonUtilsTest.java b/base/src/test/java/com/tinyengine/it/common/utils/JsonUtilsTest.java index 80562311..37cdcc38 100644 --- a/base/src/test/java/com/tinyengine/it/common/utils/JsonUtilsTest.java +++ b/base/src/test/java/com/tinyengine/it/common/utils/JsonUtilsTest.java @@ -270,7 +270,7 @@ void testEncodePrettily_ComplexObject() { void testDecode_ByteArray() { // Arrange String json = "{\"key\":\"value\",\"number\":123}"; - byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + byte[] jsonBytes = json.getBytes(); // Act Map result = JsonUtils.decode(jsonBytes, Map.class); diff --git a/checkstyle/code-check-checkstyle.xml b/checkstyle/code-check-checkstyle.xml new file mode 100644 index 00000000..1872b986 --- /dev/null +++ b/checkstyle/code-check-checkstyle.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..ab6b9fd7 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.equalsAndHashCode.callSuper = skip diff --git a/pom.xml b/pom.xml index c651a73c..282ce201 100644 --- a/pom.xml +++ b/pom.xml @@ -235,6 +235,105 @@ 17 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + + ../checkstyle/code-check-checkstyle.xml + + true + warning + + xml + ${project.build.directory}/checkstyle-result.xml + + ${project.basedir}/src/main/java + ${project.basedir}/src/test/java + + + + + + checkstyle-check + validate + + check + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.21.0 + + + category/java/bestpractices.xml + category/java/codestyle.xml + category/java/design.xml + category/java/errorprone.xml + category/java/performance.xml + category/java/security.xml + + true + false + 100 + + + + pmd-check + verify + + check + + + true + + + + + cpd-check + verify + + cpd-check + + + true + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.1 + + + spotbugs-check + verify + + check + + + + + Max + Low + true + true + true + ${project.build.directory}/spotbugs-reports + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..dfb04fd9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1 @@ +sonar.sourceEncoding=UTF-8 \ No newline at end of file