Skip to content

Commit adb6d51

Browse files
github-actions[bot]Copilotmitchdennydavidfowlbaronfel
authored
[release/9.4] Add .NET SDK availability check to Aspire CLI commands (#10552)
* Initial plan * Implement SDK installation check for Aspire CLI commands Co-authored-by: mitchdenny <[email protected]> * Move DotNet classes to dedicated DotNet folder and update namespaces Co-authored-by: mitchdenny <[email protected]> * Move helper class. * Enhanced CheckAsync to support minimum SDK version checking with 9.0.302 requirement Co-authored-by: mitchdenny <[email protected]> * Put check behind feature flag (default to on but allow it to be disabled). Refine error string. * Fix tests. * Change IDotNetSdkInstaller registration from Transient to Singleton Co-authored-by: davidfowl <[email protected]> * Change minimum SDK version to 9.0.100 for Linux distro compatibility Co-authored-by: baronfel <[email protected]> * Add --arch flag to dotnet --list-sdks command for architecture-specific SDK filtering Co-authored-by: baronfel <[email protected]> * Revert minimum SDK version back to 9.0.302 for product-level Aspire constraint Co-authored-by: davidfowl <[email protected]> * Add configuration override for minimum SDK version requirement Co-authored-by: mitchdenny <[email protected]> * Update error string. * Fix missing using statements. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: mitchdenny <[email protected]> Co-authored-by: Mitch Denny <[email protected]> Co-authored-by: davidfowl <[email protected]> Co-authored-by: baronfel <[email protected]>
1 parent 5447e45 commit adb6d51

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+720
-10
lines changed

src/Aspire.Cli/Certificates/CertificateService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Globalization;
6+
using Aspire.Cli.DotNet;
67
using Aspire.Cli.Interaction;
78
using Aspire.Cli.Resources;
89
using Aspire.Cli.Telemetry;

src/Aspire.Cli/Commands/AddCommand.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.CommandLine;
55
using System.Globalization;
66
using Aspire.Cli.Configuration;
7+
using Aspire.Cli.DotNet;
78
using Aspire.Cli.Interaction;
89
using Aspire.Cli.NuGet;
910
using Aspire.Cli.Projects;
@@ -23,8 +24,9 @@ internal sealed class AddCommand : BaseCommand
2324
private readonly IProjectLocator _projectLocator;
2425
private readonly IAddCommandPrompter _prompter;
2526
private readonly AspireCliTelemetry _telemetry;
27+
private readonly IDotNetSdkInstaller _sdkInstaller;
2628

27-
public AddCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache, IInteractionService interactionService, IProjectLocator projectLocator, IAddCommandPrompter prompter, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier)
29+
public AddCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache, IInteractionService interactionService, IProjectLocator projectLocator, IAddCommandPrompter prompter, AspireCliTelemetry telemetry, IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier)
2830
: base("add", AddCommandStrings.Description, features, updateNotifier)
2931
{
3032
ArgumentNullException.ThrowIfNull(runner);
@@ -33,13 +35,15 @@ public AddCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache,
3335
ArgumentNullException.ThrowIfNull(projectLocator);
3436
ArgumentNullException.ThrowIfNull(prompter);
3537
ArgumentNullException.ThrowIfNull(telemetry);
38+
ArgumentNullException.ThrowIfNull(sdkInstaller);
3639

3740
_runner = runner;
3841
_nuGetPackageCache = nuGetPackageCache;
3942
_interactionService = interactionService;
4043
_projectLocator = projectLocator;
4144
_prompter = prompter;
4245
_telemetry = telemetry;
46+
_sdkInstaller = sdkInstaller;
4347

4448
var integrationArgument = new Argument<string>("integration");
4549
integrationArgument.Description = AddCommandStrings.IntegrationArgumentDescription;
@@ -61,6 +65,12 @@ public AddCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache,
6165

6266
protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
6367
{
68+
// Check if the .NET SDK is available
69+
if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, cancellationToken))
70+
{
71+
return ExitCodeConstants.SdkNotInstalled;
72+
}
73+
6474
using var activity = _telemetry.ActivitySource.StartActivity(this.Name);
6575

6676
var outputCollector = new OutputCollector();

