/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
eng/common/tools.ps1
968 строк
37 KB
dotnet-maestro[bot]
[main] Source code updates from dotnet/dotnet (#68090)
29 июл 2026, 22:04
Не верифицирован
29 июл 2026, 22:04
2e05e26
Код
Авторство
О чём код?
# Initialize variables if they aren't already defined. # These may be defined as parameters of the importing script, or set after importing this script. # CI mode - set to true on CI server for PR validation build or official build. [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. [string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } # Set to true to opt out of outputting binary log while running in CI [bool]$excludeCIBinarylog = if (Test-Path variable:excludeCIBinarylog) { $excludeCIBinarylog } else { $false } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. [bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci -and !$excludeCIBinarylog } # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-in for the feature while validating it across # our consumers. It will be deleted in the future. [bool]$pipelinesLog = if (Test-Path variable:pipelinesLog) { $pipelinesLog } else { $ci } # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). [bool]$prepareMachine = if (Test-Path variable:prepareMachine) { $prepareMachine } else { $false } # True to restore toolsets and dependencies. [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. [string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } # Specifies semi-colon delimited list of warning codes that should not be treated as errors. [string]$warnNotAsError = if (Test-Path variable:warnNotAsError) { $warnNotAsError } else { '' } # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } # True to exclude prerelease versions Visual Studio during build [bool]$excludePrereleaseVS = if (Test-Path variable:excludePrereleaseVS) { $excludePrereleaseVS } else { $false } # An array of names of processes to stop on script exit if prepareMachine is true. $processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } $disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } set-strictmode -version 2.0 $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # If specifies, provides an alternate path for getting .NET Core SDKs and Runtimes. This script will still try public sources first. [string]$runtimeSourceFeed = if (Test-Path variable:runtimeSourceFeed) { $runtimeSourceFeed } else { $null } # Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed [string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null } # True when the build is running within the VMR. [bool]$fromVMR = if (Test-Path variable:fromVMR) { $fromVMR } else { $false } [bool]$disablePipelineSetResult = if (Test-Path variable:disablePipelineSetResult) { $disablePipelineSetResult } else { $false } function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } function Unzip([string]$zipfile, [string]$outpath) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) } # This will exec a process using the console and return it's exit code. # This will not throw when the process fails. # Returns process exit code. function Exec-Process([string]$command, [string]$commandArgs) { $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $command $startInfo.Arguments = $commandArgs $startInfo.UseShellExecute = $false $startInfo.WorkingDirectory = Get-Location $process = New-Object System.Diagnostics.Process $process.StartInfo = $startInfo $process.Start() | Out-Null $finished = $false try { while (-not $process.WaitForExit(100)) { # Non-blocking loop done to allow ctr-c interrupts } $finished = $true return $global:LASTEXITCODE = $process.ExitCode } finally { # If we didn't finish then an error occurred or the user hit ctrl-c. Either # way kill the process if (-not $finished) { $process.Kill() } } } # Take the given block, print it, print what the block probably references from the current set of # variables using low-effort string matching, then run the block. # # This is intended to replace the pattern of manually copy-pasting a command, wrapping it in quotes, # and printing it using "Write-Host". The copy-paste method is more readable in build logs, but less # maintainable and less reliable. It is easy to make a mistake and modify the command without # properly updating the "Write-Host" line, resulting in misleading build logs. The probability of # this mistake makes the pattern hard to trust when it shows up in build logs. Finding the bug in # existing source code can also be difficult, because the strings are not aligned to each other and # the line may be 300+ columns long. # # By removing the need to maintain two copies of the command, Exec-BlockVerbosely avoids the issues. # # In Bash (or any posix-like shell), "set -x" prints usable verbose output automatically. # "Set-PSDebug" appears to be similar at first glance, but unfortunately, it isn't very useful: it # doesn't print any info about the variables being used by the command, which is normally the # interesting part to diagnose. function Exec-BlockVerbosely([scriptblock] $block) { Write-Host "--- Running script block:" $blockString = $block.ToString().Trim() Write-Host $blockString Write-Host "--- List of variables that might be used:" # For each variable x in the environment, check the block for a reference to x via simple "$x" or # "@x" syntax. This doesn't detect other ways to reference variables ("${x}" nor "$variable:x", # among others). It only catches what this function was originally written for: simple # command-line commands. $variableTable = Get-Variable | Where-Object { $blockString.Contains("`$$($_.Name)") -or $blockString.Contains("@$($_.Name)") } | Format-Table -AutoSize -HideTableHeaders -Wrap | Out-String Write-Host $variableTable.Trim() Write-Host "--- Executing:" & $block Write-Host "--- Done running script block!" } # createSdkLocationFile parameter enables a file being generated under the toolset directory # which writes the sdk's location into. This is only necessary for cmd --> powershell invocations # as dot sourcing isn't possible. function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } # Disable first run since we do not need all ASP.NET packages restored. $env:DOTNET_NOLOGO=1 # Disable telemetry on CI. if ($ci) { $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 } # Find the first path on %PATH% that contains the dotnet.exe if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { $dotnetExecutable = GetExecutableFileName 'dotnet' $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue if ($dotnetCmd -ne $null) { $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent } } $dotnetSdkVersion = $GlobalJson.tools.dotnet # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { if (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR } else { $dotnetRoot = Join-Path $RepoRoot '.dotnet' } if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } $env:DOTNET_INSTALL_DIR = $dotnetRoot } # Creates a temporary file under the toolset dir. # The following code block is protecting against concurrent access so that this function can # be called in parallel. if ($createSdkLocationFile) { do { $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) } until (!(Test-Path $sdkCacheFileTemp)) Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot try { Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') } catch { # Somebody beat us Remove-Item -Path $sdkCacheFileTemp } } # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. $env:PATH = "$dotnetRoot;$env:PATH" # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot } function Retry($downloadBlock, $maxRetries = 5) { $retries = 1 while($true) { try { & $downloadBlock break } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ } if (++$retries -le $maxRetries) { $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." Start-Sleep -Seconds $delayInSeconds } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." break } } } function GetDotNetInstallScript([string] $dotnetRoot) { $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' $shouldDownload = $false if (!(Test-Path $installScript)) { $shouldDownload = $true } else { # Check if the script is older than 30 days $fileAge = (Get-Date) - (Get-Item $installScript).LastWriteTime if ($fileAge.Days -gt 30) { Write-Host "Existing install script is too old, re-downloading..." $shouldDownload = $true } } if ($shouldDownload) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" Invoke-WebRequest $uri -UseBasicParsing -OutFile $installScript }) } return $installScript } function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath } function InstallDotNet([string] $dotnetRoot, [string] $version, [string] $architecture = '', [string] $runtime = '', [bool] $skipNonVersionedFiles = $false, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $noPath) { $dotnetVersionLabel = "'sdk v$version'" # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs # if you are making changes here, consider if you need to make changes there as well. if ($runtime -ne '' -and $runtime -ne 'sdk') { $runtimePath = $dotnetRoot $runtimePath = $runtimePath + "\shared" if ($runtime -eq "dotnet") { $runtimePath = $runtimePath + "\Microsoft.NETCore.App" } if ($runtime -eq "aspnetcore") { $runtimePath = $runtimePath + "\Microsoft.AspNetCore.App" } if ($runtime -eq "windowsdesktop") { $runtimePath = $runtimePath + "\Microsoft.WindowsDesktop.App" } $runtimePath = $runtimePath + "\" + $version $dotnetVersionLabel = "runtime toolset '$runtime/$architecture v$version'" if (Test-Path $runtimePath) { Write-Host " Runtime toolset '$runtime/$architecture v$version' already installed." $installSuccess = $true Exit } } $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ Version = $version InstallDir = $dotnetRoot } if ($architecture) { $installParameters.Architecture = $architecture } if ($runtime) { $installParameters.Runtime = $runtime } if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } if ($noPath) { $installParameters.NoPath = $True } $variations = @() $variations += @($installParameters) $dotnetBuilds = $installParameters.Clone() $dotnetbuilds.AzureFeed = "https://ci.dot.net/public" $variations += @($dotnetBuilds) if ($runtimeSourceFeed) { $runtimeSource = $installParameters.Clone() $runtimeSource.AzureFeed = $runtimeSourceFeed if ($runtimeSourceFeedKey) { $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) $runtimeSource.FeedCredential = $decodedString } $variations += @($runtimeSource) } $installSuccess = $false foreach ($variation in $variations) { if ($variation | Get-Member AzureFeed) { $location = $variation.AzureFeed } else { $location = "public location"; } Write-Host " Attempting to install $dotnetVersionLabel from $location." try { & $installScript @variation $installSuccess = $true break } catch { Write-Host " Failed to install $dotnetVersionLabel from $location." } } if (-not $installSuccess) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install $dotnetVersionLabel from any of the specified locations." ExitWithExitCode 1 } } # # Locates Visual Studio MSBuild installation. # The preference order for MSBuild to use is as follows: # # 1. MSBuild from an active VS command prompt # 2. MSBuild from a compatible VS installation # # Returns full path to msbuild.exe. # Throws on failure. # function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { if (-not (IsWindowsPlatform)) { throw "Cannot initialize Visual Studio on non-Windows" } if (Test-Path variable:global:_MSBuildExe) { return $global:_MSBuildExe } # Minimum VS version to require. $vsMinVersionReqdStr = '18.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { $vsRequirements = $GlobalJson.tools.vs } else { $vsRequirements = New-Object PSObject -Property @{ version = $vsMinVersionReqdStr } } } $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path } # Report error - the developer environment is initialized with incompatible VS version. throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" } } # Locate Visual Studio installation. $vsInfo = LocateVisualStudio $vsRequirements if ($vsInfo -ne $null) { # Ensure vsInstallDir has a trailing slash $vsInstallDir = Join-Path $vsInfo.installationPath "\" $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { throw 'Unable to find Visual Studio that has required version and components installed' } $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" # Use the MSBuild matching the host's process architecture (e.g. amd64 or arm64), # falling back to the 32-bit MSBuild in the root Bin folder when no matching subfolder exists. # Determine the architecture of the current process, accounting for a 32-bit process # running on a 64-bit OS (PROCESSOR_ARCHITEW6432 holds the real machine architecture). $local:ProcessArchitecture = $env:PROCESSOR_ARCHITECTURE if (($local:ProcessArchitecture -eq 'x86') -and ($env:PROCESSOR_ARCHITEW6432)) { $local:ProcessArchitecture = $env:PROCESSOR_ARCHITEW6432 } # Map the architecture to the corresponding MSBuild subfolder. The 32-bit MSBuild lives in the # root Bin folder, so x86 maps to an empty subfolder. $local:MSBuildArchSubFolder = switch ($local:ProcessArchitecture) { 'AMD64' { 'amd64' } 'ARM64' { 'arm64' } default { '' } } $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" if ($local:MSBuildArchSubFolder) { $local:ArchMSBuildExe = Join-Path $local:BinFolder (Join-Path $local:MSBuildArchSubFolder "msbuild.exe") if (Test-Path $local:ArchMSBuildExe) { $global:_MSBuildExe = $local:ArchMSBuildExe } } return $global:_MSBuildExe } function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { $env:VSINSTALLDIR = $vsInstallDir Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" if (Test-Path $vsSdkInstallDir) { Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir $env:VSSDKInstall = $vsSdkInstallDir } } # # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # # The following properties of tools.vs are recognized: # "version": "{major}.{minor}" # Two part minimal VS version, e.g. "15.9", "16.0", etc. # "components": ["componentId1", "componentId2", ...] # Array of ids of workload components that must be available in the VS instance. # See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 # # Returns JSON describing the located VS instance (same format as returned by vswhere), # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ if (-not (IsWindowsPlatform)) { throw "Cannot run vswhere on non-Windows platforms." } if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { $vswhereVersion = '3.1.7' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir Write-Host "Downloading vswhere $vswhereVersion" $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit Retry({ Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -UseBasicParsing -OutFile $vswhereExe }) } if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs' -ErrorAction SilentlyContinue) { $vsRequirements = $GlobalJson.tools.vs } else { $vsRequirements = $null } } $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') if (!$excludePrereleaseVS) { $args += '-prerelease' } if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'version' -ErrorAction SilentlyContinue)) { $args += '-version' $args += $vsRequirements.version } if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'components' -ErrorAction SilentlyContinue)) { foreach ($component in $vsRequirements.components) { $args += '-requires' $args += $component } } $vsInfo =& $vsWhereExe $args | ConvertFrom-Json if ($lastExitCode -ne 0) { return $null } if ($null -eq $vsInfo -or $vsInfo.Count -eq 0) { throw "No instance of Visual Studio meeting the requirements specified was found. Requirements: $($args -join ' ')" return $null } # use first matching instance return $vsInfo[0] } function InitializeBuildTool() { # Allow a caller (e.g. a bootstrap script running out-of-proc) to inject the build tool via # environment variables instead of the in-proc $global:_BuildTool variable. Only Path and # Command are consumed by the MSBuild function below, so those are all that's needed. if ($env:_BuildToolPath) { return $global:_BuildTool = @{ Path = $env:_BuildToolPath Command = $env:_BuildToolCommand } } if (Test-Path variable:global:_BuildTool) { # If the requested msbuild parameters do not match, clear the cached variables. if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { Remove-Item variable:global:_BuildTool Remove-Item variable:global:_MSBuildExe } else { return $global:_BuildTool } } if (-not $msbuildEngine) { $msbuildEngine = GetDefaultMSBuildEngine } # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { $dotnetRoot = InitializeDotNetCli -install:$restore } if ($msbuildEngine -eq 'dotnet') { if (!$dotnetRoot) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') $buildTool = @{ Path = $dotnetPath; Command = 'msbuild' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; ExcludePrereleaseVS = $excludePrereleaseVS } } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } return $global:_BuildTool = $buildTool } function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { return 'vs' } if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { return 'dotnet' } Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } function InitializeNuGetPackageCachePath() { if ($env:NUGET_PACKAGES -eq $null) { # Use local cache on CI to ensure deterministic build. # use global cache in dev builds to avoid cost of downloading packages. # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 if ($useGlobalNuGetCache) { $userProfile = if (IsWindowsPlatform) { $env:UserProfile } else { $env:HOME } $env:NUGET_PACKAGES = [IO.Path]::Combine($userProfile, '.nuget', 'packages') + [IO.Path]::DirectorySeparatorChar } else { $env:NUGET_PACKAGES = [IO.Path]::Combine($RepoRoot, '.packages') + [IO.Path]::DirectorySeparatorChar } } return $env:NUGET_PACKAGES } # Returns a full path to an Arcade SDK task project file. function GetSdkTaskProject([string]$taskName) { $toolsetDir = Split-Path (InitializeToolset) -Parent $proj = Join-Path $toolsetDir "$taskName.proj" if (Test-Path $proj) { return $proj } throw "Unable to find $taskName.proj in toolset at: $toolsetDir" } function InitializeNativeTools() { if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ InstallDirectory = "$ToolsDir" } } if ($env:NativeToolsOnMachine) { Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." $nativeArgs += @{ PathPromotion = $true } } & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs } } function Read-ArcadeSdkVersion() { return $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' } function InitializeToolset() { # For Unified Build/Source-build support, check whether the environment variable is # set. If it is, then use this as the toolset build project. if ($env:_InitializeToolset -ne $null) { return $global:_InitializeToolset = $env:_InitializeToolset } if (Test-Path variable:global:_InitializeToolset) { return $global:_InitializeToolset } $toolsetVersion = Read-ArcadeSdkVersion $toolsetToolsDir = Join-Path $ToolsetDir $toolsetVersion # Check if the toolset has already been extracted $toolsetBuildProj = $null $buildProjPath = Join-Path $toolsetToolsDir 'Build.proj' if (Test-Path $buildProjPath) { $toolsetBuildProj = $buildProjPath } if ($toolsetBuildProj -ne $null) { return $global:_InitializeToolset = $toolsetBuildProj } if (-not $restore) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } $downloadArgs = @("package", "download", "Microsoft.DotNet.Arcade.Sdk@$toolsetVersion", "--verbosity", "minimal", "--prerelease", "--output", "$nugetPackageCachePath") $nugetConfig = $env:NUGET_CONFIG if (-not $nugetConfig) { # Search for any variation of nuget.config in the RepoRoot $configFile = Get-ChildItem -Path $RepoRoot -File | Where-Object { $_.Name -ieq "nuget.config" } | Select-Object -First 1 if ($configFile) { $nugetConfig = $configFile.FullName } } if ($nugetConfig) { $downloadArgs += "--configfile" $downloadArgs += $nugetConfig } # 'dotnet package download' fails outright if any source in the repo's NuGet.config is # unavailable (for example a transport feed that was decommissioned after a release). The # Arcade SDK is always published to the public dotnet-eng feed, so if the config-driven # download fails, retry once against that feed directly (which ignores the other sources) # before giving up, so a single dead source doesn't block the build. $downloadExitCode = DotNet -ignoreFailure @downloadArgs if ($downloadExitCode) { Write-Host "Restoring the Arcade SDK from the configured sources failed; retrying from the public dotnet-eng feed." DotNet @downloadArgs --source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" } $packageDir = Join-Path $nugetPackageCachePath (Join-Path 'microsoft.dotnet.arcade.sdk' $toolsetVersion) $packageToolsetDir = Join-Path $packageDir 'toolset' if (!(Test-Path $packageToolsetDir)) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Arcade SDK package does not contain a toolset or tools folder: $packageDir" ExitWithExitCode 3 } New-Item -ItemType Directory -Path $toolsetToolsDir -Force | Out-Null Copy-Item -Path "$packageToolsetDir\*" -Destination $toolsetToolsDir -Recurse -Force if (Test-Path $buildProjPath) { $toolsetBuildProj = $buildProjPath } else { throw "Unable to find Build.proj in toolset at: $toolsetToolsDir" } return $global:_InitializeToolset = $toolsetBuildProj } function ExitWithExitCode([int] $exitCode) { if ($prepareMachine) { Stop-Processes } exit $exitCode } # Check if $LASTEXITCODE is a nonzero exit code (NZEC). If so, print a Azure Pipeline error for # diagnostics, then exit the script with the $LASTEXITCODE. function Exit-IfNZEC([string] $category = "General") { Write-Host "Exit code $LASTEXITCODE" if ($LASTEXITCODE -ne 0) { $message = "Last command failed with exit code $LASTEXITCODE." Write-PipelineTelemetryError -Force -Category $category -Message $message ExitWithExitCode $LASTEXITCODE } } function Stop-Processes() { Write-Host 'Killing running build processes...' foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } } # # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. # The arguments are automatically quoted. # Terminates the script if the build fails. # function MSBuild() { if ($ci) { if (!$binaryLog -and !$excludeCIBinarylog) { Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' ExitWithExitCode 1 } } $buildTool = InitializeBuildTool if ($pipelinesLog) { $toolsetBuildProject = InitializeToolset $basePath = Split-Path -parent $toolsetBuildProject $selectedPath = Join-Path $basePath (Join-Path 'net' 'Microsoft.DotNet.ArcadeLogging.dll') # Only inject the logger when it's present. A last-known-good Arcade used to bootstrap # the build may not ship the logger yet, so its absence must not be a hard error. # Specify the logger type explicitly so loading is deterministic. if (Test-Path $selectedPath) { $args += "/logger:Microsoft.DotNet.ArcadeLogging.PipelinesLogger,$selectedPath" } } $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" # Add -mt flag for MSBuild multithreaded mode if enabled via environment variable if ($env:MSBUILD_MT_ENABLED -eq "1") { $cmdArgs += ' -mt' } if ($warnAsError) { $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } else { $cmdArgs += ' /p:TreatWarningsAsErrors=false' } if ($warnAsError -and $warnNotAsError) { $escapedWarnNotAsError = $warnNotAsError -replace ';', '%3B' $cmdArgs += " /warnnotaserror:$warnNotAsError /p:AdditionalWarningsNotAsErrors=$escapedWarnNotAsError" } foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { $arg = $arg + "\" } $cmdArgs += " `"$arg`"" } } # Be sure quote the path in case there are spaces in the dotnet installation location. $env:ARCADE_BUILD_TOOL_COMMAND = "`"$($buildTool.Path)`" $cmdArgs" $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { # We should not Write-PipelineTaskError here because that message shows up in the build summary # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR build, or when -disablePipelineSetResult is set so the real exit code propagates. if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR -and !$disablePipelineSetResult) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error ExitWithExitCode 0 } else { ExitWithExitCode $exitCode } } } # # Executes a dotnet command with arguments passed to the function. # Terminates the script if the command fails. # function DotNet([switch]$ignoreFailure) { $dotnetRoot = InitializeDotNetCli -install:$restore $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') $cmdArgs = "" foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { $arg = $arg + "\" } $cmdArgs += " `"$arg`"" } } $env:ARCADE_BUILD_TOOL_COMMAND = "`"$dotnetPath`" $cmdArgs" $exitCode = Exec-Process $dotnetPath $cmdArgs if ($exitCode -ne 0) { # When -ignoreFailure is set, return the exit code to the caller so it can implement # its own fallback logic instead of terminating the script. if ($ignoreFailure) { return $exitCode } Write-Host "dotnet command failed with exit code $exitCode. Check errors above." -ForegroundColor Red if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR -and !$disablePipelineSetResult) { Write-PipelineSetResult -Result "Failed" -Message "dotnet command execution failed." ExitWithExitCode 0 } else { ExitWithExitCode $exitCode } } } function GetExecutableFileName($baseName) { if (IsWindowsPlatform) { return "$baseName.exe" } else { return $baseName } } function IsWindowsPlatform() { return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT } function Get-Darc($version) { $darcPath = "$TempDir\darc\$([guid]::NewGuid())" if ($version -ne $null) { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host } else { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host } return "$darcPath\darc.exe" } . $PSScriptRoot\pipeline-logging-functions.ps1 $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\') $EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') $ArtifactsDir = Join-Path $RepoRoot 'artifacts' $ToolsetDir = Join-Path $ArtifactsDir 'toolset' $ToolsDir = Join-Path $RepoRoot '.tools' $LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration $TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration $GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } Create-Directory $ToolsetDir Create-Directory $TempDir Create-Directory $LogDir # Direct MSBuild crash diagnostics (MSB4166 failure.txt files) to a known location # under artifacts/log so they are captured as build artifacts in CI. if (-not $env:MSBUILDDEBUGPATH) { $env:MSBUILDDEBUGPATH = Join-Path $LogDir 'MsbuildDebugLogs' } Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir # Import custom tools configuration, if present in the repo. # Note: Import in global scope so that the script set top-level variables without qualification. if (!$disableConfigureToolsetImport) { $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' if (Test-Path $configureToolsetScript) { . $configureToolsetScript if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' ExitWithExitCode $LastExitCode } } } } # Initialize the nuget package cache vars $nugetPackageCachePath = InitializeNuGetPackageCachePath