#!/bin/bash # Static Analysis Scanner for QuanTAlib # Runs SonarCloud, Codacy coverage, and Qodana # # Usage: wsl -d Debian -- bash /mnt/z/github/QuanTAlib/.github/scanner.sh # Or from WSL: cd /mnt/z/github/QuanTAlib && ./.github/scanner.sh set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' CYAN='\033[0;36m' YELLOW='\033[1;33m' GRAY='\033[0;90m' NC='\033[0m' log_info() { echo -e "${CYAN}==> $1${NC}"; } log_success() { echo -e "${GREEN}==> $1${NC}"; } log_warn() { echo -e "${YELLOW}WARNING: $1${NC}"; } log_error() { echo -e "${RED}ERROR: $1${NC}"; } log_detail() { echo -e "${GRAY} $1${NC}"; } # Change to project root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" cd "$PROJECT_ROOT" # Set DOTNET_ROOT for tools like dotnet-sonarscanner export DOTNET_ROOT="$HOME/.dotnet" export PATH="$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools" log_info "Working directory: $(pwd)" # Parse arguments SKIP_SONAR=false SKIP_CODACY=false SKIP_QODANA=false SKIP_BUILD=false for arg in "$@"; do case $arg in --skip-sonar) SKIP_SONAR=true ;; --skip-codacy) SKIP_CODACY=true ;; --skip-qodana) SKIP_QODANA=true ;; --skip-build) SKIP_BUILD=true ;; --codacy-only) SKIP_SONAR=true; SKIP_QODANA=true; SKIP_BUILD=true ;; --help) echo "Usage: $0 [options]" echo "Options:" echo " --skip-sonar Skip SonarCloud analysis" echo " --skip-codacy Skip Codacy coverage upload" echo " --skip-qodana Skip Qodana analysis" echo " --skip-build Skip build/test (use existing coverage)" echo " --codacy-only Only upload coverage to Codacy" exit 0 ;; esac done # Load tokens from environment or Windows user environment load_token() { local var_name=$1 local current_value="${!var_name}" if [ -z "$current_value" ]; then # Try to get from Windows environment via PowerShell if command -v powershell.exe &> /dev/null; then current_value=$(powershell.exe -NoProfile -Command "[System.Environment]::GetEnvironmentVariable('$var_name', 'User')" 2>/dev/null | tr -d '\r') fi fi if [ -n "$current_value" ]; then export "$var_name"="$current_value" return 0 fi return 1 } # Load required tokens load_token "SONAR_TOKEN" || true load_token "QODANA_TOKEN" || true load_token "CODACY_PROJECT_TOKEN" || true # Verify tokens if [ -z "$SONAR_TOKEN" ] && [ "$SKIP_SONAR" = false ]; then log_warn "SONAR_TOKEN not set - SonarCloud analysis will be skipped" SKIP_SONAR=true fi if [ -z "$CODACY_PROJECT_TOKEN" ] && [ "$SKIP_CODACY" = false ]; then log_warn "CODACY_PROJECT_TOKEN not set - Codacy upload will be skipped" SKIP_CODACY=true fi if [ -z "$QODANA_TOKEN" ] && [ "$SKIP_QODANA" = false ]; then log_warn "QODANA_TOKEN not set - Qodana analysis will be skipped" SKIP_QODANA=true fi # Coverage directory for Qodana COVERAGE_DIR=".qodana/code-coverage" mkdir -p "$COVERAGE_DIR" # ============================================ # Build and Test with Coverage # ============================================ if [ "$SKIP_BUILD" = false ]; then # Clean any leftover SonarQube state from previous runs rm -rf /tmp/.sonarqube .sonarqube 2>/dev/null || true # Clean obj/bin directories to avoid cross-platform cache issues log_info "Cleaning build artifacts..." find . -type d -name "obj" -exec rm -rf {} + 2>/dev/null || true find . -type d -name "bin" -exec rm -rf {} + 2>/dev/null || true log_info "Building solution..." # Skip GitVersion in WSL builds - it has path issues with Windows mounts dotnet build /p:DisableGitVersionTask=true /p:Version=0.0.0-wsl /p:AssemblyVersion=0.0.0.0 /p:FileVersion=0.0.0.0 log_info "Running tests with coverage..." set +e dotnet test --no-build --collect:"XPlat Code Coverage" \ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover,lcov TEST_EXIT_CODE=$? set -e if [ $TEST_EXIT_CODE -ne 0 ]; then log_error "Tests failed with exit code $TEST_EXIT_CODE. Stopping analysis." exit $TEST_EXIT_CODE fi # Copy coverage files to Qodana directory and convert Windows paths to Linux log_info "Copying coverage files for Qodana..." index=0 while read -r file; do # Convert Windows paths (Z:\github\...) to Linux paths (/mnt/z/github/...) sed -e 's|SF:Z:\\|SF:/mnt/z/|g' \ -e 's|SF:z:\\|SF:/mnt/z/|g' \ -e 's|\\|/|g' \ "$file" > "$COVERAGE_DIR/coverage_$index.info" log_detail "Converted: $file -> coverage_$index.info (Windows→Linux paths)" ((index++)) || true done < <(find . -name "coverage.info" -type f) fi # ============================================ # SonarCloud Analysis (requires dotnet-sonarscanner) # ============================================ if [ "$SKIP_SONAR" = false ]; then if command -v dotnet-sonarscanner &> /dev/null; then log_info "Starting SonarCloud analysis..." # Clean sonarqube temp from previous runs rm -rf /tmp/.sonarqube .sonarqube 2>/dev/null || true # Remove generated AssemblyInfo files that may conflict with fresh build find . -name "*.AssemblyInfo.cs" -path "*/obj/*" -delete 2>/dev/null || true find . -name "*.AssemblyInfoInputs.cache" -path "*/obj/*" -delete 2>/dev/null || true dotnet-sonarscanner begin \ /o:"mihakralj-quantalib" \ /k:"mihakralj_QuanTAlib" \ /d:sonar.token="$SONAR_TOKEN" \ /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" \ /d:sonar.exclusions=".github/**" # Full rebuild with SonarScanner targets (skip GitVersion in WSL) dotnet build --no-incremental /p:DisableGitVersionTask=true /p:Version=0.0.0-wsl /p:AssemblyVersion=0.0.0.0 /p:FileVersion=0.0.0.0 dotnet-sonarscanner end /d:sonar.token="$SONAR_TOKEN" log_success "SonarCloud: https://sonarcloud.io/project/overview?id=mihakralj_QuanTAlib" else log_warn "dotnet-sonarscanner not found - skipping SonarCloud" fi fi # ============================================ # Codacy Coverage Upload # ============================================ if [ "$SKIP_CODACY" = false ]; then log_info "Uploading coverage to Codacy..." # Find the most recent coverage files (one per test project) mapfile -t coverage_files < <(find . -name "coverage.opencover.xml" -type f -printf '%T@ %p\n' | sort -rn | head -2 | cut -d' ' -f2-) if [ ${#coverage_files[@]} -gt 0 ]; then # Download and verify Codacy reporter CODACY_VERSION="14.1.0" CODACY_SHA256="f1db13a9b21a9d161ddfeadf0cf6a65ffb0e9eaae8c314d3d14502946ee08475" CODACY_URL="https://github.com/codacy/codacy-coverage-reporter/releases/download/${CODACY_VERSION}/codacy-coverage-reporter-linux" CODACY_BIN="/tmp/codacy-coverage-reporter" log_info "Downloading Codacy coverage reporter v${CODACY_VERSION}..." if ! curl -fSL -o "$CODACY_BIN" "$CODACY_URL"; then log_error "Failed to download Codacy coverage reporter from $CODACY_URL" rm -f "$CODACY_BIN" exit 1 fi # Verify hash using temporary checksum file CHECKSUM_FILE=$(mktemp) printf "%s %s\n" "$CODACY_SHA256" "$CODACY_BIN" > "$CHECKSUM_FILE" if ! sha256sum -c --status "$CHECKSUM_FILE"; then log_error "Codacy reporter checksum verification failed!" log_error "Expected: $CODACY_SHA256" log_error "File: $CODACY_BIN" rm -f "$CODACY_BIN" "$CHECKSUM_FILE" exit 1 fi rm -f "$CHECKSUM_FILE" chmod +x "$CODACY_BIN" for file in "${coverage_files[@]}"; do log_detail "Uploading: $file" "$CODACY_BIN" report -r "$file" --partial || true done # Send final notification log_detail "Finalizing coverage report..." "$CODACY_BIN" final || true rm -f "$CODACY_BIN" log_success "Codacy: https://app.codacy.com/gh/mihakralj/QuanTAlib/dashboard" else log_warn "No coverage.opencover.xml files found" fi fi # ============================================ # Qodana Analysis # ============================================ if [ "$SKIP_QODANA" = false ]; then if command -v qodana &> /dev/null; then log_info "Starting Qodana analysis..." # Install dependencies required for Qodana (IntelliJ) on minimal Debian # Note: CI environments must run as root or have passwordless sudo configured if ! dpkg -s libfreetype6 fontconfig &> /dev/null; then log_info "Installing missing dependencies (libfreetype6, fontconfig)..." if [ "$EUID" -ne 0 ]; then # Use sudo -n to fail fast if password is required if ! sudo -n apt-get update || ! sudo -n DEBIAN_FRONTEND=noninteractive apt-get install -y libfreetype6 fontconfig; then log_error "Dependency installation failed. Ensure passwordless sudo is configured." exit 1 fi else if ! apt-get update || ! DEBIAN_FRONTEND=noninteractive apt-get install -y libfreetype6 fontconfig; then log_error "Dependency installation failed." exit 1 fi fi fi export CI=true qodana scan --within-docker=false -l qodana-dotnet --coverage-dir "$COVERAGE_DIR" || true log_success "Qodana analysis complete" else log_warn "qodana not found - skipping Qodana analysis" fi fi log_success "Done!"