Skip to content

Commit 89d5b4a

Browse files
[release/9.1] Fix arg annotations when running in IDE mode (#7722)
* Fix arg annotations when running in IDE mode * Comment * Comment * Comments and refactor --------- Co-authored-by: James Newton-King <[email protected]>
1 parent b78ef60 commit 89d5b4a

File tree

2 files changed

+65
-46
lines changed

2 files changed

+65
-46
lines changed

src/Aspire.Hosting/Dcp/DcpExecutor.cs

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -966,31 +966,31 @@ private async Task CreateExecutableAsync(AppResource er, ILogger resourceLogger,
966966
}
967967
var spec = exe.Spec;
968968

969+
// Don't create an args collection unless needed. A null args collection means a project run by the will use args provided by the launch profile.
970+
// https://github.com/dotnet/aspire/blob/main/docs/specs/IDE-execution.md#launch-profile-processing-project-launch-configuration
971+
spec.Args = null;
972+
969973
// An executable can be restarted so args must be reset to an empty state.
970974
// After resetting, first apply any dotnet project related args, e.g. configuration, and then add args from the model resource.
971-
spec.Args = [];
972-
if (er.DcpResource.TryGetAnnotationAsObjectList<string>(CustomResource.ResourceProjectArgsAnnotation, out var projectArgs))
975+
if (er.DcpResource.TryGetAnnotationAsObjectList<string>(CustomResource.ResourceProjectArgsAnnotation, out var projectArgs) && projectArgs.Count > 0)
973976
{
977+
spec.Args ??= [];
974978
spec.Args.AddRange(projectArgs);
975979
}
976980

977-
var launchArgs = new List<(string Value, bool IsSensitive, bool AnnotationOnly)>();
981+
// Get args from app host model resource.
982+
(var appHostArgs, var failedToApplyArgs) = await BuildArgsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);
978983

979-
// If the executable is a project then include any command line args from the launch profile.
980-
if (er.ModelResource is ProjectResource project)
981-
{
982-
// When the .NET project is launched from an IDE the launch profile args are automatically added.
983-
// We still want to display the args in the dashboard so only add them to the custom arg annotations.
984-
var annotationOnly = spec.ExecutionType == ExecutionType.IDE;
984+
var launchArgs = BuildLaunchArgs(er, spec, appHostArgs);
985985

986-
var launchProfileArgs = GetLaunchProfileArgs(project.GetEffectiveLaunchProfile()?.LaunchProfile);
987-
launchArgs.AddRange(launchProfileArgs.Select(a => (a, isSensitive: false, annotationOnly)));
986+
var executableArgs = launchArgs.Where(a => !a.AnnotationOnly).Select(a => a.Value).ToList();
987+
if (executableArgs.Count > 0)
988+
{
989+
spec.Args ??= [];
990+
spec.Args.AddRange(executableArgs);
988991
}
989992

990-
(var args, var failedToApplyArgs) = await BuildArgsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);
991-
launchArgs.AddRange(args.Select(a => (a.Value, a.IsSensitive, annotationOnly: false)));
992-
993-
spec.Args.AddRange(launchArgs.Where(a => !a.AnnotationOnly).Select(a => a.Value));
993+
// Arg annotations are what is displayed in the dashboard.
994994
er.DcpResource.SetAnnotationAsObjectList(CustomResource.ResourceAppArgsAnnotation, launchArgs.Select(a => new AppLaunchArgumentAnnotation(a.Value, isSensitive: a.IsSensitive)));
995995

996996
(spec.Env, var failedToApplyConfiguration) = await BuildEnvVarsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);
@@ -1003,6 +1003,37 @@ private async Task CreateExecutableAsync(AppResource er, ILogger resourceLogger,
10031003
await _kubernetesService.CreateAsync(exe, cancellationToken).ConfigureAwait(false);
10041004
}
10051005

