Skip to content
Merged
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
20 changes: 17 additions & 3 deletions Roslyn.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36203.30
# Visual Studio Version 18
VisualStudioVersion = 18.0.11101.28 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynDeployment", "src\Deployment\RoslynDeployment.csproj", "{600AF682-E097-407B-AD85-EE3CED37E680}"
EndProject
Expand Down Expand Up @@ -727,7 +727,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeA
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.BuildClient.Package", "src\NuGet\Microsoft.CodeAnalysis.BuildClient.Package\Microsoft.CodeAnalysis.BuildClient.Package.csproj", "{5E4F7448-B00B-4F5B-859F-6ED0354253D5}"
EndProject
Project("{9a19103f-16f7-4668-be54-9a1e7a4f7556}") = "Microsoft.CodeAnalysis.SemanticSearch.Extensions", "src\Tools\SemanticSearch\Extensions\Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj", "{66C8265C-2C79-F259-9807-3E97CA586F1E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.SemanticSearch.Extensions", "src\Tools\SemanticSearch\Extensions\Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj", "{66C8265C-2C79-F259-9807-3E97CA586F1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.HotReload", "src\Features\ExternalAccess\HotReload\Microsoft.CodeAnalysis.ExternalAccess.HotReload.csproj", "{FDDC4384-F466-DB42-61A7-E21DF5EB84BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.HotReload.UnitTests", "src\Features\ExternalAccess\HotReloadTest\Microsoft.CodeAnalysis.ExternalAccess.HotReload.UnitTests.csproj", "{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -1803,6 +1807,14 @@ Global
{66C8265C-2C79-F259-9807-3E97CA586F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66C8265C-2C79-F259-9807-3E97CA586F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66C8265C-2C79-F259-9807-3E97CA586F1E}.Release|Any CPU.Build.0 = Release|Any CPU
{FDDC4384-F466-DB42-61A7-E21DF5EB84BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDDC4384-F466-DB42-61A7-E21DF5EB84BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDDC4384-F466-DB42-61A7-E21DF5EB84BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDDC4384-F466-DB42-61A7-E21DF5EB84BB}.Release|Any CPU.Build.0 = Release|Any CPU
{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -2144,6 +2156,8 @@ Global
{5399BBCC-417F-C710-46DE-EB0C0074C34D} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{5E4F7448-B00B-4F5B-859F-6ED0354253D5} = {C52D8057-43AF-40E6-A01B-6CDBB7301985}
{66C8265C-2C79-F259-9807-3E97CA586F1E} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20}
{FDDC4384-F466-DB42-61A7-E21DF5EB84BB} = {58A2876A-618D-4AE6-A136-E44B42BBDE11}
{D03262C0-4AE6-1ED5-7B38-4150DACFF9D8} = {58A2876A-618D-4AE6-A136-E44B42BBDE11}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29}
Expand Down
1 change: 1 addition & 0 deletions eng/config/PublishData.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator": "vs-impl",
"Microsoft.CodeAnalysis.ExternalAccess.Extensions": "vs-impl",
"Microsoft.CodeAnalysis.ExternalAccess.FSharp": "vs-impl",
"Microsoft.CodeAnalysis.ExternalAccess.HotReload": "arcade",
"Microsoft.CodeAnalysis.ExternalAccess.OmniSharp": "arcade",
"Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp": "arcade",
"Microsoft.CodeAnalysis.ExternalAccess.AspNetCore": "vs-impl",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<InternalsVisibleTo Include="Microsoft.Build.Tasks.CodeAnalysis.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.Test.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload.UnitTests" />
<InternalsVisibleTo Include="SemanticSearch.BuildTask.UnitTests" />
<InternalsVisibleTo Include="Roslyn.Test.PdbUtilities"/>
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Rebuild.UnitTests" />
<InternalsVisibleTo Include="Roslyn.Test.PdbUtilities" />
<InternalsVisibleTo Include="Roslyn.VisualStudio.Next.UnitTests" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.Xaml" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.DiagnosticsTests.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.Test.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.UnitTests" />
Expand Down Expand Up @@ -105,7 +107,7 @@
<InternalsVisibleTo Include="VBCSCompiler" />
<InternalsVisibleTo Include="AITools.CodeAnalysis" Key="$(AIToolsKey)" />

<RestrictedInternalsVisibleTo Include="dotnet-watch" Partner="Watch" Key="$(AspNetCoreKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.DotNet.HotReload.Watch" Partner="Watch" Key="$(AspNetCoreKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.Orchestrator" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery.Core" Partner="UnitTesting" Key="$(UnitTestingKey)" />
Expand Down
2 changes: 2 additions & 0 deletions src/Features/ExternalAccess/HotReload/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[**]
dotnet_public_api_analyzer.skip_namespaces = Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.Internal
268 changes: 268 additions & 0 deletions src/Features/ExternalAccess/HotReload/Api/HotReloadService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Host;

