Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,31 +966,49 @@ private async Task CreateExecutableAsync(AppResource er, ILogger resourceLogger,
}
var spec = exe.Spec;

// 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.
// https://github.com/dotnet/aspire/blob/main/docs/specs/IDE-execution.md#launch-profile-processing-project-launch-configuration
spec.Args = null;

// An executable can be restarted so args must be reset to an empty state.
// After resetting, first apply any dotnet project related args, e.g. configuration, and then add args from the model resource.
spec.Args = [];
if (er.DcpResource.TryGetAnnotationAsObjectList<string>(CustomResource.ResourceProjectArgsAnnotation, out var projectArgs))
if (er.DcpResource.TryGetAnnotationAsObjectList<string>(CustomResource.ResourceProjectArgsAnnotation, out var projectArgs) && projectArgs.Count > 0)
{
spec.Args ??= [];
spec.Args.AddRange(projectArgs);
}

(var args, var failedToApplyArgs) = await BuildArgsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);

var launchArgs = new List<(string Value, bool IsSensitive, bool AnnotationOnly)>();

// If the executable is a project then include any command line args from the launch profile.
if (er.ModelResource is ProjectResource project)
{
// When the .NET project is launched from an IDE the launch profile args are automatically added.
// We still want to display the args in the dashboard so only add them to the custom arg annotations.
var annotationOnly = spec.ExecutionType == ExecutionType.IDE;
// Args in the launch profile is used when:
// 1. The project is run as an executable. Launch profile args are combined with app host supplied args.
// 2. The project is run by the IDE and no app host args are specified.
if (spec.ExecutionType == ExecutionType.Process || (spec.ExecutionType == ExecutionType.IDE && args.Count == 0))
{
// When the .NET project is launched from an IDE the launch profile args are automatically added.
// We still want to display the args in the dashboard so only add them to the custom arg annotations.
var annotationOnly = spec.ExecutionType == ExecutionType.IDE;

var launchProfileArgs = GetLaunchProfileArgs(project.GetEffectiveLaunchProfile()?.LaunchProfile);
launchArgs.AddRange(launchProfileArgs.Select(a => (a, isSensitive: false, annotationOnly)));
var launchProfileArgs = GetLaunchProfileArgs(project.GetEffectiveLaunchProfile()?.LaunchProfile);
launchArgs.AddRange(launchProfileArgs.Select(a => (a, isSensitive: false, annotationOnly)));
}
}

(var args, var failedToApplyArgs) = await BuildArgsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);
launchArgs.AddRange(args.Select(a => (a.Value, a.IsSensitive, annotationOnly: false)));
Comment thread
JamesNK marked this conversation as resolved.
Outdated

spec.Args.AddRange(launchArgs.Where(a => !a.AnnotationOnly).Select(a => a.Value));
var executableArgs = launchArgs.Where(a => !a.AnnotationOnly).Select(a => a.Value).ToList();
if (executableArgs.Count > 0)
{
spec.Args ??= [];
spec.Args.AddRange(executableArgs);
}

er.DcpResource.SetAnnotationAsObjectList(CustomResource.ResourceAppArgsAnnotation, launchArgs.Select(a => new AppLaunchArgumentAnnotation(a.Value, isSensitive: a.IsSensitive)));

(spec.Env, var failedToApplyConfiguration) = await BuildEnvVarsAsync(resourceLogger, er.ModelResource, cancellationToken).ConfigureAwait(false);
Expand Down
50 changes: 19 additions & 31 deletions tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,19 @@ public async Task ResourceStarted_ProjectHasReplicas_EventRaisedOnce()
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAdded(bool isIDE)
[InlineData(ExecutionType.IDE, false, null, new string[] { "--", "--test1", "--test2" })]
[InlineData(ExecutionType.IDE, true, new string[] { "--withargs-test" }, new string[] { "--withargs-test" })]
[InlineData(ExecutionType.Process, false, new string[] { "--", "--test1", "--test2" }, new string[] { "--", "--test1", "--test2" })]
[InlineData(ExecutionType.Process, true, new string[] { "--", "--test1", "--test2", "--withargs-test" }, new string[] { "--", "--test1", "--test2", "--withargs-test" })]
public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAdded(string executionType, bool addAppHostArgs, string[]? expectedArgs, string[]? expectedAnnotations)
{
var builder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions
{
AssemblyName = typeof(DistributedApplicationTests).Assembly.FullName
});

IConfiguration? configuration = null;
if (isIDE)
if (executionType == ExecutionType.IDE)
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(new Dictionary<string, string?>
Expand All @@ -127,11 +129,15 @@ public async Task CreateExecutable_LaunchProfileHasCommandLineArgs_AnnotationsAd
configuration = configurationBuilder.Build();
}

var resource = builder.AddProject<Projects.ServiceA>("ServiceA")
.WithArgs(c =>
{
c.Args.Add("--withargs-test");
}).Resource;
var resourceBuilder = builder.AddProject<Projects.ServiceA>("ServiceA");
if (addAppHostArgs)
{
resourceBuilder
.WithArgs(c =>
{
c.Args.Add("--withargs-test");
});
}

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

var exe = Assert.Single(executables);

if (isIDE)
{
var callArg = Assert.Single(exe.Spec.Args!);
Assert.Equal("--withargs-test", callArg);
}
else
{
// ignore dotnet specific args for .NET project
var callArgs = exe.Spec.Args![^4..];

Assert.Collection(callArgs,
a => Assert.Equal("--", a),
a => Assert.Equal("--test1", a),
a => Assert.Equal("--test2", a),
a => Assert.Equal("--withargs-test", a));
}
// Ignore dotnet specific args for .NET project in process execution.
var callArgs = executionType == ExecutionType.IDE ? exe.Spec.Args : exe.Spec.Args![^(expectedArgs?.Length ?? 0)..];
Assert.Equal(expectedArgs, callArgs);

Assert.True(exe.TryGetAnnotationAsObjectList<AppLaunchArgumentAnnotation>(CustomResource.ResourceAppArgsAnnotation, out var argAnnotations));

Assert.Collection(argAnnotations,
a => Assert.Equal("--", a.Argument),
a => Assert.Equal("--test1", a.Argument),
a => Assert.Equal("--test2", a.Argument),
a => Assert.Equal("--withargs-test", a.Argument));
Assert.Equal(expectedAnnotations, argAnnotations.Select(a => a.Argument));
}

[Fact]
Expand Down