From 03044bd52c59b880da759205226baa58ce138e8a Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:40:29 +1000 Subject: [PATCH] RoslynAnalysis ReloadSolution --- .../Features/Analysis/RoslynAnalysis.cs | 43 +++++++++++++++++-- .../Features/Build/BuildService.cs | 11 +---- src/SharpIDE.Godot/IdeRoot.cs | 3 +- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index 1f51594..9f5570c 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Composition.Hosting; using System.Diagnostics; using Ardalis.GuardClauses; @@ -19,6 +19,8 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using SharpIDE.Application.Features.Analysis.FixLoaders; using SharpIDE.Application.Features.Analysis.Razor; +using SharpIDE.Application.Features.Build; +using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; using SharpIDE.RazorAccess; @@ -54,7 +56,7 @@ public static class RoslynAnalysis } }); } - public static async Task Analyse(SharpIdeSolutionModel solutionModel) + public static async Task Analyse(SharpIdeSolutionModel solutionModel, CancellationToken cancellationToken = default) { Console.WriteLine($"RoslynAnalysis: Loading solution"); using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(Analyse)}"); @@ -84,7 +86,11 @@ public static class RoslynAnalysis } using (var ___ = SharpIdeOtel.Source.StartActivity("OpenSolution")) { - var solutionInfo = await _msBuildProjectLoader!.LoadSolutionInfoAsync(_sharpIdeSolutionModel.FilePath); + //_msBuildProjectLoader!.LoadMetadataForReferencedProjects = true; + + // MsBuildProjectLoader doesn't do a restore which is absolutely required for resolving PackageReferences, if they have changed. I am guessing it just reads from project.assets.json + await BuildService.Instance.MsBuildAsync(_sharpIdeSolutionModel.FilePath, BuildType.Restore, cancellationToken); + var solutionInfo = await _msBuildProjectLoader!.LoadSolutionInfoAsync(_sharpIdeSolutionModel.FilePath, cancellationToken: cancellationToken); _workspace.ClearSolution(); var solution = _workspace.AddSolution(solutionInfo); } @@ -144,8 +150,37 @@ public static class RoslynAnalysis Console.WriteLine("RoslynAnalysis: Analysis completed."); } + /// Callers should call UpdateSolutionDiagnostics after this + /// Ensure that the SharpIdeSolutionModel has been updated before calling this and any subsequent calls + public static async Task ReloadSolution(CancellationToken cancellationToken = default) + { + Console.WriteLine($"RoslynAnalysis: Reloading Solution"); + using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(ReloadSolution)}"); + await _solutionLoadedTcs.Task; + Guard.Against.Null(_workspace, nameof(_workspace)); + Guard.Against.Null(_msBuildProjectLoader, nameof(_msBuildProjectLoader)); + + // It is important to note that a Workspace has no concept of MSBuild, nuget packages etc. It is just told about project references and "metadata" references, which are dlls. This is the what MSBuild does - it reads the csproj, and most importantly resolves nuget package references to dlls + await BuildService.Instance.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, cancellationToken); + var __ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.MSBuildProjectLoader.LoadSolutionInfoAsync"); + // This call is the expensive part - MSBuild is slow. There doesn't seem to be any incrementalism for solutions. + // The best we could do to speed it up is do .LoadProjectInfoAsync for the single project, and somehow munge that into the existing solution + var newSolutionInfo = await _msBuildProjectLoader.LoadSolutionInfoAsync(_sharpIdeSolutionModel!.FilePath, cancellationToken: cancellationToken); + __?.Dispose(); + + var ___ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.Workspace.OnSolutionReloaded"); + // There doesn't appear to be any noticeable difference between ClearSolution + AddSolution vs OnSolutionReloaded + //_workspace.OnSolutionReloaded(newSolutionInfo); + _workspace.ClearSolution(); + _workspace.AddSolution(newSolutionInfo); + ___?.Dispose(); + Console.WriteLine("RoslynAnalysis: Solution reloaded"); + } + public static async Task UpdateSolutionDiagnostics() { + Console.WriteLine("RoslynAnalysis: Updating solution diagnostics"); + var timer = Stopwatch.StartNew(); using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(UpdateSolutionDiagnostics)}"); await _solutionLoadedTcs.Task; foreach (var project in _sharpIdeSolutionModel!.AllProjects) @@ -155,6 +190,8 @@ public static class RoslynAnalysis project.Diagnostics.RemoveRange(project.Diagnostics); project.Diagnostics.AddRange(projectDiagnostics); } + timer.Stop(); + Console.WriteLine($"RoslynAnalysis: Solution diagnostics updated in {timer.ElapsedMilliseconds}ms"); } public static async Task> GetProjectDiagnostics(SharpIdeProjectModel projectModel) diff --git a/src/SharpIDE.Application/Features/Build/BuildService.cs b/src/SharpIDE.Application/Features/Build/BuildService.cs index 15499d6..542bf02 100644 --- a/src/SharpIDE.Application/Features/Build/BuildService.cs +++ b/src/SharpIDE.Application/Features/Build/BuildService.cs @@ -16,10 +16,12 @@ public enum BuildType } public class BuildService { + public static BuildService Instance { get; set; } = null!; public event Func BuildStarted = () => Task.CompletedTask; public ChannelTextWriter BuildTextWriter { get; } = new ChannelTextWriter(); public async Task MsBuildAsync(string solutionOrProjectFilePath, BuildType buildType = BuildType.Build, CancellationToken cancellationToken = default) { + using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(BuildService)}.{nameof(MsBuildAsync)}"); var normalOut = Console.Out; Console.SetOut(BuildTextWriter); var terminalLogger = InternalTerminalLoggerFactory.CreateLogger(); @@ -86,12 +88,3 @@ public class BuildService return nodesToBuildWith; } } - -// To build a single project -// var solutionFile = GetNodesInSolution.ParseSolutionFileFromPath(_solutionFilePath); -// ArgumentNullException.ThrowIfNull(solutionFile); -// var projects = GetNodesInSolution.GetCSharpProjectObjectsFromSolutionFile(solutionFile); -// var projectRoot = projects.First(); -// var buildRequest = new BuildRequestData( -// ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions()), -// targetsToBuild: ["Restore", "Build"]); diff --git a/src/SharpIDE.Godot/IdeRoot.cs b/src/SharpIDE.Godot/IdeRoot.cs index 0e08a57..3a769ff 100644 --- a/src/SharpIDE.Godot/IdeRoot.cs +++ b/src/SharpIDE.Godot/IdeRoot.cs @@ -44,8 +44,9 @@ public partial class IdeRoot : Control { GodotGlobalEvents.Instance = new GodotGlobalEvents(); GlobalEvents.Instance = new GlobalEvents(); + BuildService.Instance = new BuildService(); // TODO: Sort out this mess with singletons, especially access across Application services Singletons.RunService = new RunService(); - Singletons.BuildService = new BuildService(); + Singletons.BuildService = BuildService.Instance; Singletons.FileWatcher?.Dispose(); Singletons.FileWatcher = new IdeFileWatcher(); Singletons.FileManager = new IdeFileManager();