From 2ef1a08430a6ad47595388ad97e5b228caa62d06 Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Wed, 10 Jun 2026 15:12:30 +0530 Subject: [PATCH 1/4] Rust::com enable the CI Job for clippy lint check * Added workflow yml * Updated bzlrc file --- .github/workflows/clippy_lint.yml | 278 ++++++++++++++++++++++++++++++ MODULE.bazel | 1 + 2 files changed, 279 insertions(+) create mode 100644 .github/workflows/clippy_lint.yml diff --git a/.github/workflows/clippy_lint.yml b/.github/workflows/clippy_lint.yml new file mode 100644 index 000000000..d3f0bf284 --- /dev/null +++ b/.github/workflows/clippy_lint.yml @@ -0,0 +1,278 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +# Workflow: Clippy PR check +# +# Runs Clippy on pull requests with the 'rust-api' label, pushes to main, and merge groups. +# - For PRs: Only runs when the 'rust-api' label is present +# - Fails if Clippy errors or warnings are found in Rust files changed by the PR. +# - Writes a job summary with error/warning counts. +# - Uploads clippy log as a downloadable artifact. + +name: Clippy Check + +on: + pull_request: + types: [opened, reopened, synchronize, labeled, unlabeled] + push: + branches: [main] + merge_group: + types: [checks_requested] + workflow_call: + outputs: + artifact-name: + description: "Name of the clippy findings artifact" + value: ${{ jobs.clippy.outputs.artifact-name }} + errors: + description: "Total clippy error count" + value: ${{ jobs.clippy.outputs.errors }} + warnings: + description: "Total clippy warning count" + value: ${{ jobs.clippy.outputs.warnings }} + conclusion: + description: "Job conclusion: success or failure" + value: ${{ jobs.clippy.outputs.conclusion }} + +permissions: + contents: read + +concurrency: + group: clippy-pr-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +env: + ANDROID_HOME: "" + ANDROID_SDK_ROOT: "" + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + precheck: + runs-on: ubuntu-24.04 + outputs: + should-run: ${{ steps.gate.outputs.should-run }} + permissions: + contents: read + pull-requests: read + steps: + - name: Evaluate Clippy workflow gate + id: gate + env: + EVENT_NAME: ${{ github.event_name }} + run: | + python3 - <<'SCRIPT_END' + import json + import os + + should_run = True + + with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as event_file: + event = json.load(event_file) + + pr = event.get("pull_request") or {} + labels = {label.get("name", "") for label in (pr.get("labels") or [])} + + # For pull requests, only run if rust-api label is present + if os.environ["EVENT_NAME"] == "pull_request": + if "rust-api" not in labels: + should_run = False + + should_run_string = 'true' if should_run else 'false' + + labels_display = ", ".join(sorted(label for label in labels if label)) or "(none)" + print( + "Clippy gate evaluation: " + f"event_name='{os.environ['EVENT_NAME']}', " + f"labels=[{labels_display}], " + f"outputs: should-run={should_run_string}" + ) + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as github_output: + print(f"should-run={should_run_string}", file=github_output) + SCRIPT_END + + clippy: + needs: precheck + if: ${{ needs.precheck.outputs.should-run == 'true' }} + runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: read + outputs: + artifact-name: clippy-findings + errors: ${{ steps.findings.outputs.errors }} + warnings: ${{ steps.findings.outputs.warnings }} + conclusion: ${{ steps.set-conclusion.outputs.conclusion }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 # Fetch full history for accurate diff + + - name: Fetch base branch for comparison + if: github.event_name == 'pull_request' + run: | + git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} + + - uses: castler/setup-bazel@cache-optimized + with: + bazelisk-cache: true + disk-cache: "clippy" + repository-cache: true + cache-optimized: true + cache-save: ${{ github.ref == 'refs/heads/main' }} + + - name: Run Clippy via Bazel + id: run-clippy + continue-on-error: true + run: | + bazel build --config=lint //score/mw/com/... \ + 2>&1 | tee clippy_raw.log + # Capture the exit code from bazel (PIPESTATUS[0]), not tee + echo "bazel_exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT + + - name: Collect findings + id: findings + run: | + # Verify clippy_raw.log exists + if [[ ! -f clippy_raw.log ]]; then + echo "::error::clippy_raw.log not found - build step may have failed" + exit 1 + fi + + # Check if this was a real build failure due to compilation errors + BUILD_FAILED=false + if grep -q "ERROR: Build did NOT complete successfully" clippy_raw.log; then + BUILD_FAILED=true + echo "::error::Build failed due to compilation errors" + fi + + # Extract clippy errors and warnings + # Clippy format: "warning: message" or "error: message" or "error[E0123]: message" + ERRORS=$(grep -c "^error" clippy_raw.log 2>/dev/null || true) + WARNINGS=$(grep -c "^warning" clippy_raw.log 2>/dev/null || true) + ERRORS=${ERRORS:-0} + WARNINGS=${WARNINGS:-0} + TOTAL=$((ERRORS + WARNINGS)) + + echo "Grep results: errors=${ERRORS}, warnings=${WARNINGS}, total=${TOTAL}" + + # Scope blocking errors to Rust files changed in this PR + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + BASE_REF="origin/${{ github.base_ref }}" + CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ + | grep -E '\.rs$' || true) + elif [[ "${{ github.event_name }}" == "merge_group" ]]; then + # For merge queue, compare against the base + BASE_REF="origin/main" + CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ + | grep -E '\.rs$' || true) + else + # For pushes to main, check all Rust files changed in the last commit + CHANGED_RUST=$(git diff --name-only HEAD~1 HEAD 2>/dev/null \ + | grep -E '\.rs$' || true) + fi + + echo "Changed Rust files:" + echo "${CHANGED_RUST}" + + if [[ -z "$CHANGED_RUST" ]]; then + ERRORS_BLOCKING=0 + WARNINGS_BLOCKING=0 + echo "No Rust files changed - skipping changed-file check" + else + PATTERN=$(echo "$CHANGED_RUST" | tr '\n' '|' | sed 's/|$//') + echo "File pattern: ${PATTERN}" + # Clippy format: "warning:" followed by " --> file:line:col" on next line + # Use -B 1 to get the line before the file path (the warning/error line) + ERRORS_BLOCKING=$(grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null \ + | grep -c "^error" || true) + ERRORS_BLOCKING=${ERRORS_BLOCKING:-0} + WARNINGS_BLOCKING=$(grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null \ + | grep -c "^warning" || true) + WARNINGS_BLOCKING=${WARNINGS_BLOCKING:-0} + echo "Blocking errors in changed files: ${ERRORS_BLOCKING}" + echo "Blocking warnings in changed files: ${WARNINGS_BLOCKING}" + fi + + BLOCKING_TOTAL=$((ERRORS_BLOCKING + WARNINGS_BLOCKING)) + + echo "errors=${ERRORS}" >> $GITHUB_OUTPUT + echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT + echo "total=${TOTAL}" >> $GITHUB_OUTPUT + echo "errors_blocking=${ERRORS_BLOCKING}" >> $GITHUB_OUTPUT + echo "warnings_blocking=${WARNINGS_BLOCKING}" >> $GITHUB_OUTPUT + echo "blocking_total=${BLOCKING_TOTAL}" >> $GITHUB_OUTPUT + echo "build_failed=${BUILD_FAILED}" >> $GITHUB_OUTPUT + + # Fail immediately if this was a build failure due to compilation errors + if [[ "${BUILD_FAILED}" == "true" ]]; then + echo "::error::Build failed due to compilation errors" + echo "## :x: Build Failed" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "The build failed due to compilation errors." >> "$GITHUB_STEP_SUMMARY" + echo "Check the build log for details." >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + # Job summary + { + echo "## Clippy Results" + echo "" + echo "| Metric | Count |" + echo "|--------|------:|" + echo "| :x: Errors | **${ERRORS}** |" + echo "| :warning: Warnings | **${WARNINGS}** |" + echo "| Total | **${TOTAL}** |" + echo "" + if [[ "${BLOCKING_TOTAL}" -gt 0 ]]; then + echo "### :x: Check Failed" + echo "" + echo "Found **${ERRORS_BLOCKING} error(s)** and **${WARNINGS_BLOCKING} warning(s)** in changed files." + echo "" + echo "#### Issues in Changed Files" + echo '```' + grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null | grep -E "(^warning|^error|^ *-->)" | head -50 || echo "See artifact for details" + echo '```' + echo "" + echo "> Download the **clippy-findings** artifact for full details." + else + echo "> :white_check_mark: **Check passed** — no clippy issues in changed files." + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload clippy log + if: always() + uses: actions/upload-artifact@v4 + with: + name: clippy-findings + path: clippy_raw.log + if-no-files-found: ignore + retention-days: 7 + + - name: Fail check if clippy issues found in changed files + if: steps.findings.outputs.blocking_total != '0' && steps.findings.outputs.blocking_total != '' + run: | + echo "::error::Clippy found ${{ steps.findings.outputs.errors_blocking }} error(s) and ${{ steps.findings.outputs.warnings_blocking }} warning(s) in changed files." + echo "Download the clippy-findings artifact to see full details." + exit 1 + + - name: Set conclusion + id: set-conclusion + if: always() + run: | + if [[ "${{ steps.findings.outputs.blocking_total }}" == "0" ]]; then + echo "conclusion=success" >> $GITHUB_OUTPUT + else + echo "conclusion=failure" >> $GITHUB_OUTPUT + fi diff --git a/MODULE.bazel b/MODULE.bazel index db223f162..84c180a2e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -23,6 +23,7 @@ bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.17") bazel_dep(name = "rules_python", version = "1.8.5") bazel_dep(name = "rules_rust", version = "0.68.1-score") +bazel_dep(name = "score_rust_policies", version = "0.0.5") # Cannot be dev-dependency due to being required in "load" statements bazel_dep(name = "score_qnx_unit_tests", version = "0.1.0") From 6dea65f709fe37ded6d4845ab313249ebb5bb201 Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Thu, 11 Jun 2026 15:30:24 +0530 Subject: [PATCH 2/4] Rust::com Updated rust lint yaml file * Auto enable CI Job when changes in rust file or workflow --- .github/workflows/clippy_lint.yml | 114 ++++++++++++++++++------------ 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/.github/workflows/clippy_lint.yml b/.github/workflows/clippy_lint.yml index d3f0bf284..5d01b8b20 100644 --- a/.github/workflows/clippy_lint.yml +++ b/.github/workflows/clippy_lint.yml @@ -13,8 +13,8 @@ # Workflow: Clippy PR check # -# Runs Clippy on pull requests with the 'rust-api' label, pushes to main, and merge groups. -# - For PRs: Only runs when the 'rust-api' label is present +# Runs Clippy on pull requests, pushes to main, and merge groups when Rust files or workflow changes. +# - For PRs: Runs if PR contains changes to .rs files or this workflow file # - Fails if Clippy errors or warnings are found in Rust files changed by the PR. # - Writes a job summary with error/warning counts. # - Uploads clippy log as a downloadable artifact. @@ -23,7 +23,7 @@ name: Clippy Check on: pull_request: - types: [opened, reopened, synchronize, labeled, unlabeled] + types: [opened, reopened, synchronize] push: branches: [main] merge_group: @@ -59,46 +59,61 @@ jobs: precheck: runs-on: ubuntu-24.04 outputs: - should-run: ${{ steps.gate.outputs.should-run }} + should-run: ${{ steps.check-rust-changes.outputs.should-run }} permissions: contents: read pull-requests: read steps: - - name: Evaluate Clippy workflow gate - id: gate + - name: Checkout repository + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Fetch base branch for comparison + if: github.event_name == 'pull_request' + run: | + git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} + + - name: Check for Rust file or workflow changes + id: check-rust-changes env: EVENT_NAME: ${{ github.event_name }} + BASE_REF: ${{ github.base_ref }} run: | - python3 - <<'SCRIPT_END' - import json - import os - - should_run = True - - with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as event_file: - event = json.load(event_file) - - pr = event.get("pull_request") or {} - labels = {label.get("name", "") for label in (pr.get("labels") or [])} - - # For pull requests, only run if rust-api label is present - if os.environ["EVENT_NAME"] == "pull_request": - if "rust-api" not in labels: - should_run = False + should_run='false' - should_run_string = 'true' if should_run else 'false' - - labels_display = ", ".join(sorted(label for label in labels if label)) or "(none)" - print( - "Clippy gate evaluation: " - f"event_name='{os.environ['EVENT_NAME']}', " - f"labels=[{labels_display}], " - f"outputs: should-run={should_run_string}" - ) - - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as github_output: - print(f"should-run={should_run_string}", file=github_output) - SCRIPT_END + # For pull requests and merge groups, check for Rust file or workflow changes + if [[ "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "merge_group" ]]; then + CHANGED_FILES=$(git diff --name-only "origin/${BASE_REF}...HEAD" 2>/dev/null || true) + + CHANGED_RUST=$(echo "$CHANGED_FILES" | grep -E '\.rs$' || true) + CHANGED_WORKFLOW=$(echo "$CHANGED_FILES" | grep -E '^\.github/workflows/clippy_lint\.yml$' || true) + + if [[ -n "$CHANGED_RUST" ]]; then + should_run='true' + echo "Found Rust file changes:" + echo "$CHANGED_RUST" + fi + + if [[ -n "$CHANGED_WORKFLOW" ]]; then + should_run='true' + echo "Found workflow file changes:" + echo "$CHANGED_WORKFLOW" + fi + + if [[ "$should_run" == "false" ]]; then + echo "No Rust or workflow files changed - skipping Clippy check" + fi + elif [[ "$EVENT_NAME" == "push" ]]; then + # For pushes to main, always run + should_run='true' + elif [[ "$EVENT_NAME" == "workflow_call" ]]; then + # For workflow_call, always run (manual or nightly) + should_run='true' + fi + + echo "should-run=${should_run}" >> $GITHUB_OUTPUT + echo "Clippy check: should-run=${should_run}" clippy: needs: precheck @@ -158,9 +173,10 @@ jobs: fi # Extract clippy errors and warnings - # Clippy format: "warning: message" or "error: message" or "error[E0123]: message" - ERRORS=$(grep -c "^error" clippy_raw.log 2>/dev/null || true) - WARNINGS=$(grep -c "^warning" clippy_raw.log 2>/dev/null || true) + ERRORS=$(grep -A 1 -E "^error(\[|:)" clippy_raw.log 2>/dev/null \ + | grep -c -E "^\s+--> .*\.rs:" || true) + WARNINGS=$(grep -A 1 -E "^warning(\[|:)" clippy_raw.log 2>/dev/null \ + | grep -c -E "^\s+--> .*\.rs:" || true) ERRORS=${ERRORS:-0} WARNINGS=${WARNINGS:-0} TOTAL=$((ERRORS + WARNINGS)) @@ -193,13 +209,12 @@ jobs: else PATTERN=$(echo "$CHANGED_RUST" | tr '\n' '|' | sed 's/|$//') echo "File pattern: ${PATTERN}" - # Clippy format: "warning:" followed by " --> file:line:col" on next line - # Use -B 1 to get the line before the file path (the warning/error line) - ERRORS_BLOCKING=$(grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null \ - | grep -c "^error" || true) + # This filters out non-Clippy warnings like "dot utility not found" and summary lines + ERRORS_BLOCKING=$(grep -A 1 -E "^error(\[|:)" clippy_raw.log 2>/dev/null \ + | grep -c -E "^\s+--> (${PATTERN}):" || true) ERRORS_BLOCKING=${ERRORS_BLOCKING:-0} - WARNINGS_BLOCKING=$(grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null \ - | grep -c "^warning" || true) + WARNINGS_BLOCKING=$(grep -A 1 -E "^warning(\[|:)" clippy_raw.log 2>/dev/null \ + | grep -c -E "^\s+--> (${PATTERN}):" || true) WARNINGS_BLOCKING=${WARNINGS_BLOCKING:-0} echo "Blocking errors in changed files: ${ERRORS_BLOCKING}" echo "Blocking warnings in changed files: ${WARNINGS_BLOCKING}" @@ -242,7 +257,16 @@ jobs: echo "" echo "#### Issues in Changed Files" echo '```' - grep -B 1 -E "(${PATTERN})" clippy_raw.log 2>/dev/null | grep -E "(^warning|^error|^ *-->)" | head -50 || echo "See artifact for details" + # Show warning/error message + file location for changed files only + grep -E "^\s+--> (${PATTERN}):" clippy_raw.log 2>/dev/null | while IFS= read -r line; do + # Get the line number of this --> line + line_num=$(grep -n -F "$line" clippy_raw.log | head -1 | cut -d: -f1) + if [[ -n "$line_num" ]]; then + # Show the warning line (1-2 lines before) and the --> line + sed -n "$((line_num-2)),$((line_num))p" clippy_raw.log + echo "" + fi + done | head -100 || echo "See artifact for details" echo '```' echo "" echo "> Download the **clippy-findings** artifact for full details." From 580388c75a7a8f1007c24a399f137ef96397421f Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Thu, 18 Jun 2026 10:50:49 +0530 Subject: [PATCH 3/4] Clippy CI jon workflow updated * Created common action for clang and clippy * Updated clippy config on static config --- .bazelrc | 3 + .github/actions/setup-bazel-lint/action.yml | 50 +++++++++ .github/workflows/clang_tidy.yml | 18 ++-- .github/workflows/clippy_lint.yml | 112 +++++++------------- MODULE.bazel | 3 +- quality/static_analysis/clippy.bazelrc | 18 ++++ 6 files changed, 116 insertions(+), 88 deletions(-) create mode 100644 .github/actions/setup-bazel-lint/action.yml create mode 100644 quality/static_analysis/clippy.bazelrc diff --git a/.bazelrc b/.bazelrc index 2505e4402..18700e92c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -131,3 +131,6 @@ try-import %workspace%/.bazelrc.ai_checker # Enable user-defined configs try-import %workspace%/user.bazelrc + +# Import Clippy linting configuration for Rust +import %workspace%/quality/static_analysis/clippy.bazelrc diff --git a/.github/actions/setup-bazel-lint/action.yml b/.github/actions/setup-bazel-lint/action.yml new file mode 100644 index 000000000..915257702 --- /dev/null +++ b/.github/actions/setup-bazel-lint/action.yml @@ -0,0 +1,50 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +# Composite action: Setup Bazel for Lint +# +# Shared Bazel configuration used by lint workflows (Clippy, Clang-Tidy, etc.) +# to eliminate duplicated setup steps. +# +# The calling workflow is responsible for checking out the repository before +# invoking this action. This separation allows workflows to insert their own +# steps (e.g. change-detection) between checkout and the Bazel setup. + +name: Setup Bazel for Lint +description: > + Configures Bazel with shared caching options. Shared by lint workflows + (Clippy, Clang-Tidy, etc.) to avoid duplicating the same setup steps. + The caller must check out the repository before using this action. + +inputs: + disk-cache: + description: Bazel disk-cache key (e.g. "clippy", "clang_tidy") + required: true + cache-save: + description: > + Save the Bazel cache to the remote store. + Should be true only for pushes to main to avoid cache poisoning. + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Setup Bazel + uses: castler/setup-bazel@cache-optimized + with: + bazelisk-cache: true + disk-cache: ${{ inputs.disk-cache }} + repository-cache: true + cache-optimized: true + cache-save: ${{ inputs.cache-save }} diff --git a/.github/workflows/clang_tidy.yml b/.github/workflows/clang_tidy.yml index 3ae73016c..004ae4c06 100644 --- a/.github/workflows/clang_tidy.yml +++ b/.github/workflows/clang_tidy.yml @@ -72,23 +72,19 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6.0.2 - + with: + fetch-depth: 0 # Full history so git-diff against origin/ works + - name: Setup lint environment + uses: ./.github/actions/setup-bazel-lint + with: + disk-cache: clang_tidy + cache-save: ${{ github.ref == 'refs/heads/main' }} - name: Free Disk Space (Ubuntu) uses: eclipse-score/more-disk-space@v1 with: level: 4 - - - uses: castler/setup-bazel@cache-optimized - with: - bazelisk-cache: true - disk-cache: "clang_tidy" - repository-cache: true - cache-optimized: true - cache-save: ${{ github.ref == 'refs/heads/main' }} - - name: Allow linux-sandbox uses: ./actions/unblock_user_namespace_for_linux_sandbox - - name: Run clang-tidy via Bazel id: run-clang-tidy # continue-on-error so we can collect findings and upload the artifact diff --git a/.github/workflows/clippy_lint.yml b/.github/workflows/clippy_lint.yml index 5d01b8b20..ddd66b217 100644 --- a/.github/workflows/clippy_lint.yml +++ b/.github/workflows/clippy_lint.yml @@ -56,108 +56,65 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: - precheck: + clippy: runs-on: ubuntu-24.04 - outputs: - should-run: ${{ steps.check-rust-changes.outputs.should-run }} permissions: contents: read - pull-requests: read + outputs: + artifact-name: clippy-findings + errors: ${{ steps.findings.outputs.errors }} + warnings: ${{ steps.findings.outputs.warnings }} + conclusion: ${{ steps.set-conclusion.outputs.conclusion }} + steps: - name: Checkout repository uses: actions/checkout@v6.0.2 with: - fetch-depth: 0 - - - name: Fetch base branch for comparison - if: github.event_name == 'pull_request' - run: | - git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} - - - name: Check for Rust file or workflow changes - id: check-rust-changes + fetch-depth: 0 # Full history so git-diff against origin/ works + - name: Check for relevant file changes + id: check-changes env: EVENT_NAME: ${{ github.event_name }} BASE_REF: ${{ github.base_ref }} run: | - should_run='false' - - # For pull requests and merge groups, check for Rust file or workflow changes + should_run='true' # default: always run + + # For PRs and merge groups, skip when no Rust or workflow files changed. + # For push/workflow_call we always run so the job never gets stuck. if [[ "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "merge_group" ]]; then CHANGED_FILES=$(git diff --name-only "origin/${BASE_REF}...HEAD" 2>/dev/null || true) - CHANGED_RUST=$(echo "$CHANGED_FILES" | grep -E '\.rs$' || true) CHANGED_WORKFLOW=$(echo "$CHANGED_FILES" | grep -E '^\.github/workflows/clippy_lint\.yml$' || true) - - if [[ -n "$CHANGED_RUST" ]]; then - should_run='true' - echo "Found Rust file changes:" - echo "$CHANGED_RUST" - fi - - if [[ -n "$CHANGED_WORKFLOW" ]]; then - should_run='true' - echo "Found workflow file changes:" - echo "$CHANGED_WORKFLOW" - fi - - if [[ "$should_run" == "false" ]]; then - echo "No Rust or workflow files changed - skipping Clippy check" + + if [[ -z "$CHANGED_RUST" && -z "$CHANGED_WORKFLOW" ]]; then + should_run='false' + echo "No Rust or workflow files changed — reporting success without running Clippy." + else + [[ -n "$CHANGED_RUST" ]] && echo "Rust file changes detected." + [[ -n "$CHANGED_WORKFLOW" ]] && echo "Workflow file changes detected." fi - elif [[ "$EVENT_NAME" == "push" ]]; then - # For pushes to main, always run - should_run='true' - elif [[ "$EVENT_NAME" == "workflow_call" ]]; then - # For workflow_call, always run (manual or nightly) - should_run='true' fi - - echo "should-run=${should_run}" >> $GITHUB_OUTPUT - echo "Clippy check: should-run=${should_run}" - - clippy: - needs: precheck - if: ${{ needs.precheck.outputs.should-run == 'true' }} - runs-on: ubuntu-24.04 - permissions: - contents: read - pull-requests: read - outputs: - artifact-name: clippy-findings - errors: ${{ steps.findings.outputs.errors }} - warnings: ${{ steps.findings.outputs.warnings }} - conclusion: ${{ steps.set-conclusion.outputs.conclusion }} - steps: - - name: Checkout repository - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 # Fetch full history for accurate diff - - - name: Fetch base branch for comparison - if: github.event_name == 'pull_request' - run: | - git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} + echo "should-run=${should_run}" >> $GITHUB_OUTPUT - - uses: castler/setup-bazel@cache-optimized + - name: Setup lint environment + if: steps.check-changes.outputs.should-run == 'true' + uses: ./.github/actions/setup-bazel-lint with: - bazelisk-cache: true - disk-cache: "clippy" - repository-cache: true - cache-optimized: true + disk-cache: clippy cache-save: ${{ github.ref == 'refs/heads/main' }} - - name: Run Clippy via Bazel id: run-clippy + if: steps.check-changes.outputs.should-run == 'true' continue-on-error: true run: | - bazel build --config=lint //score/mw/com/... \ + bazel build --config=clippy //score/... \ 2>&1 | tee clippy_raw.log # Capture the exit code from bazel (PIPESTATUS[0]), not tee echo "bazel_exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT - - name: Collect findings id: findings + if: steps.check-changes.outputs.should-run == 'true' run: | # Verify clippy_raw.log exists if [[ ! -f clippy_raw.log ]]; then @@ -184,11 +141,11 @@ jobs: echo "Grep results: errors=${ERRORS}, warnings=${WARNINGS}, total=${TOTAL}" # Scope blocking errors to Rust files changed in this PR - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - BASE_REF="origin/${{ github.base_ref }}" + if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then + BASE_REF="origin/${GITHUB_BASE_REF}" CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ | grep -E '\.rs$' || true) - elif [[ "${{ github.event_name }}" == "merge_group" ]]; then + elif [[ "${GITHUB_EVENT_NAME}" == "merge_group" ]]; then # For merge queue, compare against the base BASE_REF="origin/main" CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ @@ -295,7 +252,10 @@ jobs: id: set-conclusion if: always() run: | - if [[ "${{ steps.findings.outputs.blocking_total }}" == "0" ]]; then + # When findings was skipped (no relevant files changed) blocking_total is + # empty — treat that as success so the job never blocks unrelated PRs. + BLOCKING="${{ steps.findings.outputs.blocking_total }}" + if [[ -z "${BLOCKING}" || "${BLOCKING}" == "0" ]]; then echo "conclusion=success" >> $GITHUB_OUTPUT else echo "conclusion=failure" >> $GITHUB_OUTPUT diff --git a/MODULE.bazel b/MODULE.bazel index 84c180a2e..ebd1e351f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -23,7 +23,8 @@ bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.17") bazel_dep(name = "rules_python", version = "1.8.5") bazel_dep(name = "rules_rust", version = "0.68.1-score") -bazel_dep(name = "score_rust_policies", version = "0.0.5") + +bazel_dep(name = "score_rust_policies", version = "0.0.5", dev_dependency = True) # Cannot be dev-dependency due to being required in "load" statements bazel_dep(name = "score_qnx_unit_tests", version = "0.1.0") diff --git a/quality/static_analysis/clippy.bazelrc b/quality/static_analysis/clippy.bazelrc new file mode 100644 index 000000000..ee13188cf --- /dev/null +++ b/quality/static_analysis/clippy.bazelrc @@ -0,0 +1,18 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +# Clippy configuration for Rust linting +# Run Clippy on all Rust targets with: bazel build --config=clippy //... +build:clippy --aspects=@score_rust_policies//clippy:linters.bzl%clippy_strict +build:clippy --output_groups=+rules_lint_human +build:clippy --@aspect_rules_lint//lint:fail_on_violation=true From bff2bcc39736b4a7ce5f380321489ede60120be7 Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Fri, 19 Jun 2026 09:44:27 +0530 Subject: [PATCH 4/4] Duplicate workflow steps moved in action.yml * Duplicate steps from clang,codeql and clippy moved in common place --- .github/actions/setup-bazel-lint/action.yml | 20 ++++++-- .github/workflows/clang_tidy.yml | 6 --- .github/workflows/clippy_lint.yml | 52 ++++++++++----------- .github/workflows/codeql.yml | 13 ++---- 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/.github/actions/setup-bazel-lint/action.yml b/.github/actions/setup-bazel-lint/action.yml index 915257702..dcb97b395 100644 --- a/.github/actions/setup-bazel-lint/action.yml +++ b/.github/actions/setup-bazel-lint/action.yml @@ -13,8 +13,13 @@ # Composite action: Setup Bazel for Lint # -# Shared Bazel configuration used by lint workflows (Clippy, Clang-Tidy, etc.) -# to eliminate duplicated setup steps. +# Shared setup used by lint/analysis workflows (Clippy, Clang-Tidy, CodeQL, etc.) +# to eliminate duplicated steps. +# +# Runs in order: +# 1. Free Disk Space — ensures enough space before Bazel downloads artifacts +# 2. Setup Bazel — configures Bazel with caching +# 3. Allow linux-sandbox — unblocks user namespaces needed by Bazel sandbox # # The calling workflow is responsible for checking out the repository before # invoking this action. This separation allows workflows to insert their own @@ -22,8 +27,9 @@ name: Setup Bazel for Lint description: > - Configures Bazel with shared caching options. Shared by lint workflows - (Clippy, Clang-Tidy, etc.) to avoid duplicating the same setup steps. + Frees disk space, configures Bazel with shared caching options, and unblocks + the linux sandbox. Shared by lint/analysis workflows (Clippy, Clang-Tidy, + CodeQL, etc.) to avoid duplicating the same setup steps. The caller must check out the repository before using this action. inputs: @@ -40,6 +46,10 @@ inputs: runs: using: composite steps: + - name: Free Disk Space (Ubuntu) + uses: eclipse-score/more-disk-space@v1 + with: + level: 4 - name: Setup Bazel uses: castler/setup-bazel@cache-optimized with: @@ -48,3 +58,5 @@ runs: repository-cache: true cache-optimized: true cache-save: ${{ inputs.cache-save }} + - name: Allow linux-sandbox + uses: ./actions/unblock_user_namespace_for_linux_sandbox diff --git a/.github/workflows/clang_tidy.yml b/.github/workflows/clang_tidy.yml index 004ae4c06..c0121d47e 100644 --- a/.github/workflows/clang_tidy.yml +++ b/.github/workflows/clang_tidy.yml @@ -79,12 +79,6 @@ jobs: with: disk-cache: clang_tidy cache-save: ${{ github.ref == 'refs/heads/main' }} - - name: Free Disk Space (Ubuntu) - uses: eclipse-score/more-disk-space@v1 - with: - level: 4 - - name: Allow linux-sandbox - uses: ./actions/unblock_user_namespace_for_linux_sandbox - name: Run clang-tidy via Bazel id: run-clang-tidy # continue-on-error so we can collect findings and upload the artifact diff --git a/.github/workflows/clippy_lint.yml b/.github/workflows/clippy_lint.yml index ddd66b217..c21c51263 100644 --- a/.github/workflows/clippy_lint.yml +++ b/.github/workflows/clippy_lint.yml @@ -13,11 +13,14 @@ # Workflow: Clippy PR check # -# Runs Clippy on pull requests, pushes to main, and merge groups when Rust files or workflow changes. -# - For PRs: Runs if PR contains changes to .rs files or this workflow file +# Runs Clippy on every pull_request, push to main, and merge_group. # - Fails if Clippy errors or warnings are found in Rust files changed by the PR. +# - The same changed-files scope is applied for both pull_request and +# merge_group (gate) so that a passing PR check guarantees a passing gate. # - Writes a job summary with error/warning counts. # - Uploads clippy log as a downloadable artifact. +# +# Cache is saved only on pushes to main. name: Clippy Check @@ -60,6 +63,7 @@ jobs: runs-on: ubuntu-24.04 permissions: contents: read + actions: write # Needed to delete old bazel caches outputs: artifact-name: clippy-findings errors: ${{ steps.findings.outputs.errors }} @@ -106,12 +110,12 @@ jobs: - name: Run Clippy via Bazel id: run-clippy if: steps.check-changes.outputs.should-run == 'true' + # continue-on-error so we can collect findings and upload the artifact + # even when the Bazel invocation exits non-zero. continue-on-error: true run: | bazel build --config=clippy //score/... \ 2>&1 | tee clippy_raw.log - # Capture the exit code from bazel (PIPESTATUS[0]), not tee - echo "bazel_exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT - name: Collect findings id: findings if: steps.check-changes.outputs.should-run == 'true' @@ -140,21 +144,15 @@ jobs: echo "Grep results: errors=${ERRORS}, warnings=${WARNINGS}, total=${TOTAL}" - # Scope blocking errors to Rust files changed in this PR - if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then - BASE_REF="origin/${GITHUB_BASE_REF}" - CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ - | grep -E '\.rs$' || true) - elif [[ "${GITHUB_EVENT_NAME}" == "merge_group" ]]; then - # For merge queue, compare against the base - BASE_REF="origin/main" - CHANGED_RUST=$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null \ - | grep -E '\.rs$' || true) - else - # For pushes to main, check all Rust files changed in the last commit - CHANGED_RUST=$(git diff --name-only HEAD~1 HEAD 2>/dev/null \ - | grep -E '\.rs$' || true) - fi + # Scope blocking issues to Rust files changed in this PR / merge-group + # commit set. The same logic is applied for all event types + # (pull_request, merge_group, push to main) so that a check passing on + # a PR is guaranteed to also pass at gate. + # + # GITHUB_BASE_REF is set by GitHub for both pull_request and + # merge_group events (it is the name of the target branch, e.g. main). + CHANGED_RUST=$(git diff --name-only "origin/${GITHUB_BASE_REF}...HEAD" 2>/dev/null \ + | grep -E '\.rs$' || true) echo "Changed Rust files:" echo "${CHANGED_RUST}" @@ -242,20 +240,22 @@ jobs: retention-days: 7 - name: Fail check if clippy issues found in changed files - if: steps.findings.outputs.blocking_total != '0' && steps.findings.outputs.blocking_total != '' + if: steps.check-changes.outputs.should-run == 'true' && (github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'merge_group') && steps.findings.outputs.blocking_total != '0' run: | - echo "::error::Clippy found ${{ steps.findings.outputs.errors_blocking }} error(s) and ${{ steps.findings.outputs.warnings_blocking }} warning(s) in changed files." - echo "Download the clippy-findings artifact to see full details." + echo "::error::Clippy found ${{ steps.findings.outputs.errors_blocking }} error(s) and ${{ steps.findings.outputs.warnings_blocking }} warning(s) in changed files. See the 'clippy-findings' artifact for details." + echo "" + echo "=== Clippy findings ===" + cat clippy_raw.log || true exit 1 - name: Set conclusion id: set-conclusion if: always() run: | - # When findings was skipped (no relevant files changed) blocking_total is - # empty — treat that as success so the job never blocks unrelated PRs. - BLOCKING="${{ steps.findings.outputs.blocking_total }}" - if [[ -z "${BLOCKING}" || "${BLOCKING}" == "0" ]]; then + # When check-changes skipped the build (no relevant files changed), + # run-clippy.outcome is 'skipped' — treat that as success. + OUTCOME="${{ steps.run-clippy.outcome }}" + if [[ "${OUTCOME}" == "success" || "${OUTCOME}" == "skipped" ]]; then echo "conclusion=success" >> $GITHUB_OUTPUT else echo "conclusion=failure" >> $GITHUB_OUTPUT diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 898beb7bc..dfe38e4f4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,16 +39,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Free Disk Space (Ubuntu) - uses: eclipse-score/more-disk-space@v1 + - name: Setup build environment + uses: ./.github/actions/setup-bazel-lint with: - level: 4 - - - name: Setup Bazel - uses: castler/setup-bazel@cache-optimized - - - name: Allow linux-sandbox - uses: ./actions/unblock_user_namespace_for_linux_sandbox + disk-cache: codeql + cache-save: 'true' # Nightly run — no cache poisoning risk from PRs - name: Create CodeQL database run: |