From e1f5100fdf2f3ee5e935632bdc37bf0f9317a589 Mon Sep 17 00:00:00 2001 From: Kevin Pilch-Bisson Date: Mon, 5 Sep 2016 21:00:06 -0700 Subject: [PATCH] Start using project reference information Also, use the right solution load event so we don't have to delay. Finally, use the IVsSolution APIs to get project file names and guids. --- .../IDeferredProjectWorkspaceService.cs | 3 +- ...lStudioProjectTracker_IVsSolutionEvents.cs | 6 + ...dioProjectTracker_IVsSolutionLoadEvents.cs | 126 +++++++++++------- .../DeferredProjectWorkspaceService.cs | 8 +- .../Core/Desktop/PublicAPI.Unshipped.txt | 1 - .../Workspace/MSBuild/MSBuildProjectLoader.cs | 32 ----- 6 files changed, 89 insertions(+), 87 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IDeferredProjectWorkspaceService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IDeferredProjectWorkspaceService.cs index b5865fb9320..13afc9c1053 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IDeferredProjectWorkspaceService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IDeferredProjectWorkspaceService.cs @@ -5,12 +5,13 @@ using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal interface IDeferredProjectWorkspaceService : IWorkspaceService { bool IsDeferredProjectLoadEnabled { get; } - Task> GetCommandLineArgumentsForProjectAsync(string projectFilePath); + Task, ImmutableArray>> GetCommandLineArgumentsAndProjectReferencesForProjectAsync(string projectFilePath); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs index 953577c426b..5c927272651 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs @@ -21,6 +21,12 @@ int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution) { + var deferredProjectWorkspaceService = _workspace.Services.GetService(); + if (deferredProjectWorkspaceService?.IsDeferredProjectLoadEnabled ?? false) + { + LoadSolutionFromMSBuild(deferredProjectWorkspaceService, _solutionParsingCancellationTokenSource.Token).FireAndForget(); + } + return VSConstants.S_OK; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs index 2840fa41bef..2b446c4d078 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs @@ -31,19 +31,11 @@ internal partial class VisualStudioProjectTracker : IVsSolutionLoadEvents int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) { - var deferredProjectWorkspaceService = _workspace.Services.GetService(); - - if (deferredProjectWorkspaceService?.IsDeferredProjectLoadEnabled ?? false) - { - LoadSolutionFromMSBuild(deferredProjectWorkspaceService, pszSolutionFilename, _solutionParsingCancellationTokenSource.Token).FireAndForget(); - } - return VSConstants.S_OK; } private async Task LoadSolutionFromMSBuild( IDeferredProjectWorkspaceService deferredProjectWorkspaceService, - string solutionFileName, CancellationToken cancellationToken) { AssertIsForeground(); @@ -53,12 +45,10 @@ int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) ErrorHandler.Succeeded(outputWindow.GetPane(ref paneGuid, out _pane)) && _pane != null) { _pane.Activate(); - OutputToOutputWindow("OnBeforeOpenSolution - waiting 3 seconds to load solution in background"); } // Continue on the UI thread for these operations, since we are touching the VisualStudioWorkspace, etc. - await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(true); - await LoadSolutionInBackground(deferredProjectWorkspaceService, solutionFileName, cancellationToken).ConfigureAwait(true); + await LoadSolutionInBackground(deferredProjectWorkspaceService, cancellationToken).ConfigureAwait(true); } int IVsSolutionLoadEvents.OnBeforeBackgroundSolutionLoadBegins() @@ -114,7 +104,6 @@ int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() private async Task LoadSolutionInBackground( IDeferredProjectWorkspaceService deferredProjectWorkspaceService, - string solutionFilename, CancellationToken cancellationToken) { AssertIsForeground(); @@ -124,8 +113,16 @@ int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() OutputToOutputWindow("Parsing solution - start"); var start = DateTimeOffset.UtcNow; - var loader = new MSBuildProjectLoader(_workspace); - var projectFilenames = loader.GetProjectPathsInSolution(solutionFilename); + var solution = _serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution; + + // Get the count of projects in the solution. + uint projectCount; + ErrorHandler.ThrowOnFailure(solution.GetProjectFilesInSolution(grfGetOpts: 0, cProjects: 0, rgbstrProjectNames: null, pcProjectsFetched: out projectCount)); + + // Now get the actual filenames. + var projectFilenames = new string[projectCount]; + ErrorHandler.ThrowOnFailure(solution.GetProjectFilesInSolution(grfGetOpts: 0, cProjects: projectCount, rgbstrProjectNames: projectFilenames, pcProjectsFetched: out projectCount)); + OutputToOutputWindow($"Parsing solution - done (took {DateTimeOffset.UtcNow - start})"); OutputToOutputWindow($"Creating projects - start ({projectFilenames.Length} to create)"); start = DateTimeOffset.UtcNow; @@ -133,13 +130,12 @@ int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() foreach (var projectFilename in projectFilenames) { // Capture the context so that we come back on the UI thread, and do the actual project creation there. - var commandLineStrings = await deferredProjectWorkspaceService.GetCommandLineArgumentsForProjectAsync(projectFilename).ConfigureAwait(true); + await CreateProjectFromArgumentsAndReferencesAsync( + workspaceProjectContextFactory, + solution, + deferredProjectWorkspaceService, + projectFilename).ConfigureAwait(true); AssertIsForeground(); - - if (commandLineStrings != null) - { - CreateProjectFromProjectInfo(workspaceProjectContextFactory, projectFilename, commandLineStrings); - } } OutputToOutputWindow($"Creating projects - done (took {DateTimeOffset.UtcNow - start})"); @@ -157,16 +153,30 @@ private void OutputToOutputWindow(string message) } } - private IWorkspaceProjectContext CreateProjectFromProjectInfo( + private async Task CreateProjectFromArgumentsAndReferencesAsync( IWorkspaceProjectContextFactory workspaceProjectContextFactory, - string projectFilename, - ImmutableArray commandLineStrings) + IVsSolution solution, + IDeferredProjectWorkspaceService deferredProjectWorkspaceService, + string projectFilename) { - var languageName = Path.GetExtension(projectFilename) == ".csproj" ? LanguageNames.CSharp : LanguageNames.VisualBasic; + var languageName = GetLanguageOfProject(projectFilename); + if (languageName == null) + { + return null; + } + + // Capture the context so that we come back on the UI thread, and do the actual project creation there. + var commandLineArgumentsAndProjectReferences = await deferredProjectWorkspaceService + .GetCommandLineArgumentsAndProjectReferencesForProjectAsync(projectFilename).ConfigureAwait(true); + if (commandLineArgumentsAndProjectReferences.Item1.IsDefaultOrEmpty) + { + return null; + } + var commandLineParser = _workspace.Services.GetLanguageServices(languageName).GetService(); var projectDirectory = Path.GetDirectoryName(projectFilename); var commandLineArguments = commandLineParser.Parse( - commandLineStrings, + commandLineArgumentsAndProjectReferences.Item1, projectDirectory, isInteractive: false, sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory()); @@ -183,15 +193,16 @@ private void OutputToOutputWindow(string message) // TODO: We need to actually get an output path somehow. OutputToOutputWindow($"\tCreating '{projectName}':\t{commandLineArguments.SourceFiles.Length} source files,\t{commandLineArguments.MetadataReferences.Length} references."); + var projectGuid = ((IVsSolution5)solution).GetGuidOfProjectFile(projectFilename); var projectContext = workspaceProjectContextFactory.CreateProjectContext( languageName, projectName, projectFilename, - Guid.Empty, + projectGuid: projectGuid, hierarchy: null, binOutputPath: null); - projectContext.SetOptions(commandLineStrings.Join(" ")); + projectContext.SetOptions(commandLineArgumentsAndProjectReferences.Item1.Join(" ")); foreach (var sourceFile in commandLineArguments.SourceFiles) { @@ -208,42 +219,44 @@ private void OutputToOutputWindow(string message) projectContext.AddMetadataReference(reference.Reference, reference.Properties); } -#if false - foreach (var reference in projectInfo.ProjectReferences) + foreach (var projectReference in commandLineArgumentsAndProjectReferences.Item2) { - var referencedProject = (IWorkspaceProjectContext)projectToProjectInfo.SingleOrDefault(kvp => kvp.Value.Id == reference.ProjectId).Key; - + var projectReferencePath = projectReference.Substring("/ProjectReference:".Length); + var referencedProject = Projects.SingleOrDefault(p => StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, projectReferencePath)); if (referencedProject == null) { - var referencedProjectInfo = solutionInfo.Projects.Single(pi => pi.Id == reference.ProjectId); - referencedProject = CreateProjectFromProjectInfo(workspaceProjectContextFactory, projectToProjectInfo, referencedProjectInfo, solutionInfo); + // Capture the context so that we come back on the UI thread, and do the actual project creation there. + referencedProject = (AbstractProject)(await CreateProjectFromArgumentsAndReferencesAsync( + workspaceProjectContextFactory, + solution, + deferredProjectWorkspaceService, + projectReferencePath).ConfigureAwait(true)); } - if (referencedProject != null) + var referencedProjectContext = referencedProject as IWorkspaceProjectContext; + if (referencedProjectContext != null) { projectContext.AddProjectReference( - referencedProject, - new MetadataReferenceProperties(aliases: reference.Aliases, embedInteropTypes: reference.EmbedInteropTypes)); + referencedProjectContext, + new MetadataReferenceProperties()); } - else + else if (referencedProject != null) { - // If referencedProject is still null, it means that this project was already created by the regular project system. - // See if we can find the matching project somehow. - var referenceInfo = solutionInfo.Projects.SingleOrDefault(p => p.Id == reference.ProjectId); - if (referenceInfo != null) + // This project was already created by the regular project system. See if we + // can find the matching project somehow. + var existingReferenceOutputPath = referencedProject?.BinOutputPath; + if (existingReferenceOutputPath != null) { - var existingReference = Projects.SingleOrDefault(p => StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, referenceInfo.FilePath)); - var existingReferenceOutputPath = existingReference?.BinOutputPath; - if (existingReferenceOutputPath != null) - { - projectContext.AddMetadataReference( - existingReferenceOutputPath, - new MetadataReferenceProperties(aliases: reference.Aliases, embedInteropTypes: reference.EmbedInteropTypes)); - } + projectContext.AddMetadataReference( + existingReferenceOutputPath, + new MetadataReferenceProperties()); } } + else + { + // We don't know how to create this project. Another language or something? + } } -#endif foreach (var reference in commandLineArguments.AnalyzerReferences) { @@ -253,6 +266,19 @@ private void OutputToOutputWindow(string message) return projectContext; } + private static string GetLanguageOfProject(string projectFilename) + { + switch (Path.GetExtension(projectFilename)) + { + case ".csproj": + return LanguageNames.CSharp; + case ".vbproj": + return LanguageNames.VisualBasic; + default: + return null; + }; + } + private void FinishLoad() { // We are now completely done, so let's simply ensure all projects are added. diff --git a/src/VisualStudio/Next/ProjectSystem/DeferredProjectWorkspaceService.cs b/src/VisualStudio/Next/ProjectSystem/DeferredProjectWorkspaceService.cs index 75e7a3c2af9..9eada88aacf 100644 --- a/src/VisualStudio/Next/ProjectSystem/DeferredProjectWorkspaceService.cs +++ b/src/VisualStudio/Next/ProjectSystem/DeferredProjectWorkspaceService.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Workspace.Extensions; using Microsoft.VisualStudio.Workspace.VSIntegration; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem { @@ -29,17 +30,18 @@ public DeferredProjectWorkspaceService(SVsServiceProvider serviceProvider) public bool IsDeferredProjectLoadEnabled { get; } - public async Task> GetCommandLineArgumentsForProjectAsync(string projectFilePath) + public async Task, ImmutableArray>> GetCommandLineArgumentsAndProjectReferencesForProjectAsync(string projectFilePath) { var commandLineInfos = await _solutionWorkspaceService.GetManagedCommandLineInfoAsync(projectFilePath).ConfigureAwait(false); //return commandLineInfos.Any() ? commandLineInfos.First().CommandLineArgs : ImmutableArray.Empty; if (commandLineInfos.Any()) { - return commandLineInfos.First().CommandLineArgs; + var commandLineInfo = commandLineInfos.First(); + return ValueTuple.Create(commandLineInfo.CommandLineArgs, commandLineInfo.ProjectReferences); } else { - return ImmutableArray.Empty; + return ValueTuple.Create(ImmutableArray.Empty, ImmutableArray.Empty); } } } diff --git a/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt index ced088451f4..e69de29bb2d 100644 --- a/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ -Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.GetProjectPathsInSolution(string solutionFilePath) -> System.Collections.Immutable.ImmutableArray \ No newline at end of file diff --git a/src/Workspaces/Core/Desktop/Workspace/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/Desktop/Workspace/MSBuild/MSBuildProjectLoader.cs index 60034c33362..b8a91b38e25 100644 --- a/src/Workspaces/Core/Desktop/Workspace/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/Desktop/Workspace/MSBuild/MSBuildProjectLoader.cs @@ -167,38 +167,6 @@ private void SetSolutionProperties(string solutionFilePath) return SolutionInfo.Create(SolutionId.CreateNewId(debugName: absoluteSolutionPath), version, absoluteSolutionPath, loadedProjects.Projects); } - public ImmutableArray GetProjectPathsInSolution(string solutionFilePath) - { - if (solutionFilePath == null) - { - throw new ArgumentNullException(nameof(solutionFilePath)); - } - - var absoluteSolutionPath = this.GetAbsoluteSolutionPath(solutionFilePath, Directory.GetCurrentDirectory()); - using (_dataGuard.DisposableWait(CancellationToken.None)) - { - this.SetSolutionProperties(absoluteSolutionPath); - } - - Microsoft.Build.Construction.SolutionFile solutionFile = Microsoft.Build.Construction.SolutionFile.Parse(absoluteSolutionPath); - var reportMode = this.SkipUnrecognizedProjects ? ReportMode.Log : ReportMode.Throw; - - var builder = ImmutableArray.CreateBuilder(); - foreach (var project in solutionFile.ProjectsInOrder) - { - if (project.ProjectType != SolutionProjectType.SolutionFolder) - { - var projectAbsolutePath = TryGetAbsolutePath(project.AbsolutePath, reportMode); - if (projectAbsolutePath != null) - { - builder.Add(projectAbsolutePath); - } - } - } - - return builder.ToImmutable(); - } - internal string GetAbsoluteSolutionPath(string path, string baseDirectory) { string absolutePath; -- GitLab