Skip to content

Commit 37c42aa

Browse files
committed
Add initial installer interfaces for dnup
1 parent 4394b54 commit 37c42aa

File tree

3 files changed

+144
-93
lines changed

3 files changed

+144
-93
lines changed

src/Installer/dnup/Commands/Sdk/Install/SdkInstallCommand.cs

Lines changed: 93 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ internal class SdkInstallCommand(ParseResult result) : CommandBase(result)
1919
private readonly bool? _updateGlobalJson = result.GetValue(SdkInstallCommandParser.UpdateGlobalJsonOption);
2020
private readonly bool _interactive = result.GetValue(SdkInstallCommandParser.InteractiveOption);
2121

22-
22+
private readonly IDotnetInstaller _dotnetInstaller = new EnvironmentVariableMockDotnetInstaller();
23+
private readonly IReleaseInfoProvider _releaseInfoProvider = new EnvironmentVariableMockReleaseInfoProvider();
2324

2425
public override int Execute()
2526
{
@@ -33,17 +34,17 @@ public override int Execute()
3334

3435
//Reporter.Output.WriteLine($"Update global.json: {_updateGlobalJson}");
3536

36-
string? globalJsonPath = FindGlobalJson();
37+
var globalJsonInfo = _dotnetInstaller.GetGlobalJsonInfo(Environment.CurrentDirectory);
3738

3839
string? currentInstallPath;
39-
DefaultInstall defaultInstallState = GetDefaultInstallState(out currentInstallPath);
40+
SdkInstallType defaultInstallState = _dotnetInstaller.GetConfiguredInstallType(out currentInstallPath);
4041

4142
string? resolvedInstallPath = null;
4243

4344
string? installPathFromGlobalJson = null;
44-
if (globalJsonPath != null)
45+
if (globalJsonInfo?.GlobalJsonPath != null)
4546
{
46-
installPathFromGlobalJson = ResolveInstallPathFromGlobalJson(globalJsonPath);
47+
installPathFromGlobalJson = globalJsonInfo.SdkPath;
4748

4849
if (installPathFromGlobalJson != null && _installPath != null &&
4950
// TODO: Is there a better way to compare paths that takes into account whether the file system is case-sensitive?
@@ -62,7 +63,7 @@ public override int Execute()
6263
resolvedInstallPath = _installPath;
6364
}
6465

65-
if (resolvedInstallPath == null && defaultInstallState == DefaultInstall.User)
66+
if (resolvedInstallPath == null && defaultInstallState == SdkInstallType.User)
6667
{
6768
// If a user installation is already set up, we don't need to prompt for the install path
6869
resolvedInstallPath = currentInstallPath;
@@ -74,19 +75,19 @@ public override int Execute()
7475
{
7576
resolvedInstallPath = SpectreAnsiConsole.Prompt(
7677
new TextPrompt<string>("Where should we install the .NET SDK to?)")
77-
.DefaultValue(GetDefaultInstallPath()));
78+
.DefaultValue(_dotnetInstaller.GetDefaultDotnetInstallPath()));
7879
}
7980
else
8081
{
8182
// If no install path is specified, use the default install path
82-
resolvedInstallPath = GetDefaultInstallPath();
83+
resolvedInstallPath = _dotnetInstaller.GetDefaultDotnetInstallPath();
8384
}
8485
}
8586

8687
string? channelFromGlobalJson = null;
87-
if (globalJsonPath != null)
88+
if (globalJsonInfo?.GlobalJsonPath != null)
8889
{
89-
channelFromGlobalJson = ResolveChannelFromGlobalJson(globalJsonPath);
90+
channelFromGlobalJson = ResolveChannelFromGlobalJson(globalJsonInfo.GlobalJsonPath);
9091
}
9192

9293
bool? resolvedUpdateGlobalJson = null;
@@ -107,7 +108,7 @@ public override int Execute()
107108

108109
if (channelFromGlobalJson != null)
109110
{
110-
SpectreAnsiConsole.WriteLine($".NET SDK {channelFromGlobalJson} will be installed since {globalJsonPath} specifies that version.");
111+
SpectreAnsiConsole.WriteLine($".NET SDK {channelFromGlobalJson} will be installed since {globalJsonInfo?.GlobalJsonPath} specifies that version.");
111112

112113
resolvedChannel = channelFromGlobalJson;
113114
}
@@ -120,7 +121,7 @@ public override int Execute()
120121
if (_interactive)
121122
{
122123

123-
SpectreAnsiConsole.WriteLine("Available supported channels: " + string.Join(' ', GetAvailableChannels()));
124+
SpectreAnsiConsole.WriteLine("Available supported channels: " + string.Join(' ', _releaseInfoProvider.GetAvailableChannels()));
124125
SpectreAnsiConsole.WriteLine("You can also specify a specific version (for example 9.0.304).");
125126

126127
resolvedChannel = SpectreAnsiConsole.Prompt(
@@ -140,13 +141,13 @@ public override int Execute()
140141
// If global.json specified an install path, we don't prompt for setting the default install path (since you probably don't want to do that for a repo-local path)
141142
if (_interactive && installPathFromGlobalJson == null)
142143
{
143-
if (defaultInstallState == DefaultInstall.None)
144+
if (defaultInstallState == SdkInstallType.None)
144145
{
145146
resolvedSetDefaultInstall = SpectreAnsiConsole.Confirm(
146147
$"Do you want to set the install path ({resolvedInstallPath}) as the default dotnet install? This will update the PATH and DOTNET_ROOT environment variables.",
147148
defaultValue: true);
148149
}
149-
else if (defaultInstallState == DefaultInstall.User)
150+
else if (defaultInstallState == SdkInstallType.User)
150151
{
151152
// Another case where we need to compare paths and the comparison may or may not need to be case-sensitive
152153
if (resolvedInstallPath.Equals(currentInstallPath, StringComparison.OrdinalIgnoreCase))
@@ -160,7 +161,7 @@ public override int Execute()
160161
defaultValue: false);
161162
}
162163
}
163-
else if (defaultInstallState == DefaultInstall.Admin)
164+
else if (defaultInstallState == SdkInstallType.Admin)
164165
{
165166
SpectreAnsiConsole.WriteLine($"You have an existing admin install of .NET in {currentInstallPath}. We can configure your system to use the new install of .NET " +
166167
$"in {resolvedInstallPath} instead. This would mean that the admin install of .NET would no longer be accessible from the PATH or from Visual Studio.");
@@ -169,7 +170,7 @@ public override int Execute()
169170
$"Do you want to set the user install path ({resolvedInstallPath}) as the default dotnet install? This will update the PATH and DOTNET_ROOT environment variables.",
170171
defaultValue: true);
171172
}
172-
else if (defaultInstallState == DefaultInstall.Inconsistent)
173+
else if (defaultInstallState == SdkInstallType.Inconsistent)
173174
{
174175
// TODO: Figure out what to do here
175176
resolvedSetDefaultInstall = false;
@@ -183,14 +184,14 @@ public override int Execute()
183184

184185
List<string> additionalVersionsToInstall = new();
185186

186-
var resolvedChannelVersion = ResolveChannelVersion(resolvedChannel);
187+
var resolvedChannelVersion = _releaseInfoProvider.GetLatestVersion(resolvedChannel);
187188

188-
if (resolvedSetDefaultInstall == true && defaultInstallState == DefaultInstall.Admin)
189+
if (resolvedSetDefaultInstall == true && defaultInstallState == SdkInstallType.Admin)
189190
{
190191
if (_interactive)
191192
{
192-
var latestAdminVersion = GetLatestInstalledAdminVersion();
193-
if (new ReleaseVersion(resolvedChannelVersion) < new ReleaseVersion(latestAdminVersion))
193+
var latestAdminVersion = _dotnetInstaller.GetLatestInstalledAdminVersion();
194+
if (latestAdminVersion != null && new ReleaseVersion(resolvedChannelVersion) < new ReleaseVersion(latestAdminVersion))
194195
{
195196
SpectreAnsiConsole.WriteLine($"Since the admin installs of the .NET SDK will no longer be accessible, we recommend installing the latest admin installed " +
196197
$"version ({latestAdminVersion}) to the new user install location. This will make sure this version of the .NET SDK continues to be used for projects that don't specify a .NET SDK version in global.json.");
@@ -208,7 +209,7 @@ public override int Execute()
208209
}
209210
}
210211

211-
212+
212213

213214
SpectreAnsiConsole.MarkupInterpolated($"Installing .NET SDK [blue]{resolvedChannelVersion}[/] to [blue]{resolvedInstallPath}[/]...");
214215

@@ -225,12 +226,12 @@ public override int Execute()
225226
List<Action> additionalDownloads = additionalVersionsToInstall.Select(version =>
226227
{
227228
var additionalTask = ctx.AddTask($"Downloading .NET SDK {version}");
228-
return (Action) (() =>
229+
return (Action)(() =>
229230
{
230231
Download(downloadLink, httpClient, additionalTask);
231232
});
232233
}).ToList();
233-
234+
234235
Download(downloadLink, httpClient, task);
235236

236237

@@ -272,107 +273,108 @@ void Download(string url, HttpClient httpClient, ProgressTask task)
272273
// }
273274
//}
274275

275-
for (int i=0; i < 100; i++)
276+
for (int i = 0; i < 100; i++)
276277
{
277278
task.Increment(1);
278279
Thread.Sleep(20); // Simulate some work
279280
}
280281
task.Value = 100;
281282
}
282283

283-
284-
string? FindGlobalJson()
285-
{
286-
//return null;
287-
return @"d:\git\dotnet-sdk\global.json";
288-
}
289-
290-
string? ResolveInstallPathFromGlobalJson(string globalJsonPath)
291-
{
292-
return Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_SDK_INSTALL_PATH");
293-
}
294-
295284
string? ResolveChannelFromGlobalJson(string globalJsonPath)
296285
{
297286
//return null;
298287
//return "9.0";
299288
return Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_SDK_CHANNEL");
300289
}
301290

302-
string GetDefaultInstallPath()
303-
{
304-
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dotnet");
305-
}
306-
307-
List<string> GetAvailableChannels()
291+
bool IsElevated()
308292
{
309-
return ["latest", "preview", "10", "10.0.1xx", "10.0.2xx", "9", "9.0.3xx", "9.0.2xx", "9.0.1xx"];
293+
return false;
310294
}
311295

312-
string ResolveChannelVersion(string channel)
296+
class EnvironmentVariableMockDotnetInstaller : IDotnetInstaller
313297
{
314-
if (channel == "preview")
315-
{
316-
return "11.0.100-preview.1.42424";
317-
}
318-
else if (channel == "latest" || channel == "10" || channel == "10.0.2xx")
298+
public GlobalJsonInfo GetGlobalJsonInfo(string initialDirectory)
319299
{
320-
return "10.0.203";
321-
}
322-
else if (channel == "10.0.1xx")
323-
{
324-
return "10.0.106";
325-
}
326-
else if (channel == "9" || channel == "9.0.3xx")
327-
{
328-
return "9.0.309";
300+
return new GlobalJsonInfo
301+
{
302+
GlobalJsonPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_PATH"),
303+
SdkVersion = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_SDK_VERSION"),
304+
AllowPrerelease = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_ALLOW_PRERELEASE"),
305+
RollForward = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_ROLLFORWARD"),
306+
SdkPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_GLOBALJSON_SDK_INSTALL_PATH")
307+
};
329308
}
330-
else if (channel == "9.0.2xx")
309+
310+
public string GetDefaultDotnetInstallPath()
331311
{
332-
return "9.0.212";
312+
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "dotnet");
333313
}
334-
else if (channel == "9.0.1xx")
314+
315+
public SdkInstallType GetConfiguredInstallType(out string? currentInstallPath)
335316
{
336-
return "9.0.115";
317+
var testHookDefaultInstall = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_DEFAULT_INSTALL");
318+
SdkInstallType returnValue = SdkInstallType.None;
319+
if (!Enum.TryParse<SdkInstallType>(testHookDefaultInstall, out returnValue))
320+
{
321+
returnValue = SdkInstallType.None;
322+
}
323+
currentInstallPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_CURRENT_INSTALL_PATH");
324+
return returnValue;
337325
}
338326

339-
return channel;
340-
341-
}
342-
343-
enum DefaultInstall
344-
{
345-
None,
346-
// Inconsistent would be when the dotnet on the path doesn't match what DOTNET_ROOT is set to
347-
Inconsistent,
348-
Admin,
349-
User
350-
}
351327

352-
DefaultInstall GetDefaultInstallState(out string? currentInstallPath)
353-
{
354-
var testHookDefaultInstall = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_DEFAULT_INSTALL");
355-
DefaultInstall returnValue = DefaultInstall.None;
356-
if (!Enum.TryParse<DefaultInstall>(testHookDefaultInstall, out returnValue))
328+
public string? GetLatestInstalledAdminVersion()
357329
{
358-
returnValue = DefaultInstall.None;
330+
var latestAdminVersion = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_LATEST_ADMIN_VERSION");
331+
if (string.IsNullOrEmpty(latestAdminVersion))
332+
{
333+
latestAdminVersion = "10.0.203";
334+
}
335+
return latestAdminVersion;
359336
}
360-
currentInstallPath = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_CURRENT_INSTALL_PATH");
361-
return returnValue;
362337
}
363338

364-
string GetLatestInstalledAdminVersion()
339+
class EnvironmentVariableMockReleaseInfoProvider : IReleaseInfoProvider
365340
{
366-
var latestAdminVersion = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_LATEST_ADMIN_VERSION");
367-
if (string.IsNullOrEmpty(latestAdminVersion))
341+
public List<string> GetAvailableChannels()
368342
{
369-
latestAdminVersion = "10.0.203";
343+
var channels = Environment.GetEnvironmentVariable("DOTNET_TESTHOOK_AVAILABLE_CHANNELS");
344+
if (string.IsNullOrEmpty(channels))
345+
{
346+
return ["latest", "preview", "10", "10.0.1xx", "10.0.2xx", "9", "9.0.3xx", "9.0.2xx", "9.0.1xx"];
347+
}
348+
return channels.Split(',').ToList();
370349
}
371-
return latestAdminVersion;
372-
}
350+
public string GetLatestVersion(string channel)
351+
{
352+
if (channel == "preview")
353+
{
354+
return "11.0.100-preview.1.42424";
355+
}
356+
else if (channel == "latest" || channel == "10" || channel == "10.0.2xx")
357+
{
358+
return "10.0.203";
359+
}
360+
else if (channel == "10.0.1xx")
361+
{
362+
return "10.0.106";
363+
}
364+
else if (channel == "9" || channel == "9.0.3xx")
365+
{
366+
return "9.0.309";
367+
}
368+
else if (channel == "9.0.2xx")
369+
{
370+
return "9.0.212";
371+
}
372+
else if (channel == "9.0.1xx")
373+
{
374+
return "9.0.115";
375+
}
373376

374-
bool IsElevated()
375-
{
376-
return false;
377+
return channel;
378+
}
377379
}
378380
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
8+
namespace Microsoft.DotNet.Tools.Bootstrapper;
9+
10+
public interface IDotnetInstaller
11+
{
12+
GlobalJsonInfo GetGlobalJsonInfo(string initialDirectory);
13+
14+
string GetDefaultDotnetInstallPath();
15+
16+
SdkInstallType GetConfiguredInstallType(out string? currentInstallPath);
17+
18+
string? GetLatestInstalledAdminVersion();
19+
}
20+
21+
public enum SdkInstallType
22+
{
23+
None,
24+
// Inconsistent would be when the dotnet on the path doesn't match what DOTNET_ROOT is set to
25+
Inconsistent,
26+
Admin,
27+
User
28+
}
29+
30+
public class GlobalJsonInfo
31+
{
32+
public string? GlobalJsonPath { get; set; }
33+
34+
public string? SdkVersion { get; set; }
35+
36+
public string? AllowPrerelease { get; set; }
37+
38+
public string? RollForward { get; set; }
39+
40+
// The sdk.path specified in the global.json, if any
41+
public string? SdkPath { get; set; }
42+
43+
}
44+
45+
public interface IReleaseInfoProvider
46+
{
47+
List<string> GetAvailableChannels();
48+
string GetLatestVersion(string channel);
49+
}

src/Installer/dnup/dnup.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
</PropertyGroup>
1717

1818
<ItemGroup>
19-
<Compile Include="..\..\Cli\dotnet\Telemetry\CIEnvironmentDetectorForTelemetry.cs" />
20-
<Compile Include="..\..\Cli\dotnet\Telemetry\ICIEnvironmentDetector.cs" />
19+
<Compile Include="..\..\Cli\dotnet\Telemetry\CIEnvironmentDetectorForTelemetry.cs" LinkBase="Shared" />
20+
<Compile Include="..\..\Cli\dotnet\Telemetry\ICIEnvironmentDetector.cs" LinkBase="Shared" />
2121
</ItemGroup>
2222

2323
<ItemGroup>

0 commit comments

Comments
 (0)