src/Aspire.Cli/Commands/ConfigCommand.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.CommandLine.Help;
66
using System.Globalization;
77
using Aspire.Cli.Configuration;
8+
using Aspire.Cli.DotNet;
89
using Aspire.Cli.Interaction;
910
using Aspire.Cli.Resources;
1011
using Aspire.Cli.Utils;
@@ -17,17 +18,20 @@ internal sealed class ConfigCommand : BaseCommand
1718
private readonly IConfiguration _configuration;
1819
private readonly IConfigurationService _configurationService;
1920
private readonly IInteractionService _interactionService;
21+
private readonly IDotNetSdkInstaller _sdkInstaller;
2022

21-
public ConfigCommand(IConfiguration configuration, IConfigurationService configurationService, IInteractionService interactionService, IFeatures features, ICliUpdateNotifier updateNotifier)
23+
public ConfigCommand(IConfiguration configuration, IConfigurationService configurationService, IInteractionService interactionService, IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier)
2224
: base("config", ConfigCommandStrings.Description, features, updateNotifier)
2325
{
2426
ArgumentNullException.ThrowIfNull(configuration);
2527
ArgumentNullException.ThrowIfNull(configurationService);
2628
ArgumentNullException.ThrowIfNull(interactionService);
29+
ArgumentNullException.ThrowIfNull(sdkInstaller);
2730

2831
_configuration = configuration;
2932
_configurationService = configurationService;
3033
_interactionService = interactionService;
34+
_sdkInstaller = sdkInstaller;
3135

3236
var getCommand = new GetCommand(configurationService, _interactionService, features, updateNotifier);
3337
var setCommand = new SetCommand(configurationService, _interactionService, features, updateNotifier);

src/Aspire.Cli/Commands/DeployCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.CommandLine.Parsing;
55
using System.Globalization;
66
using Aspire.Cli.Configuration;
7+
using Aspire.Cli.DotNet;
78
using Aspire.Cli.Interaction;
89
using Aspire.Cli.Projects;
910
using Aspire.Cli.Resources;
@@ -14,8 +15,8 @@ namespace Aspire.Cli.Commands;
1415

1516
internal sealed class DeployCommand : PublishCommandBase
1617
{
17-
public DeployCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier)
18-
: base("deploy", DeployCommandStrings.Description, runner, interactionService, projectLocator, telemetry, features, updateNotifier)
18+
public DeployCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier)
19+
: base("deploy", DeployCommandStrings.Description, runner, interactionService, projectLocator, telemetry, sdkInstaller, features, updateNotifier)
1920
{
2021
}
2122

src/Aspire.Cli/Commands/ExecCommand.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Aspire.Cli.Backchannel;
77
using Aspire.Cli.Certificates;
88
using Aspire.Cli.Configuration;
9+
using Aspire.Cli.DotNet;
910
using Aspire.Cli.Interaction;
1011
using Aspire.Cli.Projects;
1112
using Aspire.Cli.Resources;
@@ -24,6 +25,7 @@ internal class ExecCommand : BaseCommand
2425
private readonly IProjectLocator _projectLocator;
2526
private readonly IAnsiConsole _ansiConsole;
2627
private readonly AspireCliTelemetry _telemetry;
28+
private readonly IDotNetSdkInstaller _sdkInstaller;
2729

