Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -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
62 changes: 62 additions & 0 deletions .github/actions/setup-bazel-lint/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# *******************************************************************************
# 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 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
# steps (e.g. change-detection) between checkout and the Bazel setup.

name: Setup Bazel for Lint
description: >
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:
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: Free Disk Space (Ubuntu)
uses: eclipse-score/more-disk-space@v1
with:
level: 4
- 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 }}
- name: Allow linux-sandbox
uses: ./actions/unblock_user_namespace_for_linux_sandbox
18 changes: 4 additions & 14 deletions .github/workflows/clang_tidy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2

- name: Free Disk Space (Ubuntu)
uses: eclipse-score/more-disk-space@v1
with:
level: 4

- uses: castler/setup-bazel@cache-optimized
fetch-depth: 0 # Full history so git-diff against origin/<base-ref> works
- name: Setup lint environment
uses: ./.github/actions/setup-bazel-lint
with:
bazelisk-cache: true
disk-cache: "clang_tidy"
repository-cache: true
cache-optimized: true
disk-cache: clang_tidy
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
Expand Down
262 changes: 262 additions & 0 deletions .github/workflows/clippy_lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# *******************************************************************************
# 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 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

on:
pull_request:
types: [opened, reopened, synchronize]
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:
clippy:
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 }}
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 # Full history so git-diff against origin/<base-ref> works
- name: Check for relevant file changes
id: check-changes
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
run: |
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 [[ -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
fi

echo "should-run=${should_run}" >> $GITHUB_OUTPUT

- name: Setup lint environment
if: steps.check-changes.outputs.should-run == 'true'
uses: ./.github/actions/setup-bazel-lint
with:
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 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
- 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
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
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))

echo "Grep results: errors=${ERRORS}, warnings=${WARNINGS}, total=${TOTAL}"

# 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}"

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}"
# 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 -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}"
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 '```'
# 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."
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.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. 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 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
fi
13 changes: 4 additions & 9 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 2 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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", 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")
bazel_dep(name = "score_baselibs", version = "0.2.7")
Expand Down
Loading
Loading