From dff4dd9acfe4a7ebee778127c503393a2d91ee17 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 23 May 2024 01:04:16 +0200 Subject: [PATCH] CI: Run PVS-Studio analysis on Windows --- .../windows-analysis/Invoke-External.ps1 | 40 +++++ .github/actions/windows-analysis/Logger.ps1 | 149 ++++++++++++++++++ .github/actions/windows-analysis/action.yaml | 90 +++++++++++ .../actions/windows-analysis/obs.pvsconfig | 10 ++ .github/workflows/analyze-project.yaml | 39 +++++ .github/workflows/scheduled.yaml | 8 + 6 files changed, 336 insertions(+) create mode 100644 .github/actions/windows-analysis/Invoke-External.ps1 create mode 100644 .github/actions/windows-analysis/Logger.ps1 create mode 100644 .github/actions/windows-analysis/action.yaml create mode 100644 .github/actions/windows-analysis/obs.pvsconfig create mode 100644 .github/workflows/analyze-project.yaml diff --git a/.github/actions/windows-analysis/Invoke-External.ps1 b/.github/actions/windows-analysis/Invoke-External.ps1 new file mode 100644 index 000000000..d1bfb7585 --- /dev/null +++ b/.github/actions/windows-analysis/Invoke-External.ps1 @@ -0,0 +1,40 @@ +function Invoke-External { + <# + .SYNOPSIS + Invokes a non-PowerShell command. + .DESCRIPTION + Runs a non-PowerShell command, and captures its return code. + Throws an exception if the command returns non-zero. + .EXAMPLE + Invoke-External 7z x $MyArchive + #> + + if ( $args.Count -eq 0 ) { + throw 'Invoke-External called without arguments.' + } + + if ( ! ( Test-Path function:Log-Information ) ) { + . $PSScriptRoot/Logger.ps1 + } + + $Command = $args[0] + $CommandArgs = @() + + if ( $args.Count -gt 1) { + $CommandArgs = $args[1..($args.Count - 1)] + } + + $_EAP = $ErrorActionPreference + $ErrorActionPreference = "Continue" + + Log-Debug "Invoke-External: ${Command} ${CommandArgs}" + + & $command $commandArgs + $Result = $LASTEXITCODE + + $ErrorActionPreference = $_EAP + + if ( $Result -ne 0 ) { + throw "${Command} ${CommandArgs} exited with non-zero code ${Result}." + } +} diff --git a/.github/actions/windows-analysis/Logger.ps1 b/.github/actions/windows-analysis/Logger.ps1 new file mode 100644 index 000000000..61a81e515 --- /dev/null +++ b/.github/actions/windows-analysis/Logger.ps1 @@ -0,0 +1,149 @@ +function Log-Debug { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + foreach($m in $Message) { + Write-Debug $m + } + } +} + +function Log-Verbose { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + foreach($m in $Message) { + Write-Verbose $m + } + } +} + +function Log-Warning { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + foreach($m in $Message) { + Write-Warning $m + } + } +} + +function Log-Error { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + foreach($m in $Message) { + Write-Error $m + } + } +} + +function Log-Information { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + if ( ! ( $script:Quiet ) ) { + $StageName = $( if ( $script:StageName -ne $null ) { $script:StageName } else { '' }) + $Icon = ' =>' + + foreach($m in $Message) { + Write-Host -NoNewLine -ForegroundColor Blue " ${StageName} $($Icon.PadRight(5)) " + Write-Host "${m}" + } + } + } +} + +function Log-Group { + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + [string[]] $Message + ) + + Process { + if ( $Env:CI -ne $null ) { + if ( $script:LogGroup ) { + Write-Output '::endgroup::' + $script:LogGroup = $false + } + + if ( $Message.count -ge 1 ) { + Write-Output "::group::$($Message -join ' ')" + $script:LogGroup = $true + } + } else { + if ( $Message.count -ge 1 ) { + Log-Information $Message + } + } + } +} + +function Log-Status { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + if ( ! ( $script:Quiet ) ) { + $StageName = $( if ( $StageName -ne $null ) { $StageName } else { '' }) + $Icon = ' >' + + foreach($m in $Message) { + Write-Host -NoNewLine -ForegroundColor Green " ${StageName} $($Icon.PadRight(5)) " + Write-Host "${m}" + } + } + } +} + +function Log-Output { + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string[]] $Message + ) + + Process { + if ( ! ( $script:Quiet ) ) { + $StageName = $( if ( $script:StageName -ne $null ) { $script:StageName } else { '' }) + $Icon = '' + + foreach($m in $Message) { + Write-Output " ${StageName} $($Icon.PadRight(5)) ${m}" + } + } + } +} + +$Columns = (Get-Host).UI.RawUI.WindowSize.Width - 5 diff --git a/.github/actions/windows-analysis/action.yaml b/.github/actions/windows-analysis/action.yaml new file mode 100644 index 000000000..1ac0ea8bc --- /dev/null +++ b/.github/actions/windows-analysis/action.yaml @@ -0,0 +1,90 @@ +name: Run PVS-Studio Analysis +inputs: + pvsUsername: + description: PVS-Studio License Username + required: true + pvsKey: + description: PVS-Studio License Key + required: true + target: + description: Build Target + required: true + config: + description: Build Configuration + required: true +runs: + using: composite + steps: + - name: Setup PVS-Studio + shell: pwsh + run: | + choco install pvs-studio --version=7.30.81185.980 -y --no-progress + + - name: Activate PVS-Studio + shell: pwsh + run: | + . ${env:GITHUB_ACTION_PATH}\Invoke-External.ps1 + Invoke-External "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" credentials -u ${{ inputs.pvsUsername }} -n ${{ inputs.pvsKey }} + + - name: Run PVS-Studio Analysis + shell: pwsh + run: | + [flags()] Enum PVSErrorCodes { + Success = 0 + AnalyzerCrash = 1 + GenericError = 2 + InvalidCommandLine = 4 + FileNotFound = 8 + ConfigurationNotFound = 16 + InvalidProject = 32 + InvalidExtension = 64 + LicenseInvalid = 128 + CodeErrorsFound = 256 + SuppressionFailed = 512 + LicenseExpiringSoon = 1024 + } + + $pvsParams = @( + "--progress" + "--disableLicenseExpirationCheck" + "-p", "${{ inputs.target }}" + "-c", "${{ inputs.config }}" + "-t", "${{ github.workspace }}\build_x64\obs-studio.sln" + "-o", "${{ github.workspace }}\analysis.plog" + "-C", "${env:GITHUB_ACTION_PATH}\obs.pvsconfig" + ) + & "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" @pvsParams + + # Success and CodeErrorsFound are fine as error codes, we only care if it is anything but those + $pvs_result = $LASTEXITCODE -band (-bnot [PVSErrorCodes]::CodeErrorsFound) + if ($pvs_result -ne 0) { + Write-Output "PVS-Studio Errors: $([PVSErrorCodes]$pvs_result)" + } + exit $pvs_result + + - name: Convert Analysis to SARIF + shell: pwsh + run: | + . ${env:GITHUB_ACTION_PATH}\Invoke-External.ps1 + + $conversionParams = @( + "-a", "GA:1,2", + "-d", "V1042,Renew" + "-t", "Sarif" + "${{ github.workspace }}\analysis.plog" + ) + Invoke-External "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" @conversionParams + + - name: Upload PVS-Studio Logs + uses: actions/upload-artifact@v4 + with: + name: 'pvs-analysis-log' + path: | + ${{ github.workspace }}/analysis.plog + ${{ github.workspace }}/analysis.plog.sarif + + - name: Upload PVS-Studio Report + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: "${{ github.workspace }}/analysis.plog.sarif" + category: 'PVS-Studio (Windows)' diff --git a/.github/actions/windows-analysis/obs.pvsconfig b/.github/actions/windows-analysis/obs.pvsconfig new file mode 100644 index 000000000..0c525ec5a --- /dev/null +++ b/.github/actions/windows-analysis/obs.pvsconfig @@ -0,0 +1,10 @@ +//V_EXCLUDE_PATH */.deps/* +//V_EXCLUDE_PATH */blake2/* +//V_EXCLUDE_PATH */json11/* +//V_EXCLUDE_PATH */simde/* +//V_EXCLUDE_PATH */w32-pthreads/* +//V_EXCLUDE_PATH */plugins/obs-browser/* +//V_EXCLUDE_PATH */plugins/obs-outputs/ftl-sdk/* +//V_EXCLUDE_PATH */plugins/obs-websocket/* +//V_EXCLUDE_PATH */plugins/win-dshow/libdshowcapture/* +//V_EXCLUDE_PATH *_autogen/* diff --git a/.github/workflows/analyze-project.yaml b/.github/workflows/analyze-project.yaml new file mode 100644 index 000000000..4eb7ae92d --- /dev/null +++ b/.github/workflows/analyze-project.yaml @@ -0,0 +1,39 @@ +name: Analyze Project +on: + workflow_call: +jobs: + windows: + name: Windows 🪟 (PVS-Studio) + runs-on: windows-2022 + defaults: + run: + shell: pwsh + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Build OBS Studio 🧱 + uses: ./.github/actions/build-obs + env: + TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }} + TWITCH_HASH: ${{ secrets.TWITCH_HASH }} + RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }} + RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }} + YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }} + YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }} + YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }} + YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }} + GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }} + with: + target: x64 + config: Debug + + - name: Run PVS-Studio Analysis + uses: ./.github/actions/windows-analysis + with: + pvsUsername: ${{ secrets.PVS_NAME }} + pvsKey: ${{ secrets.PVS_KEY }} + target: x64 + config: Debug diff --git a/.github/workflows/scheduled.yaml b/.github/workflows/scheduled.yaml index 84c247bac..68ecac274 100644 --- a/.github/workflows/scheduled.yaml +++ b/.github/workflows/scheduled.yaml @@ -88,6 +88,14 @@ jobs: needs: cache-cleanup secrets: inherit + analyze-project: + name: Analyze 🔬 + uses: ./.github/workflows/analyze-project.yaml + needs: cache-cleanup + secrets: inherit + permissions: + security-events: write + upload-language-files: name: Upload Language Files 🌐 if: github.repository_owner == 'obsproject' && github.ref_name == 'master'