Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost" Version="$(MicrosoftCodeAnalysisWorkspacesMSBuildBuildHostPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload" Version="$(MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion)" />

<!-- roslyn-sdk dependencies-->
Expand Down
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<add key="richnav" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-buildservices/nuget/v3/index.json" />
<!-- mstest dependencies -->
<add key="test-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/test-tools/nuget/v3/index.json" />
<add key="R0" value="C:\R0\artifacts\packages\Debug\Shipping" />
</packageSources>
<disabledPackageSources>
<clear />
Expand Down
25 changes: 13 additions & 12 deletions eng/Version.Details.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ This file should be imported by eng/Versions.props
<MicrosoftBuildLocalizationPackageVersion>18.3.0-preview-26076-108</MicrosoftBuildLocalizationPackageVersion>
<MicrosoftBuildNuGetSdkResolverPackageVersion>7.3.0-preview.1.7708</MicrosoftBuildNuGetSdkResolverPackageVersion>
<MicrosoftBuildTasksGitPackageVersion>10.0.300-alpha.26076.108</MicrosoftBuildTasksGitPackageVersion>
<MicrosoftCodeAnalysisPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisPackageVersion>
<MicrosoftCodeAnalysisBuildClientPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisBuildClientPackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftCodeAnalysisCSharpCodeStylePackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisCSharpCodeStylePackageVersion>
<MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>
<MicrosoftCodeAnalysisPublicApiAnalyzersPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisPublicApiAnalyzersPackageVersion>
<MicrosoftCodeAnalysisPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisPackageVersion>
<MicrosoftCodeAnalysisBuildClientPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisBuildClientPackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftCodeAnalysisCSharpCodeStylePackageVersion>5.4.0-dev</MicrosoftCodeAnalysisCSharpCodeStylePackageVersion>
<MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisWorkspacesMSBuildBuildHostPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisWorkspacesMSBuildBuildHostPackageVersion>
<MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>
<MicrosoftCodeAnalysisPublicApiAnalyzersPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisPublicApiAnalyzersPackageVersion>
<MicrosoftCodeAnalysisRazorToolingInternalPackageVersion>10.0.0-preview.26076.108</MicrosoftCodeAnalysisRazorToolingInternalPackageVersion>
<MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
<MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>5.3.0-2.26076.108</MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>
<MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
<MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>5.4.0-dev</MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>
<MicrosoftDotNetArcadeSdkPackageVersion>10.0.0-beta.26076.108</MicrosoftDotNetArcadeSdkPackageVersion>
<MicrosoftDotNetBuildTasksInstallersPackageVersion>10.0.0-beta.26076.108</MicrosoftDotNetBuildTasksInstallersPackageVersion>
<MicrosoftDotNetBuildTasksTemplatingPackageVersion>10.0.0-beta.26076.108</MicrosoftDotNetBuildTasksTemplatingPackageVersion>
Expand All @@ -31,8 +32,8 @@ This file should be imported by eng/Versions.props
<MicrosoftDotNetXliffTasksPackageVersion>10.0.0-beta.26076.108</MicrosoftDotNetXliffTasksPackageVersion>
<MicrosoftDotNetXUnitExtensionsPackageVersion>10.0.0-beta.26076.108</MicrosoftDotNetXUnitExtensionsPackageVersion>
<MicrosoftFSharpCompilerPackageVersion>15.2.300-servicing.26076.108</MicrosoftFSharpCompilerPackageVersion>
<MicrosoftNetCompilersToolsetPackageVersion>5.3.0-2.26076.108</MicrosoftNetCompilersToolsetPackageVersion>
<MicrosoftNetCompilersToolsetFrameworkPackageVersion>5.3.0-2.26076.108</MicrosoftNetCompilersToolsetFrameworkPackageVersion>
<MicrosoftNetCompilersToolsetPackageVersion>5.4.0-dev</MicrosoftNetCompilersToolsetPackageVersion>
<MicrosoftNetCompilersToolsetFrameworkPackageVersion>5.4.0-dev</MicrosoftNetCompilersToolsetFrameworkPackageVersion>
<MicrosoftNETRuntimeEmscriptenSdkInternalPackageVersion>10.0.0-preview.7.25377.103</MicrosoftNETRuntimeEmscriptenSdkInternalPackageVersion>
<MicrosoftNETSdkRazorSourceGeneratorsTransportPackageVersion>10.0.0-preview.26076.108</MicrosoftNETSdkRazorSourceGeneratorsTransportPackageVersion>
<MicrosoftNETTestSdkPackageVersion>18.3.0-release-26076-108</MicrosoftNETTestSdkPackageVersion>
Expand Down
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>3b3cc2b93b356d46a6fb36768479ea53515fc2cd</Sha>
</Dependency>
<Dependency Name="Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost" Version="5.3.0-2.25610.11">
<Uri>https://github.com/dotnet/roslyn</Uri>
<Sha>46a48b8c1dfce7c35da115308bedd6a5954fd78a</Sha>
</Dependency>
<Dependency Name="NuGet.Build.Tasks" Version="7.3.0-preview.1.7708">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>3b3cc2b93b356d46a6fb36768479ea53515fc2cd</Sha>
Expand Down
4 changes: 2 additions & 2 deletions src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -225,7 +225,7 @@ static ImmutableArray<RuntimeManagedCodeUpdate> ToRuntimeUpdates(IEnumerable<Hot

public override async Task<Task<bool>> ApplyStaticAssetUpdatesAsync(ImmutableArray<HotReloadStaticAssetUpdate> updates, CancellationToken processExitedCancellationToken, CancellationToken cancellationToken)
{
if (!enableStaticAssetUpdates)
if (!handlesStaticAssetUpdates)
{
// The client has no concept of static assets.
return Task.FromResult(true);
Expand Down
72 changes: 51 additions & 21 deletions src/BuiltInTools/HotReloadClient/HotReloadClients.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,13 +17,25 @@

namespace Microsoft.DotNet.HotReload;

internal sealed class HotReloadClients(ImmutableArray<(HotReloadClient client, string name)> clients, AbstractBrowserRefreshServer? browserRefreshServer) : IDisposable
/// <summary>
/// Facilitates Hot Reload updates across multiple clients/processes.
/// </summary>
/// <param name="clients">
/// Clients that handle managed updates and static asset updates if <paramref name="useRefreshServerToApplyStaticAssets"/> is false.
/// </param>
/// <param name="browserRefreshServer">
/// Browser refresh server used to communicate managed code update status and errors to the browser,
/// and to apply static asset updates if <paramref name="useRefreshServerToApplyStaticAssets"/> is true.
/// </param>
/// <param name="useRefreshServerToApplyStaticAssets">
/// True to use <paramref name="browserRefreshServer"/> to apply static asset updates (if available).
/// False to use the <paramref name="clients"/> to apply static asset updates.
/// </param>
internal sealed class HotReloadClients(
ImmutableArray<(HotReloadClient client, string name)> clients,
AbstractBrowserRefreshServer? browserRefreshServer,
bool useRefreshServerToApplyStaticAssets) : IDisposable
{
public HotReloadClients(HotReloadClient client, AbstractBrowserRefreshServer? browserRefreshServer)
: this([(client, "")], browserRefreshServer)
{
}

/// <summary>
/// Disposes all clients. Can occur unexpectedly whenever the process exits.
/// </summary>
Expand All @@ -34,6 +47,16 @@ public void Dispose()
}
}

/// <summary>
/// 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.
/// </summary>
public bool IsManagedAgentSupported
=> !clients.IsEmpty;

public bool UseRefreshServerToApplyStaticAssets
=> useRefreshServerToApplyStaticAssets;

public AbstractBrowserRefreshServer? BrowserRefreshServer
=> browserRefreshServer;

Expand All @@ -59,18 +82,6 @@ public event Action<int, string> OnRuntimeRudeEdit
}
}

/// <summary>
/// All clients share the same loggers.
/// </summary>
public ILogger ClientLogger
=> clients.First().client.Logger;

/// <summary>
/// All clients share the same loggers.
/// </summary>
public ILogger AgentLogger
=> clients.First().client.AgentLogger;

internal void ConfigureLaunchEnvironment(IDictionary<string, string> environmentBuilder)
{
foreach (var (client, _) in clients)
Expand Down Expand Up @@ -99,6 +110,12 @@ internal async ValueTask WaitForConnectionEstablishedAsync(CancellationToken can
/// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
public async ValueTask<ImmutableArray<string>> GetUpdateCapabilitiesAsync(CancellationToken cancellationToken)
{
if (!IsManagedAgentSupported)
{
// empty capabilities will cause rude edit ENC0097: NotSupportedByRuntime.
return [];
}

if (clients is [var (singleClient, _)])
{
return await singleClient.GetUpdateCapabilitiesAsync(cancellationToken);
Expand All @@ -114,6 +131,9 @@ public async ValueTask<ImmutableArray<string>> GetUpdateCapabilitiesAsync(Cancel
/// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
public async Task<Task> ApplyManagedCodeUpdatesAsync(ImmutableArray<HotReloadManagedCodeUpdate> 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.
Expand All @@ -137,6 +157,9 @@ async Task CompleteApplyOperationAsync()
/// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
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);
Expand All @@ -150,23 +173,26 @@ public async ValueTask InitialUpdatesAppliedAsync(CancellationToken cancellation
/// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
public async Task<Task> ApplyStaticAssetUpdatesAsync(IEnumerable<StaticWebAsset> 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<HotReloadStaticAssetUpdate>();

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;
}
}
Expand All @@ -177,6 +203,10 @@ public async Task<Task> ApplyStaticAssetUpdatesAsync(IEnumerable<StaticWebAsset>
/// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
public async ValueTask<Task> ApplyStaticAssetUpdatesAsync(ImmutableArray<HotReloadStaticAssetUpdate> 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);
Expand Down
14 changes: 14 additions & 0 deletions src/BuiltInTools/HotReloadClient/StaticAsset.cs
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 3 additions & 3 deletions src/BuiltInTools/Watch/AppModels/BlazorWebAssemblyAppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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), "")];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
];
}
}
9 changes: 7 additions & 2 deletions src/BuiltInTools/Watch/AppModels/DefaultAppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace Microsoft.DotNet.Watch;
/// </summary>
internal sealed class DefaultAppModel(ProjectGraphNode project) : HotReloadAppModel
{
public override ValueTask<HotReloadClients?> TryCreateClientsAsync(ILogger clientLogger, ILogger agentLogger, CancellationToken cancellationToken)
=> new(new HotReloadClients(new DefaultHotReloadClient(clientLogger, agentLogger, GetStartupHookPath(project), enableStaticAssetUpdates: true), browserRefreshServer: null));
public override ValueTask<HotReloadClients> 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));
}
Loading
Loading