diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f9ab0875..fc5cb13d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,7 +136,7 @@ Please install conda if you don't have it already. You can install [miniforge](h # create the conda environment (assuming in base `cuopt` directory) # note: cuOpt currently doesn't support `channel_priority: strict`; # use `channel_priority: flexible` instead -conda env create --name cuopt_dev --file conda/environments/all_cuda-130_arch-$(uname -m).yaml +conda env create --name cuopt_dev --file conda/environments/all_cuda-131_arch-$(uname -m).yaml # activate the environment conda activate cuopt_dev ``` diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..ae8d6bd25 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# SonarQube Project Configuration +sonar.projectKey=GPUSW_cuOpt_Nvidia-cuOpt_cuopt +sonar.projectName=NVIDIA cuOpt +sonar.projectVersion=1.0 + +# Source code location +sonar.sources=. diff --git a/sonarqube/README.md b/sonarqube/README.md new file mode 100644 index 000000000..5fc0dc1b6 --- /dev/null +++ b/sonarqube/README.md @@ -0,0 +1,60 @@ +# SonarQube Analysis + +This directory contains the configuration and scripts for running automated SonarQube analysis on cuOpt branches. + +## Files + +- `sonar-branches.txt` - List of branches to analyze (one per line) +- `run-sonar-analysis.sh` - Automated script that clones, builds, and analyzes branches + +## Quick Start + +### 1. Configure Branches + +Edit `sonar-branches.txt` to specify which branches to analyze: + +```bash +# One branch per line +main +release/26.02 + +# Lines starting with # are comments +# Empty lines are ignored +``` + +### 2. Set Required Environment Variable + +The script requires authentication: + +```bash +export SONAR_TOKEN="your_token_here" +``` + +**Note**: Contact the cuOpt team for token details. + +### 3. Run the Analysis + +```bash +cd /path/to/cuopt +./sonarqube/run-sonar-analysis.sh +``` + +## Script Behavior + +The script will automatically: + +1. ✅ Validate branch configuration file exists and has at least one branch +2. ✅ Clone each branch into a fresh temporary directory +3. ✅ Create an isolated conda environment per branch +4. ✅ Build the project using `./build.sh` +5. ✅ Run SonarQube analysis with branch-specific tagging +6. ✅ Clean up temporary files and conda environments +7. ✅ Provide a summary of successful and failed branches + +## Support + +**Contact**: cuOpt team + +For issues with: +- Build failures: See [CONTRIBUTING.md](../CONTRIBUTING.md) +- Script bugs: Report to the cuOpt team diff --git a/sonarqube/run-sonar-analysis.sh b/sonarqube/run-sonar-analysis.sh new file mode 100755 index 000000000..ce2cab79b --- /dev/null +++ b/sonarqube/run-sonar-analysis.sh @@ -0,0 +1,238 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BRANCHES_FILE="$SCRIPT_DIR/sonar-branches.txt" +WORK_DIR="/tmp/sonar-analysis-$(date +%Y%m%d-%H%M%S)" + +# Conda environment file to use for building +# Adjust this path based on your CUDA version and architecture +# Available: all_cuda-129_arch-{x86_64,aarch64}.yaml, all_cuda-131_arch-{x86_64,aarch64}.yaml +ARCH=$(uname -m) +CONDA_ENV_FILE="conda/environments/all_cuda-131_arch-${ARCH}.yaml" + +# SonarQube Configuration +# The token should be set via environment variable SONAR_TOKEN for security +if [ -z "$SONAR_TOKEN" ]; then + echo "ERROR: SONAR_TOKEN environment variable is not set" + echo "Please set it with: export SONAR_TOKEN=your_sonarqube_token" + HAD_FAILURES=1 + exit 1 +fi + +# Repository URL +REPO_URL="git@github.com:NVIDIA/cuopt.git" + +echo "Repository URL: $REPO_URL" +echo "Working directory: $WORK_DIR" + +# Create working directory and logs directory +mkdir -p "$WORK_DIR" +LOG_DIR="$WORK_DIR/logs" +mkdir -p "$LOG_DIR" + +# Persistent log directory for failures +PERSISTENT_LOG_DIR="/var/log/sonarqube/runs" +mkdir -p "$PERSISTENT_LOG_DIR" 2>/dev/null || PERSISTENT_LOG_DIR="$HOME/.sonarqube/logs/runs" +mkdir -p "$PERSISTENT_LOG_DIR" + +# Track if we had failures (for cleanup decision) +HAD_FAILURES=0 + +# Cleanup function +cleanup() { + echo "" + if [ "$HAD_FAILURES" -eq 0 ] && [ ${#failed_branches[@]} -eq 0 ]; then + echo "All branches succeeded - cleaning up working directory: $WORK_DIR" + rm -rf "$WORK_DIR" + else + # Preserve logs on failure + RUN_ID=$(date +%Y%m%d-%H%M%S) + SAVED_LOG_DIR="$PERSISTENT_LOG_DIR/failed-run-$RUN_ID" + echo "Failures detected - preserving logs to: $SAVED_LOG_DIR" + mkdir -p "$SAVED_LOG_DIR" + cp -r "$LOG_DIR"/* "$SAVED_LOG_DIR/" 2>/dev/null || true + echo "Logs saved. Check: $SAVED_LOG_DIR" + echo "Cleaning up working directory: $WORK_DIR" + rm -rf "$WORK_DIR" + fi +} + +# Register cleanup on exit +trap cleanup EXIT + +# Check if branches file exists +if [ ! -f "$BRANCHES_FILE" ]; then + echo "ERROR: Branches file not found: $BRANCHES_FILE" + HAD_FAILURES=1 + exit 1 +fi + +# Read and validate branches +branches=() +while IFS= read -r branch || [ -n "$branch" ]; do + # Skip comments and empty lines + [[ "$branch" =~ ^#.*$ ]] && continue + [[ -z "${branch// }" ]] && continue + + # Trim whitespace and add to array + branch=$(echo "$branch" | xargs) + branches+=("$branch") +done < "$BRANCHES_FILE" + +# Fail if no branches found +if [ ${#branches[@]} -eq 0 ]; then + echo "ERROR: No branches configured in $BRANCHES_FILE" + echo "Please add at least one branch to the file." + HAD_FAILURES=1 + exit 1 +fi + +echo "Found ${#branches[@]} branch(es) to process: ${branches[*]}" +echo "Host: $(hostname)" +echo "Start time: $(date)" + +# Track success/failure +successful_branches=() +failed_branches=() + +# Process each branch +for branch in "${branches[@]}"; do + echo "==========================================" + echo "Processing branch: $branch" + echo "==========================================" + + # Create a safe directory name from branch name + safe_branch_name="${branch//\//_}" + clone_dir="$WORK_DIR/$safe_branch_name" + + # Clone the specific branch + echo "Cloning branch: $branch into $clone_dir" + git clone --single-branch --branch "$branch" --depth 1 "$REPO_URL" "$clone_dir" 2>&1 | tee "$LOG_DIR/clone_${safe_branch_name}.log" + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + echo "ERROR: Failed to clone branch: $branch" + failed_branches+=("$branch (clone failed)") + continue + fi + + # Change to cloned directory + if ! cd "$clone_dir"; then + echo "ERROR: Failed to change directory to: $clone_dir" + failed_branches+=("$branch (cd failed)") + continue + fi + + # Setup conda environment, build, and analyze + echo "Setting up conda environment for: $branch" + + # Create a unique conda environment name for this branch + conda_env_name="cuopt_sonar_${safe_branch_name}" + + # Create conda environment + mamba env create -n "$conda_env_name" -f "$CONDA_ENV_FILE" 2>&1 | tee "$LOG_DIR/conda_create_${safe_branch_name}.log" + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + echo "ERROR: Conda environment creation failed for branch: $branch. Check logs at $LOG_DIR/conda_create_${safe_branch_name}.log" + failed_branches+=("$branch (conda env creation failed)") + cd "$WORK_DIR" || echo "WARNING: Failed to cd to $WORK_DIR" + rm -rf "$clone_dir" + continue + fi + + # Activate conda environment and run build + analysis in a subshell + echo "Building and analyzing branch: $branch in conda environment: $conda_env_name" + + if ! bash -c " + set -e + source \$(conda info --base)/etc/profile.d/conda.sh + conda activate $conda_env_name + + echo 'Conda environment activated: $conda_env_name' + echo 'Python version:' \$(python --version) + + # Build the project + echo 'Building project...' + ./build.sh 2>&1 | tee '$LOG_DIR/build_${safe_branch_name}.log' + if [ \${PIPESTATUS[0]} -ne 0 ]; then + echo 'Build failed' + exit 1 + fi + + # Run SonarQube analysis + # Note: SONAR_TOKEN is read from environment automatically by sonar-scanner + echo 'Running SonarQube analysis...' + sonar-scanner \ + -Dsonar.branch.name='$branch' \ + 2>&1 | tee '$LOG_DIR/sonar_${safe_branch_name}.log' + if [ \${PIPESTATUS[0]} -ne 0 ]; then + echo 'SonarQube analysis failed' + exit 1 + fi + + echo 'Build and analysis completed successfully' + "; then + echo "ERROR: Build or analysis failed for branch: $branch" + if grep -q "Build failed" "$LOG_DIR/build_${safe_branch_name}.log" 2>/dev/null; then + failed_branches+=("$branch (build failed)") + else + failed_branches+=("$branch (sonar analysis failed)") + fi + + # Clean up conda environment + conda env remove -n "$conda_env_name" -y 2>/dev/null || true + cd "$WORK_DIR" || echo "WARNING: Failed to cd to $WORK_DIR" + rm -rf "$clone_dir" + continue + fi + + # Clean up conda environment after successful analysis + echo "Cleaning up conda environment: $conda_env_name" + conda env remove -n "$conda_env_name" -y 2>/dev/null || true + + successful_branches+=("$branch") + echo "✓ Successfully completed analysis for: $branch" + echo "Progress: ${#successful_branches[@]} succeeded, ${#failed_branches[@]} failed out of ${#branches[@]} total" + + # Clean up clone directory after successful analysis + echo "Cleaning up clone directory for: $branch" + cd "$WORK_DIR" || echo "WARNING: Failed to cd to $WORK_DIR" + rm -rf "$clone_dir" +done + +# Final summary +echo "==========================================" +echo "SonarQube Analysis Complete" +echo "==========================================" +echo "Total branches: ${#branches[@]}" +echo "Successful: ${#successful_branches[@]}" +echo "Failed: ${#failed_branches[@]}" +echo "" + +if [ ${#successful_branches[@]} -gt 0 ]; then + echo "✓ Successful branches:" + for branch in "${successful_branches[@]}"; do + echo " - $branch" + done + echo "" +fi + +if [ ${#failed_branches[@]} -gt 0 ]; then + echo "✗ Failed branches:" + for branch in "${failed_branches[@]}"; do + echo " - $branch" + done + echo "" +fi + +echo "End time: $(date)" +echo "==========================================" + +# Exit with error if any branches failed +if [ ${#failed_branches[@]} -gt 0 ]; then + echo "ERROR: ${#failed_branches[@]} branch(es) failed analysis" + HAD_FAILURES=1 + exit 1 +fi + +echo "All branches processed successfully!" +exit 0 diff --git a/sonarqube/sonar-branches.txt b/sonarqube/sonar-branches.txt new file mode 100644 index 000000000..a75ecac67 --- /dev/null +++ b/sonarqube/sonar-branches.txt @@ -0,0 +1,12 @@ +# SonarQube Branch List +# One branch name per line +# Lines starting with # are comments and should be ignored by the cron job +# Empty lines are also ignored + +# Main development branches +main +release/26.02 + +# Add release branches as needed +# release/v1.0 +# release/v2.0