2830
public ExecCommand(
2931
IDotNetCliRunner runner,
@@ -32,6 +34,7 @@ public ExecCommand(
3234
IProjectLocator projectLocator,
3335
IAnsiConsole ansiConsole,
3436
AspireCliTelemetry telemetry,
37+
IDotNetSdkInstaller sdkInstaller,
3538
IFeatures features,
3639
ICliUpdateNotifier updateNotifier)
3740
: base("exec", ExecCommandStrings.Description, features, updateNotifier)
@@ -42,13 +45,15 @@ public ExecCommand(
4245
ArgumentNullException.ThrowIfNull(projectLocator);
4346
ArgumentNullException.ThrowIfNull(ansiConsole);
4447
ArgumentNullException.ThrowIfNull(telemetry);
48+
ArgumentNullException.ThrowIfNull(sdkInstaller);
4549

4650
_runner = runner;
4751
_interactionService = interactionService;
4852
_certificateService = certificateService;
4953
_projectLocator = projectLocator;
5054
_ansiConsole = ansiConsole;
5155
_telemetry = telemetry;
56+
_sdkInstaller = sdkInstaller;
5257

5358
var projectOption = new Option<FileInfo?>("--project");
5459
projectOption.Description = ExecCommandStrings.ProjectArgumentDescription;
@@ -67,6 +72,12 @@ public ExecCommand(
6772

6873
protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
6974
{
75+
// Check if the .NET SDK is available
76+
if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, cancellationToken))
77+
{
78+
return ExitCodeConstants.SdkNotInstalled;
79+
}
80+
7081
var buildOutputCollector = new OutputCollector();
7182
var runOutputCollector = new OutputCollector();
7283

src/Aspire.Cli/Commands/NewCommand.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text.RegularExpressions;
66
using Aspire.Cli.Certificates;
77
using Aspire.Cli.Configuration;
8+
using Aspire.Cli.DotNet;
89
using Aspire.Cli.Interaction;
910
using Aspire.Cli.NuGet;
1011
using Aspire.Cli.Resources;
@@ -26,6 +27,7 @@ internal sealed class NewCommand : BaseCommand
2627
private readonly IInteractionService _interactionService;
2728
private readonly IEnumerable<ITemplate> _templates;
2829
private readonly AspireCliTelemetry _telemetry;
30+
private readonly IDotNetSdkInstaller _sdkInstaller;
2931

3032
public NewCommand(
3133
IDotNetCliRunner runner,
@@ -35,6 +37,7 @@ public NewCommand(
3537
ICertificateService certificateService,
3638
ITemplateProvider templateProvider,
3739
AspireCliTelemetry telemetry,
40+
IDotNetSdkInstaller sdkInstaller,
3841
IFeatures features,
3942
ICliUpdateNotifier updateNotifier)
4043
: base("new", NewCommandStrings.Description, features, updateNotifier)
@@ -46,13 +49,15 @@ public NewCommand(
4649
ArgumentNullException.ThrowIfNull(interactionService);
4750
ArgumentNullException.ThrowIfNull(templateProvider);
4851
ArgumentNullException.ThrowIfNull(telemetry);
52+
ArgumentNullException.ThrowIfNull(sdkInstaller);
4953

5054
_runner = runner;
5155
_nuGetPackageCache = nuGetPackageCache;
5256
_certificateService = certificateService;
5357
_prompter = prompter;
5458
_interactionService = interactionService;
5559
_telemetry = telemetry;
60+
_sdkInstaller = sdkInstaller;
5661

5762
var nameOption = new Option<string>("--name", "-n");
5863
nameOption.Description = NewCommandStrings.NameArgumentDescription;
@@ -101,6 +106,12 @@ private async Task<ITemplate> GetProjectTemplateAsync(ParseResult parseResult, C
101106

102107
protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
103108
{
109+
// Check if the .NET SDK is available
110+
if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, cancellationToken))
111+
{
112+
return ExitCodeConstants.SdkNotInstalled;
113+
}
114+
104115
using var activity = _telemetry.ActivitySource.StartActivity(this.Name);
105116

106117
var template = await GetProjectTemplateAsync(parseResult, cancellationToken);

src/Aspire.Cli/Commands/PublishCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.CommandLine.Parsing;
55
using System.Globalization;
66
using Aspire.Cli.Configuration;
7+
using Aspire.Cli.DotNet;
78
using Aspire.Cli.Interaction;
89
using Aspire.Cli.Projects;
910
using Aspire.Cli.Resources;
@@ -34,8 +35,8 @@ internal sealed class PublishCommand : PublishCommandBase
3435
{
3536
private readonly IPublishCommandPrompter _prompter;
3637

37-
public PublishCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, IPublishCommandPrompter prompter, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier)
38-
: base("publish", PublishCommandStrings.Description, runner, interactionService, projectLocator, telemetry, features, updateNotifier)
38+
public PublishCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, IPublishCommandPrompter prompter, AspireCliTelemetry telemetry, IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier)
39+
: base("publish", PublishCommandStrings.Description, runner, interactionService, projectLocator, telemetry, sdkInstaller, features, updateNotifier)
3940
{
4041
ArgumentNullException.ThrowIfNull(prompter);
4142
_prompter = prompter;

src/Aspire.Cli/Commands/PublishCommandBase.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Globalization;
99
using Aspire.Cli.Backchannel;
1010
using Aspire.Cli.Configuration;
11+
using Aspire.Cli.DotNet;
1112
using Aspire.Cli.Interaction;
1213
using Aspire.Cli.Projects;
1314
using Aspire.Cli.Resources;
@@ -25,6 +26,7 @@ internal abstract class PublishCommandBase : BaseCommand
2526
protected readonly IInteractionService _interactionService;
2627
protected readonly IProjectLocator _projectLocator;
2728
protected readonly AspireCliTelemetry _telemetry;
29+
protected readonly IDotNetSdkInstaller _sdkInstaller;
2830

2931
private static bool IsCompletionStateComplete(string completionState) =>
3032
completionState is CompletionStates.Completed or CompletionStates.CompletedWithWarning or CompletionStates.CompletedWithError;
@@ -35,18 +37,20 @@ private static bool IsCompletionStateError(string completionState) =>
3537
private static bool IsCompletionStateWarning(string completionState) =>
3638
completionState == CompletionStates.CompletedWithWarning;
3739

38-
protected PublishCommandBase(string name, string description, IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier)
40+
protected PublishCommandBase(string name, string description, IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IDotNetSdkInstaller sdkInstaller, IFeatures features, ICliUpdateNotifier updateNotifier)
3941
: base(name, description, features, updateNotifier)
4042
{
4143
ArgumentNullException.ThrowIfNull(runner);
4244
ArgumentNullException.ThrowIfNull(interactionService);
4345
ArgumentNullException.ThrowIfNull(projectLocator);
4446
ArgumentNullException.ThrowIfNull(telemetry);
47+
ArgumentNullException.ThrowIfNull(sdkInstaller);
4548

4649
_runner = runner;
4750
_interactionService = interactionService;
4851
_projectLocator = projectLocator;
4952
_telemetry = telemetry;
53+
_sdkInstaller = sdkInstaller;
5054

5155
var projectOption = new Option<FileInfo?>("--project")
5256
{
@@ -76,6 +80,12 @@ protected PublishCommandBase(string name, string description, IDotNetCliRunner r
7680

7781
protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
7882
{
83+
// Check if the .NET SDK is available
84+
if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, cancellationToken))
85+
{
86+
return ExitCodeConstants.SdkNotInstalled;
87+
}
88+
7989
var buildOutputCollector = new OutputCollector();
8090
var operationOutputCollector = new OutputCollector();
8191

src/Aspire.Cli/Commands/RunCommand.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Aspire.Cli.Backchannel;
77
using Aspire.Cli.Certificates;
88
using Aspire.Cli.Configuration;
9+
using Aspire.Cli.DotNet;
910
using Aspire.Cli.Interaction;
1011
using Aspire.Cli.Projects;
1112
using Aspire.Cli.Resources;
@@ -27,6 +28,7 @@ internal sealed class RunCommand : BaseCommand
2728
private readonly IAnsiConsole _ansiConsole;
2829
private readonly AspireCliTelemetry _telemetry;
2930
private readonly IConfiguration _configuration;
31+
private readonly IDotNetSdkInstaller _sdkInstaller;
3032

3133
public RunCommand(
3234
IDotNetCliRunner runner,
@@ -36,6 +38,7 @@ public RunCommand(
3638
IAnsiConsole ansiConsole,
3739
AspireCliTelemetry telemetry,
3840
IConfiguration configuration,
41+
IDotNetSdkInstaller sdkInstaller,
3942
IFeatures features,
4043
ICliUpdateNotifier updateNotifier
4144
)
@@ -48,6 +51,7 @@ ICliUpdateNotifier updateNotifier
4851
ArgumentNullException.ThrowIfNull(ansiConsole);
4952
ArgumentNullException.ThrowIfNull(telemetry);
5053
ArgumentNullException.ThrowIfNull(configuration);
54+
ArgumentNullException.ThrowIfNull(sdkInstaller);
5155

5256
_runner = runner;
5357
_interactionService = interactionService;
@@ -56,6 +60,7 @@ ICliUpdateNotifier updateNotifier
5660
_ansiConsole = ansiConsole;
5761
_telemetry = telemetry;
5862
_configuration = configuration;
63+
_sdkInstaller = sdkInstaller;
5964

6065
var projectOption = new Option<FileInfo?>("--project");
6166
projectOption.Description = RunCommandStrings.ProjectArgumentDescription;
@@ -70,6 +75,12 @@ ICliUpdateNotifier updateNotifier
7075

7176
protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
7277
{
78+
// Check if the .NET SDK is available
79+
if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, _interactionService, cancellationToken))
80+
{
81+
return ExitCodeConstants.SdkNotInstalled;
82+
}
83+
7384
var buildOutputCollector = new OutputCollector();
7485
var runOutputCollector = new OutputCollector();
7586

src/Aspire.Cli/DotNetCliRunner.cs renamed to src/Aspire.Cli/DotNet/DotNetCliRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
using Microsoft.Extensions.Logging;
1818
using NuGetPackage = Aspire.Shared.NuGetPackageCli;
1919

20-
namespace Aspire.Cli;
20+
namespace Aspire.Cli.DotNet;
2121

2222
internal interface IDotNetCliRunner
2323
{

0 commit comments

Comments
 (0)