1006+
private static List<(string Value, bool IsSensitive, bool AnnotationOnly)> BuildLaunchArgs(AppResource er, ExecutableSpec spec, List<(string Value, bool IsSensitive)> appHostArgs)
1007+
{
1008+
// Launch args is the final list of args that are displayed in the UI and possibly added to the executable spec.
1009+
// They're built from app host resource model args and any args in the effective launch profile.
1010+
// Follows behavior in the IDE execution spec when in IDE execution mode:
1011+
// https://github.com/dotnet/aspire/blob/main/docs/specs/IDE-execution.md#launch-profile-processing-project-launch-configuration
1012+
var launchArgs = new List<(string Value, bool IsSensitive, bool AnnotationOnly)>();
1013+
1014+
// If the executable is a project then include any command line args from the launch profile.
1015+
if (er.ModelResource is ProjectResource project)
1016+
{
1017+
// Args in the launch profile is used when:
1018+
// 1. The project is run as an executable. Launch profile args are combined with app host supplied args.
1019+
// 2. The project is run by the IDE and no app host args are specified.
1020+
if (spec.ExecutionType == ExecutionType.Process || (spec.ExecutionType == ExecutionType.IDE && appHostArgs.Count == 0))
1021+
{
1022+
// When the .NET project is launched from an IDE the launch profile args are automatically added.
1023+
// We still want to display the args in the dashboard so only add them to the custom arg annotations.
1024+
var annotationOnly = spec.ExecutionType == ExecutionType.IDE;
1025+
1026+
var launchProfileArgs = GetLaunchProfileArgs(project.GetEffectiveLaunchProfile()?.LaunchProfile);
1027+
launchArgs.AddRange(launchProfileArgs.Select(a => (a, isSensitive: false, annotationOnly)));
1028+
}
1029+
}
1030+
1031+
// In the situation where args are combined (process execution) the app host args are added after the launch profile args.
1032+
launchArgs.AddRange(appHostArgs.Select(a => (a.Value, a.IsSensitive, annotationOnly: false)));
1033+
1034+
return launchArgs;
1035+
}
1036+
10061037
private static List<string> GetLaunchProfileArgs(LaunchProfile? launchProfile)
10071038
{
10081039
var args = new List<string>();

tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,19 @@ public async Task ResourceStarted_ProjectHasReplicas_EventRaisedOnce()
106106
}
107107

108108
[Theory]
109-
[InlineData(true)]
110-
[InlineData(false)]
111-
public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAdded(bool isIDE)
109+
[InlineData(ExecutionType.IDE, false, null, new string[] { "--", "--test1", "--test2" })]
110+
[InlineData(ExecutionType.IDE, true, new string[] { "--withargs-test" }, new string[] { "--withargs-test" })]
111+
[InlineData(ExecutionType.Process, false, new string[] { "--", "--test1", "--test2" }, new string[] { "--", "--test1", "--test2" })]
112+
[InlineData(ExecutionType.Process, true, new string[] { "--", "--test1", "--test2", "--withargs-test" }, new string[] { "--", "--test1", "--test2", "--withargs-test" })]
113+
public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAdded(string executionType, bool addAppHostArgs, string[]? expectedArgs, string[]? expectedAnnotations)
112114
{
113115
var builder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions
114116
{
115117
AssemblyName = typeof(DistributedApplicationTests).Assembly.FullName
116118
});
117119

118120
IConfiguration? configuration = null;
119-
if (isIDE)
121+
if (executionType == ExecutionType.IDE)
120122
{
121123
var configurationBuilder = new ConfigurationBuilder();
122124
configurationBuilder.AddInMemoryCollection(new Dictionary<string, string?>
@@ -127,11 +129,15 @@ public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAd
127129
configuration = configurationBuilder.Build();
128130
}
129131

130-
var resource = builder.AddProject<Projects.ServiceA>("ServiceA")
131-
.WithArgs(c =>
132-
{
133-
c.Args.Add("--withargs-test");
134-
}).Resource;
132+
var resourceBuilder = builder.AddProject<Projects.ServiceA>("ServiceA");
133+
if (addAppHostArgs)
134+
{
135+
resourceBuilder
136+
.WithArgs(c =>
137+
{
138+
c.Args.Add("--withargs-test");
139+
});
140+
}
135141

136142
var kubernetesService = new TestKubernetesService();
137143
using var app = builder.Build();
@@ -148,30 +154,12 @@ public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAd
148154

149155
var exe = Assert.Single(executables);
150156

151-
if (isIDE)
152-
{
153-
var callArg = Assert.Single(exe.Spec.Args!);
154-
Assert.Equal("--withargs-test", callArg);
155-
}
156-
else
157-
{
158-
// ignore dotnet specific args for .NET project
159-
var callArgs = exe.Spec.Args![^4..];
160-
161-
Assert.Collection(callArgs,
162-
a => Assert.Equal("--", a),
163-
a => Assert.Equal("--test1", a),
164-
a => Assert.Equal("--test2", a),
165-
a => Assert.Equal("--withargs-test", a));
166-
}
157+
// Ignore dotnet specific args for .NET project in process execution.
158+
var callArgs = executionType == ExecutionType.IDE ? exe.Spec.Args : exe.Spec.Args![^(expectedArgs?.Length ?? 0)..];
159+
Assert.Equal(expectedArgs, callArgs);
167160

168161
Assert.True(exe.TryGetAnnotationAsObjectList<AppLaunchArgumentAnnotation>(CustomResource.ResourceAppArgsAnnotation, out var argAnnotations));
169-
170-
Assert.Collection(argAnnotations,
171-
a => Assert.Equal("--", a.Argument),
172-
a => Assert.Equal("--test1", a.Argument),
173-
a => Assert.Equal("--test2", a.Argument),
174-
a => Assert.Equal("--withargs-test", a.Argument));
162+
Assert.Equal(expectedAnnotations, argAnnotations.Select(a => a.Argument));
175163
}
176164

177165
[Fact]

0 commit comments

Comments
 (0)