tools.ps1 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. # Initialize variables if they aren't already defined.
  2. # These may be defined as parameters of the importing script, or set after importing this script.
  3. # CI mode - set to true on CI server for PR validation build or official build.
  4. [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false }
  5. # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names.
  6. [string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' }
  7. # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build.
  8. # Binary log must be enabled on CI.
  9. [bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci }
  10. # Set to true to use the pipelines logger which will enable Azure logging output.
  11. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
  12. # This flag is meant as a temporary opt-opt for the feature while validate it across
  13. # our consumers. It will be deleted in the future.
  14. [bool]$pipelinesLog = if (Test-Path variable:pipelinesLog) { $pipelinesLog } else { $ci }
  15. # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes).
  16. [bool]$prepareMachine = if (Test-Path variable:prepareMachine) { $prepareMachine } else { $false }
  17. # True to restore toolsets and dependencies.
  18. [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true }
  19. # Adjusts msbuild verbosity level.
  20. [string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' }
  21. # Set to true to reuse msbuild nodes. Recommended to not reuse on CI.
  22. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci }
  23. # Configures warning treatment in msbuild.
  24. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true }
  25. # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json).
  26. [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null }
  27. # True to attempt using .NET Core already that meets requirements specified in global.json
  28. # installed on the machine instead of downloading one.
  29. [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true }
  30. # Enable repos to use a particular version of the on-line dotnet-install scripts.
  31. # default URL: https://dot.net/v1/dotnet-install.ps1
  32. [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' }
  33. # True to use global NuGet cache instead of restoring packages to repository-local directory.
  34. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci }
  35. # An array of names of processes to stop on script exit if prepareMachine is true.
  36. $processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') }
  37. $disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null }
  38. set-strictmode -version 2.0
  39. $ErrorActionPreference = 'Stop'
  40. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  41. function Create-Directory([string[]] $path) {
  42. if (!(Test-Path $path)) {
  43. New-Item -path $path -force -itemType 'Directory' | Out-Null
  44. }
  45. }
  46. function Unzip([string]$zipfile, [string]$outpath) {
  47. Add-Type -AssemblyName System.IO.Compression.FileSystem
  48. [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
  49. }
  50. # This will exec a process using the console and return it's exit code.
  51. # This will not throw when the process fails.
  52. # Returns process exit code.
  53. function Exec-Process([string]$command, [string]$commandArgs) {
  54. $startInfo = New-Object System.Diagnostics.ProcessStartInfo
  55. $startInfo.FileName = $command
  56. $startInfo.Arguments = $commandArgs
  57. $startInfo.UseShellExecute = $false
  58. $startInfo.WorkingDirectory = Get-Location
  59. $process = New-Object System.Diagnostics.Process
  60. $process.StartInfo = $startInfo
  61. $process.Start() | Out-Null
  62. $finished = $false
  63. try {
  64. while (-not $process.WaitForExit(100)) {
  65. # Non-blocking loop done to allow ctr-c interrupts
  66. }
  67. $finished = $true
  68. return $global:LASTEXITCODE = $process.ExitCode
  69. }
  70. finally {
  71. # If we didn't finish then an error occurred or the user hit ctrl-c. Either
  72. # way kill the process
  73. if (-not $finished) {
  74. $process.Kill()
  75. }
  76. }
  77. }
  78. # createSdkLocationFile parameter enables a file being generated under the toolset directory
  79. # which writes the sdk's location into. This is only necessary for cmd --> powershell invocations
  80. # as dot sourcing isn't possible.
  81. function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) {
  82. if (Test-Path variable:global:_DotNetInstallDir) {
  83. return $global:_DotNetInstallDir
  84. }
  85. # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism
  86. $env:DOTNET_MULTILEVEL_LOOKUP=0
  87. # Disable first run since we do not need all ASP.NET packages restored.
  88. $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
  89. # Disable telemetry on CI.
  90. if ($ci) {
  91. $env:DOTNET_CLI_TELEMETRY_OPTOUT=1
  92. }
  93. # Source Build uses DotNetCoreSdkDir variable
  94. if ($env:DotNetCoreSdkDir -ne $null) {
  95. $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir
  96. }
  97. # Find the first path on %PATH% that contains the dotnet.exe
  98. if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) {
  99. $dotnetCmd = Get-Command 'dotnet.exe' -ErrorAction SilentlyContinue
  100. if ($dotnetCmd -ne $null) {
  101. $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent
  102. }
  103. }
  104. $dotnetSdkVersion = $GlobalJson.tools.dotnet
  105. # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version,
  106. # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues.
  107. if ((-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -ne $null) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) {
  108. $dotnetRoot = $env:DOTNET_INSTALL_DIR
  109. } else {
  110. $dotnetRoot = Join-Path $RepoRoot '.dotnet'
  111. if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) {
  112. if ($install) {
  113. InstallDotNetSdk $dotnetRoot $dotnetSdkVersion
  114. } else {
  115. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'"
  116. ExitWithExitCode 1
  117. }
  118. }
  119. $env:DOTNET_INSTALL_DIR = $dotnetRoot
  120. }
  121. # Creates a temporary file under the toolset dir.
  122. # The following code block is protecting against concurrent access so that this function can
  123. # be called in parallel.
  124. if ($createSdkLocationFile) {
  125. do {
  126. $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName())
  127. }
  128. until (!(Test-Path $sdkCacheFileTemp))
  129. Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot
  130. try {
  131. Rename-Item -Force -Path $sdkCacheFileTemp 'sdk.txt'
  132. } catch {
  133. # Somebody beat us
  134. Remove-Item -Path $sdkCacheFileTemp
  135. }
  136. }
  137. # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom
  138. # build steps from using anything other than what we've downloaded.
  139. # It also ensures that VS msbuild will use the downloaded sdk targets.
  140. $env:PATH = "$dotnetRoot;$env:PATH"
  141. # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build
  142. Write-PipelinePrependPath -Path $dotnetRoot
  143. Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0'
  144. Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1'
  145. return $global:_DotNetInstallDir = $dotnetRoot
  146. }
  147. function GetDotNetInstallScript([string] $dotnetRoot) {
  148. $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1'
  149. if (!(Test-Path $installScript)) {
  150. Create-Directory $dotnetRoot
  151. $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit
  152. Invoke-WebRequest "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" -OutFile $installScript
  153. }
  154. return $installScript
  155. }
  156. function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '') {
  157. InstallDotNet $dotnetRoot $version $architecture
  158. }
  159. function InstallDotNet([string] $dotnetRoot,
  160. [string] $version,
  161. [string] $architecture = '',
  162. [string] $runtime = '',
  163. [bool] $skipNonVersionedFiles = $false,
  164. [string] $runtimeSourceFeed = '',
  165. [string] $runtimeSourceFeedKey = '') {
  166. $installScript = GetDotNetInstallScript $dotnetRoot
  167. $installParameters = @{
  168. Version = $version
  169. InstallDir = $dotnetRoot
  170. }
  171. if ($architecture) { $installParameters.Architecture = $architecture }
  172. if ($runtime) { $installParameters.Runtime = $runtime }
  173. if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles }
  174. try {
  175. & $installScript @installParameters
  176. }
  177. catch {
  178. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from public location."
  179. # Only the runtime can be installed from a custom [private] location.
  180. if ($runtime -and ($runtimeSourceFeed -or $runtimeSourceFeedKey)) {
  181. if ($runtimeSourceFeed) { $installParameters.AzureFeed = $runtimeSourceFeed }
  182. if ($runtimeSourceFeedKey) {
  183. $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey)
  184. $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes)
  185. $installParameters.FeedCredential = $decodedString
  186. }
  187. try {
  188. & $installScript @installParameters
  189. }
  190. catch {
  191. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'."
  192. ExitWithExitCode 1
  193. }
  194. } else {
  195. ExitWithExitCode 1
  196. }
  197. }
  198. }
  199. #
  200. # Locates Visual Studio MSBuild installation.
  201. # The preference order for MSBuild to use is as follows:
  202. #
  203. # 1. MSBuild from an active VS command prompt
  204. # 2. MSBuild from a compatible VS installation
  205. # 3. MSBuild from the xcopy tool package
  206. #
  207. # Returns full path to msbuild.exe.
  208. # Throws on failure.
  209. #
  210. function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) {
  211. if (Test-Path variable:global:_MSBuildExe) {
  212. return $global:_MSBuildExe
  213. }
  214. if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs }
  215. $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { '15.9' }
  216. $vsMinVersion = [Version]::new($vsMinVersionStr)
  217. # Try msbuild command available in the environment.
  218. if ($env:VSINSTALLDIR -ne $null) {
  219. $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue
  220. if ($msbuildCmd -ne $null) {
  221. # Workaround for https://github.com/dotnet/roslyn/issues/35793
  222. # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+
  223. $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0])
  224. if ($msbuildVersion -ge $vsMinVersion) {
  225. return $global:_MSBuildExe = $msbuildCmd.Path
  226. }
  227. # Report error - the developer environment is initialized with incompatible VS version.
  228. throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window"
  229. }
  230. }
  231. # Locate Visual Studio installation or download x-copy msbuild.
  232. $vsInfo = LocateVisualStudio $vsRequirements
  233. if ($vsInfo -ne $null) {
  234. $vsInstallDir = $vsInfo.installationPath
  235. $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0]
  236. InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion
  237. } else {
  238. if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') {
  239. $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild'
  240. $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0]
  241. } else {
  242. $vsMajorVersion = $vsMinVersion.Major
  243. $xcopyMSBuildVersion = "$vsMajorVersion.$($vsMinVersion.Minor).0-alpha"
  244. }
  245. $vsInstallDir = $null
  246. if ($xcopyMSBuildVersion.Trim() -ine "none") {
  247. $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install
  248. }
  249. if ($vsInstallDir -eq $null) {
  250. throw 'Unable to find Visual Studio that has required version and components installed'
  251. }
  252. }
  253. $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" }
  254. return $global:_MSBuildExe = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin\msbuild.exe"
  255. }
  256. function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) {
  257. $env:VSINSTALLDIR = $vsInstallDir
  258. Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\")
  259. $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\"
  260. if (Test-Path $vsSdkInstallDir) {
  261. Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir
  262. $env:VSSDKInstall = $vsSdkInstallDir
  263. }
  264. }
  265. function InstallXCopyMSBuild([string]$packageVersion) {
  266. return InitializeXCopyMSBuild $packageVersion -install $true
  267. }
  268. function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) {
  269. $packageName = 'RoslynTools.MSBuild'
  270. $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion"
  271. $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg"
  272. if (!(Test-Path $packageDir)) {
  273. if (!$install) {
  274. return $null
  275. }
  276. Create-Directory $packageDir
  277. Write-Host "Downloading $packageName $packageVersion"
  278. $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit
  279. Invoke-WebRequest "https://dotnet.myget.org/F/roslyn-tools/api/v2/package/$packageName/$packageVersion/" -OutFile $packagePath
  280. Unzip $packagePath $packageDir
  281. }
  282. return Join-Path $packageDir 'tools'
  283. }
  284. #
  285. # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json.
  286. #
  287. # The following properties of tools.vs are recognized:
  288. # "version": "{major}.{minor}"
  289. # Two part minimal VS version, e.g. "15.9", "16.0", etc.
  290. # "components": ["componentId1", "componentId2", ...]
  291. # Array of ids of workload components that must be available in the VS instance.
  292. # See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017
  293. #
  294. # Returns JSON describing the located VS instance (same format as returned by vswhere),
  295. # or $null if no instance meeting the requirements is found on the machine.
  296. #
  297. function LocateVisualStudio([object]$vsRequirements = $null){
  298. if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') {
  299. $vswhereVersion = $GlobalJson.tools.vswhere
  300. } else {
  301. $vswhereVersion = '2.5.2'
  302. }
  303. $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion"
  304. $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe'
  305. if (!(Test-Path $vsWhereExe)) {
  306. Create-Directory $vsWhereDir
  307. Write-Host 'Downloading vswhere'
  308. Invoke-WebRequest "https://github.com/Microsoft/vswhere/releases/download/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe
  309. }
  310. if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs }
  311. $args = @('-latest', '-prerelease', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*')
  312. if (Get-Member -InputObject $vsRequirements -Name 'version') {
  313. $args += '-version'
  314. $args += $vsRequirements.version
  315. }
  316. if (Get-Member -InputObject $vsRequirements -Name 'components') {
  317. foreach ($component in $vsRequirements.components) {
  318. $args += '-requires'
  319. $args += $component
  320. }
  321. }
  322. $vsInfo =& $vsWhereExe $args | ConvertFrom-Json
  323. if ($lastExitCode -ne 0) {
  324. return $null
  325. }
  326. # use first matching instance
  327. return $vsInfo[0]
  328. }
  329. function InitializeBuildTool() {
  330. if (Test-Path variable:global:_BuildTool) {
  331. return $global:_BuildTool
  332. }
  333. if (-not $msbuildEngine) {
  334. $msbuildEngine = GetDefaultMSBuildEngine
  335. }
  336. # Initialize dotnet cli if listed in 'tools'
  337. $dotnetRoot = $null
  338. if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') {
  339. $dotnetRoot = InitializeDotNetCli -install:$restore
  340. }
  341. if ($msbuildEngine -eq 'dotnet') {
  342. if (!$dotnetRoot) {
  343. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'."
  344. ExitWithExitCode 1
  345. }
  346. $buildTool = @{ Path = Join-Path $dotnetRoot 'dotnet.exe'; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp2.1' }
  347. } elseif ($msbuildEngine -eq "vs") {
  348. try {
  349. $msbuildPath = InitializeVisualStudioMSBuild -install:$restore
  350. } catch {
  351. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_
  352. ExitWithExitCode 1
  353. }
  354. $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472" }
  355. } else {
  356. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'."
  357. ExitWithExitCode 1
  358. }
  359. return $global:_BuildTool = $buildTool
  360. }
  361. function GetDefaultMSBuildEngine() {
  362. # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows.
  363. if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') {
  364. return 'vs'
  365. }
  366. if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') {
  367. return 'dotnet'
  368. }
  369. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'."
  370. ExitWithExitCode 1
  371. }
  372. function GetNuGetPackageCachePath() {
  373. if ($env:NUGET_PACKAGES -eq $null) {
  374. # Use local cache on CI to ensure deterministic build,
  375. # use global cache in dev builds to avoid cost of downloading packages.
  376. if ($useGlobalNuGetCache) {
  377. $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages'
  378. } else {
  379. $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages'
  380. }
  381. }
  382. return $env:NUGET_PACKAGES
  383. }
  384. # Returns a full path to an Arcade SDK task project file.
  385. function GetSdkTaskProject([string]$taskName) {
  386. return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj"
  387. }
  388. function InitializeNativeTools() {
  389. if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) {
  390. $nativeArgs= @{}
  391. if ($ci) {
  392. $nativeArgs = @{
  393. InstallDirectory = "$ToolsDir"
  394. }
  395. }
  396. & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs
  397. }
  398. }
  399. function InitializeToolset() {
  400. if (Test-Path variable:global:_ToolsetBuildProj) {
  401. return $global:_ToolsetBuildProj
  402. }
  403. $nugetCache = GetNuGetPackageCachePath
  404. $toolsetVersion = $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk'
  405. $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt"
  406. if (Test-Path $toolsetLocationFile) {
  407. $path = Get-Content $toolsetLocationFile -TotalCount 1
  408. if (Test-Path $path) {
  409. return $global:_ToolsetBuildProj = $path
  410. }
  411. }
  412. if (-not $restore) {
  413. Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored."
  414. ExitWithExitCode 1
  415. }
  416. $buildTool = InitializeBuildTool
  417. $proj = Join-Path $ToolsetDir 'restore.proj'
  418. $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' }
  419. '<Project Sdk="Microsoft.DotNet.Arcade.Sdk"/>' | Set-Content $proj
  420. MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile
  421. $path = Get-Content $toolsetLocationFile -TotalCount 1
  422. if (!(Test-Path $path)) {
  423. throw "Invalid toolset path: $path"
  424. }
  425. return $global:_ToolsetBuildProj = $path
  426. }
  427. function ExitWithExitCode([int] $exitCode) {
  428. if ($ci -and $prepareMachine) {
  429. Stop-Processes
  430. }
  431. exit $exitCode
  432. }
  433. function Stop-Processes() {
  434. Write-Host 'Killing running build processes...'
  435. foreach ($processName in $processesToStopOnExit) {
  436. Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process
  437. }
  438. }
  439. #
  440. # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function.
  441. # The arguments are automatically quoted.
  442. # Terminates the script if the build fails.
  443. #
  444. function MSBuild() {
  445. if ($pipelinesLog) {
  446. $buildTool = InitializeBuildTool
  447. # Work around issues with Azure Artifacts credential provider
  448. # https://github.com/dotnet/arcade/issues/3932
  449. if ($ci -and $buildTool.Tool -eq 'dotnet') {
  450. dotnet nuget locals http-cache -c
  451. $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20
  452. $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20
  453. Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20'
  454. Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20'
  455. }
  456. $toolsetBuildProject = InitializeToolset
  457. $path = Split-Path -parent $toolsetBuildProject
  458. $path = Join-Path $path (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')
  459. $args += "/logger:$path"
  460. }
  461. MSBuild-Core @args
  462. }
  463. #
  464. # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function.
  465. # The arguments are automatically quoted.
  466. # Terminates the script if the build fails.
  467. #
  468. function MSBuild-Core() {
  469. if ($ci) {
  470. if (!$binaryLog) {
  471. Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build.'
  472. ExitWithExitCode 1
  473. }
  474. if ($nodeReuse) {
  475. Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.'
  476. ExitWithExitCode 1
  477. }
  478. }
  479. $buildTool = InitializeBuildTool
  480. $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci"
  481. if ($warnAsError) {
  482. $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true'
  483. }
  484. else {
  485. $cmdArgs += ' /p:TreatWarningsAsErrors=false'
  486. }
  487. foreach ($arg in $args) {
  488. if ($arg -ne $null -and $arg.Trim() -ne "") {
  489. $cmdArgs += " `"$arg`""
  490. }
  491. }
  492. $exitCode = Exec-Process $buildTool.Path $cmdArgs
  493. if ($exitCode -ne 0) {
  494. Write-PipelineTelemetryError Category 'Build' -Message 'Build failed.'
  495. $buildLog = GetMSBuildBinaryLogCommandLineArgument $args
  496. if ($buildLog -ne $null) {
  497. Write-Host "See log: $buildLog" -ForegroundColor DarkGray
  498. }
  499. ExitWithExitCode $exitCode
  500. }
  501. }
  502. function GetMSBuildBinaryLogCommandLineArgument($arguments) {
  503. foreach ($argument in $arguments) {
  504. if ($argument -ne $null) {
  505. $arg = $argument.Trim()
  506. if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) {
  507. return $arg.Substring('/bl:'.Length)
  508. }
  509. if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) {
  510. return $arg.Substring('/binaryLogger:'.Length)
  511. }
  512. }
  513. }
  514. return $null
  515. }
  516. . $PSScriptRoot\pipeline-logging-functions.ps1
  517. $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..')
  518. $EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
  519. $ArtifactsDir = Join-Path $RepoRoot 'artifacts'
  520. $ToolsetDir = Join-Path $ArtifactsDir 'toolset'
  521. $ToolsDir = Join-Path $RepoRoot '.tools'
  522. $LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration
  523. $TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration
  524. $GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json
  525. # true if global.json contains a "runtimes" section
  526. $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false }
  527. Create-Directory $ToolsetDir
  528. Create-Directory $TempDir
  529. Create-Directory $LogDir
  530. Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir
  531. Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir
  532. Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir
  533. Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir
  534. Write-PipelineSetVariable -Name 'TMP' -Value $TempDir
  535. # Import custom tools configuration, if present in the repo.
  536. # Note: Import in global scope so that the script set top-level variables without qualification.
  537. if (!$disableConfigureToolsetImport) {
  538. $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1'
  539. if (Test-Path $configureToolsetScript) {
  540. . $configureToolsetScript
  541. if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) {
  542. if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) {
  543. Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code'
  544. ExitWithExitCode $LastExitCode
  545. }
  546. }
  547. }
  548. }