1 [CmdletBinding(PositionalBinding=$false)]
2 Param(
3 [switch] $build,
4 [switch] $ci,
5 [string] $configuration = "Debug",
6 [switch] $help,
7 [switch] $nolog,
8 [switch] $pack,
9 [switch] $prepareMachine,
10 [switch] $rebuild,
11 [switch] $norestore,
12 [switch] $sign,
13 [switch] $skiptests,
14 [switch] $bootstrapOnly,
15 [string] $verbosity = "minimal",
16 [string] $hostType,
17 [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties
18 )
19
20 Set-StrictMode -Version 2.0
21 $ErrorActionPreference = "Stop"
22
Print-Usage()23 function Print-Usage() {
24 Write-Host "Common settings:"
25 Write-Host " -configuration <value> Build configuration Debug, Release"
26 Write-Host " -verbosity <value> Msbuild verbosity (q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic])"
27 Write-Host " -help Print help and exit"
28 Write-Host ""
29 Write-Host "Actions:"
30 Write-Host " -norestore Don't automatically run restore"
31 Write-Host " -build Build solution"
32 Write-Host " -rebuild Rebuild solution"
33 Write-Host " -skipTests Don't run tests"
34 Write-Host " -bootstrapOnly Don't run build again with bootstrapped MSBuild"
35 Write-Host " -sign Sign build outputs"
36 Write-Host " -pack Package build outputs into NuGet packages and Willow components"
37 Write-Host ""
38 Write-Host "Advanced settings:"
39 Write-Host " -ci Set when running on CI server"
40 Write-Host " -nolog Disable logging"
41 Write-Host " -prepareMachine Prepare machine for CI run"
42 Write-Host " -hostType Host / MSBuild flavor to use. Possible values: full, core"
43 Write-Host ""
44 Write-Host "Command line arguments not listed above are passed through to MSBuild."
45 Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)."
46 }
47
Create-Directory([string[]] $Path)48 function Create-Directory([string[]] $Path) {
49 if (!(Test-Path -Path $Path)) {
50 New-Item -Path $Path -Force -ItemType "Directory" | Out-Null
51 }
52 }
53
GetVersionsPropsVersion([string[]] $Name)54 function GetVersionsPropsVersion([string[]] $Name) {
55 [xml]$Xml = Get-Content $VersionsProps
56
57 foreach ($PropertyGroup in $Xml.Project.PropertyGroup) {
58 if (Get-Member -InputObject $PropertyGroup -name $Name) {
59 return $PropertyGroup.$Name
60 }
61 }
62
63 throw "Failed to locate the $Name property"
64 }
65
InstallDotNetCli()66 function InstallDotNetCli {
67 $DotNetCliVersion = GetVersionsPropsVersion -Name "DotNetCliVersion"
68 $DotNetInstallVerbosity = ""
69
70 if (!$env:DOTNET_INSTALL_DIR) {
71 $env:DOTNET_INSTALL_DIR = Join-Path $RepoRoot "artifacts\.dotnet\$DotNetCliVersion\"
72 }
73
74 $DotNetRoot = $env:DOTNET_INSTALL_DIR
75 $DotNetInstallScript = Join-Path $DotNetRoot "dotnet-install.ps1"
76
77 if (!(Test-Path $DotNetInstallScript)) {
78 Create-Directory $DotNetRoot
79 Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -UseBasicParsing -OutFile $DotNetInstallScript
80 }
81
82 if ($verbosity -eq "diagnostic") {
83 $DotNetInstallVerbosity = "-Verbose"
84 }
85
86 # Install a stage 0
87 $SdkInstallDir = Join-Path $DotNetRoot "sdk\$DotNetCliVersion"
88
89 if (!(Test-Path $SdkInstallDir)) {
90 # Use Invoke-Expression so that $DotNetInstallVerbosity is not positionally bound when empty
91 Invoke-Expression -Command "& '$DotNetInstallScript' -Version $DotNetCliVersion $DotNetInstallVerbosity"
92
93 if($LASTEXITCODE -ne 0) {
94 throw "Failed to install stage0"
95 }
96 }
97
98 # Put the stage 0 on the path
99 $env:PATH = "$DotNetRoot;$env:PATH"
100
101 # Disable first run since we want to control all package sources
102 $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
103
104 # Don't resolve runtime, shared framework, or SDK from other locations
105 $env:DOTNET_MULTILEVEL_LOOKUP=0
106 }
107
InstallNuGet()108 function InstallNuGet {
109 $NugetInstallDir = Join-Path $RepoRoot "artifacts\.nuget"
110 $NugetExe = Join-Path $NugetInstallDir "nuget.exe"
111
112 if (!(Test-Path -Path $NugetExe)) {
113 Create-Directory $NugetInstallDir
114 Invoke-WebRequest "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -UseBasicParsing -OutFile $NugetExe
115 }
116 }
117
InstallRepoToolset()118 function InstallRepoToolset {
119 $RepoToolsetVersion = GetVersionsPropsVersion -Name "RoslynToolsRepoToolsetVersion"
120 $RepoToolsetDir = Join-Path $NuGetPackageRoot "roslyntools.repotoolset\$RepoToolsetVersion\tools"
121 $RepoToolsetBuildProj = Join-Path $RepoToolsetDir "Build.proj"
122
123 if (!(Test-Path -Path $RepoToolsetBuildProj)) {
124 $ToolsetProj = Join-Path $PSScriptRoot "Toolset.proj"
125 $msbuildArgs = "/t:restore", "/m", "/clp:Summary", "/warnaserror", "/v:$verbosity"
126 $msbuildArgs = AddLogCmd "Toolset" $msbuildArgs
127 # Piping to Out-Null is important here, as otherwise the MSBuild output will be included in the return value
128 # of the function (Powershell handles return values a bit... weirdly)
129 CallMSBuild $ToolsetProj @msbuildArgs | Out-Null
130
131 if($LASTEXITCODE -ne 0) {
132 throw "Failed to build $ToolsetProj"
133 }
134 }
135
136 return $RepoToolsetBuildProj
137 }
138
LocateVisualStudio()139 function LocateVisualStudio {
140 $VSWhereVersion = GetVersionsPropsVersion -Name "VSWhereVersion"
141 $VSWhereDir = Join-Path $ArtifactsDir ".tools\vswhere\$VSWhereVersion"
142 $VSWhereExe = Join-Path $vsWhereDir "vswhere.exe"
143
144 if (!(Test-Path $VSWhereExe)) {
145 Create-Directory $VSWhereDir
146 Invoke-WebRequest "http://github.com/Microsoft/vswhere/releases/download/$VSWhereVersion/vswhere.exe" -UseBasicParsing -OutFile $VSWhereExe
147 }
148
149 $VSInstallDir = & $VSWhereExe -latest -property installationPath -requires Microsoft.Component.MSBuild -requires Microsoft.VisualStudio.Component.VSSDK -requires Microsoft.Net.Component.4.6.TargetingPack -requires Microsoft.VisualStudio.Component.Roslyn.Compiler -requires Microsoft.VisualStudio.Component.VSSDK
150
151 if (!(Test-Path $VSInstallDir)) {
152 throw "Failed to locate Visual Studio (exit code '$LASTEXITCODE')."
153 }
154
155 return $VSInstallDir
156 }
157
KillProcessesFromRepo()158 function KillProcessesFromRepo {
159 # Jenkins does not allow taskkill
160 if (-not $ci) {
161 # Kill compiler server and MSBuild node processes from bootstrapped MSBuild (otherwise a second build will fail to copy files in use)
162 foreach ($process in Get-Process | Where-Object {'msbuild', 'dotnet', 'vbcscompiler' -contains $_.Name})
163 {
164
165 if ([string]::IsNullOrEmpty($process.Path))
166 {
167 Write-Host "Process $($process.Id) $($process.Name) does not have a Path. Skipping killing it."
168 continue
169 }
170
171 if ($process.Path.StartsWith($RepoRoot, [StringComparison]::InvariantCultureIgnoreCase))
172 {
173 Write-Host "Killing $($process.Name) from $($process.Path)"
174 taskkill /f /pid $process.Id
175 }
176 }
177 }
178 }
179
Build()180 function Build {
181 InstallDotNetCli
182 $env:DOTNET_HOST_PATH = Join-Path $env:DOTNET_INSTALL_DIR "dotnet.exe"
183
184 if ($prepareMachine) {
185 Create-Directory $NuGetPackageRoot
186 & "$env:DOTNET_HOST_PATH" nuget locals all --clear
187
188 if($LASTEXITCODE -ne 0) {
189 throw "Failed to clear NuGet cache"
190 }
191 }
192
193 InstallNuget
194
195 if ($hostType -eq 'full')
196 {
197 $msbuildHost = $null
198 }
199 elseif ($hostType -eq 'core')
200 {
201 $msbuildHost = $env:DOTNET_HOST_PATH
202 }
203 else
204 {
205 throw "Unknown hostType parameter: $hostType"
206 }
207
208 $RepoToolsetBuildProj = InstallRepoToolset
209
210 $solution = Join-Path $RepoRoot "MSBuild.sln"
211
212 $commonMSBuildArgs = "/m", "/clp:Summary", "/v:$verbosity", "/p:Configuration=$configuration", "/p:SolutionPath=$solution", "/p:CIBuild=$ci"
213 if ($ci)
214 {
215 # Only enable warnaserror on CI runs. For local builds, we will generate a warning if we can't run EditBin because
216 # the C++ tools aren't installed, and we don't want this to fail the build
217 $commonMSBuildArgs = $commonMSBuildArgs + "/warnaserror"
218 }
219
220 if ($hostType -ne 'full')
221 {
222 # Make signing a no-op if building not using full Framework MSBuild (as all of the assets that are signed won't be produced)
223 $emptySignToolDataPath = [io.path]::combine($RepoRoot, 'build', 'EmptySignToolData.json')
224 $commonMSBuildArgs = $commonMSBuildArgs + "/p:SignToolDataPath=`"$emptySignToolDataPath`""
225 }
226
227 # Only test using stage 0 MSBuild if -bootstrapOnly is specified
228 $testStage0 = $false
229 if ($bootstrapOnly)
230 {
231 $testStage0 = $test
232 }
233
234 $msbuildArgs = AddLogCmd "Build" $commonMSBuildArgs
235
236 CallMSBuild $RepoToolsetBuildProj @msbuildArgs /p:Restore=$restore /p:Build=$build /p:Rebuild=$rebuild /p:Test=$testStage0 /p:Sign=$sign /p:Pack=$pack /p:CreateBootstrap=true @properties
237
238 if (-not $bootstrapOnly)
239 {
240 $bootstrapRoot = Join-Path $ArtifactsConfigurationDir "bootstrap"
241
242 if ($hostType -eq 'full')
243 {
244 $msbuildToUse = Join-Path $bootstrapRoot "net46\MSBuild\15.0\Bin\MSBuild.exe"
245
246 if ($configuration -eq "Debug-MONO" -or $configuration -eq "Release-MONO")
247 {
248 # Copy MSBuild.dll to MSBuild.exe so we can run it without a host
249 $sourceDll = Join-Path $bootstrapRoot "net46\MSBuild\15.0\Bin\MSBuild.dll"
250 Copy-Item -Path $sourceDll -Destination $msbuildToUse
251 }
252 }
253 else
254 {
255 $msbuildToUse = Join-Path $bootstrapRoot "netcoreapp2.1\MSBuild\\MSBuild.dll"
256 }
257
258 # Use separate artifacts folder for stage 2
259 $env:ArtifactsDir = Join-Path $ArtifactsDir "2\"
260
261 $msbuildArgs = AddLogCmd "BuildWithBootstrap" $commonMSBuildArgs
262
263 # When using bootstrapped MSBuild:
264 # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files)
265 # - Don't sign
266 # - Don't pack
267 # - Do run tests (if not skipped)
268 # - Don't try to create a bootstrap deployment
269 CallMSBuild $RepoToolsetBuildProj @msbuildArgs /nr:false /p:Restore=$restore /p:Build=$build /p:Rebuild=$rebuild /p:Test=$test /p:Sign=false /p:Pack=false /p:CreateBootstrap=false @properties
270 }
271
272 if ($ci)
273 {
274 # CallMSBuild $ToolsetProj /t:restore /m /clp:Summary /warnaserror /v:$verbosity @logCmd | Out-Null
275 git status | Out-Null
276 git --no-pager diff HEAD --word-diff=plain --exit-code | Out-Null
277
278 if($LASTEXITCODE -ne 0) {
279 throw "[ERROR] After building, there are changed files. Please build locally and include these changes in your pull request."
280 }
281 }
282
283 }
CallMSBuild()284 function CallMSBuild
285 {
286 try
287 {
288 if ($msbuildHost)
289 {
290 & $msbuildHost $msbuildToUse $args
291 }
292 else
293 {
294 & $msbuildToUse $args
295 }
296
297 if($LASTEXITCODE -ne 0) {
298 throw "Failed to build $args"
299 }
300 }
301 finally
302 {
303 KillProcessesFromRepo
304 }
305 }
306
AddLogCmd([string] $logName, [string[]] $extraArgs)307 function AddLogCmd([string] $logName, [string[]] $extraArgs)
308 {
309
310 if ($ci -or $log) {
311 Create-Directory $LogDir
312 $extraArgs = $extraArgs + ("/bl:" + (Join-Path $LogDir "$logName.binlog"))
313
314 # When running under CI, also create a text log, so it can be viewed in the Jenkins UI
315 if ($ci) {
316 $extraArgs = $extraArgs + ("/flp:Verbosity=diag;LogFile=" + '"' + (Join-Path $LogDir "$logName.log") + '"')
317 }
318 }
319
320 return $extraArgs;
321 }
322
Stop-Processes()323 function Stop-Processes() {
324 Write-Host "Killing running build processes..."
325 Get-Process -Name "msbuild" -ErrorAction SilentlyContinue | Stop-Process
326 Get-Process -Name "vbcscompiler" -ErrorAction SilentlyContinue | Stop-Process
327 }
328
329 if ($help -or (($properties -ne $null) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) {
330 Print-Usage
331 exit 0
332 }
333
334 $RepoRoot = Join-Path $PSScriptRoot "..\"
335 $RepoRoot = [System.IO.Path]::GetFullPath($RepoRoot);
336 $ArtifactsDir = Join-Path $RepoRoot "artifacts"
337 $ArtifactsConfigurationDir = Join-Path $ArtifactsDir $configuration
338 $LogDir = Join-Path $ArtifactsConfigurationDir "log"
339 $VersionsProps = Join-Path $PSScriptRoot "Versions.props"
340
341 $log = -not $nolog
342 $restore = -not $norestore
343 $test = -not $skiptests
344
345 if ($hostType -eq '')
346 {
347 $hostType = 'full'
348 }
349
350 # TODO: If host type is full, either make sure we're running in a developer command prompt, or attempt to locate VS, or fail
351
352 $msbuildHost = $null
353 $msbuildToUse = "msbuild"
354
355 try {
356
357 KillProcessesFromRepo
358
359 if ($ci) {
360 $TempDir = Join-Path $ArtifactsConfigurationDir "tmp"
361 Create-Directory $TempDir
362
363 $env:TEMP = $TempDir
364 $env:TMP = $TempDir
365 $env:MSBUILDDEBUGPATH = $TempDir
366 }
367
368 if (!($env:NUGET_PACKAGES)) {
369 $env:NUGET_PACKAGES = Join-Path $env:UserProfile ".nuget\packages"
370 }
371
372 $NuGetPackageRoot = $env:NUGET_PACKAGES
373
374 Build
375 exit $lastExitCode
376 }
377 catch {
378 Write-Host $_
379 Write-Host $_.Exception
380 Write-Host $_.ScriptStackTrace
381 exit 1
382 }
383 finally {
384 Pop-Location
385 if ($ci -and $prepareMachine) {
386 Stop-Processes
387 }
388 }
389