From 5d746f650e74a2bc52198b5f4f317fedfadb2d5c Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Fri, 23 Jun 2023 17:11:07 +0200 Subject: [PATCH] CI: Add new Windows build scripts --- .github/scripts/.Wingetfile | 2 + .github/scripts/Build-Windows.ps1 | 137 ++++++++++++++++ .github/scripts/Package-Windows.ps1 | 79 ++++++++++ .../scripts/utils.pwsh/Ensure-Location.ps1 | 29 ++++ .../scripts/utils.pwsh/Expand-ArchiveExt.ps1 | 70 ++++++++ .../utils.pwsh/Install-BuildDependencies.ps1 | 70 ++++++++ .../scripts/utils.pwsh/Invoke-External.ps1 | 40 +++++ .github/scripts/utils.pwsh/Logger.ps1 | 149 ++++++++++++++++++ 8 files changed, 576 insertions(+) create mode 100644 .github/scripts/.Wingetfile create mode 100644 .github/scripts/Build-Windows.ps1 create mode 100644 .github/scripts/Package-Windows.ps1 create mode 100644 .github/scripts/utils.pwsh/Ensure-Location.ps1 create mode 100644 .github/scripts/utils.pwsh/Expand-ArchiveExt.ps1 create mode 100644 .github/scripts/utils.pwsh/Install-BuildDependencies.ps1 create mode 100644 .github/scripts/utils.pwsh/Invoke-External.ps1 create mode 100644 .github/scripts/utils.pwsh/Logger.ps1 diff --git a/.github/scripts/.Wingetfile b/.github/scripts/.Wingetfile new file mode 100644 index 000000000..3b8f56963 --- /dev/null +++ b/.github/scripts/.Wingetfile @@ -0,0 +1,2 @@ +package '7zip.7zip', path: '7-zip', bin: '7z' +package 'cmake', path: 'Cmake\bin', bin: 'cmake' diff --git a/.github/scripts/Build-Windows.ps1 b/.github/scripts/Build-Windows.ps1 new file mode 100644 index 000000000..ac3659323 --- /dev/null +++ b/.github/scripts/Build-Windows.ps1 @@ -0,0 +1,137 @@ +[CmdletBinding()] +param( + [ValidateSet('x64')] + [string] $Target = 'x64', + [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')] + [string] $Configuration = 'RelWithDebInfo', + [switch] $SkipAll, + [switch] $SkipBuild, + [switch] $SkipDeps +) + +$ErrorActionPreference = 'Stop' + +if ( $DebugPreference -eq 'Continue' ) { + $VerbosePreference = 'Continue' + $InformationPreference = 'Continue' +} + +if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) { + throw "obs-studio requires a 64-bit system to build and run." +} + +if ( $PSVersionTable.PSVersion -lt '7.2.0' ) { + Write-Warning 'The obs-studio PowerShell build script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6' + exit 2 +} + +function Build { + trap { + Pop-Location -Stack BuildTemp -ErrorAction 'SilentlyContinue' + Write-Error $_ + Log-Group + exit 2 + } + + $ScriptHome = $PSScriptRoot + $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.." + $BuildSpecFile = "${ProjectRoot}/buildspec.json" + + $UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse + + foreach($Utility in $UtilityFunctions) { + Write-Debug "Loading $($Utility.FullName)" + . $Utility.FullName + } + + $BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json + + if ( ! $SkipDeps ) { + Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile" + } + + Push-Location -Stack BuildTemp + if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) { + Ensure-Location $ProjectRoot + + $Preset = "windows-$(if ( $env:CI -ne $null ) { 'ci-' })${Target}" + $CmakeArgs = @( + '--preset', $Preset + ) + + $CmakeBuildArgs = @('--build') + $CmakeInstallArgs = @() + + if ( ( $env:TWITCH_CLIENTID -ne '' ) -and ( $env:TWITCH_HASH -ne '' ) ) { + $CmakeArgs += @( + "-DTWITCH_CLIENTID:STRING=${env:TWITCH_CLIENTID}" + "-DTWITCH_HASH:STRING=${env:TWITCH_HASH}" + ) + } + + if ( ( $env:RESTREAM_CLIENTID -ne '' ) -and ( $env:RESTREAM_HASH -ne '' ) ) { + $CmakeArgs += @( + "-DRESTREAM_CLIENTID:STRING=${env:RESTREAM_CLIENTID}" + "-DRESTREAM_HASH:STRING=${env:RESTREAM_HASH}" + ) + } + + if ( ( $env:YOUTUBE_CLIENTID -ne '' ) -and ( $env:YOUTUBE_CLIENTID_HASH -ne '' ) -and + ( $env:YOUTUBE_SECRET -ne '' ) -and ( $env:YOUTUBE_SECRET_HASH-ne '' ) ) { + $CmakeArgs += @( + "-DYOUTUBE_CLIENTID:STRING=${env:YOUTUBE_CLIENTID}" + "-DYOUTUBE_CLIENTID_HASH:STRING=${env:YOUTUBE_CLIENTID_HASH}" + "-DYOUTUBE_SECRET:STRING=${env:YOUTUBE_SECRET}" + "-DYOUTUBE_SECRET_HASH:STRING=${env:YOUTUBE_SECRET_HASH}" + ) + } + + if ( $env:GPU_PRIORITY_VAL -ne '' ) { + $CmakeArgs += @( + "-DGPU_PRIORITY_VAL:STRING=${env:GPU_PRIORITY_VAL}" + ) + } + + if ( ( $env:CI -ne $null ) -and ( $env:CCACHE_CONFIGPATH -ne $null ) ) { + $CmakeArgs += @( + "-DENABLE_CCACHE:BOOL=TRUE" + ) + } + + if ( $VerbosePreference -eq 'Continue' ) { + $CmakeBuildArgs += ('--verbose') + $CmakeInstallArgs += ('--verbose') + } + + if ( $DebugPreference -eq 'Continue' ) { + $CmakeArgs += ('--debug-output') + } + + $CmakeBuildArgs += @( + '--preset', "windows-${Target}" + '--config', $Configuration + '--parallel' + '--', '/consoleLoggerParameters:Summary', '/noLogo' + ) + + $CmakeInstallArgs += @( + '--install', "build_${Target}" + '--prefix', "${ProjectRoot}/build_${Target}/install" + '--config', $Configuration + ) + + Log-Group "Configuring obs-studio..." + Invoke-External cmake @CmakeArgs + + Log-Group "Building obs-studio..." + Invoke-External cmake @CmakeBuildArgs + } + + Log-Group "Installing obs-studio..." + Invoke-External cmake @CmakeInstallArgs + + Pop-Location -Stack BuildTemp + Log-Group +} + +Build diff --git a/.github/scripts/Package-Windows.ps1 b/.github/scripts/Package-Windows.ps1 new file mode 100644 index 000000000..c26be25dc --- /dev/null +++ b/.github/scripts/Package-Windows.ps1 @@ -0,0 +1,79 @@ +[CmdletBinding()] +param( + [ValidateSet('x64')] + [string] $Target = 'x64', + [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')] + [string] $Configuration = 'RelWithDebInfo', + [switch] $SkipDeps +) + +$ErrorActionPreference = 'Stop' + +if ( $DebugPreference -eq 'Continue' ) { + $VerbosePreference = 'Continue' + $InformationPreference = 'Continue' +} + +if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) { + throw "obs-studio requires a 64-bit system to build and run." +} + +if ( $PSVersionTable.PSVersion -lt '7.2.0' ) { + Write-Warning 'The obs-studio packaging script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6' + exit 2 +} + +function Package { + trap { + Write-Error $_ + exit 2 + } + + $ScriptHome = $PSScriptRoot + $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.." + $BuildSpecFile = "${ProjectRoot}/buildspec.json" + + $UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse + + foreach( $Utility in $UtilityFunctions ) { + Write-Debug "Loading $($Utility.FullName)" + . $Utility.FullName + } + + if ( ! $SkipDeps ) { + Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile" + } + + $GitDescription = Invoke-External git describe --tags --long + $Tokens = ($GitDescription -split '-') + $CommitVersion = $Tokens[0..$($Tokens.Count - 3)] -join '-' + $CommitHash = $($Tokens[-1]).SubString(1) + $CommitDistance = $Tokens[-2] + + if ( $CommitDistance -gt 0 ) { + $OutputName = "obs-studio-${CommitVersion}-${CommitHash}" + } else { + $OutputName = "obs-studio-${CommitVersion}" + } + + $CpackArgs = @( + '-C', "${Configuration}" + ) + + if ( $VerbosePreference -eq 'Continue' ) { + $CpackArgs += ('--verbose') + } + + Log-Group "Packaging obs-studio..." + + Push-Location -Stack PackageTemp "build_${Target}" + + cpack @CpackArgs + + $Package = Get-ChildItem -filter "obs-studio-*-windows-x64.zip" -File + Move-Item -Path $Package -Destination "${OutputName}-windows-x64.zip" + + Pop-Location -Stack PackageTemp +} + +Package diff --git a/.github/scripts/utils.pwsh/Ensure-Location.ps1 b/.github/scripts/utils.pwsh/Ensure-Location.ps1 new file mode 100644 index 000000000..36fb2f7ae --- /dev/null +++ b/.github/scripts/utils.pwsh/Ensure-Location.ps1 @@ -0,0 +1,29 @@ +function Ensure-Location { + <# + .SYNOPSIS + Ensures current location to be set to specified directory. + .DESCRIPTION + If specified directory exists, switch to it. Otherwise create it, + then switch. + .EXAMPLE + Ensure-Location "My-Directory" + Ensure-Location -Path "Path-To-My-Directory" + #> + + param( + [Parameter(Mandatory)] + [string] $Path + ) + + if ( ! ( Test-Path $Path ) ) { + $_Params = @{ + ItemType = "Directory" + Path = ${Path} + ErrorAction = "SilentlyContinue" + } + + New-Item @_Params | Set-Location + } else { + Set-Location -Path ${Path} + } +} diff --git a/.github/scripts/utils.pwsh/Expand-ArchiveExt.ps1 b/.github/scripts/utils.pwsh/Expand-ArchiveExt.ps1 new file mode 100644 index 000000000..13d6e6f74 --- /dev/null +++ b/.github/scripts/utils.pwsh/Expand-ArchiveExt.ps1 @@ -0,0 +1,70 @@ +function Expand-ArchiveExt { + <# + .SYNOPSIS + Expands archive files. + .DESCRIPTION + Allows extraction of zip, 7z, gz, and xz archives. + Requires tar and 7-zip to be available on the system. + Archives ending with .zip but created using LZMA compression are + expanded using 7-zip as a fallback. + .EXAMPLE + Expand-ArchiveExt -Path + Expand-ArchiveExt -Path -DestinationPath + #> + + param( + [Parameter(Mandatory)] + [string] $Path, + [string] $DestinationPath = [System.IO.Path]::GetFileNameWithoutExtension($Path), + [switch] $Force + ) + + switch ( [System.IO.Path]::GetExtension($Path) ) { + .zip { + try { + Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force:$Force + } catch { + if ( Get-Command 7z ) { + Invoke-External 7z x -y $Path "-o${DestinationPath}" + } else { + throw "Fallback utility 7-zip not found. Please install 7-zip first." + } + } + break + } + { ( $_ -eq ".7z" ) -or ( $_ -eq ".exe" ) } { + if ( Get-Command 7z ) { + Invoke-External 7z x -y $Path "-o${DestinationPath}" + } else { + throw "Extraction utility 7-zip not found. Please install 7-zip first." + } + break + } + .gz { + try { + Invoke-External tar -x -o $DestinationPath -f $Path + } catch { + if ( Get-Command 7z ) { + Invoke-External 7z x -y $Path "-o${DestinationPath}" + } else { + throw "Fallback utility 7-zip not found. Please install 7-zip first." + } + } + break + } + .xz { + try { + Invoke-External tar -x -o $DestinationPath -f $Path + } catch { + if ( Get-Command 7z ) { + Invoke-External 7z x -y $Path "-o${DestinationPath}" + } else { + throw "Fallback utility 7-zip not found. Please install 7-zip first." + } + } + } + default { + throw "Unsupported archive extension provided." + } + } +} diff --git a/.github/scripts/utils.pwsh/Install-BuildDependencies.ps1 b/.github/scripts/utils.pwsh/Install-BuildDependencies.ps1 new file mode 100644 index 000000000..d2bcca65e --- /dev/null +++ b/.github/scripts/utils.pwsh/Install-BuildDependencies.ps1 @@ -0,0 +1,70 @@ +function Install-BuildDependencies { + <# + .SYNOPSIS + Installs required build dependencies. + .DESCRIPTION + Additional packages might be needed for successful builds. This module contains additional + dependencies available for installation via winget and, if possible, adds their locations + to the environment path for future invocation. + .EXAMPLE + Install-BuildDependencies + #> + + param( + [string] $WingetFile = "$PSScriptRoot/.Wingetfile" + ) + + if ( ! ( Test-Path function:Log-Warning ) ) { + . $PSScriptRoot/Logger.ps1 + } + + $Prefixes = @{ + 'x64' = ${env:ProgramFiles} + 'x86' = ${env:ProgramFiles(x86)} + 'arm64' = ${env:ProgramFiles(arm)} + } + + $Paths = $env:Path -split [System.IO.Path]::PathSeparator + + $WingetOptions = @('install', '--accept-package-agreements', '--accept-source-agreements') + + if ( $script:Quiet ) { + $WingetOptions += '--silent' + } + + Log-Group 'Check Windows build requirements' + Get-Content $WingetFile | ForEach-Object { + $_, $Package, $_, $Path, $_, $Binary, $_, $Version = $_ -replace ',','' -split " +(?=(?:[^\']*\'[^\']*\')*[^\']*$)" -replace "'",'' + + $Prefixes.GetEnumerator() | ForEach-Object { + $Prefix = $_.value + $FullPath = "${Prefix}\${Path}" + if ( ( Test-Path $FullPath ) -and ! ( $Paths -contains $FullPath ) ) { + $Paths = @($FullPath) + $Paths + $env:Path = $Paths -join [System.IO.Path]::PathSeparator + } + } + + Log-Debug "Checking for command ${Binary}" + $Found = Get-Command -ErrorAction SilentlyContinue $Binary + + if ( $Found ) { + Log-Status "Found dependency ${Binary} as $($Found.Source)" + } else { + Log-Status "Installing package ${Package} $(if ( $Version -ne $null ) { "Version: ${Version}" } )" + + if ( $Version -ne $null ) { + $WingGetOptions += @('--version', ${Version}) + } + + try { + $Params = $WingetOptions + $Package + + winget @Params + } catch { + throw "Error while installing winget package ${Package}: $_" + } + } + } + Log-Group +} diff --git a/.github/scripts/utils.pwsh/Invoke-External.ps1 b/.github/scripts/utils.pwsh/Invoke-External.ps1 new file mode 100644 index 000000000..d1bfb7585 --- /dev/null +++ b/.github/scripts/utils.pwsh/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/scripts/utils.pwsh/Logger.ps1 b/.github/scripts/utils.pwsh/Logger.ps1 new file mode 100644 index 000000000..61a81e515 --- /dev/null +++ b/.github/scripts/utils.pwsh/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