namespace Microsoft.CodeAnalysis.ExternalAccess.HotReload.Api;

internal sealed class HotReloadService(SolutionServices services, Func<ValueTask<ImmutableArray<string>>> capabilitiesProvider)
{
private sealed class DebuggerService(Func<ValueTask<ImmutableArray<string>>> capabilitiesProvider) : IManagedHotReloadService
{
public ValueTask<ImmutableArray<ManagedActiveStatementDebugInfo>> GetActiveStatementsAsync(CancellationToken cancellationToken)
=> ValueTask.FromResult(ImmutableArray<ManagedActiveStatementDebugInfo>.Empty);

public ValueTask<ManagedHotReloadAvailability> GetAvailabilityAsync(Guid module, CancellationToken cancellationToken)
=> ValueTask.FromResult(new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.Available));

public ValueTask<ImmutableArray<string>> GetCapabilitiesAsync(CancellationToken cancellationToken)
=> capabilitiesProvider();

public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}

public readonly struct Update
{
public readonly Guid ModuleId;
public readonly ProjectId ProjectId;
public readonly ImmutableArray<byte> ILDelta;
public readonly ImmutableArray<byte> MetadataDelta;
public readonly ImmutableArray<byte> PdbDelta;
public readonly ImmutableArray<int> UpdatedTypes;
public readonly ImmutableArray<string> RequiredCapabilities;

internal Update(
Guid moduleId,
ProjectId projectId,
ImmutableArray<byte> ilDelta,
ImmutableArray<byte> metadataDelta,
ImmutableArray<byte> pdbDelta,
ImmutableArray<int> updatedTypes,
ImmutableArray<string> requiredCapabilities)
{
ModuleId = moduleId;
ProjectId = projectId;
ILDelta = ilDelta;
MetadataDelta = metadataDelta;
PdbDelta = pdbDelta;
UpdatedTypes = updatedTypes;
RequiredCapabilities = requiredCapabilities;
}
}

public readonly struct RunningProjectInfo
{
public required bool RestartWhenChangesHaveNoEffect { get; init; }
}

public enum Status
{
/// <summary>
/// No significant changes made that need to be applied.
/// </summary>
NoChangesToApply,

/// <summary>
/// Changes can be applied either via updates or restart.
/// </summary>
ReadyToApply,

/// <summary>
/// Some changes are errors that block rebuild of the module.
/// This means that the code is in a broken state that cannot be resolved by restarting the application.
/// </summary>
Blocked,
}

public readonly struct Updates
{
/// <summary>
/// Status of the updates.
/// </summary>
public readonly Status Status { get; init; }

/// <summary>
/// Returns all diagnostics that can't be addressed by rebuilding/restarting the project.
/// Syntactic, semantic and emit diagnostics.
/// </summary>
/// <remarks>
/// <see cref="Status"/> is <see cref="Status.Blocked"/> if these diagnostics contain any errors.
/// </remarks>
public required ImmutableArray<Diagnostic> PersistentDiagnostics { get; init; }

/// <summary>
/// Transient diagnostics (rude edits) per project.
/// All diagnostics that can be addressed by rebuilding/restarting the project.
/// </summary>
public required ImmutableArray<(ProjectId project, ImmutableArray<Diagnostic> diagnostics)> TransientDiagnostics { get; init; }

/// <summary>
/// Updates to be applied to modules. Empty if there are blocking rude edits.
/// Only updates to projects that are not included in <see cref="ProjectsToRebuild"/> are listed.
/// </summary>
public required ImmutableArray<Update> ProjectUpdates { get; init; }

/// <summary>
/// Running projects that need to be restarted due to rude edits in order to apply changes.
/// </summary>
public required ImmutableDictionary<ProjectId, ImmutableArray<ProjectId>> ProjectsToRestart { get; init; }

/// <summary>
/// Projects with changes that need to be rebuilt in order to apply changes.
/// </summary>
public required ImmutableArray<ProjectId> ProjectsToRebuild { get; init; }

/// <summary>
/// Projects whose dependencies need to be deployed to their output directory, if not already present.
/// </summary>
public required ImmutableArray<ProjectId> ProjectsToRedeploy { get; init; }
}

private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider =
(_, _, _) => ValueTask.FromResult(ImmutableArray<ActiveStatementSpan>.Empty);

private readonly IEditAndContinueService _encService = services.GetRequiredService<IEditAndContinueWorkspaceService>().Service;

private DebuggingSessionId _sessionId;

