1 #!/usr/bin/env pwsh
2 
3 Param(
4     [Parameter(Mandatory=$true)]
5     [ValidateSet('Build', 'Test')]
6     [string] $Mode,
7 
8     [Parameter(Mandatory=$true)]
9     [ValidateSet('x86', 'x64')]
10     [string] $BuildArch,
11 
12     [Parameter()]
13     [string] $SourceDir,
14 
15     [Parameter()]
16     [string] $RootDir,
17 
18     [Parameter()]
19     [string] $ScriptBaseUrl
20 )
21 
22 Set-StrictMode -Version '3.0'
23 
24 $ErrorActionPreference = 'Stop'
25 $PSDefaultParameterValues['*:ErrorAction'] = $ErrorActionPreference
26 
27 $ScriptDir = Split-Path -Path $PSCommandPath -Parent
28 
29 if (-not $RootDir) {
30     $RootDir = (Get-Item $ScriptDir).Root.Name
31 }
32 
33 $CacheDir = Join-Path $RootDir "${BuildArch}-cache"
34 $TempDir = Join-Path $RootDir "${BuildArch}-temp"
35 $PrefixDir = Join-Path $RootDir "${BuildArch}-prefix"
36 
Invoke-NativeCommand()37 function Invoke-NativeCommand() {
38     $Command = $Args[0]
39     $CommandArgs = @()
40     if ($Args.Count -gt 1) {
41         $CommandArgs = $Args[1..($Args.Count - 1)]
42     }
43 
44     Write-Debug "Executing native command: $Command $CommandArgs"
45     & $Command $CommandArgs
46     $Result = $LastExitCode
47 
48     if ($Result -ne 0) {
49         throw "$Command $CommandArgs exited with code $Result."
50     }
51 }
52 
Invoke-Download([string] $Url, [string] $OutFile)53 function Invoke-Download([string] $Url, [string] $OutFile) {
54     if (-not (Test-Path $OutFile)) {
55         Write-Information "Downloading ${Url} to ${OutFile}" -InformationAction Continue
56         $OldProgressPreference = $ProgressPreference
57         $ProgressPreference = 'SilentlyContinue'
58         Invoke-WebRequest -Uri $Url -OutFile $OutFile
59         $ProgressPreference = $OldProgressPreference
60     }
61 }
62 
Invoke-DownloadAndUnpack([string] $Url, [string] $Filename, [string[]] $MoreFlags = @()63 function Invoke-DownloadAndUnpack([string] $Url, [string] $Filename, [string[]] $MoreFlags = @()) {
64     New-Item -Path $CacheDir -ItemType Directory -ErrorAction Ignore | Out-Null
65     $ArchivePath = Join-Path $CacheDir $Filename
66     Invoke-Download $Url $ArchivePath
67 
68     if ($ArchivePath -match '^(.+)(\.(gz|bz2|xz))$') {
69         $SubArchivePath = $Matches[1]
70         if (-not (Test-Path $SubArchivePath)) {
71             Write-Information "Unpacking archive ${ArchivePath} to ${CacheDir}" -InformationAction Continue
72             Invoke-NativeCommand 7z x -y $ArchivePath "-o${CacheDir}" | Out-Host
73         }
74         $ArchivePath = $SubArchivePath
75     }
76 
77     if ($ArchivePath -match '^(.+)(\.(tar|zip))$') {
78         $FinalArchivePath = Join-Path $TempDir (Split-Path -Path $Matches[1] -Leaf)
79 
80         New-Item -Path $TempDir -ItemType Directory -ErrorAction Ignore | Out-Null
81         Push-Location -Path $TempDir
82 
83         Write-Information "Unpacking archive ${ArchivePath} to ${TempDir}" -InformationAction Continue
84         Invoke-NativeCommand 7z x -y $ArchivePath @MoreFlags | Out-Host
85         $ArchivePath = $FinalArchivePath
86 
87         Pop-Location
88     } else {
89         throw "Archive type is not supported: ${ArchivePath}"
90     }
91 
92     return $ArchivePath
93 }
94 
Invoke-DownloadAndInclude([string] $Uri, [string] $ScriptFile)95 function Invoke-DownloadAndInclude([string] $Uri, [string] $ScriptFile) {
96     Invoke-Download $Uri $ScriptFile
97     . $ScriptFile
98 }
99 
Edit-TextFile([string] $PatchedFile, [string] $MatchPattern, [string] $ReplacementString)100 function Edit-TextFile([string] $PatchedFile, [string] $MatchPattern, [string] $ReplacementString) {
101     (Get-Content $PatchedFile) -Replace $MatchPattern, $ReplacementString | Out-File "${PatchedFile}_new" -Encoding ascii
102     Move-Item -Path "${PatchedFile}_new" -Destination $PatchedFile -Force
103 }
104 
Invoke-VcEnvCommand()105 function Invoke-VcEnvCommand() {
106     $VcEnvScript = Join-Path $TempDir vcenv.cmd
107 
108     if (-not (Test-Path $VcEnvScript)) {
109         New-Item -Path $TempDir -ItemType Directory -ErrorAction Ignore | Out-Null
110         Set-Content $VcEnvScript @"
111             @pushd .
112             @call "${VcVarsScript}" ${BuildArch} || exit /b 1
113             @popd
114             @%* 2>&1
115 "@
116     }
117 
118     Invoke-NativeCommand $VcEnvScript @args
119 }
120 
Get-StringHash([string] $String, [string] $HashName = 'sha1')121 function Get-StringHash([string] $String, [string] $HashName = 'sha1')
122 {
123     $StringBuilder = New-Object Text.StringBuilder
124     [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | `
125         ForEach-Object { [void] $StringBuilder.Append($_.ToString('x2')) }
126     return $StringBuilder.ToString()
127 }
128 
Invoke-CMakeBuildAndInstall([string] $SourceDir, [string] $BuildDir, [string[]] $ConfigOptions)129 function Invoke-CMakeBuildAndInstall([string] $SourceDir, [string] $BuildDir, [string[]] $ConfigOptions) {
130     Invoke-VcEnvCommand cmake -S $SourceDir -B $BuildDir -G Ninja @ConfigOptions
131     Invoke-VcEnvCommand cmake --build $BuildDir
132     Invoke-VcEnvCommand cmake --build $BuildDir --target install
133 }
134 
Publish-CTestResults([string] $ReportXmlFilesMask)135 function Publish-CTestResults([string] $ReportXmlFilesMask) {
136     if ($env:APPVEYOR_URL) {
137         $CTestToJUnit = New-Object System.Xml.Xsl.XslCompiledTransform
138         $CTestToJUnit.Load("https://raw.githubusercontent.com/rpavlik/jenkins-ctest-plugin/master/ctest-to-junit.xsl")
139         $WebClient = New-Object System.Net.WebClient
140         foreach ($ReportXmlFile in (Get-ChildItem $ReportXmlFilesMask)) {
141             $CTestToJUnit.Transform($ReportXmlFile.FullName, "$($ReportXmlFile.FullName).junit.xml")
142             $WebClient.UploadFile("${env:APPVEYOR_URL}/api/testresults/junit/${env:APPVEYOR_JOB_ID}", "$($ReportXmlFile.FullName).junit.xml")
143         }
144     }
145 }
146 
Import-Script([string] $Name)147 function Import-Script([string] $Name) {
148     $ScriptFile = Join-Path $ScriptDir "$($Name.ToLower()).ps1"
149     Invoke-DownloadAndInclude "${ScriptBaseUrl}/$($Name.ToLower()).ps1" $ScriptFile
150     Set-Variable -Name "$($Name -replace '\W+', '')ScriptFileHash" -Value (Get-StringHash (Get-Content -Path $ScriptFile)) -Scope Global
151 }
152 
Invoke-Build([string] $Name, [switch] $NoCache = $false, [string[]] $MoreArguments = @()153 function Invoke-Build([string] $Name, [switch] $NoCache = $false, [string[]] $MoreArguments = @()) {
154     Import-Script "Build-${Name}"
155 
156     if (-not $NoCache) {
157         $BuildScriptFileHash = Get-Variable -Name "Build${Name}ScriptFileHash" -ValueOnly
158         $BuildVersion = Get-Variable -Name "${Name}Version" -ValueOnly
159         $BuildDeps = Get-Variable -Name "${Name}Deps" -ValueOnly
160 
161         $CacheArchiveDeps = @(
162             "$($Name.ToLower()):${BuildScriptFileHash}"
163             "toolchain:${ToolchainScriptFileHash}"
164         )
165         $BuildDeps | `
166             ForEach-Object { $CacheArchiveDeps += "$($_.ToLower()):$(Get-Variable -Name "Build$($_)ScriptFileHash" -ValueOnly)" }
167         $CacheArchiveDepsString = ($CacheArchiveDeps | Sort-Object) -join ' '
168         $CacheArchiveDepsHash = Get-StringHash $CacheArchiveDepsString
169 
170         $CacheArchiveName = "$($Name.ToLower())_${BuildVersion}-vs_${VsVersion}-${BuildArch}-${CacheArchiveDepsHash}.7z"
171         $CacheArchive = Join-Path $CacheDir $CacheArchiveName
172 
173         Write-Information "Cache archive: ${CacheArchiveName} (${CacheArchiveDepsString})" -InformationAction Continue
174     } else {
175         $CacheArchiveName = $null
176         $CacheArchive = $null
177     }
178 
179     while (-not $CacheArchive -or -not (Test-Path $CacheArchive)) {
180         if ($CacheArchive -and $env:AWS_S3_BUCKET_NAME) {
181             try {
182                 Write-Information "Downloading cache archive ${CacheArchiveName} from S3" -InformationAction Continue
183                 Invoke-NativeCommand aws s3 cp "s3://${env:AWS_S3_BUCKET_NAME}/windows/${CacheArchiveName}" $CacheArchive
184                 break
185             } catch {
186                 Write-Warning "Cache archive ${CacheArchiveName} download from S3 failed"
187             }
188         }
189 
190         $Builder = (Get-Command "Build-${Name}" -CommandType Function).ScriptBlock
191 
192         $TempPrefixDir = Join-Path $TempDir "${Name}-Prefix"
193         $BuilderArguments = @($TempPrefixDir, $BuildArch, $PrefixDir) + $MoreArguments
194         Write-Information "Running build for ${Name}" -InformationAction Continue
195         Invoke-Command -ScriptBlock $Builder -ArgumentList $BuilderArguments
196 
197         if ($CacheArchive) {
198             Write-Information "Packing cache archive ${CacheArchive} from ${TempPrefixDir}" -InformationAction Continue
199             Invoke-NativeCommand cmake -E chdir $TempPrefixDir 7z a -t7z -m0=lzma -mx=9 -y $CacheArchive
200 
201             if ($CacheArchive -and $env:AWS_S3_BUCKET_NAME) {
202                 try {
203                     Write-Information "Uploading cache archive ${CacheArchiveName} to S3" -InformationAction Continue
204                     Invoke-NativeCommand aws s3 cp $CacheArchive "s3://${env:AWS_S3_BUCKET_NAME}/windows/${CacheArchiveName}" `
205                         --metadata "build-deps=${CacheArchiveDepsString}"
206                 } catch {
207                     Write-Warning "Cache archive ${CacheArchiveName} upload to S3 failed"
208                 }
209             }
210         }
211 
212         break
213     }
214 
215     if ($CacheArchive) {
216         Write-Information "Unpacking cache archive ${CacheArchive} to ${PrefixDir}" -InformationAction Continue
217         Invoke-NativeCommand 7z x -y $CacheArchive "-o${PrefixDir}"
218     }
219 }
220 
Invoke-Test([string] $Name, [string[]] $MoreArguments = @()221 function Invoke-Test([string] $Name, [string[]] $MoreArguments = @()) {
222     Import-Script "Build-${Name}"
223 
224     $Tester = (Get-Command "Test-${Name}" -CommandType Function).ScriptBlock
225 
226     Write-Information "Running test for ${Name}" -InformationAction Continue
227     Invoke-Command -ScriptBlock $Tester -ArgumentList $MoreArguments
228 }
229 
230 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
231 
232 if (-not $SourceDir) {
233     $SourceDir = (Get-Item $ScriptDir).Parent.Parent.FullName
234 }
235 
236 if ($Mode -eq 'Build') {
237     Import-Script Toolchain
238 
239     $env:CFLAGS = $CompilerFlags -join ' '
240     $env:CXXFLAGS = $CompilerFlags -join ' '
241     $env:LDFLAGS = $LinkerFlags -join ' '
242 
243     Invoke-Build Expat
244     Invoke-Build DBus
245     Invoke-Build Zlib
246     Invoke-Build OpenSsl
247     Invoke-Build Curl
248     Invoke-Build Qt
249 
250     Invoke-Build Transmission -NoCache -MoreArguments @($SourceDir, $SourceDir)
251 }
252 
253 if ($Mode -eq 'Test') {
254     Invoke-Test Transmission -MoreArguments @($PrefixDir, $SourceDir)
255 }
256