diff --git a/Directory.Build.rsp b/Directory.Build.rsp
new file mode 100644
index 00000000..9a833a03
--- /dev/null
+++ b/Directory.Build.rsp
@@ -0,0 +1,16 @@
+#------------------------------------------------------------------------------
+# This file contains command-line options that MSBuild will process as part of
+# every build, unless the "/noautoresponse" switch is specified.
+#
+# MSBuild processes the options in this file first, before processing the
+# options on the command line. As a result, options on the command line can
+# override the options in this file. However, depending on the options being
+# set, the overriding can also result in conflicts.
+#
+# NOTE: The "/noautoresponse" switch cannot be specified in this file, nor in
+# any response file that is referenced by this file.
+#------------------------------------------------------------------------------
+/nr:false
+/m
+/verbosity:minimal
+/clp:Summary;ForceNoAlign
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 5b6d4c0e..568f36ea 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -37,7 +37,7 @@ stages:
steps:
- checkout: self
clean: true
- submodules: false
+ submodules: true # keep the warnings quiet about the wiki not being enlisted
- script: |
git config --global user.name ci
git config --global user.email me@ci.com
diff --git a/azure-pipelines/Set-EnvVars.ps1 b/azure-pipelines/Set-EnvVars.ps1
new file mode 100644
index 00000000..9d14d9aa
--- /dev/null
+++ b/azure-pipelines/Set-EnvVars.ps1
@@ -0,0 +1,79 @@
+<#
+.SYNOPSIS
+ Set environment variables in the environment.
+ Azure Pipeline and CMD environments are considered.
+.PARAMETER Variables
+ A hashtable of variables to be set.
+.OUTPUTS
+ A boolean indicating whether the environment variables can be expected to propagate to the caller's environment.
+#>
+[CmdletBinding(SupportsShouldProcess=$true)]
+Param(
+ [Parameter(Mandatory=$true, Position=1)]
+ $Variables,
+ [string[]]$PrependPath
+)
+
+if ($Variables.Count -eq 0) {
+ return $true
+}
+
+$cmdInstructions = !$env:TF_BUILD -and !$env:GITHUB_ACTIONS -and $env:PS1UnderCmd -eq '1'
+if ($cmdInstructions) {
+ Write-Warning "Environment variables have been set that will be lost because you're running under cmd.exe"
+ Write-Host "Environment variables that must be set manually:" -ForegroundColor Blue
+} else {
+ Write-Host "Environment variables set:" -ForegroundColor Blue
+ Write-Host ($Variables | Out-String)
+ if ($PrependPath) {
+ Write-Host "Paths prepended to PATH: $PrependPath"
+ }
+}
+
+if ($env:TF_BUILD) {
+ Write-Host "Azure Pipelines detected. Logging commands will be used to propagate environment variables and prepend path."
+}
+
+if ($env:GITHUB_ACTIONS) {
+ Write-Host "GitHub Actions detected. Logging commands will be used to propagate environment variables and prepend path."
+}
+
+$Variables.GetEnumerator() |% {
+ Set-Item -Path env:$($_.Key) -Value $_.Value
+
+ # If we're running in a cloud CI, set these environment variables so they propagate.
+ if ($env:TF_BUILD) {
+ Write-Host "##vso[task.setvariable variable=$($_.Key);]$($_.Value)"
+ }
+ if ($env:GITHUB_ACTIONS) {
+ Write-Host "::set-env name=$($_.Key)::$($_.Value)"
+ }
+
+ if ($cmdInstructions) {
+ Write-Host "SET $($_.Key)=$($_.Value)"
+ }
+}
+
+$pathDelimiter = ';'
+if ($IsMacOS -or $IsLinux) {
+ $pathDelimiter = ':'
+}
+
+if ($PrependPath) {
+ $PrependPath |% {
+ $newPathValue = "$_$pathDelimiter$env:PATH"
+ Set-Item -Path env:PATH -Value $newPathValue
+ if ($cmdInstructions) {
+ Write-Host "SET PATH=$newPathValue"
+ }
+
+ if ($env:TF_BUILD) {
+ Write-Host "##vso[task.prependpath]$_"
+ }
+ if ($env:GITHUB_ACTIONS) {
+ Write-Host "::add-path::$_"
+ }
+ }
+}
+
+return !$cmdInstructions
diff --git a/azure-pipelines/variables/DotNetSdkVersion.ps1 b/azure-pipelines/variables/DotNetSdkVersion.ps1
new file mode 100644
index 00000000..b213fbc2
--- /dev/null
+++ b/azure-pipelines/variables/DotNetSdkVersion.ps1
@@ -0,0 +1,2 @@
+$globalJson = Get-Content -Path "$PSScriptRoot\..\..\global.json" | ConvertFrom-Json
+$globalJson.sdk.version
diff --git a/init.cmd b/init.cmd
index 502bfb6e..970285c2 100644
--- a/init.cmd
+++ b/init.cmd
@@ -1 +1,4 @@
-powershell.exe -ExecutionPolicy bypass -NoProfile -Command "& '%~dpn0.ps1'" %*
+@echo off
+SETLOCAL
+set PS1UnderCmd=1
+powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }"
diff --git a/init.ps1 b/init.ps1
old mode 100644
new mode 100755
index 72193c64..8b11d60b
--- a/init.ps1
+++ b/init.ps1
@@ -1,17 +1,53 @@
+#!/usr/bin/env pwsh
+
<#
.SYNOPSIS
-Restores all NuGet, NPM and Typings packages necessary to build this repository.
+Installs dependencies required to build and test the projects in this repository.
+.DESCRIPTION
+This MAY not require elevation, as the SDK and runtimes are installed to a per-user location,
+unless the `-InstallLocality` switch is specified directing to a per-repo or per-machine location.
+See detailed help on that switch for more information.
+.PARAMETER InstallLocality
+A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
+Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
+Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
+Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
+When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
+Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`.
+Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
+.PARAMETER NoPrerequisites
+Skips the installation of prerequisite software (e.g. SDKs, tools).
+.PARAMETER NoRestore
+Skips the package restore step.
#>
-[CmdletBinding(SupportsShouldProcess)]
+[CmdletBinding(SupportsShouldProcess=$true)]
Param(
+ [ValidateSet('repo','user','machine')]
+ [string]$InstallLocality='user',
+ [Parameter()]
+ [switch]$NoPrerequisites,
+ [Parameter()]
+ [switch]$NoRestore
)
+if (!$NoPrerequisites) {
+ & "$PSScriptRoot\tools\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality
+}
+
$oldPlatform=$env:Platform
$env:Platform='Any CPU' # Some people wander in here from a platform-specific build window.
Push-Location $PSScriptRoot
try {
- msbuild "$PSScriptRoot\src" /t:restore /v:minimal /m /nologo
+ $HeaderColor = 'Green'
+
+ if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) {
+ Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor
+ dotnet restore "$PSScriptRoot\src"
+ if ($lastexitcode -ne 0) {
+ throw "Failure while restoring packages."
+ }
+ }
Write-Host "Restoring NPM packages..." -ForegroundColor Yellow
Push-Location "$PSScriptRoot\src\nerdbank-gitversioning.npm"
@@ -25,8 +61,8 @@ try {
Write-Host "Successfully restored all dependencies" -ForegroundColor Yellow
} catch {
- # we have the try so that PS fails when we get failure exit codes from build steps.
- throw;
+ Write-Error $error[0]
+ exit $lastexitcode
} finally {
$env:Platform=$oldPlatform
Pop-Location
diff --git a/src/Cake.GitVersioning/Cake.GitVersioning.csproj b/src/Cake.GitVersioning/Cake.GitVersioning.csproj
index 331d6222..c97f2355 100644
--- a/src/Cake.GitVersioning/Cake.GitVersioning.csproj
+++ b/src/Cake.GitVersioning/Cake.GitVersioning.csproj
@@ -32,7 +32,7 @@
-
+
@@ -71,4 +71,4 @@
-
\ No newline at end of file
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 387cb264..763fdf85 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -24,6 +24,7 @@
2.0.306
+
diff --git a/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj b/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj
index 33823156..a0a31aeb 100644
--- a/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj
+++ b/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj
@@ -38,7 +38,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/src/NerdBank.GitVersioning/NerdBank.GitVersioning.csproj b/src/NerdBank.GitVersioning/NerdBank.GitVersioning.csproj
index ae5378e1..27c8bf93 100644
--- a/src/NerdBank.GitVersioning/NerdBank.GitVersioning.csproj
+++ b/src/NerdBank.GitVersioning/NerdBank.GitVersioning.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Nerdbank.GitVersioning.Tasks/Nerdbank.GitVersioning.Tasks.csproj b/src/Nerdbank.GitVersioning.Tasks/Nerdbank.GitVersioning.Tasks.csproj
index f3c3a071..1c9f00f0 100644
--- a/src/Nerdbank.GitVersioning.Tasks/Nerdbank.GitVersioning.Tasks.csproj
+++ b/src/Nerdbank.GitVersioning.Tasks/Nerdbank.GitVersioning.Tasks.csproj
@@ -76,7 +76,7 @@
-
+
diff --git a/src/nbgv/nbgv.csproj b/src/nbgv/nbgv.csproj
index a315294b..888e7838 100644
--- a/src/nbgv/nbgv.csproj
+++ b/src/nbgv/nbgv.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/nuget.config b/src/nuget.config
index ef71f32e..5ee93301 100644
--- a/src/nuget.config
+++ b/src/nuget.config
@@ -6,7 +6,6 @@
-
-
+
diff --git a/tools/Install-DotNetSdk.ps1 b/tools/Install-DotNetSdk.ps1
new file mode 100755
index 00000000..47548418
--- /dev/null
+++ b/tools/Install-DotNetSdk.ps1
@@ -0,0 +1,159 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+Installs the .NET SDK specified in the global.json file at the root of this repository,
+along with supporting .NET Core runtimes used for testing.
+.DESCRIPTION
+This MAY not require elevation, as the SDK and runtimes are installed locally to this repo location,
+unless `-InstallLocality machine` is specified.
+.PARAMETER InstallLocality
+A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
+Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
+Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
+Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
+When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
+Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`.
+Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
+#>
+[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
+Param (
+ [ValidateSet('repo','user','machine')]
+ [string]$InstallLocality='user'
+)
+
+$DotNetInstallScriptRoot = "$PSScriptRoot/../obj/tools"
+if (!(Test-Path $DotNetInstallScriptRoot)) { New-Item -ItemType Directory -Path $DotNetInstallScriptRoot -WhatIf:$false | Out-Null }
+$DotNetInstallScriptRoot = Resolve-Path $DotNetInstallScriptRoot
+
+# Look up actual required .NET Core SDK version from global.json
+$sdkVersion = & "$PSScriptRoot/../azure-pipelines/variables/DotNetSdkVersion.ps1"
+
+# Search for all .NET Core runtime versions referenced from MSBuild projects and arrange to install them.
+$runtimeVersions = @()
+Get-ChildItem "$PSScriptRoot\..\src\*.*proj","$PSScriptRoot\..\Directory.Build.props" -Recurse |% {
+ $projXml = [xml](Get-Content -Path $_)
+ $targetFrameworks = $projXml.Project.PropertyGroup.TargetFramework
+ if (!$targetFrameworks) {
+ $targetFrameworks = $projXml.Project.PropertyGroup.TargetFrameworks
+ if ($targetFrameworks) {
+ $targetFrameworks = $targetFrameworks -Split ';'
+ }
+ }
+ $targetFrameworks |? { $_ -match 'netcoreapp(\d+\.\d+)' } |% {
+ $runtimeVersions += $Matches[1]
+ }
+}
+
+Function Get-FileFromWeb([Uri]$Uri, $OutDir) {
+ $OutFile = Join-Path $OutDir $Uri.Segments[-1]
+ if (!(Test-Path $OutFile)) {
+ Write-Verbose "Downloading $Uri..."
+ try {
+ (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile)
+ } finally {
+ # This try/finally causes the script to abort
+ }
+ }
+
+ $OutFile
+}
+
+Function Get-InstallerExe($Version, [switch]$Runtime) {
+ $sdkOrRuntime = 'Sdk'
+ if ($Runtime) { $sdkOrRuntime = 'Runtime' }
+
+ # Get the latest/actual version for the specified one
+ if (([Version]$Version).Build -eq -1) {
+ $versionInfo = -Split (Invoke-WebRequest -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/latest.version" -UseBasicParsing)
+ $Version = $versionInfo[-1]
+ }
+
+ Get-FileFromWeb -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/dotnet-$($sdkOrRuntime.ToLowerInvariant())-$Version-win-x64.exe" -OutDir "$DotNetInstallScriptRoot"
+}
+
+Function Install-DotNet($Version, [switch]$Runtime) {
+ if ($Runtime) { $sdkSubstring = '' } else { $sdkSubstring = 'SDK ' }
+ Write-Host "Downloading .NET Core $sdkSubstring$Version..."
+ $Installer = Get-InstallerExe -Version $Version -Runtime:$Runtime
+ Write-Host "Installing .NET Core $sdkSubstring$Version..."
+ cmd /c start /wait $Installer /install /quiet
+ if ($LASTEXITCODE -ne 0) {
+ throw "Failure to install .NET Core SDK"
+ }
+}
+
+$switches = @(
+ '-Architecture','x64'
+)
+$envVars = @{
+ # For locally installed dotnet, skip first time experience which takes a long time
+ 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' = 'true';
+}
+
+if ($InstallLocality -eq 'machine') {
+ if ($IsWindows) {
+ if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) {
+ Install-DotNet -Version $sdkVersion
+ }
+
+ $runtimeVersions | Get-Unique |% {
+ if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) {
+ Install-DotNet -Version $_ -Runtime
+ }
+ }
+
+ return
+ } else {
+ $DotNetInstallDir = '/usr/share/dotnet'
+ }
+} elseif ($InstallLocality -eq 'repo') {
+ $DotNetInstallDir = "$DotNetInstallScriptRoot/.dotnet"
+} elseif ($env:AGENT_TOOLSDIRECTORY) {
+ $DotNetInstallDir = "$env:AGENT_TOOLSDIRECTORY/dotnet"
+} else {
+ $DotNetInstallDir = Join-Path $HOME .dotnet
+}
+
+Write-Host "Installing .NET Core SDK and runtimes to $DotNetInstallDir" -ForegroundColor Blue
+
+if ($DotNetInstallDir) {
+ $switches += '-InstallDir',$DotNetInstallDir
+ $envVars['DOTNET_MULTILEVEL_LOOKUP'] = '0'
+ $envVars['DOTNET_ROOT'] = $DotNetInstallDir
+}
+
+if ($IsMacOS -or $IsLinux) {
+ $DownloadUri = "https://dot.net/v1/dotnet-install.sh"
+ $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.sh"
+} else {
+ $DownloadUri = "https://dot.net/v1/dotnet-install.ps1"
+ $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.ps1"
+}
+
+if (-not (Test-Path $DotNetInstallScriptPath)) {
+ Invoke-WebRequest -Uri $DownloadUri -OutFile $DotNetInstallScriptPath -UseBasicParsing
+ if ($IsMacOS -or $IsLinux) {
+ chmod +x $DotNetInstallScriptPath
+ }
+}
+
+if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) {
+ Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches"
+} else {
+ Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches -DryRun"
+}
+
+$switches += '-Runtime','dotnet'
+
+$runtimeVersions | Get-Unique |% {
+ if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) {
+ Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches"
+ } else {
+ Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches -DryRun"
+ }
+}
+
+if ($PSCmdlet.ShouldProcess("Set DOTNET environment variables to discover these installed runtimes?")) {
+ & "$PSScriptRoot/../azure-pipelines/Set-EnvVars.ps1" -Variables $envVars -PrependPath $DotNetInstallDir | Out-Null
+}