public HotReloadService(HostWorkspaceServices services, ImmutableArray<string> capabilities)
: this(services.SolutionServices, () => ValueTask.FromResult(AddImplicitDotNetCapabilities(capabilities)))
{
}

private DebuggingSessionId GetDebuggingSession()
{
var sessionId = _sessionId;
Contract.ThrowIfFalse(sessionId != default, "Session has not started");
return sessionId;
}

/// <summary>
/// Adds capabilities that are available by default on runtimes supported by dotnet-watch: .NET and Mono
/// and not on .NET Framework (they are not in <see cref="EditAndContinueCapabilities.Baseline"/>.
/// </summary>
private static ImmutableArray<string> AddImplicitDotNetCapabilities(ImmutableArray<string> capabilities)
=> capabilities.Add(nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation));

/// <summary>
/// Starts the watcher.
/// </summary>
/// <param name="solution">Solution that represents sources that match the built binaries on disk.</param>
public async Task StartSessionAsync(Solution solution, CancellationToken cancellationToken)
{
var newSessionId = await _encService.StartDebuggingSessionAsync(
solution,
new DebuggerService(capabilitiesProvider),
NullPdbMatchingSourceTextProvider.Instance,
captureMatchingDocuments: [],
captureAllMatchingDocuments: true,
reportDiagnostics: false,
cancellationToken).ConfigureAwait(false);
Contract.ThrowIfFalse(_sessionId == default, "Session already started");
_sessionId = newSessionId;
}

/// <summary>
/// Invoke when capabilities have changed.
/// </summary>
public void CapabilitiesChanged()
{
_encService.BreakStateOrCapabilitiesChanged(GetDebuggingSession(), inBreakState: null);
}

/// <summary>
/// Returns TFM of a given project.
/// </summary>
public static string? GetTargetFramework(Project project)
=> project.State.NameAndFlavor.flavor;

/// <summary>
/// Emits updates for all projects that differ between the given <paramref name="solution"/> snapshot and the one given to the previous successful call or
/// the one passed to <see cref="StartSessionAsync(Solution, CancellationToken)"/> for the first invocation.
/// </summary>
/// <param name="solution">Solution snapshot.</param>
/// <param name="runningProjects">Identifies projects that launched a process.</param>
/// <returns>
/// Updates (one for each changed project) and Rude Edit diagnostics. Does not include syntax or semantic diagnostics.
/// May include both updates and Rude Edits for different projects.
/// </returns>
public async Task<Updates> GetUpdatesAsync(Solution solution, ImmutableDictionary<ProjectId, RunningProjectInfo> runningProjects, CancellationToken cancellationToken)
{
var sessionId = GetDebuggingSession();

var runningProjectsImpl = runningProjects.ToImmutableDictionary(
static e => e.Key,
static e => new RunningProjectOptions()
{
RestartWhenChangesHaveNoEffect = e.Value.RestartWhenChangesHaveNoEffect
});

var results = await _encService.EmitSolutionUpdateAsync(sessionId, solution, runningProjectsImpl, s_solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false);

return new Updates
{
Status = results.ModuleUpdates.Status switch
{
ModuleUpdateStatus.None => Status.NoChangesToApply,
ModuleUpdateStatus.Ready => Status.ReadyToApply,
ModuleUpdateStatus.Blocked => Status.Blocked,
_ => throw ExceptionUtilities.UnexpectedValue(results.ModuleUpdates.Status)
},
PersistentDiagnostics = results.GetPersistentDiagnostics(),
TransientDiagnostics = results.GetTransientDiagnostics(),
ProjectUpdates = results.ModuleUpdates.Updates.SelectAsArray(static update => new Update(
update.Module,
update.ProjectId,
update.ILDelta,
update.MetadataDelta,
update.PdbDelta,
update.UpdatedTypes,
update.RequiredCapabilities)),
ProjectsToRestart = results.ProjectsToRestart,
ProjectsToRebuild = results.ProjectsToRebuild,
ProjectsToRedeploy = results.ProjectsToRedeploy,
};
}

public void CommitUpdate()
{
var sessionId = GetDebuggingSession();
_encService.CommitSolutionUpdate(sessionId);
}

public void DiscardUpdate()
{
var sessionId = GetDebuggingSession();
_encService.DiscardSolutionUpdate(sessionId);
}

public void EndSession()
{
_encService.EndDebuggingSession(GetDebuggingSession());
_sessionId = default;
}

// access to internal API:
public static Solution WithProjectInfo(Solution solution, ProjectInfo info)
=> solution.WithProjectInfo(info);

internal TestAccessor GetTestAccessor()
=> new(this);

internal readonly struct TestAccessor(HotReloadService instance)
{
public DebuggingSessionId SessionId
=> instance._sessionId;

public IEditAndContinueService EncService
=> instance._encService;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Loading