name: Publish Workflow on: push: paths-ignore: - '**.md' - 'docs/**' - '.gitignore' - 'LICENSE' pull_request: paths-ignore: - '**.md' - 'docs/**' - '.gitignore' - 'LICENSE' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: write pull-requests: read security-events: write checks: write actions: read env: DOTNET_VERSION: '10.x' DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: # ============================================================================== # 1. Build & Test (Generates Coverage) # ============================================================================== Build_Test_Coverage: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Cache NuGet packages uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} restore-keys: ${{ runner.os }}-nuget- - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore --configuration Debug - name: Run Tests run: | dotnet test --no-build --configuration Debug \ --collect:"XPlat Code Coverage" \ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover - name: Collect Coverage Files run: | mkdir -p coverage count=1 while read -r file; do dest="coverage/coverage_${count}.opencover.xml" cp "$file" "$dest" # Sanitize paths for Qodana (convert absolute to relative) # This strips the current working directory from the paths in the XML sed -i "s|$(pwd)/||g" "$dest" count=$((count+1)) done < <(find . -name "coverage.opencover.xml" -type f) - name: Upload Coverage Artifacts uses: actions/upload-artifact@v4 with: name: coverage-reports path: coverage/ # ============================================================================== # 2. SonarCloud Analysis # ============================================================================== Sonar_Analysis: needs: Build_Test_Coverage runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: 17 distribution: 'zulu' - name: Cache NuGet packages uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} restore-keys: ${{ runner.os }}-nuget- - name: Download Coverage Reports uses: actions/download-artifact@v4 with: name: coverage-reports path: coverage - name: Install SonarScanner run: | dotnet tool install --global dotnet-sonarscanner --version 9.0.0 echo "$HOME/.dotnet/tools" >> $GITHUB_PATH - name: Run SonarCloud Analysis env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: bash run: | set -e # Fail immediately if any command exits with a non-zero status if [ -z "$SONAR_TOKEN" ]; then echo "WARNING: SONAR_TOKEN is not set. Skipping analysis." exit 0 fi echo "SONAR_TOKEN is set (Length: ${#SONAR_TOKEN})" dotnet restore # Added verbose logging to debug 9.0.0 auth issues # Reverted to sonar.token since the token has been updated # Removed quotes from /k and /o to ensure they are parsed correctly dotnet sonarscanner begin /k:mihakralj_QuanTAlib /o:mihakralj-quantalib \ /d:sonar.token="$SONAR_TOKEN" \ /d:sonar.host.url="https://sonarcloud.io" \ /d:sonar.cs.opencover.reportsPaths="coverage/*.xml" \ /d:sonar.coverage.exclusions="**Tests.cs,**/*.md,**/*.html,**/*.css,**/docs/**/*,**/archive/**/*,**/notebooks/**/*,**/obj/**/*,**/bin/**/*" \ /d:sonar.exclusions="**/TestResults/**/*,**/bin/**/*,**/obj/**/*,**/*.html,**/coverage/**/*,**/CoverageReport/**/*,**/*.md,**/*.css,**/docs/**/*,**/archive/**/*,**/notebooks/**/*" \ /d:sonar.test.exclusions="**Tests.cs,**/obj/**/*,**/bin/**/*" \ /d:sonar.scanner.scanAll="false" dotnet build --no-restore --configuration Debug dotnet sonarscanner end /d:sonar.token="$SONAR_TOKEN" # ============================================================================== # 3. Qodana Scan # ============================================================================== Qodana_Scan: needs: Build_Test_Coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download Coverage Reports uses: actions/download-artifact@v4 with: name: coverage-reports path: .qodana/code-coverage - name: Qodana Scan uses: JetBrains/qodana-action@v2024.3.4 env: QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} with: args: --coverage-dir,.qodana/code-coverage # ============================================================================== # 5. Codacy Upload # ============================================================================== Codacy_Upload: needs: Build_Test_Coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download Coverage Reports uses: actions/download-artifact@v4 with: name: coverage-reports path: coverage - name: Upload to Codacy env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} run: | if [ -z "$CODACY_PROJECT_TOKEN" ]; then echo "CODACY_PROJECT_TOKEN is not set. Skipping upload." exit 0 fi # Download the reporter script curl -Ls https://coverage.codacy.com/get.sh -o codacy.sh chmod +x codacy.sh # Upload each file for file in coverage/*.xml; do echo "Uploading $file..." ./codacy.sh report -r "$file" --partial done # Finalize ./codacy.sh final continue-on-error: true # ============================================================================== # 6. Snyk Scan # ============================================================================== Snyk_Scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Install Snyk CLI uses: snyk/actions/setup@806182742461562b67788a64410098c9d9b96adb # master - name: Restore dependencies run: dotnet restore - name: Run Snyk on Solution continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} run: snyk test --all-projects # ============================================================================== # 7. CodeQL Analysis # ============================================================================== CodeQL_Analysis: runs-on: ubuntu-latest permissions: security-events: write actions: read contents: read steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: 'csharp' queries: security-and-quality config-file: ./.github/codeql/codeql-config.yml - name: Build run: dotnet build --configuration Debug - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: output: results upload: true # ============================================================================== # 8. Publish Package # ============================================================================== Publish_Package: needs: [Build_Test_Coverage, Sonar_Analysis, Qodana_Scan, Codacy_Upload, Snyk_Scan, CodeQL_Analysis] if: | success() && ( (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || endsWith(github.ref, '-dev'))) || github.event_name == 'workflow_dispatch' ) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Install GitVersion uses: gittools/actions/gitversion/setup@v4.2.0 with: versionSpec: '6.x' includePrerelease: true - name: Determine Version id: gitversion uses: gittools/actions/gitversion/execute@v4.2.0 with: updateAssemblyInfo: false - name: Build & Pack run: | dotnet restore dotnet build ./lib/quantalib.csproj --configuration Release --nologo dotnet build ./quantower/Momentum.csproj --configuration Release --nologo dotnet build ./quantower/Trends.csproj --configuration Release --nologo dotnet build ./quantower/Volume.csproj --configuration Release --nologo - name: Create Development Release if: github.ref == 'refs/heads/dev' || endsWith(github.ref, '-dev') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release delete development --yes || true gh release create development \ --title "Development Build" \ --notes "Latest development build from commit ${{ github.sha }}" \ --prerelease \ --target ${{ github.sha }} \ lib/bin/Release/net10.0/QuanTAlib.dll \ quantower/bin/Release/Momentum.dll \ quantower/bin/Release/Trends.dll \ quantower/bin/Release/Volume.dll - name: Push to MyGet if: github.ref == 'refs/heads/dev' || endsWith(github.ref, '-dev') continue-on-error: true run: | dotnet nuget push 'lib/bin/Release/QuanTAlib.*.nupkg' \ --source https://www.myget.org/F/quantalib/api/v3/index.json \ --force-english-output \ --api-key ${{ secrets.MYGET_DEPLOY_KEY_QUANTALIB }} - name: Create GitHub Release if: github.ref == 'refs/heads/main' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release create v${{ steps.gitversion.outputs.MajorMinorPatch }} \ --title "Release from commit ${{ steps.gitversion.outputs.MajorMinorPatch }}" \ --notes "Release notes for this version." \ quantower/bin/Release/Momentum.dll \ quantower/bin/Release/Trends.dll \ quantower/bin/Release/Volume.dll - name: Push to NuGet if: ${{ github.ref == 'refs/heads/main' }} run: | dotnet nuget push 'lib/bin/Release/QuanTAlib.*.nupkg' \ --source https://api.nuget.org/v3/index.json \ --skip-duplicate \ --api-key ${{ secrets.NUGET_DEPLOY_KEY_QUANTLIB }}