- 
                Notifications
    You must be signed in to change notification settings 
- Fork 4.2k
Move Watch EA to a separate assembly Microsoft.CodeAnalysis.ExternalAccess.HotReload #80556
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
          
     Merged
      
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            3 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      
    File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | 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
          
          268 
        
  src/Features/ExternalAccess/HotReload/Api/HotReloadService.cs
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | 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; | ||
| } | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1 @@ | ||
| #nullable enable | 
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.