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 # Allows SonarCloud to decorate PRs with analysis results security-events: write # Required for CodeQL analysis and uploading SARIF results env: DOTNET_VERSION: '8.x' DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: Code_Coverage: timeout-minutes: 30 runs-on: windows-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: Cache dotnet tools uses: actions/cache@v4 with: path: ~/.dotnet/tools key: ${{ runner.os }}-dotnet-tools-${{ hashFiles('**/*.csproj') }} - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: 17 distribution: 'zulu' - name: Cache SonarCloud scanner id: cache-sonar-scanner uses: actions/cache@v4 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner restore-keys: ${{ runner.os }}-sonar-scanner - name: Install dotnet tools run: | dotnet tool install JetBrains.dotCover.GlobalTool --global dotnet tool install dotnet-sonarscanner --global dotnet tool install dotnet-coverage --global dotnet tool install --global coverlet.console dotnet tool install --global dotnet-reportgenerator-globaltool dotnet restore - name: Begin SonarCloud Analysis env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | dotnet sonarscanner begin /k:"mihakralj_QuanTAlib" /o:"mihakralj-quantalib" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" ` /d:sonar.solution.file="QuanTAlib.sln" ` /d:sonar.cs.opencover.reportsPaths="**/*cover*.xml" ` /d:sonar.cs.dotcover.reportsPaths="**/dotcover.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.cpd.exclusions="**Tests.cs" ` /d:sonar.scanner.scanAll="false" ` /d:sonar.cs.roslyn.ignoreIssues="false" ` /d:sonar.issue.ignore.multicriteria="e1" ` /d:sonar.issue.ignore.multicriteria.e1.ruleKey="csharpsquid:S1944,csharpsquid:S2053,csharpsquid:S2222,csharpsquid:S2259,csharpsquid:S2583,csharpsquid:S2589,csharpsquid:S3329,csharpsquid:S3655,csharpsquid:S3900,csharpsquid:S3949,csharpsquid:S3966,csharpsquid:S4158,csharpsquid:S4347,csharpsquid:S5773,csharpsquid:S6781" ` /d:sonar.issue.ignore.multicriteria.e1.resourceKey="**/*.cs" ` /d:sonar.verbose="true" - name: Build Projects id: build continue-on-error: true run: | dotnet build --no-restore --configuration Debug dotnet build ./lib/quantalib.csproj --configuration Release --nologo dotnet build ./quantower/Averages/_Averages.csproj --configuration Release --nologo dotnet build ./quantower/Statistics/_Statistics.csproj --configuration Release --nologo dotnet build ./quantower/Volatility/_Volatility.csproj --configuration Release --nologo dotnet build ./SyntheticVendor/SyntheticVendor.csproj --configuration Release --nologo if ($LASTEXITCODE -ne 0) { Write-Error "Build failed" } - name: Check Build Status if: steps.build.outcome == 'failure' run: exit 1 - name: Run Tests with Coverage id: tests continue-on-error: true run: | dotnet test --no-build --configuration Debug /p:CollectCoverage=true /p:CoverletOutputFormat=opencover dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml" dotnet dotcover test Tests/Tests.csproj --dcReportType=HTML --dcoutput=./dotcover.html dotnet dotcover test Tests/Tests.csproj --dcReportType=DetailedXML --dcoutput=./dotcover.xml --verbosity=Detailed dotnet test -p:CollectCoverage=true --collect:"XPlat Code Coverage" --results-directory "./" - name: Generate Coverage Report run: | reportgenerator -reports:*cover*.xml -targetdir:./coverage-report - name: Upload Coverage Reports if: always() uses: actions/upload-artifact@v4 with: name: coverage-reports path: | **/TestResults **/coverage-report **/*cover*.xml **/dotcover.* - name: End SonarCloud Analysis if: always() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" - name: Upload Coverage to Codacy uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: '*cover*.xml' - name: Upload Coverage to Codecov uses: codecov/codecov-action@v4 with: files: 'cover*' verbose: true CodeQL: timeout-minutes: 30 runs-on: ubuntu-latest permissions: security-events: write actions: read contents: read 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: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: 'csharp' queries: security-and-quality config-file: ./.github/codeql/codeql-config.yml tools: latest - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore --configuration Debug - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: output: results upload: true - name: Run Snyk to check for vulnerabilities uses: snyk/actions/dotnet@master continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} LD_PRELOAD: '' # Clear the LD_PRELOAD to avoid CodeQL conflicts with: args: | --file=./lib/quantalib.csproj --severity-threshold=low --detection-depth=4 --package-manager=nuget - name: Run Snyk on Solution uses: snyk/actions/dotnet@master if: always() continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} LD_PRELOAD: '' with: args: | --file=QuanTAlib.sln --all-projects --detection-depth=4 - name: Run Snyk IaC uses: snyk/actions/iac@master continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} LD_PRELOAD: '' with: args: | --severity-threshold=low build_publish: timeout-minutes: 20 needs: [Code_Coverage, CodeQL] if: | success() && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')) || github.event_name == 'workflow_dispatch' 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: Install GitVersion uses: gittools/actions/gitversion/setup@v0 with: versionSpec: '6.x' includePrerelease: true - name: Determine Version id: gitversion uses: gittools/actions/gitversion/execute@v0 with: useConfigFile: true updateAssemblyInfo: true - name: Build projects run: | dotnet build ./lib/quantalib.csproj --configuration Release --nologo \ -p:PackageVersion=${{ steps.gitversion.outputs.MajorMinorPatch }} dotnet build ./quantower/Averages/_Averages.csproj --configuration Release --nologo dotnet build ./quantower/Statistics/_Statistics.csproj --configuration Release --nologo dotnet build ./quantower/Volatility/_Volatility.csproj --configuration Release --nologo dotnet build ./SyntheticVendor/SyntheticVendor.csproj --configuration Release --nologo - name: Create or Update Development Release if: github.ref == 'refs/heads/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/QuanTAlib.dll \ quantower/Averages/bin/Release/Averages.dll \ quantower/Statistics/bin/Release/Statistics.dll \ quantower/Volatility/bin/Release/Volatility.dll \ SyntheticVendor/bin/Release/SyntheticVendor.dll - name: Push prerelease package to myget.org if: github.ref == 'refs/heads/dev' continue-on-error: true id: myget-push 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 ${{ steps.gitversion.outputs.MajorMinorPatch }}" \ --notes "Release notes for version ${{ steps.gitversion.outputs.MajorMinorPatch }}" \ quantower/Averages/bin/Release/Averages.dll \ quantower/Statistics/bin/Release/Statistics.dll \ quantower/Volatility/bin/Release/Volatility.dll \ SyntheticVendor/bin/Release/SyntheticVendor.dll - name: Push release package to nuget.org 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 }}