#!/usr/bin/env pwsh Param( [Parameter(Mandatory=$true)] [ValidateSet('DepsHash', 'Build', 'Test')] [string] $Mode, [Parameter(Mandatory=$true)] [ValidateSet('x86', 'x64')] [string] $BuildArch, [Parameter()] [ValidateSet('All', 'Deps', 'App')] [string] $BuildPart = 'All', [Parameter()] [string] $SourceDir, [Parameter()] [string] $RootDir, [Parameter()] [string] $ScriptBaseUrl, [Parameter()] [switch] $PackDebugSyms ) Set-StrictMode -Version '6.0' $ErrorActionPreference = 'Stop' $PSDefaultParameterValues['*:ErrorAction'] = $ErrorActionPreference $ScriptDir = Split-Path -Path $PSCommandPath -Parent if (-not $RootDir) { $RootDir = (Get-Item $ScriptDir).Root.Name } $CacheDir = Join-Path $RootDir "${BuildArch}-cache" $TempDir = Join-Path $RootDir "${BuildArch}-temp" $PrefixDir = Join-Path $RootDir "${BuildArch}-prefix" function Invoke-NativeCommand() { $Command = $Args[0] $CommandArgs = @() if ($Args.Count -gt 1) { $CommandArgs = $Args[1..($Args.Count - 1)] } Write-Debug "Executing native command: $Command $CommandArgs" & $Command $CommandArgs $Result = $LastExitCode if ($Result -ne 0) { throw "$Command $CommandArgs exited with code $Result." } } function Invoke-Download([string] $Url, [string] $OutFile) { if (-not (Test-Path $OutFile)) { Write-Information "Downloading ${Url} to ${OutFile}" -InformationAction Continue $OldProgressPreference = $ProgressPreference $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri $Url -OutFile $OutFile $ProgressPreference = $OldProgressPreference } } function Invoke-DownloadAndUnpack([string] $Url, [string] $Filename, [string[]] $MoreFlags = @()) { New-Item -Path $CacheDir -ItemType Directory -ErrorAction Ignore | Out-Null $ArchivePath = Join-Path $CacheDir $Filename Invoke-Download $Url $ArchivePath if ($ArchivePath -match '^(.+)(\.(gz|bz2|xz))$') { $SubArchivePath = $Matches[1] if (-not (Test-Path $SubArchivePath)) { Write-Information "Unpacking archive ${ArchivePath} to ${CacheDir}" -InformationAction Continue Invoke-NativeCommand 7z x -y $ArchivePath "-o${CacheDir}" | Out-Host } $ArchivePath = $SubArchivePath } if ($ArchivePath -match '^(.+)(\.(tar|zip))$') { $FinalArchivePath = Join-Path $TempDir (Split-Path -Path $Matches[1] -Leaf) New-Item -Path $TempDir -ItemType Directory -ErrorAction Ignore | Out-Null Push-Location -Path $TempDir Write-Information "Unpacking archive ${ArchivePath} to ${TempDir}" -InformationAction Continue Invoke-NativeCommand 7z x -y $ArchivePath @MoreFlags | Out-Host $ArchivePath = $FinalArchivePath Pop-Location } else { throw "Archive type is not supported: ${ArchivePath}" } return $ArchivePath } function Invoke-DownloadAndInclude([string] $Uri, [string] $ScriptFile) { Invoke-Download $Uri $ScriptFile . $ScriptFile } function Edit-TextFile([string] $PatchedFile, [string] $MatchPattern, [string] $ReplacementString) { (Get-Content $PatchedFile) -Replace $MatchPattern, $ReplacementString | Out-File "${PatchedFile}_new" -Encoding ascii Move-Item -Path "${PatchedFile}_new" -Destination $PatchedFile -Force } function Invoke-VcEnvCommand() { $VcEnvScript = Join-Path $TempDir vcenv.cmd if (-not (Test-Path $VcEnvScript)) { New-Item -Path $TempDir -ItemType Directory -ErrorAction Ignore | Out-Null Set-Content $VcEnvScript @" @pushd . @call "${VcVarsScript}" ${BuildArch} || exit /b 1 @popd @%* 2>&1 "@ } Invoke-NativeCommand $VcEnvScript @args } function Get-StringHash([string] $String, [string] $HashName = 'sha1') { $StringBuilder = New-Object Text.StringBuilder [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ` ForEach-Object { [void] $StringBuilder.Append($_.ToString('x2')) } return $StringBuilder.ToString() } function Invoke-CMakeBuildAndInstall([string] $SourceDir, [string] $BuildDir, [string[]] $ConfigOptions) { Invoke-VcEnvCommand cmake -S $SourceDir -B $BuildDir -G Ninja @ConfigOptions Invoke-VcEnvCommand cmake --build $BuildDir Invoke-VcEnvCommand cmake --build $BuildDir --target install } function Publish-CTestResults([string] $ReportXmlFilesMask) { if ($env:APPVEYOR_URL) { $CTestToJUnit = New-Object System.Xml.Xsl.XslCompiledTransform $CTestToJUnit.Load("https://raw.githubusercontent.com/rpavlik/jenkins-ctest-plugin/master/ctest-to-junit.xsl") $WebClient = New-Object System.Net.WebClient foreach ($ReportXmlFile in (Get-ChildItem $ReportXmlFilesMask)) { $CTestToJUnit.Transform($ReportXmlFile.FullName, "$($ReportXmlFile.FullName).junit.xml") $WebClient.UploadFile("${env:APPVEYOR_URL}/api/testresults/junit/${env:APPVEYOR_JOB_ID}", "$($ReportXmlFile.FullName).junit.xml") } } } function Import-Script([string] $Name) { $ScriptFile = Join-Path $ScriptDir "$($Name.ToLower()).ps1" Invoke-DownloadAndInclude "${ScriptBaseUrl}/$($Name.ToLower()).ps1" $ScriptFile Set-Variable -Name "$($Name -replace '\W+', '')ScriptFileHash" -Value (Get-StringHash (Get-Content -Path $ScriptFile)) -Scope Global } function Invoke-Build([string] $Name, [switch] $NoCache = $false, [switch] $CacheArchiveNameOnly = $false, [string[]] $MoreArguments = @()) { Import-Script "Build-${Name}" if (-not $NoCache -or $CacheArchiveNameOnly) { $BuildScriptFileHash = Get-Variable -Name "Build${Name}ScriptFileHash" -ValueOnly $BuildVersion = Get-Variable -Name "${Name}Version" -ValueOnly $BuildDeps = Get-Variable -Name "${Name}Deps" -ValueOnly $CacheArchiveDeps = @( "$($Name.ToLower()):${BuildScriptFileHash}" "toolchain:${ToolchainScriptFileHash}" ) $BuildDeps | ` ForEach-Object { $CacheArchiveDeps += "$($_.ToLower()):$(Get-Variable -Name "Build$($_)ScriptFileHash" -ValueOnly)" } $CacheArchiveDepsString = ($CacheArchiveDeps | Sort-Object) -join ' ' $CacheArchiveDepsHash = Get-StringHash $CacheArchiveDepsString $CacheArchiveName = "$($Name.ToLower())_${BuildVersion}-vs_${VsVersion}-${BuildArch}-${CacheArchiveDepsHash}.7z" $CacheArchive = Join-Path $CacheDir $CacheArchiveName Write-Information "Cache archive: ${CacheArchiveName} (${CacheArchiveDepsString})" -InformationAction Continue } else { $CacheArchiveName = $null $CacheArchive = $null } if ($CacheArchiveNameOnly) { return $CacheArchiveName } while (-not $CacheArchive -or -not (Test-Path $CacheArchive)) { if ($CacheArchive -and $env:AWS_S3_BUCKET_NAME) { try { Write-Information "Downloading cache archive ${CacheArchiveName} from S3" -InformationAction Continue Invoke-NativeCommand aws s3 cp "s3://${env:AWS_S3_BUCKET_NAME}/windows/${CacheArchiveName}" $CacheArchive break } catch { Write-Warning "Cache archive ${CacheArchiveName} download from S3 failed" } } $Builder = (Get-Command "Build-${Name}" -CommandType Function).ScriptBlock $TempPrefixDir = Join-Path $TempDir "${Name}-Prefix" $BuilderArguments = @($TempPrefixDir, $BuildArch, $PrefixDir) + $MoreArguments Write-Information "Running build for ${Name}" -InformationAction Continue Invoke-Command -ScriptBlock $Builder -ArgumentList $BuilderArguments if ($CacheArchive) { Write-Information "Packing cache archive ${CacheArchive} from ${TempPrefixDir}" -InformationAction Continue Invoke-NativeCommand cmake -E chdir $TempPrefixDir 7z a -t7z -m0=lzma -mx=9 -y $CacheArchive if ($CacheArchive -and $env:AWS_S3_BUCKET_NAME) { try { Write-Information "Uploading cache archive ${CacheArchiveName} to S3" -InformationAction Continue Invoke-NativeCommand aws s3 cp $CacheArchive "s3://${env:AWS_S3_BUCKET_NAME}/windows/${CacheArchiveName}" ` --metadata "build-deps=${CacheArchiveDepsString}" } catch { Write-Warning "Cache archive ${CacheArchiveName} upload to S3 failed" } } } break } if ($CacheArchive) { Write-Information "Unpacking cache archive ${CacheArchive} to ${PrefixDir}" -InformationAction Continue Invoke-NativeCommand 7z x -y $CacheArchive "-o${PrefixDir}" } } function Invoke-Test([string] $Name, [string[]] $MoreArguments = @()) { Import-Script "Build-${Name}" $Tester = (Get-Command "Test-${Name}" -CommandType Function).ScriptBlock Write-Information "Running test for ${Name}" -InformationAction Continue Invoke-Command -ScriptBlock $Tester -ArgumentList $MoreArguments } [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 if (-not $SourceDir) { $SourceDir = (Get-Item $ScriptDir).Parent.Parent.FullName } if ($Mode -eq 'DepsHash') { Import-Script Toolchain $Names = @( Invoke-Build Expat -CacheArchiveNameOnly Invoke-Build DBus -CacheArchiveNameOnly Invoke-Build Zlib -CacheArchiveNameOnly Invoke-Build OpenSsl -CacheArchiveNameOnly Invoke-Build Curl -CacheArchiveNameOnly Invoke-Build Qt -CacheArchiveNameOnly ) Write-Output (Get-StringHash ($Names -join ':')) } if ($Mode -eq 'Build') { Import-Script Toolchain $env:CFLAGS = $CompilerFlags -join ' ' $env:CXXFLAGS = $CompilerFlags -join ' ' $env:LDFLAGS = $LinkerFlags -join ' ' if (@('All', 'Deps') -contains $BuildPart) { Invoke-Build Expat Invoke-Build DBus Invoke-Build Zlib Invoke-Build OpenSsl Invoke-Build Curl Invoke-Build Qt } if (@('All', 'App') -contains $BuildPart) { Invoke-Build Transmission -NoCache -MoreArguments @($SourceDir, $SourceDir, $PackDebugSyms.IsPresent) } } if ($Mode -eq 'Test') { Invoke-Test Transmission -MoreArguments @($PrefixDir, $SourceDir) }