Files
OpenMQTTGateway/scripts/ci_qa.sh
Alessandro Staniscia 134c03362c [CI] Refactor GitHub Actions workflows for build, documentation, and linting (#2260)
* Refactor GitHub Actions workflows for build, documentation, and linting

- Consolidated build logic into reusable workflows (`task-build.yml` and `task-docs.yml`) to reduce duplication across multiple workflows.
- Introduced `environments.json` to centralize the list of PlatformIO build environments, improving maintainability and clarity.
- Updated `build.yml` and `build_and_docs_to_dev.yml` to utilize the new reusable workflows and environment definitions.
- Enhanced `release.yml` to streamline the release process and integrate documentation generation.
- Created reusable linting workflow (`task-lint.yml`) to standardize code formatting checks across the repository.
- Simplified manual documentation workflow by leveraging the new reusable documentation workflow.
- Improved artifact management and retention policies across workflows.
- Updated dependencies and versions in workflows to ensure compatibility and performance.

* CI/CD pipeline agnostic of Workflow Engine and integrated on github actions

- Implemented ci.sh for orchestrating the complete build pipeline.
- Created ci_00_config.sh for centralized configuration of build scripts.
- Created ci_build_firmware.sh for building firmware for specified PlatformIO environments.
- Created ci_prepare_artifacts.sh for preparing firmware artifacts for upload or deployment.
- Created ci_set_version.sh for updating version tags in firmware configuration files.
- Created ci_build.sh to orchestrate the complete build pipeline.
- Created ci_qa.sh for code linting and formatting checks using clang-format.
- Created ci_site.sh for building and deploying VuePress documentation with version management.
- Implemented checks for required tools and dependencies in the new scripts.
- Updated common_wu.py, compressFirmware.py, gen_wu.py, generate_board_docs.py, and prepare_deploy.sh with descriptive comments.

Refactor CI/CD scripts for improved modularity and clarity

- Consolidated build steps in task-build.yml to utilize ci.sh for version tagging, building, and artifact preparation.
- Updated task-lint.yml to use ci.sh for code formatting checks instead of ci_qa.sh.
- Enhanced CI_SCRIPTS.md documentation to reflect changes in script usage, command structure, and output organization.
- Improved internal scripts for better error handling and logging.
- Streamlined the output structure for build artifacts and documentation.
2026-02-15 14:58:58 -06:00

410 lines
11 KiB
Bash
Executable File

#!/bin/bash
# CI/CD Quality Assurance (QA) - Code Linting and Formatting
# Checks and fixes code formatting using clang-format
# Usage: ./scripts/ci_qa.sh [OPTIONS]
set -euo pipefail
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Load shared configuration
if [[ -f "${SCRIPT_DIR}/ci_00_config.sh" ]]; then
source "${SCRIPT_DIR}/ci_00_config.sh"
else
echo "ERROR: ci_00_config.sh not found" >&2
exit 1
fi
# Default values
CHECK_MODE=true
FIX_MODE=false
FORMAT_ONLY=false
SOURCE_DIR="main"
EXTENSIONS="h,ino,cpp"
CLANG_FORMAT_VERSION="9"
VERBOSE=false
# Function to check if clang-format is available
check_clang_format() {
local version="$1"
local cmd="clang-format-${version}"
if command -v "$cmd" >/dev/null 2>&1; then
echo "$cmd"
return 0
fi
# Try without version suffix
if command -v clang-format >/dev/null 2>&1; then
echo "clang-format"
return 0
fi
return 1
}
# Function to find files to check
find_files() {
local source="$1"
local extensions="$2"
if [[ ! -d "${PROJECT_ROOT}/${source}" ]]; then
return 1
fi
local find_patterns=()
IFS=',' read -ra exts <<< "$extensions"
for ext in "${exts[@]}"; do
find_patterns+=(-name "*.${ext}" -o)
done
# Remove last -o
unset 'find_patterns[-1]'
local files
files=$(find "${PROJECT_ROOT}/${source}" -type f \( "${find_patterns[@]}" \) 2>/dev/null || true)
if [[ -z "$files" ]]; then
return 1
fi
echo "$files"
}
# Function to check formatting
check_formatting() {
local clang_format_cmd="$1"
local files="$2"
log_info "Checking code formatting..."
local failed_files=()
local checked_count=0
local has_issues=false
while IFS= read -r file; do
if [[ -z "$file" ]]; then
continue
fi
checked_count=$((checked_count + 1))
if [[ "$VERBOSE" == true ]]; then
log_info "Checking: $file"
fi
# Check if file needs formatting and capture diff
local diff_output
diff_output=$("$clang_format_cmd" --dry-run --Werror "$file" 2>&1)
local format_result=$?
if [[ $format_result -ne 0 ]]; then
failed_files+=("$file")
has_issues=true
# Show the actual formatting differences
echo ""
log_warn "⚠ Formatting issues in: $file"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Generate and show diff with colors
local actual_diff
actual_diff=$(diff -u "$file" <("$clang_format_cmd" "$file") 2>/dev/null || true)
if [[ -n "$actual_diff" ]]; then
echo "$actual_diff" | head -50 | while IFS= read -r line; do
if [[ "$line" =~ ^-[^-] ]]; then
echo -e "\033[31m$line\033[0m" # Red for removed lines
elif [[ "$line" =~ ^+[^+] ]]; then
echo -e "\033[32m$line\033[0m" # Green for added lines
elif [[ "$line" =~ ^@@ ]]; then
echo -e "\033[36m$line\033[0m" # Cyan for line numbers
else
echo "$line"
fi
done
else
echo "$diff_output"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
if [[ "$VERBOSE" == true ]]; then
log_info " ✓ OK"
fi
fi
done <<< "$files"
echo ""
log_info "Checked ${checked_count} files"
if [[ $has_issues == true ]]; then
echo ""
log_error "Found ${#failed_files[@]} files with formatting issues:"
for file in "${failed_files[@]}"; do
log_error " - $file"
done
echo ""
log_error "To fix these issues automatically, run:"
log_error " $0 --fix"
echo ""
return 1
fi
log_info "✓ All files are properly formatted"
return 0
}
# Function to fix formatting
fix_formatting() {
local clang_format_cmd="$1"
local files="$2"
log_info "Fixing code formatting..."
local fixed_count=0
local total_count=0
while IFS= read -r file; do
if [[ -z "$file" ]]; then
continue
fi
total_count=$((total_count + 1))
if [[ "$VERBOSE" == true ]]; then
log_info "Processing: $file"
fi
# Apply formatting in-place
if "$clang_format_cmd" -i "$file" 2>/dev/null; then
fixed_count=$((fixed_count + 1))
if [[ "$VERBOSE" == true ]]; then
log_info " ✓ Formatted"
fi
else
if [[ "$VERBOSE" == true ]]; then
log_warn " ✗ Failed to format"
fi
fi
done <<< "$files"
echo ""
log_info "Processed ${total_count} files"
log_info "✓ Formatting applied to ${fixed_count} files"
if [[ $fixed_count -gt 0 ]]; then
log_warn ""
log_warn "Files have been modified. Please review and commit the changes:"
log_warn " git diff"
log_warn " git add -u"
log_warn " git commit -m 'style: apply clang-format'"
fi
}
# Function to run all QA checks
run_all_checks() {
log_info "Running all quality assurance checks..."
local all_passed=true
# Format check
log_info "═══ Code Formatting ═══"
if ! run_format_check; then
all_passed=false
fi
echo ""
# Future: Add more checks here
# - cppcheck static analysis
# - code complexity metrics
# - TODO/FIXME detection
# - license header validation
if [[ "$all_passed" == false ]]; then
log_error "Some QA checks failed"
return 1
fi
log_info "✓ All QA checks passed"
return 0
}
# Function to run format check
run_format_check() {
log_info "Checking for clang-format version ${CLANG_FORMAT_VERSION}..."
local clang_format_cmd
clang_format_cmd=$(check_clang_format "$CLANG_FORMAT_VERSION")
if [[ $? -ne 0 ]] || [[ -z "$clang_format_cmd" ]]; then
log_error "clang-format not found"
log_error "Please install clang-format:"
log_error " Ubuntu/Debian: sudo apt-get install clang-format-${CLANG_FORMAT_VERSION}"
log_error " macOS: brew install clang-format"
return 1
fi
if [[ "$clang_format_cmd" == "clang-format-${CLANG_FORMAT_VERSION}" ]]; then
log_info "✓ clang-format-${CLANG_FORMAT_VERSION} found"
else
local installed_version
installed_version=$(clang-format --version | grep -oP '\d+\.\d+' | head -1 || echo "unknown")
log_warn "clang-format-${CLANG_FORMAT_VERSION} not found, using clang-format (version ${installed_version})"
fi
log_info "Finding files in '${SOURCE_DIR}' with extensions: ${EXTENSIONS}"
local files
files=$(find_files "$SOURCE_DIR" "$EXTENSIONS")
if [[ $? -ne 0 ]] || [[ -z "$files" ]]; then
log_error "Source directory not found: ${PROJECT_ROOT}/${SOURCE_DIR}"
return 1
fi
local file_count
file_count=$(echo "$files" | wc -l)
log_info "Found ${file_count} files to check"
if [[ "$FIX_MODE" == true ]]; then
fix_formatting "$clang_format_cmd" "$files"
else
check_formatting "$clang_format_cmd" "$files"
fi
}
# Function to show usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Run quality assurance checks on OpenMQTTGateway code.
Options:
--check Check formatting only (CI mode) [default]
--fix Fix formatting issues automatically
--format Run only format checks
--all Run all QA checks [default]
--source DIR Source directory to check [default: main]
--extensions EXTS File extensions (comma-separated) [default: h,ino,cpp]
--clang-format-version V clang-format version [default: 9]
--verbose Enable verbose output
--help Show this help message
Examples:
# Check formatting (CI mode)
$0 --check
# Fix formatting issues
$0 --fix
# Check specific directory
$0 --check --source lib/LEDManager
# Check with custom extensions
$0 --check --extensions h,cpp
# Verbose output
$0 --check --verbose
EOF
exit 0
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--check)
CHECK_MODE=true
FIX_MODE=false
shift
;;
--fix)
FIX_MODE=true
CHECK_MODE=false
shift
;;
--format)
FORMAT_ONLY=true
shift
;;
--all)
FORMAT_ONLY=false
shift
;;
--source)
SOURCE_DIR="$2"
shift 2
;;
--extensions)
EXTENSIONS="$2"
shift 2
;;
--clang-format-version)
CLANG_FORMAT_VERSION="$2"
shift 2
;;
--verbose)
VERBOSE=true
shift
;;
--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
}
# Main execution
main() {
local start_time
start_time=$(date +%s)
parse_args "$@"
log_info "Starting QA pipeline..."
if [[ "$FIX_MODE" == true ]]; then
log_info "Mode: FIX (will modify files)"
else
log_info "Mode: CHECK (read-only)"
fi
# Run checks
local result=0
if [[ "$FORMAT_ONLY" == true ]]; then
run_format_check || result=$?
else
run_all_checks || result=$?
fi
local end_time
end_time=$(date +%s)
local duration=$((end_time - start_time))
echo ""
echo "╔════════════════════════════════════════╗"
echo "║ QA Pipeline Summary ║"
echo "╚════════════════════════════════════════╝"
echo " Duration: ${duration}s"
if [[ $result -eq 0 ]]; then
echo " Status: SUCCESS ✓"
echo "╚════════════════════════════════════════╝"
return 0
else
echo " Status: FAILED ✗"
echo "╚════════════════════════════════════════╝"
return 1
fi
}
# Execute main function
main "$@"