| # Copyright 2019 The Bazel Authors. All rights reserved. | 
 | # | 
 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
 | # you may not use this file except in compliance with the License. | 
 | # You may obtain a copy of the License at | 
 | # | 
 | #    http://www.apache.org/licenses/LICENSE-2.0 | 
 | # | 
 | # Unless required by applicable law or agreed to in writing, software | 
 | # distributed under the License is distributed on an "AS IS" BASIS, | 
 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | # See the License for the specific language governing permissions and | 
 | # limitations under the License. | 
 |  | 
 | # Set path constants. These are relative to the Bazel source tree root. | 
 | $Icon = 'site\images\favicon.ico' | 
 | $ArtworkBig = 'scripts\packages\msi\dialog.bmp' | 
 | $ArtworkSmall = 'scripts\packages\msi\banner.bmp' | 
 | $Licence = 'scripts\packages\msi\license.rtf' | 
 | $Guids = 'scripts\packages\msi\guids.txt' | 
 | $Wxs = 'scripts\packages\msi\bazelmsi.wxs' | 
 |  | 
 | # Logs a message to stdout. | 
 | function Log-Info { | 
 |     param ( | 
 |       [string]$msg | 
 |     ) | 
 |     $basename = $PSCommandPath.Substring($PSCommandPath.LastIndexOfAny('/\') + 1) | 
 |     $time = (Get-Date).ToString('HH:mm:ss') | 
 |     Write-Host "INFO[$basename $time] $msg" | 
 | } | 
 |  | 
 | function Replace-Slashes { | 
 |     param ( | 
 |       [string]$s | 
 |     ) | 
 |     return $s.Replace('/', '\') | 
 | } | 
 |  | 
 | # Computes the release name from the 'BazelExe'. | 
 | # 'BazelExe' must already have been validated to match this regex. | 
 | # 'ReleaseName' is like "0.28.0rc5" | 
 | # 'Version' is the SemVer part of 'ReleaseName', e.g. "0.28.0" | 
 | function Compute-RelaseNameAndVersion { | 
 |     param ( | 
 |       [Parameter(Mandatory=$true)][string]$BazelExe | 
 |     ) | 
 |  | 
 |     $rel = [regex]::Match($BazelExe, "bazel-([^-]+)-windows.*\.exe$").captures.groups[1].value | 
 |     $ver = [regex]::Match($rel, "^([0-9]+\.[0-9]+\.[0-9]+)(rc[0-9]+)?$").captures.groups[1].value | 
 |     return $rel, $ver | 
 | } | 
 |  | 
 | # Generates a new GUID, prints it as uppercase. | 
 | # The WiX Toolkit expects uppercase GUIDs in the .wxs file. | 
 | function Generate-Guid { | 
 |     return [guid]::NewGuid().ToString().ToUpper() | 
 | } | 
 |  | 
 | # Returns the UpgradeGuid for this release. | 
 | function Get-UpgradeGuid { | 
 |     param ( | 
 |       [Parameter(Mandatory=$true)][string]$release_name | 
 |     ) | 
 |  | 
 |     $d = @{} | 
 |     $result = $null | 
 |     foreach ($line in Get-Content -Path $Guids) { | 
 |         $line = $line.Split('#')[0] | 
 |         if ($line) { | 
 |             $k, $v = $line.Split(' ', 2) | 
 |             # Ensure all version prefixes in the file are unique. | 
 |             if ($d.ContainsKey($k)) { | 
 |                 throw "Duplicate relase prefix $k in $Guids" | 
 |             } | 
 |             else { | 
 |                 $d[$k] = $null | 
 |             } | 
 |             if (! $v) { | 
 |                 throw "Null key '$v' in line '$line'" | 
 |             } | 
 |             # Ensure all GUIDs in the file are unique. We can use the same | 
 |             # hashtable because the value domains are distinct. | 
 |             if ($d.ContainsKey($v)) { | 
 |                 throw "Duplicate GUID $v in $Guids" | 
 |             } | 
 |             else { | 
 |                 $d[$v] = $null | 
 |             } | 
 |             if (! $result -and $release_name.StartsWith($k)) { | 
 |                 $result = $v | 
 |                 # Do not return yet, so we check that all GUIDs are unique. | 
 |             } | 
 |         } | 
 |     } | 
 |     if ($result) { | 
 |         return $result | 
 |     } | 
 |     else { | 
 |         throw "UpgradeGuid for $release_name not found in $Guids" | 
 |     } | 
 | } | 
 |  | 
 | # Returns the Bazel version (as a SemVer string) from the release name. | 
 | function Get-BazelVersion { | 
 |     if ($ReleaseName -match "rc[0-9]+$") { | 
 |         return $ReleaseName.Substring(0, $ReleaseName.LastIndexOf('rc')) | 
 |     } | 
 |     else { | 
 |         return $ReleaseName | 
 |     } | 
 | } | 
 |  | 
 | # Downloads and extracts the WiX Toolkit. | 
 | # Returns the path where the tools are (e.g. "candle.exe"). | 
 | function Download-Wix { | 
 |     # Use TLS1.2 for HTTPS (fixes an issue where later steps can't connect to github.com). | 
 |     # See https://github.com/bazelbuild/continuous-integration/blob/master/buildkite/setup-windows.ps1 | 
 |     [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | 
 |  | 
 |     $wix_dir = $(New-Item -Path "${WorkDir}\_wix" -ItemType Directory -Force).FullName | 
 |     $zip_file = "$wix_dir\wix311.zip" | 
 |     (New-Object Net.WebClient).DownloadFile( | 
 |         'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip', | 
 |         $zip_file) | 
 |  | 
 |     $actual_sha = (Get-FileHash -Path $zip_file -Algorithm SHA256).Hash.ToString() | 
 |     $expected_sha = '37F0A533B0978A454EFB5DC3BD3598BECF9660AAF4287E55BF68CA6B527D051D' | 
 |     if ($actual_sha -ne $expected_sha) { | 
 |         throw "Bad checksum: wix311-binaries.zip SHA256 is $actual_sha, expected $expected_sha" | 
 |     } | 
 |  | 
 |     Expand-Archive -Path $zip_file -DestinationPath $wix_dir -Force | 
 |  | 
 |     return $wix_dir | 
 | } | 
 |  | 
 | # Creates the .wixobj file using candle.exe (the "compiler"). | 
 | function Run-Candle { | 
 |     param ( | 
 |       [Parameter(Mandatory=$true)][string]$wix_root | 
 |     ) | 
 |  | 
 |     $out = "${WorkDir}\bazelmsi.wixobj" | 
 |  | 
 |     $output = & "${wix_root}\candle.exe" ` | 
 |         -nologo ` | 
 |         -arch x64 ` | 
 |         "-dBAZEL_EXE=$BazelExe" ` | 
 |         "-dICON=$Icon" ` | 
 |         "-dUPGRADE_GUID=$(Get-UpgradeGuid $ReleaseName)" ` | 
 |         "-dRELEASE_NAME=$ReleaseName" ` | 
 |         "-dVERSION=$(Get-BazelVersion)" ` | 
 |         "-dRANDOM_GUID_1=$(Generate-Guid)" ` | 
 |         "-dRANDOM_GUID_2=$(Generate-Guid)" ` | 
 |         -o $out ` | 
 |         $Wxs | 
 |     if (! $?) { | 
 |         throw $output | 
 |     } | 
 |     return $out | 
 | } | 
 |  | 
 | # Creates the .msi file using light.exe (the "linker"). | 
 | function Run-Light { | 
 |     param ( | 
 |         [Parameter(Mandatory=$true)][string]$wix_root, | 
 |         [Parameter(Mandatory=$true)][string]$wixobj | 
 |     ) | 
 |  | 
 |     $output = & "${wix_root}\light.exe" ` | 
 |         -nologo ` | 
 |         -ext WixUIExtension ` | 
 |         '-cultures:en-us' ` | 
 |         "-dWixUILicenseRtf=$Licence" ` | 
 |         "-dWixUIDialogBmp=$ArtworkBig" ` | 
 |         "-dWixUIBannerBmp=$ArtworkSmall" ` | 
 |         -o $OutMsi ` | 
 |         $wixobj | 
 |     if (! $?) { | 
 |         throw $output | 
 |     } | 
 | } | 
 |  | 
 | function Make-Msi { | 
 |     param ( | 
 |         [Parameter(Mandatory=$true)] | 
 |         [ValidatePattern("bazel-[0-9]+\.[0-9]+\.[0-9]+(rc[1-9]|rc[1-9][0-9]+)?-windows-x86_64.exe$")] | 
 |         [string] | 
 |         $BazelExe, | 
 |  | 
 |         [Parameter(Mandatory=$true)][string]$WorkDir, | 
 |         [Parameter(Mandatory=$true)][string]$OutMsi | 
 |     ) | 
 |  | 
 |     $ReleaseName, $Version = Compute-RelaseNameAndVersion $BazelExe | 
 |  | 
 |     Log-Info 'Downloading WiX Toolkit' | 
 |     $wix_root = Download-Wix | 
 |  | 
 |     Log-Info 'Creating wixobj' | 
 |     $wixobj = Run-Candle -wix_root $wix_root | 
 |  | 
 |     Log-Info 'Creating msi' | 
 |     Run-Light -wix_root $wix_root -wixobj $wixobj | 
 |  | 
 |     Log-Info "Done: $OutMsi" | 
 | } |