Check msbuild globs before adding file to workspace
This commit is contained in:
@@ -111,7 +111,7 @@ public partial class CustomMsBuildProjectLoader
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ImmutableArray<ProjectInfo>> LoadAsync(CancellationToken cancellationToken)
|
||||
public async Task<(ImmutableArray<ProjectInfo>, Dictionary<ProjectId, ProjectFileInfo>)> LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var results = ImmutableArray.CreateBuilder<ProjectInfo>();
|
||||
var processedPaths = new HashSet<string>(PathUtilities.Comparer);
|
||||
@@ -150,7 +150,7 @@ public partial class CustomMsBuildProjectLoader
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToImmutableAndClear();
|
||||
return (results.ToImmutableAndClear(), _projectIdToFileInfoMap);
|
||||
}
|
||||
|
||||
private async Task<ImmutableArray<ProjectFileInfo>> LoadProjectFileInfosAsync(string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace SharpIDE.Application.Features.Analysis.ProjectLoader;
|
||||
// https://github.com/dotnet/roslyn/blob/main/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs
|
||||
public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDictionary<string, string>? properties = null) : MSBuildProjectLoader(workspace, properties)
|
||||
{
|
||||
public async Task<ImmutableArray<ProjectInfo>> LoadProjectInfosAsync(
|
||||
public async Task<(ImmutableArray<ProjectInfo>, Dictionary<ProjectId, ProjectFileInfo>)> LoadProjectInfosAsync(
|
||||
List<string> projectFilePaths,
|
||||
ProjectMap? projectMap = null,
|
||||
IProgress<ProjectLoadProgress>? progress = null,
|
||||
@@ -64,7 +64,7 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi
|
||||
/// <param name="progress">An optional <see cref="IProgress{T}"/> that will receive updates as the solution is loaded.</param>
|
||||
/// <param name="msbuildLogger">An optional <see cref="ILogger"/> that will log MSBuild results.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> to allow cancellation of this operation.</param>
|
||||
public new async Task<SolutionInfo> LoadSolutionInfoAsync(
|
||||
public new async Task<(SolutionInfo, Dictionary<ProjectId, ProjectFileInfo>)> LoadSolutionInfoAsync(
|
||||
string solutionFilePath,
|
||||
IProgress<ProjectLoadProgress>? progress = null,
|
||||
ILogger? msbuildLogger = null,
|
||||
@@ -109,13 +109,15 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi
|
||||
discoveredProjectOptions: reportingOptions,
|
||||
preferMetadataForReferencesOfDiscoveredProjects: false);
|
||||
|
||||
var projectInfos = await worker.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||
var (projectInfos, projectFileInfos) = await worker.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// construct workspace from loaded project infos
|
||||
return SolutionInfo.Create(
|
||||
var solutionInfo = SolutionInfo.Create(
|
||||
SolutionId.CreateNewId(debugName: absoluteSolutionPath),
|
||||
version: default,
|
||||
absoluteSolutionPath,
|
||||
projectInfos);
|
||||
|
||||
return (solutionInfo, projectFileInfos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens;
|
||||
using Microsoft.CodeAnalysis.Rename;
|
||||
using Microsoft.CodeAnalysis.Shared.Extensions;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NuGet.Frameworks;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
@@ -53,6 +54,9 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
private static HashSet<CodeFixProvider> _codeFixProviders = [];
|
||||
private static HashSet<CodeRefactoringProvider> _codeRefactoringProviders = [];
|
||||
|
||||
// Primarily used for getting the globs for a project
|
||||
private Dictionary<ProjectId, ProjectFileInfo> _projectFileInfoMap = new();
|
||||
|
||||
private TaskCompletionSource _solutionLoadedTcs = null!;
|
||||
private SharpIdeSolutionModel? _sharpIdeSolutionModel;
|
||||
public void StartSolutionAnalysis(SharpIdeSolutionModel solutionModel)
|
||||
@@ -110,7 +114,8 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
|
||||
// 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.MsBuildAsync(_sharpIdeSolutionModel.FilePath, BuildType.Restore, cancellationToken);
|
||||
var solutionInfo = await _msBuildProjectLoader!.LoadSolutionInfoAsync(_sharpIdeSolutionModel.FilePath, cancellationToken: cancellationToken);
|
||||
var (solutionInfo, projectFileInfos) = await _msBuildProjectLoader!.LoadSolutionInfoAsync(_sharpIdeSolutionModel.FilePath, cancellationToken: cancellationToken);
|
||||
_projectFileInfoMap = projectFileInfos;
|
||||
_workspace.ClearSolution();
|
||||
var solution = _workspace.AddSolution(solutionInfo);
|
||||
}
|
||||
@@ -185,7 +190,8 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
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);
|
||||
var (newSolutionInfo, projectFileInfos) = await _msBuildProjectLoader.LoadSolutionInfoAsync(_sharpIdeSolutionModel!.FilePath, cancellationToken: cancellationToken);
|
||||
_projectFileInfoMap = projectFileInfos;
|
||||
__?.Dispose();
|
||||
|
||||
var ___ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.Workspace.OnSolutionReloaded");
|
||||
@@ -219,7 +225,11 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
// 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);
|
||||
var (loadedProjectInfos, projectFileInfos) = await _msBuildProjectLoader.LoadProjectInfosAsync(projectPathsToReload, null, cancellationToken: cancellationToken);
|
||||
foreach (var (projectId, projectFileInfo) in projectFileInfos)
|
||||
{
|
||||
_projectFileInfoMap[projectId] = projectFileInfo;
|
||||
}
|
||||
__?.Dispose();
|
||||
|
||||
var ___ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.Workspace.UpdateSolution");
|
||||
@@ -960,12 +970,46 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
Guard.Against.Null(fileModel, nameof(fileModel));
|
||||
Guard.Against.Null(content, nameof(content));
|
||||
|
||||
var project = GetProjectForSharpIdeFile(fileModel);
|
||||
var sharpIdeProject = GetSharpIdeProjectForSharpIdeFile(fileModel);
|
||||
var probableProject = GetProjectForSharpIdeProjectModel(sharpIdeProject);
|
||||
// This file probably belongs to this project, but we need to check its path against the globs for the project to make sure
|
||||
var projectFileInfo = _projectFileInfoMap.GetValueOrDefault(probableProject.Id);
|
||||
Guard.Against.Null(projectFileInfo);
|
||||
var matchers = projectFileInfo.FileGlobs.Select(glob =>
|
||||
{
|
||||
var matcher = new Matcher();
|
||||
matcher.AddIncludePatterns(glob.Includes);
|
||||
matcher.AddExcludePatterns(glob.Excludes);
|
||||
matcher.AddExcludePatterns(glob.Removes);
|
||||
return matcher;
|
||||
});
|
||||
|
||||
var belongsToProject = false;
|
||||
// Check if the file path matches any of the globs in the project file.
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
// CPS re-creates the msbuild globs from the includes/excludes/removes and the project XML directory and
|
||||
// ignores the MSBuildGlob.FixedDirectoryPart. We'll do the same here and match using the project directory as the relative path.
|
||||
// See https://devdiv.visualstudio.com/DevDiv/_git/CPS?path=/src/Microsoft.VisualStudio.ProjectSystem/Build/MsBuildGlobFactory.cs
|
||||
var relativeDirectory = sharpIdeProject.DirectoryPath;
|
||||
|
||||
var matches = matcher.Match(relativeDirectory, fileModel.Path);
|
||||
if (matches.HasMatches)
|
||||
{
|
||||
belongsToProject = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (belongsToProject is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existingDocument = fileModel switch
|
||||
{
|
||||
{ IsRazorFile: true } => project.AdditionalDocuments.SingleOrDefault(s => s.FilePath == fileModel.Path),
|
||||
{ IsCsharpFile: true } => project.Documents.SingleOrDefault(s => s.FilePath == fileModel.Path),
|
||||
{ IsRazorFile: true } => probableProject.AdditionalDocuments.SingleOrDefault(s => s.FilePath == fileModel.Path),
|
||||
{ IsCsharpFile: true } => probableProject.Documents.SingleOrDefault(s => s.FilePath == fileModel.Path),
|
||||
_ => throw new InvalidOperationException("AddDocument failed: File is not a workspace file")
|
||||
};
|
||||
if (existingDocument is not null)
|
||||
@@ -977,8 +1021,8 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
|
||||
var newSolution = fileModel switch
|
||||
{
|
||||
{ IsRazorFile: true } => _workspace.CurrentSolution.AddAdditionalDocument(DocumentId.CreateNewId(project.Id), fileModel.Name, sourceText, filePath: fileModel.Path),
|
||||
{ IsCsharpFile: true } => _workspace.CurrentSolution.AddDocument(DocumentId.CreateNewId(project.Id), fileModel.Name, sourceText, filePath: fileModel.Path),
|
||||
{ IsRazorFile: true } => _workspace.CurrentSolution.AddAdditionalDocument(DocumentId.CreateNewId(probableProject.Id), fileModel.Name, sourceText, filePath: fileModel.Path),
|
||||
{ IsCsharpFile: true } => _workspace.CurrentSolution.AddDocument(DocumentId.CreateNewId(probableProject.Id), fileModel.Name, sourceText, filePath: fileModel.Path),
|
||||
_ => throw new InvalidOperationException("AddDocument failed: File is not in workspace")
|
||||
};
|
||||
|
||||
@@ -1037,9 +1081,15 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
private static Project GetProjectForSharpIdeFile(SharpIdeFile sharpIdeFile)
|
||||
private static SharpIdeProjectModel GetSharpIdeProjectForSharpIdeFile(SharpIdeFile sharpIdeFile)
|
||||
{
|
||||
var sharpIdeProjectModel = ((IChildSharpIdeNode)sharpIdeFile).GetNearestProjectNode()!;
|
||||
return sharpIdeProjectModel;
|
||||
}
|
||||
|
||||
private static Project GetProjectForSharpIdeFile(SharpIdeFile sharpIdeFile)
|
||||
{
|
||||
var sharpIdeProjectModel = GetSharpIdeProjectForSharpIdeFile(sharpIdeFile);
|
||||
var project = GetProjectForSharpIdeProjectModel(sharpIdeProjectModel);
|
||||
return project;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user