Add single project reload method
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.MSBuild;
|
||||
|
||||
namespace SharpIDE.Application.Features.Analysis;
|
||||
|
||||
public class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDictionary<string, string>? properties = null) : MSBuildProjectLoader(workspace, properties)
|
||||
{
|
||||
public async Task<ImmutableArray<ProjectInfo>> LoadProjectInfosAsync(
|
||||
List<string> projectFilePaths,
|
||||
ProjectMap? projectMap = null,
|
||||
IProgress<ProjectLoadProgress>? progress = null,
|
||||
#pragma warning disable IDE0060 // TODO: decide what to do with this unusued ILogger, since we can't reliabily use it if we're sending builds out of proc
|
||||
ILogger? msbuildLogger = null,
|
||||
#pragma warning restore IDE0060
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (projectFilePaths.Count is 0)
|
||||
{
|
||||
throw new ArgumentException("At least one project file path must be specified.", nameof(projectFilePaths));
|
||||
}
|
||||
|
||||
var requestedProjectOptions = DiagnosticReportingOptions.ThrowForAll;
|
||||
|
||||
var reportingMode = GetReportingModeForUnrecognizedProjects();
|
||||
|
||||
var discoveredProjectOptions = new DiagnosticReportingOptions(
|
||||
onPathFailure: reportingMode,
|
||||
onLoaderFailure: reportingMode);
|
||||
|
||||
var buildHostProcessManager = new BuildHostProcessManager(Properties, loggerFactory: _loggerFactory);
|
||||
await using var _ = buildHostProcessManager.ConfigureAwait(false);
|
||||
|
||||
var worker = new Worker(
|
||||
_solutionServices,
|
||||
_diagnosticReporter,
|
||||
_pathResolver,
|
||||
_projectFileExtensionRegistry,
|
||||
buildHostProcessManager,
|
||||
requestedProjectPaths: projectFilePaths.ToImmutableArray(),
|
||||
baseDirectory: Directory.GetCurrentDirectory(),
|
||||
projectMap,
|
||||
progress,
|
||||
requestedProjectOptions,
|
||||
discoveredProjectOptions,
|
||||
this.LoadMetadataForReferencedProjects);
|
||||
|
||||
return await worker.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace SharpIDE.Application.Features.Analysis;
|
||||
public static class RoslynAnalysis
|
||||
{
|
||||
public static AdhocWorkspace? _workspace;
|
||||
private static MSBuildProjectLoader? _msBuildProjectLoader;
|
||||
private static CustomMsBuildProjectLoader? _msBuildProjectLoader;
|
||||
private static RemoteSnapshotManager? _snapshotManager;
|
||||
private static RemoteSemanticTokensLegendService? _semanticTokensLegendService;
|
||||
private static SharpIdeSolutionModel? _sharpIdeSolutionModel;
|
||||
@@ -74,7 +74,7 @@ public static class RoslynAnalysis
|
||||
|
||||
var host = MefHostServices.Create(container);
|
||||
_workspace = new AdhocWorkspace(host);
|
||||
_workspace.RegisterWorkspaceFailedHandler(o => throw new InvalidOperationException($"Workspace failed: {o.Diagnostic.Message}"));
|
||||
_workspace.RegisterWorkspaceFailedHandler(o => Console.WriteLine($"Workspace failed: {o.Diagnostic.Message}"));
|
||||
|
||||
var snapshotManager = container.GetExports<RemoteSnapshotManager>().FirstOrDefault();
|
||||
_snapshotManager = snapshotManager;
|
||||
@@ -82,7 +82,7 @@ public static class RoslynAnalysis
|
||||
_semanticTokensLegendService = container.GetExports<RemoteSemanticTokensLegendService>().FirstOrDefault();
|
||||
_semanticTokensLegendService!.SetLegend(TokenTypeProvider.ConstructTokenTypes(false), TokenTypeProvider.ConstructTokenModifiers());
|
||||
|
||||
_msBuildProjectLoader = new MSBuildProjectLoader(_workspace);
|
||||
_msBuildProjectLoader = new CustomMsBuildProjectLoader(_workspace);
|
||||
}
|
||||
using (var ___ = SharpIdeOtel.Source.StartActivity("OpenSolution"))
|
||||
{
|
||||
@@ -177,6 +177,91 @@ public static class RoslynAnalysis
|
||||
Console.WriteLine("RoslynAnalysis: Solution reloaded");
|
||||
}
|
||||
|
||||
/// Callers should call UpdateSolutionDiagnostics after this
|
||||
/// Ensure that the SharpIdeSolutionModel has been updated before calling this and any subsequent calls
|
||||
public static async Task ReloadProject(SharpIdeProjectModel projectModel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Console.WriteLine($"RoslynAnalysis: Reloading Project {projectModel.FilePath}");
|
||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(ReloadSolution)}");
|
||||
await _solutionLoadedTcs.Task;
|
||||
Guard.Against.Null(_workspace, nameof(_workspace));
|
||||
Guard.Against.Null(_msBuildProjectLoader, nameof(_msBuildProjectLoader));
|
||||
|
||||
await BuildService.Instance.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, cancellationToken);
|
||||
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(MSBuildProjectLoader)}.{nameof(MSBuildProjectLoader.LoadProjectInfoAsync)}");
|
||||
|
||||
var thisProject = _workspace.CurrentSolution.Projects.Single(s => s.FilePath == projectModel.FilePath);
|
||||
|
||||
// we can reliably rely on the Solution's graph of project inter-references, as a project has only been reloaded - no projects have been added or removed from the solution
|
||||
var dependentProjects = _workspace.CurrentSolution.GetProjectDependencyGraph().GetProjectsThatTransitivelyDependOnThisProject(thisProject.Id);
|
||||
var projectPathsToReload = dependentProjects.Select(id => _workspace.CurrentSolution.GetProject(id)!.FilePath!).Append(thisProject.FilePath!).Distinct().ToList();
|
||||
//var projectMap = ProjectMap.Create(_workspace.CurrentSolution); // using a projectMap may speed up LoadProjectInfosAsync, TODO: test
|
||||
// This will get all projects necessary to build this group of projects, regardless of whether those projects are actually affected by the original project change
|
||||
// We can potentially optimise this, but given this is the expensive part, lets just proceed with reloading them all in the solution
|
||||
// We potentially lose performance because Workspace/Solution caches are dropped, but lets not prematurely optimise
|
||||
var loadedProjectInfos = await _msBuildProjectLoader.LoadProjectInfosAsync(projectPathsToReload, null, cancellationToken: cancellationToken);
|
||||
__?.Dispose();
|
||||
|
||||
var ___ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.Workspace.asdf");
|
||||
|
||||
var oldProjectIdFilePathMap = _workspace.CurrentSolution.Projects.ToDictionary(keySelector: static p => (p.FilePath!, p.Name), elementSelector: static p => p.Id);
|
||||
|
||||
var projectIdMap = loadedProjectInfos.ToDictionary(
|
||||
keySelector: static info => info.Id,
|
||||
elementSelector: info => oldProjectIdFilePathMap[(info.FilePath!, info.Name)]);
|
||||
|
||||
// When we "reload" a project, we assume: no projects have been removed from the solution, and none added (TODO: Consider/handle a project gaining a project reference to a project outside of the solution)
|
||||
// Therefore, loadedProjectInfos ⊆ (is a subset of) _workspace.CurrentSolution.Projects
|
||||
// The ProjectIds will not match however, so we need to match on FilePath
|
||||
// Since the ProjectIds don't match, we also need to remap all ProjectReferences to the existing ProjectIds
|
||||
// same for documents
|
||||
var projectInfosToUpdateWith = loadedProjectInfos.Select(loadedProjectInfo =>
|
||||
{
|
||||
var existingProject = _workspace.CurrentSolution.Projects.Single(p => p.FilePath == loadedProjectInfo.FilePath);
|
||||
var projectInfo = loadedProjectInfo
|
||||
.WithId(existingProject.Id)
|
||||
.WithDocuments(MapDocuments(_workspace.CurrentSolution, existingProject.Id, loadedProjectInfo.Documents))
|
||||
.WithProjectReferences(loadedProjectInfo.ProjectReferences.Select(MapProjectReference))
|
||||
.WithAdditionalDocuments(MapDocuments(_workspace.CurrentSolution, existingProject.Id, loadedProjectInfo.AdditionalDocuments))
|
||||
.WithAnalyzerConfigDocuments(MapDocuments(_workspace.CurrentSolution, existingProject.Id, loadedProjectInfo.AnalyzerConfigDocuments));
|
||||
return projectInfo;
|
||||
}).ToList();
|
||||
|
||||
var newSolution = _workspace.CurrentSolution;
|
||||
foreach (var projectInfo in projectInfosToUpdateWith)
|
||||
{
|
||||
newSolution = newSolution.WithProjectInfo(projectInfo);
|
||||
}
|
||||
// Doesn't raise a workspace change event, for now we don't care?
|
||||
_workspace.SetCurrentSolution(newSolution);
|
||||
|
||||
// We should potentially use the below instead of SetCurrentSolution, as it is async, and potentially has better locking semantics
|
||||
// I think we will run into this imminently, when we handle multiple rapid project reloads
|
||||
// await _workspace.SetCurrentSolutionAsync(true,
|
||||
// transformation: oldSolution =>
|
||||
// {
|
||||
// // Move above code in here
|
||||
// return oldSolution;
|
||||
// },
|
||||
// changeKind: (oldSln, newSln) => (WorkspaceChangeKind.SolutionChanged, null, null),
|
||||
// null, null, cancellationToken
|
||||
// );
|
||||
|
||||
_workspace.UpdateReferencesAfterAdd();
|
||||
|
||||
___?.Dispose();
|
||||
Console.WriteLine("RoslynAnalysis: Project reloaded");
|
||||
return;
|
||||
ProjectReference MapProjectReference(ProjectReference oldRef) => new ProjectReference(projectIdMap[oldRef.ProjectId], oldRef.Aliases, oldRef.EmbedInteropTypes);
|
||||
|
||||
static ImmutableArray<DocumentInfo> MapDocuments(Solution oldSolution, ProjectId mappedProjectId, IReadOnlyList<DocumentInfo> documents)
|
||||
=> documents.Select(docInfo =>
|
||||
{
|
||||
var mappedDocumentId = oldSolution.GetDocumentIdsWithFilePath(docInfo.FilePath).Single(id => id.ProjectId == mappedProjectId);
|
||||
return docInfo.WithId(mappedDocumentId);
|
||||
}).ToImmutableArray();
|
||||
}
|
||||
|
||||
public static async Task UpdateSolutionDiagnostics()
|
||||
{
|
||||
Console.WriteLine("RoslynAnalysis: Updating solution diagnostics");
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Remote.Razor" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Razor.Workspaces" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Razor.Compiler" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.VisualStudioCode.RazorExtension" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.ExternalAccess.Razor.Features" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Razor.SemanticTokens" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Workspaces" />
|
||||
<IgnoresAccessChecksToExcludeTypeName Include="System.Linq.RoslynEnumerableExtensions" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Remote.Razor" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Razor.Workspaces" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Razor.Compiler" />
|
||||
<Publicize Include="Microsoft.VisualStudioCode.RazorExtension" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.LanguageServer.Protocol" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.ExternalAccess.Razor.Features" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Razor.SemanticTokens" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Workspaces" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" />
|
||||
<DoNotPublicize Include="Microsoft.CodeAnalysis.Workspaces:System.Linq.RoslynEnumerableExtensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -31,7 +32,7 @@
|
||||
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" />
|
||||
<PackageReference Include="IgnoresAccessChecksToGenerator">
|
||||
<PackageReference Include="Krafs.Publicizer">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
Reference in New Issue
Block a user