diff --git a/Directory.Packages.props b/Directory.Packages.props
index a44ed722182f..3e9b3bd272de 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -37,6 +37,7 @@
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c85ec7c9ebaa..75069972e216 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -124,6 +124,10 @@
https://github.com/dotnet/dotnet
9477b510bb25fc69515d2ab188af3b72799929ac
+
+ https://github.com/dotnet/roslyn
+ 46a48b8c1dfce7c35da115308bedd6a5954fd78a
+
https://github.com/dotnet/dotnet
9477b510bb25fc69515d2ab188af3b72799929ac
diff --git a/sdk.slnx b/sdk.slnx
index 1f9a8f45530d..250dcce38c1a 100644
--- a/sdk.slnx
+++ b/sdk.slnx
@@ -23,6 +23,7 @@
+
diff --git a/src/BuiltInTools/BuiltInTools.sln b/src/BuiltInTools/BuiltInTools.sln
new file mode 100644
index 000000000000..f3bcfa7210f8
--- /dev/null
+++ b/src/BuiltInTools/BuiltInTools.sln
@@ -0,0 +1,146 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspireService", "AspireService", "{364DE115-7FAE-CA31-C478-9A2E79CB519F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BrowserRefresh", "BrowserRefresh", "{AC3BAF4A-B943-5F19-3116-A3FD7C885CF5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-format", "dotnet-format\dotnet-format.csproj", "{6C5FBD7D-2E3D-D482-5415-893AE4661749}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-watch", "dotnet-watch\dotnet-watch.csproj", "{31818B17-178A-0598-6C6E-E15CD63E1274}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotNetDeltaApplier", "DotNetDeltaApplier", "{1E2BF55A-FFB6-7358-58F1-01FF3B44BF81}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetWatchTasks", "DotNetWatchTasks\DotNetWatchTasks.csproj", "{4855C790-EFE7-94ED-C369-0A02387F88B4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadAgent", "HotReloadAgent", "{5CDE4159-9BB7-F002-81EE-FE1EB501625E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadAgent.Data", "HotReloadAgent.Data", "{2E82323A-31E4-7672-52A7-A568AFE575B9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadAgent.Host", "HotReloadAgent.Host", "{0305277F-4D82-FB13-2499-FC48C606CE40}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadAgent.PipeRpc", "HotReloadAgent.PipeRpc", "{DE1641C4-FDB5-AA6D-6CB4-DF3846EF52B5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadAgent.WebAssembly.Browser", "HotReloadAgent.WebAssembly.Browser", "{4ECEBE05-09E3-57E8-4D2B-FE584872CA1A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotReloadClient", "HotReloadClient", "{D2C117CF-0177-4E2D-50C8-838AB4048BBB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Watch", "Watch", "{7DA52029-B853-C26E-9887-EED38DF1A1F0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Watch.Aspire", "Watch.Aspire", "{4F97FBBD-9A86-61E0-BF34-CA189133CA2A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web.Middleware", "Web.Middleware", "{6416C8BE-C082-13BD-174D-AC0F1C4E3A1F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WebTools.AspireService.Package", "AspireService\Microsoft.WebTools.AspireService.Package.csproj", "{DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Watch.BrowserRefresh", "BrowserRefresh\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", "{9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.DotNetDeltaApplier", "DotNetDeltaApplier\Microsoft.Extensions.DotNetDeltaApplier.csproj", "{6BF335B3-9AE2-45DA-BF06-984D1FC76D12}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Agent.Package", "HotReloadAgent\Microsoft.DotNet.HotReload.Agent.Package.csproj", "{5EA9373C-1BAB-FA35-C694-5D55DF99EED4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Agent.Data.Package", "HotReloadAgent.Data\Microsoft.DotNet.HotReload.Agent.Data.Package.csproj", "{EE06EDB0-986D-753A-FE69-F65321BEED81}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Agent.Host.Package", "HotReloadAgent.Host\Microsoft.DotNet.HotReload.Agent.Host.Package.csproj", "{806A277E-2C0C-22FB-921A-78886FFDDAA7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Agent.PipeRpc.Package", "HotReloadAgent.PipeRpc\Microsoft.DotNet.HotReload.Agent.PipeRpc.Package.csproj", "{AB9EFD18-6E38-A909-42D0-CFFAB80A8497}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.WebAssembly.Browser", "HotReloadAgent.WebAssembly.Browser\Microsoft.DotNet.HotReload.WebAssembly.Browser.csproj", "{4399F1FA-7146-7FC6-527B-80445C258185}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Client.Package", "HotReloadClient\Microsoft.DotNet.HotReload.Client.Package.csproj", "{C736CE49-22AA-C13A-A009-110F2BB42B3A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Watch", "Watch\Microsoft.DotNet.HotReload.Watch.csproj", "{A66CCBDA-9D70-725D-1E21-BE53E0CB767E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Watch.Aspire", "Watch.Aspire\Microsoft.DotNet.HotReload.Watch.Aspire.csproj", "{A3C70BA5-7E79-E064-E5BB-07C0A3FB830F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Web.Middleware.Package", "Web.Middleware\Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj", "{1344423B-CFA7-FC1A-935D-4B9D2BE8C512}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6C5FBD7D-2E3D-D482-5415-893AE4661749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C5FBD7D-2E3D-D482-5415-893AE4661749}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C5FBD7D-2E3D-D482-5415-893AE4661749}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C5FBD7D-2E3D-D482-5415-893AE4661749}.Release|Any CPU.Build.0 = Release|Any CPU
+ {31818B17-178A-0598-6C6E-E15CD63E1274}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {31818B17-178A-0598-6C6E-E15CD63E1274}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {31818B17-178A-0598-6C6E-E15CD63E1274}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {31818B17-178A-0598-6C6E-E15CD63E1274}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4855C790-EFE7-94ED-C369-0A02387F88B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4855C790-EFE7-94ED-C369-0A02387F88B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4855C790-EFE7-94ED-C369-0A02387F88B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4855C790-EFE7-94ED-C369-0A02387F88B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6BF335B3-9AE2-45DA-BF06-984D1FC76D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6BF335B3-9AE2-45DA-BF06-984D1FC76D12}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6BF335B3-9AE2-45DA-BF06-984D1FC76D12}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6BF335B3-9AE2-45DA-BF06-984D1FC76D12}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5EA9373C-1BAB-FA35-C694-5D55DF99EED4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5EA9373C-1BAB-FA35-C694-5D55DF99EED4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5EA9373C-1BAB-FA35-C694-5D55DF99EED4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5EA9373C-1BAB-FA35-C694-5D55DF99EED4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE06EDB0-986D-753A-FE69-F65321BEED81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE06EDB0-986D-753A-FE69-F65321BEED81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE06EDB0-986D-753A-FE69-F65321BEED81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE06EDB0-986D-753A-FE69-F65321BEED81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {806A277E-2C0C-22FB-921A-78886FFDDAA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {806A277E-2C0C-22FB-921A-78886FFDDAA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {806A277E-2C0C-22FB-921A-78886FFDDAA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {806A277E-2C0C-22FB-921A-78886FFDDAA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB9EFD18-6E38-A909-42D0-CFFAB80A8497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB9EFD18-6E38-A909-42D0-CFFAB80A8497}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB9EFD18-6E38-A909-42D0-CFFAB80A8497}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB9EFD18-6E38-A909-42D0-CFFAB80A8497}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4399F1FA-7146-7FC6-527B-80445C258185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4399F1FA-7146-7FC6-527B-80445C258185}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4399F1FA-7146-7FC6-527B-80445C258185}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4399F1FA-7146-7FC6-527B-80445C258185}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C736CE49-22AA-C13A-A009-110F2BB42B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C736CE49-22AA-C13A-A009-110F2BB42B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C736CE49-22AA-C13A-A009-110F2BB42B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C736CE49-22AA-C13A-A009-110F2BB42B3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A66CCBDA-9D70-725D-1E21-BE53E0CB767E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A66CCBDA-9D70-725D-1E21-BE53E0CB767E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A66CCBDA-9D70-725D-1E21-BE53E0CB767E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A66CCBDA-9D70-725D-1E21-BE53E0CB767E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3C70BA5-7E79-E064-E5BB-07C0A3FB830F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3C70BA5-7E79-E064-E5BB-07C0A3FB830F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3C70BA5-7E79-E064-E5BB-07C0A3FB830F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3C70BA5-7E79-E064-E5BB-07C0A3FB830F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1344423B-CFA7-FC1A-935D-4B9D2BE8C512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1344423B-CFA7-FC1A-935D-4B9D2BE8C512}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1344423B-CFA7-FC1A-935D-4B9D2BE8C512}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1344423B-CFA7-FC1A-935D-4B9D2BE8C512}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {DF68EDC4-66E0-CD0A-C3AE-DA75F6E6FB48} = {364DE115-7FAE-CA31-C478-9A2E79CB519F}
+ {9AA89CA7-A32B-7B0F-3F6E-D8BFF3A7394F} = {AC3BAF4A-B943-5F19-3116-A3FD7C885CF5}
+ {6BF335B3-9AE2-45DA-BF06-984D1FC76D12} = {1E2BF55A-FFB6-7358-58F1-01FF3B44BF81}
+ {5EA9373C-1BAB-FA35-C694-5D55DF99EED4} = {5CDE4159-9BB7-F002-81EE-FE1EB501625E}
+ {EE06EDB0-986D-753A-FE69-F65321BEED81} = {2E82323A-31E4-7672-52A7-A568AFE575B9}
+ {806A277E-2C0C-22FB-921A-78886FFDDAA7} = {0305277F-4D82-FB13-2499-FC48C606CE40}
+ {AB9EFD18-6E38-A909-42D0-CFFAB80A8497} = {DE1641C4-FDB5-AA6D-6CB4-DF3846EF52B5}
+ {4399F1FA-7146-7FC6-527B-80445C258185} = {4ECEBE05-09E3-57E8-4D2B-FE584872CA1A}
+ {C736CE49-22AA-C13A-A009-110F2BB42B3A} = {D2C117CF-0177-4E2D-50C8-838AB4048BBB}
+ {A66CCBDA-9D70-725D-1E21-BE53E0CB767E} = {7DA52029-B853-C26E-9887-EED38DF1A1F0}
+ {A3C70BA5-7E79-E064-E5BB-07C0A3FB830F} = {4F97FBBD-9A86-61E0-BF34-CA189133CA2A}
+ {1344423B-CFA7-FC1A-935D-4B9D2BE8C512} = {6416C8BE-C082-13BD-174D-AC0F1C4E3A1F}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {347C85E1-C073-4D1E-B70E-7341B4888A27}
+ EndGlobalSection
+EndGlobal
diff --git a/src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs b/src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs
index 1642f63f8b82..7792d925bbac 100644
--- a/src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs
+++ b/src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs
@@ -20,7 +20,7 @@
namespace Microsoft.DotNet.HotReload
{
- internal sealed class DefaultHotReloadClient(ILogger logger, ILogger agentLogger, string startupHookPath, bool enableStaticAssetUpdates)
+ internal sealed class DefaultHotReloadClient(ILogger logger, ILogger agentLogger, string startupHookPath, bool handlesStaticAssetUpdates)
: HotReloadClient(logger, agentLogger)
{
private readonly string _namedPipeName = Guid.NewGuid().ToString("N");
@@ -225,7 +225,7 @@ static ImmutableArray ToRuntimeUpdates(IEnumerable> ApplyStaticAssetUpdatesAsync(ImmutableArray updates, CancellationToken processExitedCancellationToken, CancellationToken cancellationToken)
{
- if (!enableStaticAssetUpdates)
+ if (!handlesStaticAssetUpdates)
{
// The client has no concept of static assets.
return Task.FromResult(true);
diff --git a/src/BuiltInTools/HotReloadClient/HotReloadClients.cs b/src/BuiltInTools/HotReloadClient/HotReloadClients.cs
index 1b02eac9de48..1400a43c7e09 100644
--- a/src/BuiltInTools/HotReloadClient/HotReloadClients.cs
+++ b/src/BuiltInTools/HotReloadClient/HotReloadClients.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@@ -16,13 +17,25 @@
namespace Microsoft.DotNet.HotReload;
-internal sealed class HotReloadClients(ImmutableArray<(HotReloadClient client, string name)> clients, AbstractBrowserRefreshServer? browserRefreshServer) : IDisposable
+///
+/// Facilitates Hot Reload updates across multiple clients/processes.
+///
+///
+/// Clients that handle managed updates and static asset updates if is false.
+///
+///
+/// Browser refresh server used to communicate managed code update status and errors to the browser,
+/// and to apply static asset updates if is true.
+///
+///
+/// True to use to apply static asset updates (if available).
+/// False to use the to apply static asset updates.
+///
+internal sealed class HotReloadClients(
+ ImmutableArray<(HotReloadClient client, string name)> clients,
+ AbstractBrowserRefreshServer? browserRefreshServer,
+ bool useRefreshServerToApplyStaticAssets) : IDisposable
{
- public HotReloadClients(HotReloadClient client, AbstractBrowserRefreshServer? browserRefreshServer)
- : this([(client, "")], browserRefreshServer)
- {
- }
-
///
/// Disposes all clients. Can occur unexpectedly whenever the process exits.
///
@@ -34,6 +47,16 @@ public void Dispose()
}
}
+ ///
+ /// True if Hot Reload is implemented via managed agents.
+ /// The update itself might not be managed code update, it may be a static asset update implemented via a managed agent.
+ ///
+ public bool IsManagedAgentSupported
+ => !clients.IsEmpty;
+
+ public bool UseRefreshServerToApplyStaticAssets
+ => useRefreshServerToApplyStaticAssets;
+
public AbstractBrowserRefreshServer? BrowserRefreshServer
=> browserRefreshServer;
@@ -59,18 +82,6 @@ public event Action OnRuntimeRudeEdit
}
}
- ///
- /// All clients share the same loggers.
- ///
- public ILogger ClientLogger
- => clients.First().client.Logger;
-
- ///
- /// All clients share the same loggers.
- ///
- public ILogger AgentLogger
- => clients.First().client.AgentLogger;
-
internal void ConfigureLaunchEnvironment(IDictionary environmentBuilder)
{
foreach (var (client, _) in clients)
@@ -99,6 +110,12 @@ internal async ValueTask WaitForConnectionEstablishedAsync(CancellationToken can
/// Cancellation token. The cancellation should trigger on process terminatation.
public async ValueTask> GetUpdateCapabilitiesAsync(CancellationToken cancellationToken)
{
+ if (!IsManagedAgentSupported)
+ {
+ // empty capabilities will cause rude edit ENC0097: NotSupportedByRuntime.
+ return [];
+ }
+
if (clients is [var (singleClient, _)])
{
return await singleClient.GetUpdateCapabilitiesAsync(cancellationToken);
@@ -114,6 +131,9 @@ public async ValueTask> GetUpdateCapabilitiesAsync(Cancel
/// Cancellation token. The cancellation should trigger on process terminatation.
public async Task ApplyManagedCodeUpdatesAsync(ImmutableArray updates, CancellationToken applyOperationCancellationToken, CancellationToken cancellationToken)
{
+ // shouldn't be called if there are no clients
+ Debug.Assert(IsManagedAgentSupported);
+
// Apply to all processes.
// The module the change is for does not need to be loaded to any of the processes, yet we still consider it successful if the application does not fail.
// In each process we store the deltas for application when/if the module is loaded to the process later.
@@ -137,6 +157,9 @@ async Task CompleteApplyOperationAsync()
/// Cancellation token. The cancellation should trigger on process terminatation.
public async ValueTask InitialUpdatesAppliedAsync(CancellationToken cancellationToken)
{
+ // shouldn't be called if there are no clients
+ Debug.Assert(IsManagedAgentSupported);
+
if (clients is [var (singleClient, _)])
{
await singleClient.InitialUpdatesAppliedAsync(cancellationToken);
@@ -150,23 +173,26 @@ public async ValueTask InitialUpdatesAppliedAsync(CancellationToken cancellation
/// Cancellation token. The cancellation should trigger on process terminatation.
public async Task ApplyStaticAssetUpdatesAsync(IEnumerable assets, CancellationToken applyOperationCancellationToken, CancellationToken cancellationToken)
{
- if (browserRefreshServer != null)
+ if (useRefreshServerToApplyStaticAssets)
{
+ Debug.Assert(browserRefreshServer != null);
return browserRefreshServer.UpdateStaticAssetsAsync(assets.Select(static a => a.RelativeUrl), applyOperationCancellationToken).AsTask();
}
+ // shouldn't be called if there are no clients
+ Debug.Assert(IsManagedAgentSupported);
+
var updates = new List();
foreach (var asset in assets)
{
try
{
- ClientLogger.LogDebug("Loading asset '{Url}' from '{Path}'.", asset.RelativeUrl, asset.FilePath);
updates.Add(await HotReloadStaticAssetUpdate.CreateAsync(asset, cancellationToken));
}
catch (Exception e) when (e is not OperationCanceledException)
{
- ClientLogger.LogError("Failed to read file {FilePath}: {Message}", asset.FilePath, e.Message);
+ clients.First().client.Logger.LogError("Failed to read file {FilePath}: {Message}", asset.FilePath, e.Message);
continue;
}
}
@@ -177,6 +203,10 @@ public async Task ApplyStaticAssetUpdatesAsync(IEnumerable
/// Cancellation token. The cancellation should trigger on process terminatation.
public async ValueTask ApplyStaticAssetUpdatesAsync(ImmutableArray updates, CancellationToken applyOperationCancellationToken, CancellationToken cancellationToken)
{
+ // shouldn't be called if there are no clients
+ Debug.Assert(IsManagedAgentSupported);
+ Debug.Assert(!useRefreshServerToApplyStaticAssets);
+
var applyTasks = await Task.WhenAll(clients.Select(c => c.client.ApplyStaticAssetUpdatesAsync(updates, applyOperationCancellationToken, cancellationToken)));
return Task.WhenAll(applyTasks);
diff --git a/src/BuiltInTools/HotReloadClient/StaticAsset.cs b/src/BuiltInTools/HotReloadClient/StaticAsset.cs
new file mode 100644
index 000000000000..74cf7d1e096d
--- /dev/null
+++ b/src/BuiltInTools/HotReloadClient/StaticAsset.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Microsoft.DotNet.HotReload;
+
+internal readonly struct StaticAsset(string filePath, string relativeUrl, string assemblyName, bool isApplicationProject)
+{
+ public string FilePath => filePath;
+ public string RelativeUrl => relativeUrl;
+ public string AssemblyName => assemblyName;
+ public bool IsApplicationProject => isApplicationProject;
+}
diff --git a/src/BuiltInTools/HotReloadClient/Web/StaticWebAssetsManifest.cs b/src/BuiltInTools/HotReloadClient/Web/StaticWebAssetsManifest.cs
index 22a1c4d5e0a5..6f9ad71f4bab 100644
--- a/src/BuiltInTools/HotReloadClient/Web/StaticWebAssetsManifest.cs
+++ b/src/BuiltInTools/HotReloadClient/Web/StaticWebAssetsManifest.cs
@@ -90,6 +90,11 @@ public bool TryGetBundleFilePath(string bundleFileName, [NotNullWhen(true)] out
{
stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
}
+ catch (FileNotFoundException)
+ {
+ logger.LogDebug("File '{FilePath}' does not exist.", path);
+ return null;
+ }
catch (Exception e)
{
logger.LogError("Failed to read '{FilePath}': {Message}", path, e.Message);
diff --git a/src/BuiltInTools/Watch.Aspire/DotNetWatchLauncher.cs b/src/BuiltInTools/Watch.Aspire/DotNetWatchLauncher.cs
index 72fadfe6f024..dd56ed47d0c3 100644
--- a/src/BuiltInTools/Watch.Aspire/DotNetWatchLauncher.cs
+++ b/src/BuiltInTools/Watch.Aspire/DotNetWatchLauncher.cs
@@ -24,15 +24,12 @@ public static async Task RunAsync(string workingDirectory, DotNetWatchOpti
commandArguments.AddRange(options.ApplicationArguments);
- var rootProjectOptions = new ProjectOptions()
+ var mainProjectOptions = new ProjectOptions()
{
- IsRootProject = true,
- ProjectPath = options.ProjectPath,
+ IsMainProject = true,
+ Representation = options.Project,
WorkingDirectory = workingDirectory,
- TargetFramework = null,
- BuildArguments = [],
- NoLaunchProfile = options.NoLaunchProfile,
- LaunchProfileName = null,
+ LaunchProfileName = options.NoLaunchProfile ? default : null,
Command = "run",
CommandArguments = [.. commandArguments],
LaunchEnvironmentVariables = [],
@@ -59,7 +56,10 @@ public static async Task RunAsync(string workingDirectory, DotNetWatchOpti
ProcessRunner = processRunner,
Options = globalOptions,
EnvironmentOptions = environmentOptions,
- RootProjectOptions = rootProjectOptions,
+ MainProjectOptions = mainProjectOptions,
+ RootProjects = [mainProjectOptions.Representation],
+ BuildArguments = [],
+ TargetFramework = null,
BrowserRefreshServerFactory = new BrowserRefreshServerFactory(),
BrowserLauncher = new BrowserLauncher(logger, reporter, environmentOptions),
};
diff --git a/src/BuiltInTools/Watch.Aspire/DotNetWatchOptions.cs b/src/BuiltInTools/Watch.Aspire/DotNetWatchOptions.cs
index 1d8f526c1fff..497078f9322d 100644
--- a/src/BuiltInTools/Watch.Aspire/DotNetWatchOptions.cs
+++ b/src/BuiltInTools/Watch.Aspire/DotNetWatchOptions.cs
@@ -3,6 +3,7 @@
using System.Collections.Immutable;
using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
@@ -16,7 +17,7 @@ internal sealed class DotNetWatchOptions
///
public required string SdkDirectory { get; init; }
- public required string ProjectPath { get; init; }
+ public required ProjectRepresentation Project { get; init; }
public required ImmutableArray ApplicationArguments { get; init; }
public LogLevel LogLevel { get; init; }
public bool NoLaunchProfile { get; init; }
@@ -24,7 +25,8 @@ internal sealed class DotNetWatchOptions
public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOptions? options)
{
var sdkOption = new Option("--sdk") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };
- var projectOption = new Option("--project") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };
+ var projectOption = new Option("--project") { Arity = ArgumentArity.ZeroOrOne, AllowMultipleArgumentsPerToken = false };
+ var fileOption = new Option("--file") { Arity = ArgumentArity.ZeroOrOne, AllowMultipleArgumentsPerToken = false };
var quietOption = new Option("--quiet") { Arity = ArgumentArity.Zero };
var verboseOption = new Option("--verbose") { Arity = ArgumentArity.Zero };
var noLaunchProfileOption = new Option("--no-launch-profile") { Arity = ArgumentArity.Zero };
@@ -32,10 +34,19 @@ public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOp
verboseOption.Validators.Add(v =>
{
- if (v.GetValue(quietOption) && v.GetValue(verboseOption))
+ if (HasOption(v, quietOption) && HasOption(v, verboseOption))
{
v.AddError("Cannot specify both '--quiet' and '--verbose' options.");
}
+
+ if (HasOption(v, projectOption) && HasOption(v, fileOption))
+ {
+ v.AddError("Cannot specify both '--file' and '--project' options.");
+ }
+ else if (!HasOption(v, projectOption) && !HasOption(v, fileOption))
+ {
+ v.AddError("Must specify either '--file' or '--project' option.");
+ }
});
var rootCommand = new RootCommand()
@@ -45,6 +56,7 @@ public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOp
{
sdkOption,
projectOption,
+ fileOption,
quietOption,
verboseOption,
noLaunchProfileOption
@@ -70,7 +82,7 @@ public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOp
options = new DotNetWatchOptions()
{
SdkDirectory = parseResult.GetRequiredValue(sdkOption),
- ProjectPath = parseResult.GetRequiredValue(projectOption),
+ Project = new ProjectRepresentation(projectPath: parseResult.GetValue(projectOption), entryPointFilePath: parseResult.GetValue(fileOption)),
LogLevel = parseResult.GetValue(quietOption) ? LogLevel.Warning : parseResult.GetValue(verboseOption) ? LogLevel.Debug : LogLevel.Information,
ApplicationArguments = [.. parseResult.GetValue(applicationArguments) ?? []],
NoLaunchProfile = parseResult.GetValue(noLaunchProfileOption),
@@ -78,4 +90,7 @@ public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOp
return true;
}
+
+ private static bool HasOption(SymbolResult symbolResult, Option option)
+ => symbolResult.GetResult(option) is OptionResult or && !or.Implicit;
}
diff --git a/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyAppModel.cs b/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyAppModel.cs
index ea3480f979f6..ef0c112b98fb 100644
--- a/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyAppModel.cs
@@ -17,11 +17,11 @@ internal sealed class BlazorWebAssemblyAppModel(DotNetWatchContext context, Proj
{
public override ProjectGraphNode LaunchingProject => clientProject;
- public override bool RequiresBrowserRefresh => true;
+ public override bool ManagedHotReloadRequiresBrowserRefresh => true;
- protected override HotReloadClients CreateClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
+ protected override ImmutableArray<(HotReloadClient client, string name)> CreateManagedClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
{
Debug.Assert(browserRefreshServer != null);
- return new(CreateWebAssemblyClient(clientLogger, agentLogger, browserRefreshServer, clientProject), browserRefreshServer);
+ return [(CreateWebAssemblyClient(clientLogger, agentLogger, browserRefreshServer, clientProject), "")];
}
}
diff --git a/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyHostedAppModel.cs b/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyHostedAppModel.cs
index 12108762305b..940a9d0668ec 100644
--- a/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyHostedAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyHostedAppModel.cs
@@ -19,17 +19,16 @@ internal sealed class BlazorWebAssemblyHostedAppModel(DotNetWatchContext context
{
public override ProjectGraphNode LaunchingProject => serverProject;
- public override bool RequiresBrowserRefresh => true;
+ public override bool ManagedHotReloadRequiresBrowserRefresh => true;
- protected override HotReloadClients CreateClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
+ protected override ImmutableArray<(HotReloadClient client, string name)> CreateManagedClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
{
Debug.Assert(browserRefreshServer != null);
- return new(
- [
- (CreateWebAssemblyClient(clientLogger, agentLogger, browserRefreshServer, clientProject), "client"),
- (new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(serverProject), enableStaticAssetUpdates: false), "host")
- ],
- browserRefreshServer);
+ return
+ [
+ (CreateWebAssemblyClient(clientLogger, agentLogger, browserRefreshServer, clientProject), "client"),
+ (new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(serverProject), handlesStaticAssetUpdates: false), "host")
+ ];
}
}
diff --git a/src/BuiltInTools/Watch/AppModels/DefaultAppModel.cs b/src/BuiltInTools/Watch/AppModels/DefaultAppModel.cs
index 300236d7250a..ca04de711594 100644
--- a/src/BuiltInTools/Watch/AppModels/DefaultAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/DefaultAppModel.cs
@@ -12,6 +12,11 @@ namespace Microsoft.DotNet.Watch;
///
internal sealed class DefaultAppModel(ProjectGraphNode project) : HotReloadAppModel
{
- public override ValueTask TryCreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken)
- => new(new HotReloadClients(new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(project), enableStaticAssetUpdates: true), browserRefreshServer: null));
+ public override ValueTask CreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken)
+ => new(new HotReloadClients(
+ clients: IsManagedAgentSupported(project, clientLogger)
+ ? [(new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(project), handlesStaticAssetUpdates: true), "")]
+ : [],
+ browserRefreshServer: null,
+ useRefreshServerToApplyStaticAssets: false));
}
diff --git a/src/BuiltInTools/Watch/AppModels/HotReloadAppModel.cs b/src/BuiltInTools/Watch/AppModels/HotReloadAppModel.cs
index 7a205a8d1fce..a6668d57714a 100644
--- a/src/BuiltInTools/Watch/AppModels/HotReloadAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/HotReloadAppModel.cs
@@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Watch;
internal abstract partial class HotReloadAppModel()
{
- public abstract ValueTask TryCreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken);
+ public abstract ValueTask CreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken);
protected static string GetInjectedAssemblyPath(string targetFramework, string assemblyName)
=> Path.Combine(Path.GetDirectoryName(typeof(HotReloadAppModel).Assembly.Location)!, "hotreload", targetFramework, assemblyName + ".dll");
@@ -45,4 +45,42 @@ public static HotReloadAppModel InferFromProject(DotNetWatchContext context, Pro
context.Logger.Log(MessageDescriptor.ApplicationKind_Default);
return new DefaultAppModel(projectNode);
}
+
+ ///
+ /// True if a managed code agent can be injected into the target process.
+ /// The agent is injected either via dotnet startup hook, or via web server middleware for WASM clients.
+ ///
+ internal static bool IsManagedAgentSupported(ProjectGraphNode project, ILogger logger)
+ {
+ if (!project.IsNetCoreApp(Versions.Version6_0))
+ {
+ LogWarning("target framework is older than 6.0");
+ return false;
+ }
+
+ // If property is not specified startup hook is enabled:
+ // https://github.com/dotnet/runtime/blob/4b0b7238ba021b610d3963313b4471517108d2bc/src/libraries/System.Private.CoreLib/src/System/StartupHookProvider.cs#L22
+ // Startup hooks are not used for WASM projects.
+ //
+ // TODO: Remove once implemented: https://github.com/dotnet/runtime/issues/123778
+ if (!project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.StartupHookSupport, defaultValue: true) &&
+ !project.GetCapabilities().Contains(ProjectCapability.WebAssembly))
+ {
+ // Report which property is causing lack of support for startup hooks:
+ var (propertyName, propertyValue) =
+ project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishAot)
+ ? (PropertyNames.PublishAot, true)
+ : project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishTrimmed)
+ ? (PropertyNames.PublishTrimmed, true)
+ : (PropertyNames.StartupHookSupport, false);
+
+ LogWarning(string.Format("'{0}' property is '{1}'", propertyName, propertyValue));
+ return false;
+ }
+
+ return true;
+
+ void LogWarning(string reason)
+ => logger.Log(MessageDescriptor.ProjectDoesNotSupportHotReload, reason);
+ }
}
diff --git a/src/BuiltInTools/Watch/AppModels/WebApplicationAppModel.cs b/src/BuiltInTools/Watch/AppModels/WebApplicationAppModel.cs
index 0f1fbb74d5d8..2460d27a79e7 100644
--- a/src/BuiltInTools/Watch/AppModels/WebApplicationAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/WebApplicationAppModel.cs
@@ -16,25 +16,24 @@ internal abstract class WebApplicationAppModel(DotNetWatchContext context) : Hot
public DotNetWatchContext Context => context;
- public abstract bool RequiresBrowserRefresh { get; }
+ public abstract bool ManagedHotReloadRequiresBrowserRefresh { get; }
///
/// Project that's used for launching the application.
///
public abstract ProjectGraphNode LaunchingProject { get; }
- protected abstract HotReloadClients CreateClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer);
+ protected abstract ImmutableArray<(HotReloadClient client, string name)> CreateManagedClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer);
- public async sealed override ValueTask TryCreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken)
+ public async sealed override ValueTask CreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken)
{
var browserRefreshServer = await context.BrowserRefreshServerFactory.GetOrCreateBrowserRefreshServerAsync(LaunchingProject, this, cancellationToken);
- if (RequiresBrowserRefresh && browserRefreshServer == null)
- {
- // Error has been reported
- return null;
- }
- return CreateClients(clientLogger, agentLogger, browserRefreshServer);
+ var managedClients = (!ManagedHotReloadRequiresBrowserRefresh || browserRefreshServer != null) && IsManagedAgentSupported(LaunchingProject, clientLogger)
+ ? CreateManagedClients(clientLogger, agentLogger, browserRefreshServer)
+ : [];
+
+ return new HotReloadClients(managedClients, browserRefreshServer, useRefreshServerToApplyStaticAssets: true);
}
protected WebAssemblyHotReloadClient CreateWebAssemblyClient(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer browserRefreshServer, ProjectGraphNode clientProject)
@@ -71,13 +70,29 @@ public bool IsServerSupported(ProjectGraphNode projectNode, ILogger logger)
{
if (context.EnvironmentOptions.SuppressBrowserRefresh)
{
- logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_SuppressedViaEnvironmentVariable.WithLevelWhen(LogLevel.Error, RequiresBrowserRefresh), EnvironmentVariables.Names.SuppressBrowserRefresh);
+ if (ManagedHotReloadRequiresBrowserRefresh)
+ {
+ logger.Log(MessageDescriptor.BrowserRefreshSuppressedViaEnvironmentVariable_ApplicationWillBeRestarted, EnvironmentVariables.Names.SuppressBrowserRefresh);
+ }
+ else
+ {
+ logger.Log(MessageDescriptor.BrowserRefreshSuppressedViaEnvironmentVariable_ManualRefreshRequired, EnvironmentVariables.Names.SuppressBrowserRefresh);
+ }
+
return false;
}
if (!projectNode.IsNetCoreApp(minVersion: s_minimumSupportedVersion))
{
- logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_TargetFrameworkNotSupported.WithLevelWhen(LogLevel.Error, RequiresBrowserRefresh));
+ if (ManagedHotReloadRequiresBrowserRefresh)
+ {
+ logger.Log(MessageDescriptor.BrowserRefreshNotSupportedByProjectTargetFramework_ApplicationWillBeRestarted);
+ }
+ else
+ {
+ logger.Log(MessageDescriptor.BrowserRefreshNotSupportedByProjectTargetFramework_ManualRefreshRequired);
+ }
+
return false;
}
diff --git a/src/BuiltInTools/Watch/AppModels/WebServerAppModel.cs b/src/BuiltInTools/Watch/AppModels/WebServerAppModel.cs
index d30703b87530..ddd4fd25d586 100644
--- a/src/BuiltInTools/Watch/AppModels/WebServerAppModel.cs
+++ b/src/BuiltInTools/Watch/AppModels/WebServerAppModel.cs
@@ -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.Immutable;
using Microsoft.Build.Graph;
using Microsoft.DotNet.HotReload;
using Microsoft.Extensions.Logging;
@@ -12,9 +13,9 @@ internal sealed class WebServerAppModel(DotNetWatchContext context, ProjectGraph
{
public override ProjectGraphNode LaunchingProject => serverProject;
- public override bool RequiresBrowserRefresh
+ public override bool ManagedHotReloadRequiresBrowserRefresh
=> false;
- protected override HotReloadClients CreateClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
- => new(new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(serverProject), enableStaticAssetUpdates: true), browserRefreshServer);
+ protected override ImmutableArray<(HotReloadClient client, string name)> CreateManagedClients(ILogger clientLogger, ILogger agentLogger, BrowserRefreshServer? browserRefreshServer)
+ => [(new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(serverProject), handlesStaticAssetUpdates: true), "")];
}
diff --git a/src/BuiltInTools/Watch/Aspire/AspireServiceFactory.cs b/src/BuiltInTools/Watch/Aspire/AspireServiceFactory.cs
index b7d669a89216..4763f4560f2b 100644
--- a/src/BuiltInTools/Watch/Aspire/AspireServiceFactory.cs
+++ b/src/BuiltInTools/Watch/Aspire/AspireServiceFactory.cs
@@ -6,12 +6,11 @@
using System.Globalization;
using System.Threading.Channels;
using Aspire.Tools.Service;
-using Microsoft.Build.Graph;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.Watch;
-internal class AspireServiceFactory : IRuntimeProcessLauncherFactory
+internal class AspireServiceFactory(ProjectOptions hostProjectOptions) : IRuntimeProcessLauncherFactory
{
internal sealed class SessionManager : IAspireServerEvents, IRuntimeProcessLauncher
{
@@ -30,8 +29,8 @@ private readonly struct Session(string dcpId, string sessionId, RunningProject r
};
private readonly ProjectLauncher _projectLauncher;
- private readonly AspireServerService _service;
private readonly ProjectOptions _hostProjectOptions;
+ private readonly AspireServerService _service;
private readonly ILogger _logger;
///
@@ -106,7 +105,7 @@ public async ValueTask StartProjectAsync(string dcpId, string se
{
ObjectDisposedException.ThrowIf(_isDisposed, this);
- _logger.LogDebug("Starting project: {Path}", projectOptions.ProjectPath);
+ _logger.LogDebug("Starting: '{Path}'", projectOptions.Representation.ProjectOrEntryPointFilePath);
var processTerminationSource = new CancellationTokenSource();
var outputChannel = Channel.CreateUnbounded(s_outputChannelOptions);
@@ -143,7 +142,7 @@ public async ValueTask StartProjectAsync(string dcpId, string se
if (runningProject == null)
{
// detailed error already reported:
- throw new ApplicationException($"Failed to launch project '{projectOptions.ProjectPath}'.");
+ throw new ApplicationException($"Failed to launch '{projectOptions.Representation.ProjectOrEntryPointFilePath}'.");
}
await _service.NotifySessionStartedAsync(dcpId, sessionId, runningProject.ProcessId, cancellationToken);
@@ -215,23 +214,16 @@ private async Task TerminateSessionAsync(Session session)
}
private ProjectOptions GetProjectOptions(ProjectLaunchRequest projectLaunchInfo)
- {
- var hostLaunchProfile = _hostProjectOptions.NoLaunchProfile ? null : _hostProjectOptions.LaunchProfileName;
-
- return new()
+ => new()
{
- IsRootProject = false,
- ProjectPath = projectLaunchInfo.ProjectPath,
+ IsMainProject = false,
+ Representation = ProjectRepresentation.FromProjectOrEntryPointFilePath(projectLaunchInfo.ProjectPath),
WorkingDirectory = Path.GetDirectoryName(projectLaunchInfo.ProjectPath) ?? throw new InvalidOperationException(),
- BuildArguments = _hostProjectOptions.BuildArguments,
Command = "run",
- CommandArguments = GetRunCommandArguments(projectLaunchInfo, hostLaunchProfile),
+ CommandArguments = GetRunCommandArguments(projectLaunchInfo, _hostProjectOptions.LaunchProfileName.Value),
LaunchEnvironmentVariables = projectLaunchInfo.Environment?.Select(e => (e.Key, e.Value))?.ToArray() ?? [],
- LaunchProfileName = projectLaunchInfo.LaunchProfile,
- NoLaunchProfile = projectLaunchInfo.DisableLaunchProfile,
- TargetFramework = _hostProjectOptions.TargetFramework,
+ LaunchProfileName = projectLaunchInfo.DisableLaunchProfile ? default : projectLaunchInfo.LaunchProfile,
};
- }
// internal for testing
internal static IReadOnlyList GetRunCommandArguments(ProjectLaunchRequest projectLaunchInfo, string? hostLaunchProfile)
@@ -276,13 +268,9 @@ internal static IReadOnlyList GetRunCommandArguments(ProjectLaunchReques
}
}
- public static readonly AspireServiceFactory Instance = new();
-
public const string AspireLogComponentName = "Aspire";
public const string AppHostProjectCapability = ProjectCapability.Aspire;
- public IRuntimeProcessLauncher? TryCreate(ProjectGraphNode projectNode, ProjectLauncher projectLauncher, ProjectOptions hostProjectOptions)
- => projectNode.GetCapabilities().Contains(AppHostProjectCapability)
- ? new SessionManager(projectLauncher, hostProjectOptions)
- : null;
+ public IRuntimeProcessLauncher Create(ProjectLauncher projectLauncher)
+ => new SessionManager(projectLauncher, hostProjectOptions);
}
diff --git a/src/BuiltInTools/Watch/Browser/BrowserLauncher.cs b/src/BuiltInTools/Watch/Browser/BrowserLauncher.cs
index 74ce7a91dde2..105b67176421 100644
--- a/src/BuiltInTools/Watch/Browser/BrowserLauncher.cs
+++ b/src/BuiltInTools/Watch/Browser/BrowserLauncher.cs
@@ -38,7 +38,7 @@ public void InstallBrowserLaunchTrigger(
WebServerProcessStateObserver.Observe(projectNode, processSpec, url =>
{
- if (projectOptions.IsRootProject &&
+ if (projectOptions.IsMainProject &&
ImmutableInterlocked.Update(ref _browserLaunchAttempted, static (set, key) => set.Add(key), projectNode.ProjectInstance.GetId()))
{
// first build iteration of a root project:
@@ -127,7 +127,10 @@ private bool CanLaunchBrowser(ProjectOptions projectOptions, [NotNullWhen(true)]
private LaunchSettingsProfile GetLaunchProfile(ProjectOptions projectOptions)
{
- return (projectOptions.NoLaunchProfile == true
- ? null : LaunchSettingsProfile.ReadLaunchProfile(projectOptions.ProjectPath, projectOptions.LaunchProfileName, logger)) ?? new();
+ var profile = projectOptions.LaunchProfileName.HasValue
+ ? LaunchSettingsProfile.ReadLaunchProfile(projectOptions.Representation, projectOptions.LaunchProfileName.Value, logger)
+ : null;
+
+ return profile ?? new();
}
}
diff --git a/src/BuiltInTools/Watch/Build/BuildNames.cs b/src/BuiltInTools/Watch/Build/BuildNames.cs
index a2d0b790ce0d..144d9ba2998a 100644
--- a/src/BuiltInTools/Watch/Build/BuildNames.cs
+++ b/src/BuiltInTools/Watch/Build/BuildNames.cs
@@ -23,6 +23,9 @@ internal static class PropertyNames
public const string SkipCompilerExecution = nameof(SkipCompilerExecution);
public const string ProvideCommandLineArgs = nameof(ProvideCommandLineArgs);
public const string NonExistentFile = nameof(NonExistentFile);
+ public const string StartupHookSupport = nameof(StartupHookSupport);
+ public const string PublishTrimmed = nameof(PublishTrimmed);
+ public const string PublishAot = nameof(PublishAot);
}
internal static class ItemNames
diff --git a/src/BuiltInTools/Watch/Build/BuildReporter.cs b/src/BuiltInTools/Watch/Build/BuildReporter.cs
index 6410476dc3c8..de404b1d07f9 100644
--- a/src/BuiltInTools/Watch/Build/BuildReporter.cs
+++ b/src/BuiltInTools/Watch/Build/BuildReporter.cs
@@ -14,12 +14,13 @@ namespace Microsoft.DotNet.Watch;
internal sealed class BuildReporter(ILogger logger, GlobalOptions options, EnvironmentOptions environmentOptions)
{
public ILogger Logger => logger;
+ public GlobalOptions GlobalOptions => options;
public EnvironmentOptions EnvironmentOptions => environmentOptions;
public Loggers GetLoggers(string projectPath, string operationName)
=> new(logger, environmentOptions.GetBinLogPath(projectPath, operationName, options));
- public void ReportWatchedFiles(Dictionary fileItems)
+ public static void ReportWatchedFiles(ILogger logger, IReadOnlyDictionary fileItems)
{
logger.Log(MessageDescriptor.WatchingFilesForChanges, fileItems.Count);
diff --git a/src/BuiltInTools/Watch/Build/BuildRequest.cs b/src/BuiltInTools/Watch/Build/BuildRequest.cs
new file mode 100644
index 000000000000..46e35ff35113
--- /dev/null
+++ b/src/BuiltInTools/Watch/Build/BuildRequest.cs
@@ -0,0 +1,23 @@
+// 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.Immutable;
+using Microsoft.Build.Execution;
+
+namespace Microsoft.DotNet.Watch;
+
+internal readonly struct BuildRequest(ProjectInstance projectInstance, ImmutableArray targets, T data)
+{
+ public ProjectInstance ProjectInstance { get; } = projectInstance;
+ public ImmutableArray Targets { get; } = targets;
+ public T Data { get; } = data;
+}
+
+internal static class BuildRequest
+{
+ public static BuildRequest