Browse Source

CI: Run PVS-Studio analysis on Windows

derrod 1 year ago
parent
commit
dff4dd9acf

+ 40 - 0
.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}."
+    }
+}

+ 149 - 0
.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

+ 90 - 0
.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)'

+ 10 - 0
.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/*

+ 39 - 0
.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

+ 8 - 0
.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'