-
Notifications
You must be signed in to change notification settings - Fork 971
Expose through extension methods maui build and launch arguments #19572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
c8962f2
e17dc45
548a443
0775fdd
8963292
0bf8a93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.Maui.Annotations; | ||
|
|
||
| /// <summary> | ||
| /// Annotation that registers a callback used to inspect or modify the arguments passed to | ||
| /// <c>dotnet</c> for a particular <see cref="MauiBuildStep"/> of a MAUI platform resource. | ||
| /// </summary> | ||
| internal sealed class MauiBuildArgumentsCallbackAnnotation( | ||
| MauiBuildStep step, | ||
| Func<MauiBuildArgumentsCallbackContext, Task> callback) : IResourceAnnotation | ||
| { | ||
| /// <summary> | ||
| /// Gets the build step this callback participates in. | ||
| /// </summary> | ||
| public MauiBuildStep Step { get; } = step; | ||
|
|
||
| /// <summary> | ||
| /// Gets the callback invoked with the mutable argument list for <see cref="Step"/>. | ||
| /// </summary> | ||
| public Func<MauiBuildArgumentsCallbackContext, Task> Callback { get; } = callback; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Concurrent; | ||
| using System.Diagnostics; | ||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Eventing; | ||
|
|
@@ -31,6 +32,12 @@ internal class MauiBuildQueueEventSubscriber( | |
| private static readonly ResourceStateSnapshot s_buildingState = new("Building", KnownResourceStateStyles.Info); | ||
| private static readonly ResourceStateSnapshot s_cancelledState = new(KnownResourceStates.Exited, KnownResourceStateStyles.Warn); | ||
|
|
||
| /// <summary> | ||
| /// Caches the pristine launch-argument-override arguments per resource so that launch callbacks | ||
| /// are always applied against the original base, preventing edits from accumulating across restarts. | ||
| /// </summary> | ||
| private readonly ConcurrentDictionary<string, IReadOnlyList<string>> _originalLaunchArgs = new(); | ||
|
|
||
| /// <summary> | ||
| /// Maximum time to wait for a <c>dotnet build</c> process before cancelling. | ||
| /// Prevents a hung build from blocking the queue indefinitely. | ||
|
|
@@ -101,6 +108,10 @@ await notificationService.PublishUpdateAsync(resource, s => s with | |
|
|
||
| await RunBuildAsync(resource, logger, resourceCts.Token).ConfigureAwait(false); | ||
|
|
||
| // Allow consumers to inspect/modify the launch arguments (dotnet build --no-restore | ||
| // /t:Run -p:NoBuild=true) before DCP reads the override annotation to start the process. | ||
| await ApplyLaunchArgumentCallbacksAsync(resource, resourceCts.Token).ConfigureAwait(false); | ||
|
|
||
| // Build succeeded. Keep the semaphore held until DCP starts the launch process. | ||
| // After this handler returns, DCP invokes `dotnet build --no-restore /t:Run -p:NoBuild=true` | ||
| // with the same configuration used here. The no-build/no-restore flags are important: | ||
|
|
@@ -160,6 +171,9 @@ internal virtual async Task RunBuildAsync(IResource resource, ILogger logger, Ca | |
|
|
||
| args.AddRange(buildInfo.AdditionalBuildArguments); | ||
|
|
||
| // Allow consumers to inspect/modify the compile arguments. | ||
| await ApplyBuildArgumentCallbacksAsync(resource, args, cancellationToken).ConfigureAwait(false); | ||
|
frederikstonge marked this conversation as resolved.
Outdated
|
||
|
|
||
| var psi = new ProcessStartInfo("dotnet") | ||
| { | ||
| WorkingDirectory = buildInfo.WorkingDirectory, | ||
|
|
@@ -221,6 +235,74 @@ internal virtual async Task RunBuildAsync(IResource resource, ILogger logger, Ca | |
| logger.LogInformation("Build succeeded for resource '{ResourceName}'.", resource.Name); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Invokes any registered <see cref="MauiBuildStep.Build"/> callbacks, letting consumers | ||
| /// mutate the compile <paramref name="arguments"/> in place. | ||
| /// </summary> | ||
| private static async Task ApplyBuildArgumentCallbacksAsync( | ||
| IResource resource, IList<string> arguments, CancellationToken cancellationToken) | ||
| { | ||
| var callbacks = resource.Annotations | ||
| .OfType<MauiBuildArgumentsCallbackAnnotation>() | ||
| .Where(a => a.Step == MauiBuildStep.Build) | ||
| .ToArray(); | ||
|
|
||
| if (callbacks.Length == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var context = new MauiBuildArgumentsCallbackContext(MauiBuildStep.Build, arguments, resource, cancellationToken); | ||
|
|
||
| foreach (var callback in callbacks) | ||
| { | ||
| await callback.Callback(context).ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Applies any registered <see cref="MauiBuildStep.Launch"/> callbacks to the resource's | ||
| /// <see cref="ProjectLaunchArgsOverrideAnnotation"/> before DCP reads it to build the launch command. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The override annotation's arguments are immutable, so callbacks operate on a copy of the pristine | ||
| /// base arguments (cached per resource) and the annotation is swapped for one carrying the result. | ||
| /// Caching the base makes repeated starts idempotent — edits never accumulate across restarts. | ||
| /// </remarks> | ||
| private async Task ApplyLaunchArgumentCallbacksAsync(IResource resource, CancellationToken cancellationToken) | ||
| { | ||
| var callbacks = resource.Annotations | ||
| .OfType<MauiBuildArgumentsCallbackAnnotation>() | ||
| .Where(a => a.Step == MauiBuildStep.Launch) | ||
| .ToArray(); | ||
|
|
||
| if (callbacks.Length == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (!resource.TryGetLastAnnotation<ProjectLaunchArgsOverrideAnnotation>(out var launchOverride)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Apply callbacks against the pristine base arguments so restarts do not accumulate edits. | ||
| var baseArgs = _originalLaunchArgs.GetOrAdd(resource.Name, _ => launchOverride.Arguments); | ||
| var arguments = new List<string>(baseArgs); | ||
|
|
||
| var context = new MauiBuildArgumentsCallbackContext(MauiBuildStep.Launch, arguments, resource, cancellationToken); | ||
|
|
||
| foreach (var callback in callbacks) | ||
| { | ||
| await callback.Callback(context).ConfigureAwait(false); | ||
| } | ||
|
|
||
| // Swap the immutable annotation for one carrying the updated arguments; DCP reads the last | ||
| // override annotation when it assembles the launch command after this handler returns. | ||
| resource.Annotations.Remove(launchOverride); | ||
| resource.Annotations.Add(new ProjectLaunchArgsOverrideAnnotation(arguments, launchOverride.LeadingResourceArgumentToRemove)); | ||
|
frederikstonge marked this conversation as resolved.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This callback edits only |
||
| } | ||
|
|
||
| private static async Task PipeOutputAsync(System.IO.StreamReader reader, ILogger logger, LogLevel level, CancellationToken cancellationToken) | ||
| { | ||
| try | ||
|
|
@@ -272,7 +354,7 @@ private static void TryKillProcess(Process process, ILogger logger) | |
| /// Without this guard a restart could match on a stale snapshot. | ||
| /// </para> | ||
| /// <para> | ||
| /// Including "Running" in the predicate is intentional: the pre-build step already compiled | ||
| /// Including "Running" in the predicate is intentional: the build step already compiled | ||
| /// the project for the same configuration that DCP will pass to the launch command, and DCP's | ||
| /// <c>dotnet build --no-restore /t:Run -p:NoBuild=true</c> launch command is configured not to | ||
| /// restore or build. Waiting for a terminal state would hold the semaphore for the entire app | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.Maui; | ||
|
|
||
| /// <summary> | ||
| /// Context passed to build-argument callbacks registered with | ||
| /// <see cref="MauiBuildArgumentsExtensions.WithMauiBuildArguments{T}(IResourceBuilder{T}, Func{MauiBuildArgumentsCallbackContext, System.Threading.Tasks.Task})"/> | ||
| /// and | ||
| /// <see cref="MauiBuildArgumentsExtensions.WithMauiLaunchArguments{T}(IResourceBuilder{T}, Func{MauiBuildArgumentsCallbackContext, System.Threading.Tasks.Task})"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Mutate <see cref="Arguments"/> in place to add, remove, or replace the arguments that will be | ||
| /// passed to <c>dotnet</c> for the <see cref="Step"/> this callback is registered for. | ||
| /// </remarks> | ||
| [AspireExport(ExposeProperties = true)] | ||
|
|
||
| public sealed class MauiBuildArgumentsCallbackContext | ||
| { | ||
| internal MauiBuildArgumentsCallbackContext( | ||
| MauiBuildStep step, | ||
| IList<string> arguments, | ||
| IResource resource, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| Step = step; | ||
| Arguments = arguments; | ||
| Resource = resource; | ||
| CancellationToken = cancellationToken; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the build step this callback is participating in. | ||
| /// </summary> | ||
| public MauiBuildStep Step { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the mutable list of arguments passed to <c>dotnet</c> for the current <see cref="Step"/>. | ||
| /// Add, remove, or replace entries to influence the command that is executed. | ||
| /// </summary> | ||
| public IList<string> Arguments { get; } | ||
|
frederikstonge marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// <summary> | ||
| /// Gets the MAUI platform resource the arguments apply to. | ||
| /// </summary> | ||
| public IResource Resource { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a token that is cancelled if the resource start is cancelled. | ||
| /// </summary> | ||
| public CancellationToken CancellationToken { get; } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Maui; | ||
| using Aspire.Hosting.Maui.Annotations; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Provides extension methods for customizing the build and launch arguments of MAUI platform resources. | ||
| /// </summary> | ||
| public static class MauiBuildArgumentsExtensions | ||
| { | ||
| /// <summary> | ||
| /// Registers a callback that can inspect or modify the arguments used for the serialized | ||
| /// compile (<c>dotnet build</c>) that runs before the app is launched. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the MAUI platform resource.</typeparam> | ||
| /// <param name="builder">The MAUI platform resource builder.</param> | ||
| /// <param name="callback"> | ||
| /// A callback invoked with a <see cref="MauiBuildArgumentsCallbackContext"/> whose | ||
| /// <see cref="MauiBuildArgumentsCallbackContext.Arguments"/> can be mutated to influence the | ||
| /// <c>dotnet build</c> command. The arguments contain the full command (verb, project path, | ||
| /// target framework, configuration, and any additional MSBuild properties). | ||
| /// </param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <remarks> | ||
| /// Multiple callbacks can be registered; they are invoked in registration order and share the | ||
| /// same mutable argument list. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// Add an MSBuild property to the compile: | ||
| /// <code lang="csharp"> | ||
| /// maui.AddAndroidEmulator("emulator") | ||
| /// .WithMauiBuildArguments(context => context.Arguments.Add("-p:MyProperty=Value")); | ||
| /// </code> | ||
| /// </example> | ||
| [AspireExport] | ||
| public static IResourceBuilder<T> WithMauiBuildArguments<T>( | ||
| this IResourceBuilder<T> builder, | ||
| Func<MauiBuildArgumentsCallbackContext, Task> callback) | ||
|
frederikstonge marked this conversation as resolved.
|
||
| where T : IMauiPlatformResource | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
|
|
||
| return builder.WithAnnotation(new MauiBuildArgumentsCallbackAnnotation(MauiBuildStep.Build, callback)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers a synchronous callback that can inspect or modify the arguments used for the | ||
| /// serialized compile (<c>dotnet build</c>) that runs before the app is launched. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the MAUI platform resource.</typeparam> | ||
| /// <param name="builder">The MAUI platform resource builder.</param> | ||
| /// <param name="callback"> | ||
| /// A callback invoked with a <see cref="MauiBuildArgumentsCallbackContext"/> whose | ||
| /// <see cref="MauiBuildArgumentsCallbackContext.Arguments"/> can be mutated to influence the | ||
| /// <c>dotnet build</c> command. | ||
| /// </param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <remarks> | ||
| /// Multiple callbacks can be registered; they are invoked in registration order and share the | ||
| /// same mutable argument list. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// Add an MSBuild property to the compile: | ||
| /// <code lang="csharp"> | ||
| /// maui.AddAndroidEmulator("emulator") | ||
| /// .WithMauiBuildArguments(context => context.Arguments.Add("-p:MyProperty=Value")); | ||
| /// </code> | ||
| /// </example> | ||
| [AspireExportIgnore(Reason = "Convenience overload. Use the asynchronous overload instead.")] | ||
| public static IResourceBuilder<T> WithMauiBuildArguments<T>( | ||
| this IResourceBuilder<T> builder, | ||
| Action<MauiBuildArgumentsCallbackContext> callback) | ||
| where T : IMauiPlatformResource | ||
| { | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
|
|
||
| return builder.WithMauiBuildArguments(context => | ||
| { | ||
| callback(context); | ||
| return Task.CompletedTask; | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers a callback that can inspect or modify the arguments used for the launch command | ||
| /// that starts the already-built app (<c>dotnet build --no-restore /t:Run -p:NoBuild=true</c>). | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the MAUI platform resource.</typeparam> | ||
| /// <param name="builder">The MAUI platform resource builder.</param> | ||
| /// <param name="callback"> | ||
| /// A callback invoked with a <see cref="MauiBuildArgumentsCallbackContext"/> whose | ||
| /// <see cref="MauiBuildArgumentsCallbackContext.Arguments"/> can be mutated to influence the | ||
| /// launch command. The arguments contain the verb and options that replace DCP's default | ||
| /// <c>run</c> verb; the project path and <c>--configuration</c> are appended by the host. | ||
| /// </param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <remarks> | ||
| /// Multiple callbacks can be registered; they are invoked in registration order and share the | ||
| /// same mutable argument list. Callbacks are applied against the pristine launch arguments on | ||
| /// every start, so edits do not accumulate across restarts. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// Force a build during launch by overriding the default <c>-p:NoBuild=true</c>: | ||
| /// <code lang="csharp"> | ||
| /// maui.AddAndroidEmulator("emulator") | ||
| /// .WithMauiLaunchArguments(context => context.Arguments.Add("-p:NoBuild=false")); | ||
| /// </code> | ||
| /// </example> | ||
| [AspireExport] | ||
| public static IResourceBuilder<T> WithMauiLaunchArguments<T>( | ||
| this IResourceBuilder<T> builder, | ||
| Func<MauiBuildArgumentsCallbackContext, Task> callback) | ||
| where T : IMauiPlatformResource | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
|
|
||
| return builder.WithAnnotation(new MauiBuildArgumentsCallbackAnnotation(MauiBuildStep.Launch, callback)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers a synchronous callback that can inspect or modify the arguments used for the launch | ||
| /// command that starts the already-built app (<c>dotnet build --no-restore /t:Run -p:NoBuild=true</c>). | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the MAUI platform resource.</typeparam> | ||
| /// <param name="builder">The MAUI platform resource builder.</param> | ||
| /// <param name="callback"> | ||
| /// A callback invoked with a <see cref="MauiBuildArgumentsCallbackContext"/> whose | ||
| /// <see cref="MauiBuildArgumentsCallbackContext.Arguments"/> can be mutated to influence the | ||
| /// launch command. | ||
| /// </param> | ||
| /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <remarks> | ||
| /// Multiple callbacks can be registered; they are invoked in registration order and share the | ||
| /// same mutable argument list. Callbacks are applied against the pristine launch arguments on | ||
| /// every start, so edits do not accumulate across restarts. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// Force a build during launch by overriding the default <c>-p:NoBuild=true</c>: | ||
| /// <code lang="csharp"> | ||
| /// maui.AddAndroidEmulator("emulator") | ||
| /// .WithMauiLaunchArguments(context => context.Arguments.Add("-p:NoBuild=false")); | ||
| /// </code> | ||
| /// </example> | ||
| [AspireExportIgnore(Reason = "Convenience overload. Use the asynchronous overload instead.")] | ||
| public static IResourceBuilder<T> WithMauiLaunchArguments<T>( | ||
| this IResourceBuilder<T> builder, | ||
| Action<MauiBuildArgumentsCallbackContext> callback) | ||
| where T : IMauiPlatformResource | ||
| { | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
|
|
||
| return builder.WithMauiLaunchArguments(context => | ||
| { | ||
| callback(context); | ||
| return Task.CompletedTask; | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Aspire.Hosting.Maui; | ||
|
|
||
| /// <summary> | ||
| /// Identifies the stage of the MAUI resource startup pipeline that a build-argument | ||
| /// callback participates in. | ||
| /// </summary> | ||
| public enum MauiBuildStep | ||
| { | ||
| /// <summary> | ||
| /// The serialized compile (<c>dotnet build</c>) that runs before the app is launched. | ||
| /// Arguments passed at this stage form the complete <c>dotnet build</c> command used to compile | ||
| /// the project for the target platform (verb, project path, target framework, configuration, and | ||
| /// any additional MSBuild properties). | ||
| /// </summary> | ||
| Build, | ||
|
|
||
| /// <summary> | ||
| /// The launch command that starts the already-built app | ||
| /// (<c>dotnet build --no-restore /t:Run -p:NoBuild=true</c>). Arguments passed at this stage are the | ||
| /// verb and options that replace DCP's default <c>run</c> verb; the project path and | ||
| /// <c>--configuration</c> are appended by the host and are not part of the editable arguments. | ||
| /// </summary> | ||
| Launch | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.