diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs index 5805df8a38767e1199df3bdabe4a27a2b2ad98b6..c6822624363adfff4282919e8993a04d39484532 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs @@ -19,10 +19,10 @@ public void AddingReferenceToProjectMetadataPromotesToProjectReference() using (var environment = new TestEnvironment()) { var project1 = CreateCSharpProject(environment, "project1"); - environment.ProjectTracker.UpdateProjectBinPath(project1, null, @"c:\project1.dll"); + project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); var project2 = CreateCSharpProject(environment, "project2"); - environment.ProjectTracker.UpdateProjectBinPath(project2, null, @"c:\project2.dll"); + project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); // since this is known to be the output path of project1, the metadata reference is converted to a project reference project2.OnImportAdded(@"c:\project1.dll", "project1"); @@ -41,10 +41,10 @@ public void AddCyclicProjectMetadataReferences() using (var environment = new TestEnvironment()) { var project1 = CreateCSharpProject(environment, "project1"); - environment.ProjectTracker.UpdateProjectBinPath(project1, null, @"c:\project1.dll"); + project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); var project2 = CreateCSharpProject(environment, "project2"); - environment.ProjectTracker.UpdateProjectBinPath(project2, null, @"c:\project2.dll"); + project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); project1.AddProjectReference(new ProjectReference(project2.Id)); @@ -108,34 +108,65 @@ public void AddCyclicProjectReferencesDeep() } [WorkItem(12707, "https://github.com/dotnet/roslyn/issues/12707")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/12707")] + [WpfFact] [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void AddingProjectReferenceAndUpdateReferenceBinPath() { using (var environment = new TestEnvironment()) { var project1 = CreateCSharpProject(environment, "project1"); - environment.ProjectTracker.UpdateProjectBinPath(project1, null, @"c:\project1.dll"); + project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); var project2 = CreateCSharpProject(environment, "project2"); - environment.ProjectTracker.UpdateProjectBinPath(project2, null, @"c:\project2.dll"); + project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); // since this is known to be the output path of project1, the metadata reference is converted to a project reference project2.OnImportAdded(@"c:\project1.dll", "project1"); - Assert.Equal(true, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); + Assert.Single(project2.GetCurrentProjectReferences().Where(pr => pr.ProjectId == project1.Id)); // update bin bath for project1. - environment.ProjectTracker.UpdateProjectBinPath(project1, @"c:\project1.dll", @"c:\new_project1.dll"); + project1.SetBinOutputPathAndRelatedData(@"c:\new_project1.dll"); // Verify project reference updated after bin path change. - Assert.Equal(true, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); + Assert.Empty(project2.GetCurrentProjectReferences()); + + // This is a metadata reference to the original path + var metadataReference = Assert.Single(project2.GetCurrentMetadataReferences()); + Assert.Equal(@"c:\project1.dll", metadataReference.FilePath); project2.Disconnect(); project1.Disconnect(); } } + [WorkItem(12707, "https://github.com/dotnet/roslyn/issues/12707")] + [WpfFact] + [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] + public void DisconnectingProjectShouldConvertConvertedReferencesBack() + { + using (var environment = new TestEnvironment()) + { + var project1 = CreateCSharpProject(environment, "project1"); + project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); + + var project2 = CreateCSharpProject(environment, "project2"); + project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); + + // since this is known to be the output path of project1, the metadata reference is converted to a project reference + project2.OnImportAdded(@"c:\project1.dll", "project1"); + + Assert.Single(project2.GetCurrentProjectReferences().Where(pr => pr.ProjectId == project1.Id)); + + project1.Disconnect(); + + // Verify project reference updated after bin path change. + Assert.Empty(project2.GetCurrentProjectReferences()); + Assert.Single(project2.GetCurrentMetadataReferences().Where(r => r.FilePath == @"c:\project1.dll")); + + project2.Disconnect(); + } + } [WorkItem(461967, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/461967")] [WpfFact()] @@ -145,10 +176,10 @@ public void AddingMetadataReferenceToProjectThatCannotCompileInTheIdeKeepsMetada using (var environment = new TestEnvironment()) { var project1 = CreateCSharpProject(environment, "project1"); - environment.ProjectTracker.UpdateProjectBinPath(project1, null, @"c:\project1.dll"); + project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); var project2 = CreateNonCompilableProject(environment, "project2", @"C:\project2.fsproj"); - environment.ProjectTracker.UpdateProjectBinPath(project2, null, @"c:\project2.dll"); + project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); project1.OnImportAdded(@"c:\project2.dll", "project2"); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs index c0d0b830dd8a76a140a1605d44e610265c8819be..776098298c752ca7c1de331fe0223440f95323b9 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs @@ -55,11 +55,6 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj /// private readonly ISet _untrackedDocuments = new HashSet(StringComparer.OrdinalIgnoreCase); - /// - /// The path to a metadata reference that was converted to project references. - /// - private readonly Dictionary _metadataFileNameToConvertedProjectReference = new Dictionary(StringComparer.OrdinalIgnoreCase); - private bool _pushingChangesToWorkspaceHosts; #endregion @@ -85,6 +80,11 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj } } + /// + /// Maps from the output path of a project that was converted to + /// + private readonly Dictionary _metadataFileNameToConvertedProjectReference = new Dictionary(StringComparer.OrdinalIgnoreCase); + #endregion // PERF: Create these event handlers once to be shared amongst all documents (the sender arg identifies which document and project) @@ -280,18 +280,8 @@ protected string ContainingDirectoryPathOpt internal VsENCRebuildableProjectImpl EditAndContinueImplOpt { get; private set; } - /// - /// Override this method to validate references when creating for current state. - /// By default, this method does nothing. - /// - protected virtual void ValidateReferences() - { - } - public ProjectInfo CreateProjectInfoForCurrentState() { - ValidateReferences(); - lock (_gate) { var info = ProjectInfo.Create( @@ -439,46 +429,6 @@ public VisualStudioMetadataReference TryGetCurrentMetadataReference(string filen } } - private void AddMetadataFileNameToConvertedProjectReference(string filePath, ProjectReference projectReference) - { - lock (_gate) - { - _metadataFileNameToConvertedProjectReference.Add(filePath, projectReference); - } - } - - private void UpdateMetadataFileNameToConvertedProjectReference(string filePath, ProjectReference projectReference) - { - lock (_gate) - { - _metadataFileNameToConvertedProjectReference[filePath] = projectReference; - } - } - - private bool RemoveMetadataFileNameToConvertedProjectReference(string filePath) - { - lock (_gate) - { - return _metadataFileNameToConvertedProjectReference.Remove(filePath); - } - } - - private bool TryGetMetadataFileNameToConvertedProjectReference(string filePath, out ProjectReference projectReference) - { - lock (_gate) - { - return _metadataFileNameToConvertedProjectReference.TryGetValue(filePath, out projectReference); - } - } - - private bool HasMetadataFileNameToConvertedProjectReference(string filePath) - { - lock (_gate) - { - return _metadataFileNameToConvertedProjectReference.ContainsKey(filePath); - } - } - public bool CurrentProjectReferencesContains(ProjectId projectId) { lock (_gate) @@ -574,7 +524,7 @@ protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(s if (CanAddProjectReference(projectReference)) { AddProjectReference(projectReference); - AddMetadataFileNameToConvertedProjectReference(filePath, projectReference); + _metadataFileNameToConvertedProjectReference.Add(filePath, projectReference); return VSConstants.S_OK; } } @@ -631,12 +581,12 @@ protected void RemoveMetadataReference(string filePath) AssertIsForeground(); // Is this a reference we converted to a project reference? - if (TryGetMetadataFileNameToConvertedProjectReference(filePath, out var projectReference)) + if (_metadataFileNameToConvertedProjectReference.TryGetValue(filePath, out var projectReference)) { // We converted this, so remove the project reference instead RemoveProjectReference(projectReference); - Contract.ThrowIfFalse(RemoveMetadataFileNameToConvertedProjectReference(filePath)); + Contract.ThrowIfFalse(_metadataFileNameToConvertedProjectReference.Remove(filePath)); } // Just a metadata reference, so remove all of those @@ -1195,7 +1145,7 @@ internal void TryProjectConversionForIntroducedOutputPath(string binPath, Abstra if (this.CanConvertToProjectReferences) { // We should not already have references for this, since we're only introducing the path for the first time - Contract.ThrowIfTrue(HasMetadataFileNameToConvertedProjectReference(binPath)); + Contract.ThrowIfTrue(_metadataFileNameToConvertedProjectReference.ContainsKey(binPath)); var metadataReference = TryGetCurrentMetadataReference(binPath); if (metadataReference != null) @@ -1210,7 +1160,7 @@ internal void TryProjectConversionForIntroducedOutputPath(string binPath, Abstra RemoveMetadataReferenceCore(metadataReference, disposeReference: true); AddProjectReference(projectReference); - AddMetadataFileNameToConvertedProjectReference(binPath, projectReference); + _metadataFileNameToConvertedProjectReference.Add(binPath, projectReference); } } } @@ -1220,7 +1170,7 @@ internal void UndoProjectReferenceConversionForDisappearingOutputPath(string bin { AssertIsForeground(); - if (TryGetMetadataFileNameToConvertedProjectReference(binPath, out var projectReference)) + if (_metadataFileNameToConvertedProjectReference.TryGetValue(binPath, out var projectReference)) { // We converted this, so convert it back to a metadata reference RemoveProjectReference(projectReference); @@ -1232,7 +1182,7 @@ internal void UndoProjectReferenceConversionForDisappearingOutputPath(string bin AddMetadataReferenceCore(MetadataReferenceProvider.CreateMetadataReference(binPath, metadataReferenceProperties)); - Contract.ThrowIfFalse(RemoveMetadataFileNameToConvertedProjectReference(binPath)); + Contract.ThrowIfFalse(_metadataFileNameToConvertedProjectReference.Remove(binPath)); } } @@ -1243,7 +1193,7 @@ protected void UpdateMetadataReferenceAliases(string file, ImmutableArray /// Used for unit testing: don't crash the process if something bad happens. /// diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs index 66addff958eb0ea09eb6723411da59a1029e728e..7054d495a882817bf627e3c4c8da678f2c868899 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -169,94 +169,5 @@ private static bool GetIsWebsiteProject(IVsHierarchy hierarchy) return false; } - - protected sealed override void ValidateReferences() - { - ValidateReferencesCore(); - } - - [Conditional("DEBUG")] - private void ValidateReferencesCore() - { - // can happen when project is unloaded and reloaded or in Venus (aspx) case - if (ProjectFilePath == null || BinOutputPath == null || ObjOutputPath == null) - { - return; - } - - if (ErrorHandler.Failed(Hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out var property))) - { - return; - } - - var dteProject = property as EnvDTE.Project; - if (dteProject == null) - { - return; - } - - var vsproject = dteProject.Object as VSProject; - if (vsproject == null) - { - return; - } - - var noReferenceOutputAssemblies = new List(); - var factory = this.ServiceProvider.GetService(typeof(SVsEnumHierarchyItemsFactory)) as IVsEnumHierarchyItemsFactory; - if (ErrorHandler.Failed(factory.EnumHierarchyItems(Hierarchy, (uint)__VSEHI.VSEHI_Leaf, (uint)VSConstants.VSITEMID.Root, out var items))) - { - return; - } - - VSITEMSELECTION[] item = new VSITEMSELECTION[1]; - while (ErrorHandler.Succeeded(items.Next(1, item, out var fetched)) && fetched == 1) - { - // ignore ReferenceOutputAssembly=false references since those will not be added to us in design time. - var storage = Hierarchy as IVsBuildPropertyStorage; - storage.GetItemAttribute(item[0].itemid, "ReferenceOutputAssembly", out var value); - Hierarchy.GetProperty(item[0].itemid, (int)__VSHPROPID.VSHPROPID_Caption, out var caption); - - if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase) || - string.Equals(value, "off", StringComparison.OrdinalIgnoreCase) || - string.Equals(value, "0", StringComparison.OrdinalIgnoreCase)) - { - noReferenceOutputAssemblies.Add((string)caption); - } - } - - var projectReferences = GetCurrentProjectReferences(); - var metadataReferences = GetCurrentMetadataReferences(); - var set = new HashSet(vsproject.References.OfType().Select(r => PathUtilities.IsAbsolute(r.Name) ? Path.GetFileNameWithoutExtension(r.Name) : r.Name), StringComparer.OrdinalIgnoreCase); - var delta = set.Count - noReferenceOutputAssemblies.Count - (projectReferences.Length + metadataReferences.Length); - if (delta == 0) - { - return; - } - - // okay, two has different set of dlls referenced. check special Microsoft.VisualBasic case. - if (delta != 1) - { - //// Contract.Requires(false, "different set of references!!!"); - return; - } - - set.ExceptWith(noReferenceOutputAssemblies); - set.ExceptWith(projectReferences.Select(r => ProjectTracker.GetProject(r.ProjectId).DisplayName)); - set.ExceptWith(metadataReferences.Select(m => Path.GetFileNameWithoutExtension(m.FilePath))); - - //// Contract.Requires(set.Count == 1); - - var reference = set.First(); - if (!string.Equals(reference, "Microsoft.VisualBasic", StringComparison.OrdinalIgnoreCase)) - { - //// Contract.Requires(false, "unknown new reference " + reference); - return; - } - -#if DEBUG - // when we are missing microsoft.visualbasic reference, make sure we have embedded vb core option on. - Contract.Requires(Debug_VBEmbeddedCoreOptionOn); -#endif - } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs index 0fca1b9997faa41c7102635f030199be50a32de9..0d4368ca0f257d669342ff8f15e78e4f3eafb17a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs @@ -431,7 +431,7 @@ internal bool TryGetProjectByBinPath(string filePath, out AbstractProject projec private static readonly char[] s_directorySeparatorChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - private bool HACK_StripRefDirectoryFromPath(string filePath, out string binFilePath) + private static bool HACK_StripRefDirectoryFromPath(string filePath, out string binFilePath) { const string refDirectoryName = "ref"; diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb index c3fd5d03fa444262b97ff9853d12c6fc386007c1..7b7a343f73bf739828ae1de186a710cfd270b712 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb @@ -148,10 +148,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using environment = New TestEnvironment() Dim project1 = CreateVisualBasicProject(environment, "project1") - environment.ProjectTracker.UpdateProjectBinPath(project1, Nothing, "C:\project1.dll") + project1.SetBinOutputPathAndRelatedData("C:\project1.dll") Dim project2 = CreateVisualBasicProject(environment, "project2") - environment.ProjectTracker.UpdateProjectBinPath(project2, Nothing, "C:\project2.dll") + project2.SetBinOutputPathAndRelatedData("C:\project2.dll") ' since this is known to be the output path of project1, the metadata reference is converted to a project reference project2.AddMetaDataReference("c:\project1.dll", True) @@ -169,10 +169,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using environment = New TestEnvironment() Dim project1 = CreateVisualBasicProject(environment, "project1") - environment.ProjectTracker.UpdateProjectBinPath(project1, Nothing, "C:\project1.dll") + project1.SetBinOutputPathAndRelatedData("C:\project1.dll") Dim project2 = CreateVisualBasicProject(environment, "project2") - environment.ProjectTracker.UpdateProjectBinPath(project2, Nothing, "C:\project2.dll") + project2.SetBinOutputPathAndRelatedData("C:\project2.dll") project1.AddProjectReference(project2) diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb index a0c2e56289dff11203b29d3976df53a5ab3dd7e7..8ff49fc91e2c06b9a728302060ade38c67232e91 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb @@ -502,13 +502,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim SetOptions(lastCompilationOptions.WithGlobalImports(_imports), CurrentParseOptions) End If End Sub - -#If DEBUG Then - Public Overrides ReadOnly Property Debug_VBEmbeddedCoreOptionOn As Boolean - Get - Return DirectCast(CurrentCompilationOptions, VisualBasicCompilationOptions).EmbedVbCoreRuntime - End Get - End Property -#End If End Class End Namespace