diff --git a/src/Tools/MicroBuild/publish-assets.ps1 b/src/Tools/MicroBuild/publish-assets.ps1 index 671696f880cf1bba2f479450bc773db63db6acaf..889c6514d89fadbe58c3ece1766589207c70fac6 100644 --- a/src/Tools/MicroBuild/publish-assets.ps1 +++ b/src/Tools/MicroBuild/publish-assets.ps1 @@ -42,7 +42,7 @@ try { "dev15.0.x" { } "master" { } - "post-dev15" { } + "dev_pilchie_Fix389698" { } default { if (-not $test) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs index bb44766f0289fde3357e926a3f7a259ed5ebac14..426b47d6aac8bc26db3f12dd64363cc09a8363f5 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; @@ -13,6 +15,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.Internal.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; @@ -74,6 +77,14 @@ internal sealed partial class VisualStudioProjectTracker : ForegroundThreadAffin private readonly Dictionary _projectPathToIdMap; #endregion + // Temporary for prototyping purposes + private IVsOutputWindowPane _pane; + + /// + /// Used to cancel our background solution parse if we get a solution close event from VS. + /// + private CancellationTokenSource _solutionParsingCancellationTokenSource = new CancellationTokenSource(); + /// /// Provided to not break CodeLens which has a dependency on this API until there is a /// public release which calls . Once there is, we should @@ -556,5 +567,344 @@ public void OnAfterCloseSolution() _solutionIsClosing = false; } + + public async Task LoadSolutionFromMSBuildAsync() + { + AssertIsForeground(); + InitializeOutputPane(); + + // Continue on the UI thread for these operations, since we are touching the VisualStudioWorkspace, etc. + await PopulateWorkspaceFromDeferredProjectInfoAsync(_solutionParsingCancellationTokenSource.Token).ConfigureAwait(true); + } + + [Conditional("DEBUG")] + private void InitializeOutputPane() + { + var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow)); + var paneGuid = new Guid("07aaa8e9-d776-47d6-a1be-5ce00332d74d"); + if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref paneGuid, "Roslyn DPL Status", fInitVisible: 1, fClearWithSolution: 1)) && + ErrorHandler.Succeeded(outputWindow.GetPane(ref paneGuid, out _pane)) && _pane != null) + { + _pane.Activate(); + } + } + private async Task PopulateWorkspaceFromDeferredProjectInfoAsync( + CancellationToken cancellationToken) + { + // NOTE: We need to check cancellationToken after each await, in case the user has + // already closed the solution. + AssertIsForeground(); + + var componentModel = _serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; + var workspaceProjectContextFactory = componentModel.GetService(); + + var dte = _serviceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + var solutionConfig = (EnvDTE80.SolutionConfiguration2)dte.Solution.SolutionBuild.ActiveConfiguration; + + OutputToOutputWindow($"Getting project information - start"); + var start = DateTimeOffset.UtcNow; + + var projectInfos = SpecializedCollections.EmptyReadOnlyDictionary(); + + // Note that `solutionConfig` may be null. For example: if the solution doesn't actually + // contain any projects. + if (solutionConfig != null) + { + // Capture the context so that we come back on the UI thread, and do the actual project creation there. + var deferredProjectWorkspaceService = _workspaceServices.GetService(); + projectInfos = await deferredProjectWorkspaceService.GetDeferredProjectInfoForConfigurationAsync( + $"{solutionConfig.Name}|{solutionConfig.PlatformName}", + cancellationToken).ConfigureAwait(true); + } + + AssertIsForeground(); + cancellationToken.ThrowIfCancellationRequested(); + OutputToOutputWindow($"Getting project information - done (took {DateTimeOffset.UtcNow - start})"); + + OutputToOutputWindow($"Creating projects - start"); + start = DateTimeOffset.UtcNow; + var targetPathsToProjectPaths = BuildTargetPathMap(projectInfos); + var analyzerAssemblyLoader = _workspaceServices.GetRequiredService().GetLoader(); + foreach (var projectFilename in projectInfos.Keys) + { + cancellationToken.ThrowIfCancellationRequested(); + GetOrCreateProjectFromArgumentsAndReferences( + workspaceProjectContextFactory, + analyzerAssemblyLoader, + projectFilename, + projectInfos, + targetPathsToProjectPaths); + } + OutputToOutputWindow($"Creating projects - done (took {DateTimeOffset.UtcNow - start})"); + + OutputToOutputWindow($"Pushing to workspace - start"); + start = DateTimeOffset.UtcNow; + FinishLoad(); + OutputToOutputWindow($"Pushing to workspace - done (took {DateTimeOffset.UtcNow - start})"); + } + + private static ImmutableDictionary BuildTargetPathMap(IReadOnlyDictionary projectInfos) + { + var builder = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); + foreach (var item in projectInfos) + { + var targetPath = item.Value.TargetPath; + if (!string.IsNullOrEmpty(targetPath)) + { + if (!builder.ContainsKey(targetPath)) + { + builder[targetPath] = item.Key; + } + else + { + Debug.Fail($"Already have a target path of '{item.Value.TargetPath}', with value '{builder[item.Value.TargetPath]}'."); + } + } + } + return builder.ToImmutable(); + } + + [Conditional("DEBUG")] + private void OutputToOutputWindow(string message) + { + _pane?.OutputString(message + Environment.NewLine); + } + + private AbstractProject GetOrCreateProjectFromArgumentsAndReferences( + IWorkspaceProjectContextFactory workspaceProjectContextFactory, + IAnalyzerAssemblyLoader analyzerAssemblyLoader, + string projectFilename, + IReadOnlyDictionary allProjectInfos, + IReadOnlyDictionary targetPathsToProjectPaths) + { + var languageName = GetLanguageOfProject(projectFilename); + if (languageName == null) + { + return null; + } + + if (!allProjectInfos.TryGetValue(projectFilename, out var projectInfo)) + { + // This could happen if we were called recursively about a dangling P2P reference + // that isn't actually in the solution. + return null; + } + + var commandLineParser = _workspaceServices.GetLanguageServices(languageName).GetService(); + var projectDirectory = PathUtilities.GetDirectoryName(projectFilename); + var commandLineArguments = commandLineParser.Parse( + projectInfo.CommandLineArguments, + projectDirectory, + isInteractive: false, + sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory()); + + // TODO: Should come from sln file? + var projectName = PathUtilities.GetFileName(projectFilename, includeExtension: false); + + // `AbstractProject` only sets the filename if it actually exists. Since we want + // our ids to match, mimic that behavior here. + var projectId = File.Exists(projectFilename) + ? GetOrCreateProjectIdForPath(projectFilename, projectName) + : GetOrCreateProjectIdForPath(projectName, projectName); + // See if we've already created this project and we're now in a recursive call to + // hook up a P2P ref. + if (_projectMap.TryGetValue(projectId, out var project)) + { + return project; + } + + OutputToOutputWindow($"\tCreating '{projectName}':\t{commandLineArguments.SourceFiles.Length} source files,\t{commandLineArguments.MetadataReferences.Length} references."); + var solution5 = _serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution5; + + // If the index is stale, it might give us a path that doesn't exist anymore that the + // solution doesn't know about - be resilient to that case. + Guid projectGuid; + try + { + projectGuid = solution5.GetGuidOfProjectFile(projectFilename); + } + catch (ArgumentException) + { + var message = $"Failed to get the project guid for '{projectFilename}' from the solution, using random guid instead."; + Debug.Fail(message); + OutputToOutputWindow(message); + projectGuid = Guid.NewGuid(); + } + + // NOTE: If the indexing service fails for a project, it will give us an *empty* + // target path, which we aren't prepared to handle. Instead, convert it to a *null* + // value, which we do handle. + var outputPath = projectInfo.TargetPath; + if (outputPath == string.Empty) + { + outputPath = null; + } + + var projectContext = workspaceProjectContextFactory.CreateProjectContext( + languageName, + projectName, + projectFilename, + projectGuid: projectGuid, + hierarchy: null, + binOutputPath: outputPath); + + project = (AbstractProject)projectContext; + projectContext.SetOptions(projectInfo.CommandLineArguments.Join(" ")); + + foreach (var sourceFile in commandLineArguments.SourceFiles) + { + projectContext.AddSourceFile(sourceFile.Path); + } + + foreach (var sourceFile in commandLineArguments.AdditionalFiles) + { + projectContext.AddAdditionalFile(sourceFile.Path); + } + + var addedProjectReferences = new HashSet(); + foreach (var projectReferencePath in projectInfo.ReferencedProjectFilePaths) + { + // NOTE: ImmutableProjects might contain projects for other languages like + // Xaml, or Typescript where the project file ends up being identical. + var referencedProject = ImmutableProjects.SingleOrDefault( + p => (p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic) + && StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, projectReferencePath)); + if (referencedProject == null) + { + referencedProject = GetOrCreateProjectFromArgumentsAndReferences( + workspaceProjectContextFactory, + analyzerAssemblyLoader, + projectReferencePath, + allProjectInfos, + targetPathsToProjectPaths); + } + + var referencedProjectContext = referencedProject as IWorkspaceProjectContext; + if (referencedProjectContext != null) + { + // TODO: Can we get the properties from corresponding metadata reference in + // commandLineArguments? + addedProjectReferences.Add(projectReferencePath); + projectContext.AddProjectReference( + referencedProjectContext, + new MetadataReferenceProperties()); + } + else if (referencedProject != 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) + { + addedProjectReferences.Add(projectReferencePath); + projectContext.AddMetadataReference( + existingReferenceOutputPath, + new MetadataReferenceProperties()); + } + } + else + { + // We don't know how to create this project. Another language or something? + OutputToOutputWindow($"Failed to create a project for '{projectReferencePath}'."); + } + } + + foreach (var reference in commandLineArguments.ResolveMetadataReferences(project.CurrentCompilationOptions.MetadataReferenceResolver)) + { + // Some references may fail to be resolved - if they are, we'll still pass them + // through, in case they come into existence later (they may be built by other + // parts of the build system). + var unresolvedReference = reference as UnresolvedMetadataReference; + var path = unresolvedReference == null + ? ((PortableExecutableReference)reference).FilePath + : unresolvedReference.Reference; + if (targetPathsToProjectPaths.TryGetValue(path, out var possibleProjectReference) && + addedProjectReferences.Contains(possibleProjectReference)) + { + // We already added a P2P reference for this, we don't need to add the file reference too. + continue; + } + + projectContext.AddMetadataReference(path, reference.Properties); + } + + foreach (var reference in commandLineArguments.ResolveAnalyzerReferences(analyzerAssemblyLoader)) + { + var path = reference.FullPath; + if (!PathUtilities.IsAbsolute(path)) + { + path = PathUtilities.CombineAbsoluteAndRelativePaths( + projectDirectory, + path); + } + + projectContext.AddAnalyzerReference(path); + } + + return (AbstractProject)projectContext; + } + + private static string GetLanguageOfProject(string projectFilename) + { + switch (PathUtilities.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. + StartPushingToWorkspaceAndNotifyOfOpenDocuments(this.ImmutableProjects); + + // Also, all remaining project adds need to immediately pushed as well, since we're now "interactive" + _solutionLoadComplete = true; + + // Check that the set of analyzers is complete and consistent. + GetAnalyzerDependencyCheckingService()?.CheckForConflictsAsync(); + } + + private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() + { + var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); + + return componentModel.GetService(); + } + + internal void OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + AssertIsForeground(); + + _projectsLoadedThisBatch.Clear(); + } + + internal void OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + AssertIsForeground(); + + if (!fIsBackgroundIdleBatch) + { + // This batch was loaded eagerly. This might be because the user is force expanding the projects in the + // Solution Explorer, or they had some files open in an .suo we need to push. + StartPushingToWorkspaceAndNotifyOfOpenDocuments(_projectsLoadedThisBatch); + } + + _projectsLoadedThisBatch.Clear(); + } + + internal void OnAfterBackgroundSolutionLoadComplete() + { + AssertIsForeground(); + + // In Non-DPL scenarios, this indicates that ASL is complete, and we should push any + // remaining information we have to the Workspace. If DPL is enabled, this is never + // called. + FinishLoad(); + } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs deleted file mode 100644 index b0508c8e2389753a7f0be098ebe4a13090dade1e..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.LanguageServices.ProjectSystem; -using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class VisualStudioProjectTracker : IVsSolutionLoadEvents - { - // Temporary for prototyping purposes - private IVsOutputWindowPane _pane; - - /// - /// Used to cancel our background solution parse if we get a solution close event from VS. - /// - private CancellationTokenSource _solutionParsingCancellationTokenSource = new CancellationTokenSource(); - - int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) - { - return VSConstants.S_OK; - } - - public async Task LoadSolutionFromMSBuildAsync() - { - AssertIsForeground(); - InitializeOutputPane(); - - // Continue on the UI thread for these operations, since we are touching the VisualStudioWorkspace, etc. - await PopulateWorkspaceFromDeferredProjectInfoAsync(_solutionParsingCancellationTokenSource.Token).ConfigureAwait(true); - } - - [Conditional("DEBUG")] - private void InitializeOutputPane() - { - var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow)); - var paneGuid = new Guid("07aaa8e9-d776-47d6-a1be-5ce00332d74d"); - if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref paneGuid, "Roslyn DPL Status", fInitVisible: 1, fClearWithSolution: 1)) && - ErrorHandler.Succeeded(outputWindow.GetPane(ref paneGuid, out _pane)) && _pane != null) - { - _pane.Activate(); - } - } - - int IVsSolutionLoadEvents.OnBeforeBackgroundSolutionLoadBegins() - { - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle) - { - pfShouldDelayLoadToNextIdle = false; - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) - { - AssertIsForeground(); - - _projectsLoadedThisBatch.Clear(); - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) - { - AssertIsForeground(); - - if (!fIsBackgroundIdleBatch) - { - // This batch was loaded eagerly. This might be because the user is force expanding the projects in the - // Solution Explorer, or they had some files open in an .suo we need to push. - StartPushingToWorkspaceAndNotifyOfOpenDocuments(_projectsLoadedThisBatch); - } - - _projectsLoadedThisBatch.Clear(); - - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() - { - AssertIsForeground(); - - // In Non-DPL scenarios, this indicates that ASL is complete, and we should push any - // remaining information we have to the Workspace. If DPL is enabled, this is never - // called. - FinishLoad(); - - return VSConstants.S_OK; - } - - private async Task PopulateWorkspaceFromDeferredProjectInfoAsync( - CancellationToken cancellationToken) - { - // NOTE: We need to check cancellationToken after each await, in case the user has - // already closed the solution. - AssertIsForeground(); - - var componentModel = _serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; - var workspaceProjectContextFactory = componentModel.GetService(); - - var dte = _serviceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; - var solutionConfig = (EnvDTE80.SolutionConfiguration2)dte.Solution.SolutionBuild.ActiveConfiguration; - - OutputToOutputWindow($"Getting project information - start"); - var start = DateTimeOffset.UtcNow; - - var projectInfos = SpecializedCollections.EmptyReadOnlyDictionary(); - - // Note that `solutionConfig` may be null. For example: if the solution doesn't actually - // contain any projects. - if (solutionConfig != null) - { - // Capture the context so that we come back on the UI thread, and do the actual project creation there. - var deferredProjectWorkspaceService = _workspaceServices.GetService(); - projectInfos = await deferredProjectWorkspaceService.GetDeferredProjectInfoForConfigurationAsync( - $"{solutionConfig.Name}|{solutionConfig.PlatformName}", - cancellationToken).ConfigureAwait(true); - } - - AssertIsForeground(); - cancellationToken.ThrowIfCancellationRequested(); - OutputToOutputWindow($"Getting project information - done (took {DateTimeOffset.UtcNow - start})"); - - OutputToOutputWindow($"Creating projects - start"); - start = DateTimeOffset.UtcNow; - var targetPathsToProjectPaths = BuildTargetPathMap(projectInfos); - var analyzerAssemblyLoader = _workspaceServices.GetRequiredService().GetLoader(); - foreach (var projectFilename in projectInfos.Keys) - { - cancellationToken.ThrowIfCancellationRequested(); - GetOrCreateProjectFromArgumentsAndReferences( - workspaceProjectContextFactory, - analyzerAssemblyLoader, - projectFilename, - projectInfos, - targetPathsToProjectPaths); - } - OutputToOutputWindow($"Creating projects - done (took {DateTimeOffset.UtcNow - start})"); - - OutputToOutputWindow($"Pushing to workspace - start"); - start = DateTimeOffset.UtcNow; - FinishLoad(); - OutputToOutputWindow($"Pushing to workspace - done (took {DateTimeOffset.UtcNow - start})"); - } - - private static ImmutableDictionary BuildTargetPathMap(IReadOnlyDictionary projectInfos) - { - var builder = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); - foreach (var item in projectInfos) - { - var targetPath = item.Value.TargetPath; - if (!string.IsNullOrEmpty(targetPath)) - { - if (!builder.ContainsKey(targetPath)) - { - builder[targetPath] = item.Key; - } - else - { - Debug.Fail($"Already have a target path of '{item.Value.TargetPath}', with value '{builder[item.Value.TargetPath]}'."); - } - } - } - return builder.ToImmutable(); - } - - [Conditional("DEBUG")] - private void OutputToOutputWindow(string message) - { - _pane?.OutputString(message + Environment.NewLine); - } - - private AbstractProject GetOrCreateProjectFromArgumentsAndReferences( - IWorkspaceProjectContextFactory workspaceProjectContextFactory, - IAnalyzerAssemblyLoader analyzerAssemblyLoader, - string projectFilename, - IReadOnlyDictionary allProjectInfos, - IReadOnlyDictionary targetPathsToProjectPaths) - { - var languageName = GetLanguageOfProject(projectFilename); - if (languageName == null) - { - return null; - } - - if (!allProjectInfos.TryGetValue(projectFilename, out var projectInfo)) - { - // This could happen if we were called recursively about a dangling P2P reference - // that isn't actually in the solution. - return null; - } - - var commandLineParser = _workspaceServices.GetLanguageServices(languageName).GetService(); - var projectDirectory = PathUtilities.GetDirectoryName(projectFilename); - var commandLineArguments = commandLineParser.Parse( - projectInfo.CommandLineArguments, - projectDirectory, - isInteractive: false, - sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory()); - - // TODO: Should come from sln file? - var projectName = PathUtilities.GetFileName(projectFilename, includeExtension: false); - - // `AbstractProject` only sets the filename if it actually exists. Since we want - // our ids to match, mimic that behavior here. - var projectId = File.Exists(projectFilename) - ? GetOrCreateProjectIdForPath(projectFilename, projectName) - : GetOrCreateProjectIdForPath(projectName, projectName); - // See if we've already created this project and we're now in a recursive call to - // hook up a P2P ref. - if (_projectMap.TryGetValue(projectId, out var project)) - { - return project; - } - - OutputToOutputWindow($"\tCreating '{projectName}':\t{commandLineArguments.SourceFiles.Length} source files,\t{commandLineArguments.MetadataReferences.Length} references."); - var solution5 = _serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution5; - - // If the index is stale, it might give us a path that doesn't exist anymore that the - // solution doesn't know about - be resilient to that case. - Guid projectGuid; - try - { - projectGuid = solution5.GetGuidOfProjectFile(projectFilename); - } - catch (ArgumentException) - { - var message = $"Failed to get the project guid for '{projectFilename}' from the solution, using random guid instead."; - Debug.Fail(message); - OutputToOutputWindow(message); - projectGuid = Guid.NewGuid(); - } - - // NOTE: If the indexing service fails for a project, it will give us an *empty* - // target path, which we aren't prepared to handle. Instead, convert it to a *null* - // value, which we do handle. - var outputPath = projectInfo.TargetPath; - if (outputPath == string.Empty) - { - outputPath = null; - } - - var projectContext = workspaceProjectContextFactory.CreateProjectContext( - languageName, - projectName, - projectFilename, - projectGuid: projectGuid, - hierarchy: null, - binOutputPath: outputPath); - - project = (AbstractProject)projectContext; - projectContext.SetOptions(projectInfo.CommandLineArguments.Join(" ")); - - foreach (var sourceFile in commandLineArguments.SourceFiles) - { - projectContext.AddSourceFile(sourceFile.Path); - } - - foreach (var sourceFile in commandLineArguments.AdditionalFiles) - { - projectContext.AddAdditionalFile(sourceFile.Path); - } - - var addedProjectReferences = new HashSet(); - foreach (var projectReferencePath in projectInfo.ReferencedProjectFilePaths) - { - // NOTE: ImmutableProjects might contain projects for other languages like - // Xaml, or Typescript where the project file ends up being identical. - var referencedProject = ImmutableProjects.SingleOrDefault( - p => (p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic) - && StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, projectReferencePath)); - if (referencedProject == null) - { - referencedProject = GetOrCreateProjectFromArgumentsAndReferences( - workspaceProjectContextFactory, - analyzerAssemblyLoader, - projectReferencePath, - allProjectInfos, - targetPathsToProjectPaths); - } - - var referencedProjectContext = referencedProject as IWorkspaceProjectContext; - if (referencedProjectContext != null) - { - // TODO: Can we get the properties from corresponding metadata reference in - // commandLineArguments? - addedProjectReferences.Add(projectReferencePath); - projectContext.AddProjectReference( - referencedProjectContext, - new MetadataReferenceProperties()); - } - else if (referencedProject != 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) - { - addedProjectReferences.Add(projectReferencePath); - projectContext.AddMetadataReference( - existingReferenceOutputPath, - new MetadataReferenceProperties()); - } - } - else - { - // We don't know how to create this project. Another language or something? - OutputToOutputWindow($"Failed to create a project for '{projectReferencePath}'."); - } - } - - foreach (var reference in commandLineArguments.ResolveMetadataReferences(project.CurrentCompilationOptions.MetadataReferenceResolver)) - { - // Some references may fail to be resolved - if they are, we'll still pass them - // through, in case they come into existence later (they may be built by other - // parts of the build system). - var unresolvedReference = reference as UnresolvedMetadataReference; - var path = unresolvedReference == null - ? ((PortableExecutableReference)reference).FilePath - : unresolvedReference.Reference; - if (targetPathsToProjectPaths.TryGetValue(path, out var possibleProjectReference) && - addedProjectReferences.Contains(possibleProjectReference)) - { - // We already added a P2P reference for this, we don't need to add the file reference too. - continue; - } - - projectContext.AddMetadataReference(path, reference.Properties); - } - - foreach (var reference in commandLineArguments.ResolveAnalyzerReferences(analyzerAssemblyLoader)) - { - var path = reference.FullPath; - if (!PathUtilities.IsAbsolute(path)) - { - path = PathUtilities.CombineAbsoluteAndRelativePaths( - projectDirectory, - path); - } - - projectContext.AddAnalyzerReference(path); - } - - return (AbstractProject)projectContext; - } - - private static string GetLanguageOfProject(string projectFilename) - { - switch (PathUtilities.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. - StartPushingToWorkspaceAndNotifyOfOpenDocuments(this.ImmutableProjects); - - // Also, all remaining project adds need to immediately pushed as well, since we're now "interactive" - _solutionLoadComplete = true; - - // Check that the set of analyzers is complete and consistent. - GetAnalyzerDependencyCheckingService()?.CheckForConflictsAsync(); - } - - private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() - { - var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - - return componentModel.GetService(); - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs new file mode 100644 index 0000000000000000000000000000000000000000..527741ff11b9db8a673319e19769c5af318634c5 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal partial class VisualStudioWorkspaceImpl : IVsSolutionLoadEvents + { + int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) + { + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnBeforeBackgroundSolutionLoadBegins() + { + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle) + { + pfShouldDelayLoadToNextIdle = false; + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + _foregroundObject.Value.AssertIsForeground(); + GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch); + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + _foregroundObject.Value.AssertIsForeground(); + GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnAfterLoadProjectBatch(fIsBackgroundIdleBatch); + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() + { + _foregroundObject.Value.AssertIsForeground(); + GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnAfterBackgroundSolutionLoadComplete(); + return VSConstants.S_OK; + } + } +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionWorkingFoldersEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionWorkingFoldersEvents.cs similarity index 65% rename from src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionWorkingFoldersEvents.cs rename to src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionWorkingFoldersEvents.cs index eb229b354f58298b5b5242a886d5e067dc1d52df..53af880bd52ead3cfbbb8686757259103d396107 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionWorkingFoldersEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionWorkingFoldersEvents.cs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Internal.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - internal partial class VisualStudioProjectTracker : IVsSolutionWorkingFoldersEvents + internal partial class VisualStudioWorkspaceImpl : IVsSolutionWorkingFoldersEvents { void IVsSolutionWorkingFoldersEvents.OnAfterLocationChange(uint location, bool contentMoved) { @@ -14,7 +15,8 @@ void IVsSolutionWorkingFoldersEvents.OnAfterLocationChange(uint location, bool c } // notify the working folder change - NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnAfterWorkingFolderChange()); + GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).NotifyWorkspaceHosts( + host => (host as IVisualStudioWorkingFolder)?.OnAfterWorkingFolderChange()); } void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bool pfCanMoveContent) @@ -27,7 +29,8 @@ void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bo // notify the working folder change pfCanMoveContent = true; - NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnBeforeWorkingFolderChange()); + GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).NotifyWorkspaceHosts( + host => (host as IVisualStudioWorkingFolder)?.OnBeforeWorkingFolderChange()); } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj index ed53a5cc1578c1d6fd3e609f0b196968d2240570..14cb0c2617f5e2da0b1188eefeff61fe536684ea 100644 --- a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj +++ b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj @@ -113,7 +113,7 @@ - + @@ -548,7 +548,7 @@ - + diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb index d703f82fd587a1aaee4bc861e38f3ec4848df11f..a0ca82594a19ccbe79d9e8f2aa8c4fe2b968e9e7 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb @@ -31,9 +31,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Public Sub DoNotDeferLoadIfInNonBackgroundBatch() Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - testEnvironment.GetSolutionLoadEvents().OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) CreateVisualBasicProject(testEnvironment, "TestProject") - testEnvironment.GetSolutionLoadEvents().OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) ' We should have pushed this project to the workspace Assert.Single(testEnvironment.Workspace.CurrentSolution.Projects) @@ -51,9 +51,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) CreateVisualBasicProject(testEnvironment, "TestProject1") - testEnvironment.GetSolutionLoadEvents().OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) CreateVisualBasicProject(testEnvironment, "TestProject2") - testEnvironment.GetSolutionLoadEvents().OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) ' We should have pushed the second project only Assert.Equal("TestProject2", testEnvironment.Workspace.CurrentSolution.Projects.Single().Name) @@ -72,10 +72,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim project1 = CreateVisualBasicProject(testEnvironment, "TestProject1") ' Include a project reference in this batch. This means that project1 must also be pushed - testEnvironment.GetSolutionLoadEvents().OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) Dim project2 = CreateVisualBasicProject(testEnvironment, "TestProject2") project2.AddProjectReference(project1) - testEnvironment.GetSolutionLoadEvents().OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) ' We should have pushed both projects Assert.Equal(2, testEnvironment.Workspace.CurrentSolution.Projects.Count()) @@ -89,9 +89,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim project1 = CreateVisualBasicProject(testEnvironment, "TestProject1") ' Include a project reference in this batch. This means that project1 must also be pushed - testEnvironment.GetSolutionLoadEvents().OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) Dim project2 = CreateVisualBasicProject(testEnvironment, "TestProject2") - testEnvironment.GetSolutionLoadEvents().OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) + testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) ' We should have pushed the second project only Assert.Equal("TestProject2", testEnvironment.Workspace.CurrentSolution.Projects.Single().Name) diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index e2082316da81818f508ffbadc8b8aed8c7f1830d..09ab9a5805ae301a636483103e34dadd58fc4903 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -59,13 +59,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Public Sub NotifySolutionAsFullyLoaded() _monitorSelectionMock.SolutionIsFullyLoaded = True - GetSolutionLoadEvents().OnAfterBackgroundSolutionLoadComplete() + _projectTracker.OnAfterBackgroundSolutionLoadComplete() End Sub - Public Function GetSolutionLoadEvents() As IVsSolutionLoadEvents - Return DirectCast(_projectTracker, IVsSolutionLoadEvents) - End Function - Public ReadOnly Property ProjectTracker As VisualStudioProjectTracker Get Return _projectTracker