Skip to content

Commit 6199722

Browse files
committed
Add init script which installs the .NET SDK required by global.json
1 parent b6b9b02 commit 6199722

File tree

6 files changed

+307
-4
lines changed

6 files changed

+307
-4
lines changed

CONTRIBUTING.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ for issues suitable if you are unfamiliar with roslyn.
77

88
You can also help by filing issues, participating in discussions and doing code review.
99

10+
## Building prerequisites
11+
12+
* Visual Studio 2017 (Community Edition or higher) is required for building this repository.
13+
* The version of the [.NET Core SDK](https://dotnet.microsoft.com/download/dotnet-core) as specified in the global.json file at the root of this repo.
14+
Use the init script at the root of the repo to conveniently acquire and install the right version.
15+
1016
## Implementing a diagnostic
1117

1218
1. To start working on a diagnostic, add a comment to the issue indicating you are working on implementing it.
@@ -23,7 +29,3 @@ You can also help by filing issues, participating in discussions and doing code
2329
2. A new issue was created for implementing tests for the item (e.g. #176).
2430
3. Evidence was given that the feature is currently operational, and the code appears to be a solid starting point
2531
for other contributors to continue the implementation effort.
26-
27-
## Building
28-
29-
Visual Studio 2017 (Community Edition or higher) is required for building this repository.

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ configuration:
66
- Debug
77
- Release
88
before_build:
9+
- ps: .\init.ps1 -NoRestore
910
- nuget restore
1011
skip_tags: true
1112
build:

azure-pipelines.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
BuildConfiguration: Release
2121
_debugArg: ''
2222
steps:
23+
- powershell: .\init.ps1 -NoRestore
24+
displayName: Install .NET Core SDK
25+
2326
- task: NuGetToolInstaller@0
2427
displayName: 'Use NuGet 5.3.1'
2528
inputs:

build/Install-DotNetSdk.ps1

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<#
2+
.SYNOPSIS
3+
Installs the .NET SDK specified in the global.json file at the root of this repository,
4+
along with supporting .NET Core runtimes used for testing.
5+
.DESCRIPTION
6+
This MAY not require elevation, as the SDK and runtimes are installed locally to this repo location,
7+
unless `-InstallLocality machine` is specified.
8+
.PARAMETER InstallLocality
9+
A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
10+
Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
11+
Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
12+
Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
13+
When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
14+
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`.
15+
Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
16+
#>
17+
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
18+
Param (
19+
[ValidateSet('repo','user','machine')]
20+
[string]$InstallLocality='user'
21+
)
22+
23+
$DotNetInstallScriptRoot = "$PSScriptRoot/../obj/tools"
24+
if (!(Test-Path $DotNetInstallScriptRoot)) { New-Item -ItemType Directory -Path $DotNetInstallScriptRoot | Out-Null }
25+
$DotNetInstallScriptRoot = Resolve-Path $DotNetInstallScriptRoot
26+
27+
# Look up actual required .NET Core SDK version from global.json
28+
$globalJson = Get-Content -Path "$PSScriptRoot\..\global.json" | ConvertFrom-Json
29+
$sdkVersion = $globalJson.sdk.version
30+
31+
# Search for all .NET Core runtime versions referenced from MSBuild projects and arrange to install them.
32+
$runtimeVersions = @()
33+
Get-ChildItem "$PSScriptRoot\..\*.*proj" -Recurse |% {
34+
$projXml = [xml](Get-Content -Path $_)
35+
$targetFrameworks = $projXml.Project.PropertyGroup.TargetFramework
36+
if (!$targetFrameworks) {
37+
$targetFrameworks = $projXml.Project.PropertyGroup.TargetFrameworks
38+
if ($targetFrameworks) {
39+
$targetFrameworks = $targetFrameworks -Split ';'
40+
}
41+
}
42+
$targetFrameworks |? { $_ -match 'netcoreapp(\d+\.\d+)' } |% {
43+
$runtimeVersions += $Matches[1]
44+
}
45+
}
46+
47+
Function Get-FileFromWeb([Uri]$Uri, $OutDir) {
48+
$OutFile = Join-Path $OutDir $Uri.Segments[-1]
49+
if (!(Test-Path $OutFile)) {
50+
Write-Verbose "Downloading $Uri..."
51+
try {
52+
(New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile)
53+
} finally {
54+
# This try/finally causes the script to abort
55+
}
56+
}
57+
58+
$OutFile
59+
}
60+
61+
Function Get-InstallerExe($Version, [switch]$Runtime) {
62+
$sdkOrRuntime = 'Sdk'
63+
if ($Runtime) { $sdkOrRuntime = 'Runtime' }
64+
65+
# Get the latest/actual version for the specified one
66+
if (([Version]$Version).Build -eq -1) {
67+
$versionInfo = -Split (Invoke-WebRequest -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/latest.version" -UseBasicParsing)
68+
$Version = $versionInfo[-1]
69+
}
70+
71+
Get-FileFromWeb -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/dotnet-$($sdkOrRuntime.ToLowerInvariant())-$Version-win-x64.exe" -OutDir "$DotNetInstallScriptRoot"
72+
}
73+
74+
Function Install-DotNet($Version, [switch]$Runtime) {
75+
if ($Runtime) { $sdkSubstring = '' } else { $sdkSubstring = 'SDK ' }
76+
Write-Host "Downloading .NET Core $sdkSubstring$Version..."
77+
$Installer = Get-InstallerExe -Version $Version -Runtime:$Runtime
78+
Write-Host "Installing .NET Core $sdkSubstring$Version..."
79+
cmd /c start /wait $Installer /install /quiet
80+
if ($LASTEXITCODE -ne 0) {
81+
throw "Failure to install .NET Core SDK"
82+
}
83+
}
84+
85+
if ($InstallLocality -eq 'machine') {
86+
if ($IsMacOS -or $IsLinux) {
87+
Write-Error "Installing the .NET Core SDK or runtime at a machine-wide location is only supported by this script on Windows."
88+
exit 1
89+
}
90+
91+
if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) {
92+
Install-DotNet -Version $sdkVersion
93+
}
94+
95+
$runtimeVersions | Get-Unique |% {
96+
if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) {
97+
Install-DotNet -Version $_ -Runtime
98+
}
99+
}
100+
101+
return
102+
}
103+
104+
$switches = @(
105+
'-Architecture','x64'
106+
)
107+
$envVars = @{
108+
# For locally installed dotnet, skip first time experience which takes a long time
109+
'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' = 'true';
110+
}
111+
112+
if ($InstallLocality -eq 'repo') {
113+
$DotNetInstallDir = "$DotNetInstallScriptRoot/.dotnet"
114+
} elseif ($env:AGENT_TOOLSDIRECTORY) {
115+
$DotNetInstallDir = "$env:AGENT_TOOLSDIRECTORY/dotnet"
116+
} else {
117+
$DotNetInstallDir = Join-Path $HOME .dotnet
118+
}
119+
120+
Write-Host "Installing .NET Core SDK and runtimes to $DotNetInstallDir" -ForegroundColor Blue
121+
122+
if ($DotNetInstallDir) {
123+
$switches += '-InstallDir',$DotNetInstallDir
124+
$envVars['DOTNET_MULTILEVEL_LOOKUP'] = '0'
125+
$envVars['DOTNET_ROOT'] = $DotNetInstallDir
126+
}
127+
128+
if ($IsMacOS -or $IsLinux) {
129+
$DownloadUri = "https://dot.net/v1/dotnet-install.sh"
130+
$DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.sh"
131+
} else {
132+
$DownloadUri = "https://dot.net/v1/dotnet-install.ps1"
133+
$DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.ps1"
134+
}
135+
136+
if (-not (Test-Path $DotNetInstallScriptPath)) {
137+
Invoke-WebRequest -Uri $DownloadUri -OutFile $DotNetInstallScriptPath -UseBasicParsing
138+
if ($IsMacOS -or $IsLinux) {
139+
chmod +x $DotNetInstallScriptPath
140+
}
141+
}
142+
143+
if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) {
144+
Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches"
145+
} else {
146+
Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches -DryRun"
147+
}
148+
149+
$switches += '-Runtime','dotnet'
150+
151+
$runtimeVersions | Get-Unique |% {
152+
if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) {
153+
Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches"
154+
} else {
155+
Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches -DryRun"
156+
}
157+
}
158+
159+
if ($PSCmdlet.ShouldProcess("Set DOTNET environment variables to discover these installed runtimes?")) {
160+
& "$PSScriptRoot/Set-EnvVars.ps1" -Variables $envVars -PrependPath $DotNetInstallDir | Out-Null
161+
}

build/Set-EnvVars.ps1

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<#
2+
.SYNOPSIS
3+
Set environment variables in the environment.
4+
Azure Pipeline and CMD environments are considered.
5+
.PARAMETER Variables
6+
A hashtable of variables to be set.
7+
.OUTPUTS
8+
A boolean indicating whether the environment variables can be expected to propagate to the caller's environment.
9+
#>
10+
[CmdletBinding(SupportsShouldProcess=$true)]
11+
Param(
12+
[Parameter(Mandatory=$true, Position=1)]
13+
$Variables,
14+
[string[]]$PrependPath
15+
)
16+
17+
if ($Variables.Count -eq 0) {
18+
return $true
19+
}
20+
21+
$cmdInstructions = !$env:TF_BUILD -and !$env:GITHUB_ACTIONS -and $env:PS1UnderCmd -eq '1'
22+
if ($cmdInstructions) {
23+
Write-Warning "Environment variables have been set that will be lost because you're running under cmd.exe"
24+
Write-Host "Environment variables that must be set manually:" -ForegroundColor Blue
25+
} else {
26+
Write-Host "Environment variables set:" -ForegroundColor Blue
27+
$envVars
28+
if ($PrependPath) {
29+
Write-Host "Paths prepended to PATH: $PrependPath"
30+
}
31+
}
32+
33+
if ($env:TF_BUILD) {
34+
Write-Host "Azure Pipelines detected. Logging commands will be used to propagate environment variables and prepend path."
35+
}
36+
37+
if ($env:GITHUB_ACTIONS) {
38+
Write-Host "GitHub Actions detected. Logging commands will be used to propagate environment variables and prepend path."
39+
}
40+
41+
$Variables.GetEnumerator() |% {
42+
Set-Item -Path env:$($_.Key) -Value $_.Value
43+
44+
# If we're running in a cloud CI, set these environment variables so they propagate.
45+
if ($env:TF_BUILD) {
46+
Write-Host "##vso[task.setvariable variable=$($_.Key);]$($_.Value)"
47+
}
48+
if ($env:GITHUB_ACTIONS) {
49+
Write-Host "::set-env name=$($_.Key)::$($_.Value)"
50+
}
51+
52+
if ($cmdInstructions) {
53+
Write-Host "SET $($_.Key)=$($_.Value)"
54+
}
55+
}
56+
57+
$pathDelimiter = ';'
58+
if ($IsMacOS -or $IsLinux) {
59+
$pathDelimiter = ':'
60+
}
61+
62+
if ($PrependPath) {
63+
$PrependPath |% {
64+
$newPathValue = "$_$pathDelimiter$env:PATH"
65+
Set-Item -Path env:PATH -Value $newPathValue
66+
if ($cmdInstructions) {
67+
Write-Host "SET PATH=$newPathValue"
68+
}
69+
70+
if ($env:TF_BUILD) {
71+
Write-Host "##vso[task.prependpath]$_"
72+
}
73+
if ($env:GITHUB_ACTIONS) {
74+
Write-Host "::add-path::$_"
75+
}
76+
}
77+
}
78+
79+
return !$cmdInstructions

init.ps1

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<#
2+
.SYNOPSIS
3+
Installs dependencies required to build and test the projects in this repository.
4+
.DESCRIPTION
5+
This MAY not require elevation, as the SDK and runtimes are installed to a per-user location,
6+
unless the `-InstallLocality` switch is specified directing to a per-repo or per-machine location.
7+
See detailed help on that switch for more information.
8+
.PARAMETER InstallLocality
9+
A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
10+
Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
11+
Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
12+
Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
13+
When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
14+
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`.
15+
Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
16+
.PARAMETER NoPrerequisites
17+
Skips the installation of prerequisite software (e.g. SDKs, tools).
18+
.PARAMETER NoRestore
19+
Skips the package restore step.
20+
#>
21+
[CmdletBinding(SupportsShouldProcess=$true)]
22+
Param (
23+
[ValidateSet('repo','user','machine')]
24+
[string]$InstallLocality='user',
25+
[Parameter()]
26+
[switch]$NoPrerequisites,
27+
[Parameter()]
28+
[switch]$NoRestore
29+
)
30+
31+
if (!$NoPrerequisites) {
32+
& "$PSScriptRoot\build\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality
33+
}
34+
35+
# Workaround nuget credential provider bug that causes very unreliable package restores on Azure Pipelines
36+
$env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20
37+
$env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20
38+
39+
Push-Location $PSScriptRoot
40+
try {
41+
$HeaderColor = 'Green'
42+
43+
if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) {
44+
Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor
45+
dotnet restore
46+
if ($lastexitcode -ne 0) {
47+
throw "Failure while restoring packages."
48+
}
49+
}
50+
}
51+
catch {
52+
Write-Error $error[0]
53+
exit $lastexitcode
54+
}
55+
finally {
56+
Pop-Location
57+
}

0 commit comments

Comments
 (0)