From e08e58f9081f564811deeae5d3980f0696e8badd Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Mon, 2 Dec 2019 13:22:51 +0000 Subject: [PATCH 1/3] [Ide] Consider MSBuild item conditions in Solution pad Creating an ASP.NET Core project when .NET Core 3.1 SDK was installed would result in .json files being displayed twice in the Solution pad. .NET Core 3.1 SDK defines .json files twice. Older .NET Core SDKs did not define the Content items more than once. The Condition was not considered when showing files in the Solution pad. To support conditional files the Solution pad asks the project for its visible files. The project uses the MSBuildEvaluationContext to evaluate the condition to see if the file is visible or not. Note that conditions on parent ItemGroups are currently not taken into account. Also that visible files are not updated if the active config is changed. Fixes VSTS #1005277 Create ASP.NET Core project, open Properties folder, there are two launchSettings.json files. --- .../MonoDevelop.Projects/Project.cs | 31 +++ .../FolderNodeBuilder.cs | 6 +- .../ProjectNodeBuilderTests.cs | 80 ++++++++ .../TestTreeBuilder.cs | 187 ++++++++++++++++++ .../Ide.Tests/MonoDevelop.Ide.Tests.csproj | 2 + .../ConditionalFiles/ConditionalFiles.csproj | 40 ++++ .../ConditionalFiles/ConditionalFiles.sln | 17 ++ .../ConditionalFiles/MyClass-Debug.cs | 11 ++ .../ConditionalFiles/MyClass-Release.cs | 11 ++ .../test-projects/ConditionalFiles/MyClass.cs | 11 ++ 10 files changed, 391 insertions(+), 5 deletions(-) create mode 100644 main/tests/Ide.Tests/MonoDevelop.Ide.Projects/ProjectNodeBuilderTests.cs create mode 100644 main/tests/Ide.Tests/MonoDevelop.Ide.Projects/TestTreeBuilder.cs create mode 100644 main/tests/test-projects/ConditionalFiles/ConditionalFiles.csproj create mode 100644 main/tests/test-projects/ConditionalFiles/ConditionalFiles.sln create mode 100644 main/tests/test-projects/ConditionalFiles/MyClass-Debug.cs create mode 100644 main/tests/test-projects/ConditionalFiles/MyClass-Release.cs create mode 100644 main/tests/test-projects/ConditionalFiles/MyClass.cs diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs index fcd55fd4271..b9ef9d8bb15 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs @@ -38,6 +38,7 @@ using MonoDevelop.Projects; using System.Threading.Tasks; using MonoDevelop.Projects.MSBuild; +using MonoDevelop.Projects.MSBuild.Conditions; using System.Xml; using MonoDevelop.Core.Instrumentation; using MonoDevelop.Core.Assemblies; @@ -1216,6 +1217,36 @@ public bool IsFileInProject (string fileName) return files.GetFile (fileName) != null; } + /// + /// Return non-hidden files based on the configuration. + /// + /// Configuration. + /// Files that should be displayed in the Solution window for a project. + public IEnumerable GetVisibleFiles (ConfigurationSelector configuration) + { + MSBuildEvaluationContext ctx = null; + + foreach (ProjectFile file in Files) { + if (!file.Visible || file.IsHidden) { + continue; + } else if (string.IsNullOrEmpty (file.Condition)) { + yield return file; + continue; + } + + if (ctx == null) { + ctx = new MSBuildEvaluationContext (); + ctx.InitEvaluation (MSBuildProject); + var config = (ProjectConfiguration)GetConfiguration (configuration); + foreach (var prop in config.Properties.GetProperties ()) + ctx.SetPropertyValue (prop.Name, prop.Value); + } + + if (ConditionParser.ParseAndEvaluate (file.Condition, ctx)) + yield return file; + } + } + /// /// Gets a list of build actions supported by this project /// diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs index d2328e2434c..cd762e70350 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs @@ -84,13 +84,9 @@ void GetFolderContent (Project project, string folder, out List fil files = new List (); folders = new List (); - foreach (ProjectFile file in project.Files) - { + foreach (ProjectFile file in project.GetVisibleFiles (IdeApp.Workspace.ActiveConfiguration)) { string dir; - if (!file.Visible || file.Flags.HasFlag (ProjectItemFlags.Hidden)) - continue; - if (file.Subtype != Subtype.Directory) { // If file depends on something other than a directory, continue if ((file.DependsOnFile != null && file.DependsOnFile.Subtype != Subtype.Directory) || FileNestingService.HasParent (file)) diff --git a/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/ProjectNodeBuilderTests.cs b/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/ProjectNodeBuilderTests.cs new file mode 100644 index 00000000000..2c915c5fd0a --- /dev/null +++ b/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/ProjectNodeBuilderTests.cs @@ -0,0 +1,80 @@ +// +// ProjectNodeBuilderTests.cs +// +// Author: +// Matt Ward +// +// Copyright (c) 2019 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Linq; +using System.Threading.Tasks; +using MonoDevelop.Core; +using MonoDevelop.Ide.Gui.Pads.ProjectPad; +using MonoDevelop.Projects; +using NUnit.Framework; +using UnitTests; + +namespace MonoDevelop.Ide.Projects +{ + [TestFixture] + [RequireService (typeof (RootWorkspace))] + class ProjectNodeBuilderTests : IdeTestBase + { + [Test] + public async Task ConditionalFiles () + { + FilePath solutionFile = Util.GetSampleProject ("ConditionalFiles", "ConditionalFiles.sln"); + + using (var solution = (Solution)await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solutionFile)) { + var p = solution.GetAllProjects ().OfType ().Single (); + + // Debug configuration. + IdeApp.Workspace.ActiveConfigurationId = "Debug"; + + var treeBuilder = new TestTreeBuilder (); + treeBuilder.ParentDataItem [typeof (Project)] = p; + + var nodeBuilder = new ProjectNodeBuilder (); + nodeBuilder.BuildChildNodes (treeBuilder, p); + + var debugFiles = treeBuilder.ChildNodes.OfType ().ToList (); + var debugFileNames = debugFiles.Select (f => f.FilePath.FileName).ToList (); + Assert.That (debugFileNames, Has.Member ("MyClass.cs")); + Assert.That (debugFileNames, Has.Member ("MyClass-Debug.cs")); + Assert.That (debugFileNames, Has.No.Member ("MyClass-Release.cs")); + Assert.AreEqual (2, debugFiles.Count); + + // Release configuration. + IdeApp.Workspace.ActiveConfigurationId = "Release"; + + treeBuilder.ChildNodes.Clear (); + nodeBuilder.BuildChildNodes (treeBuilder, p); + var releaseFiles = treeBuilder.ChildNodes.OfType ().ToList (); + var releaseFileNames = releaseFiles.Select (f => f.FilePath.FileName).ToList (); + Assert.That (releaseFileNames, Has.Member ("MyClass.cs")); + Assert.That (releaseFileNames, Has.Member ("MyClass-Release.cs")); + Assert.That (releaseFileNames, Has.No.Member ("MyClass-Debug.cs")); + Assert.AreEqual (2, releaseFiles.Count); + } + } + } + +} diff --git a/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/TestTreeBuilder.cs b/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/TestTreeBuilder.cs new file mode 100644 index 00000000000..9e8692f2da5 --- /dev/null +++ b/main/tests/Ide.Tests/MonoDevelop.Ide.Projects/TestTreeBuilder.cs @@ -0,0 +1,187 @@ +// +// TestTreeBuilder.cs +// +// Author: +// Matt Ward +// +// Copyright (c) 2019 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using MonoDevelop.Ide.Gui.Components; + +namespace MonoDevelop.Ide.Projects +{ + class TestTreeBuilder : ITreeBuilder + { + public object DataItem { get; set; } + public string NodeName { get; set; } + public bool Selected { get; set; } + public bool Expanded { get; set; } + public ITreeOptions Options { get; } + public TypeNodeBuilder TypeNodeBuilder { get; } + public NodePosition CurrentPosition { get; } + public bool Filled { get; } + + public List ChildNodes = new List (); + + public void AddChild (object dataObject) + { + ChildNodes.Add (dataObject); + } + + public void AddChild (object dataObject, bool moveToChild) + { + ChildNodes.Add (dataObject); + } + + public void AddChildren (IEnumerable dataObjects) + { + foreach (object dataObject in dataObjects) { + ChildNodes.Add (dataObject); + } + } + + public ITreeNavigator Clone () + { + throw new NotImplementedException (); + } + + public void ExpandToNode () + { + } + + public bool FindChild (object dataObject) + { + throw new NotImplementedException (); + } + + public bool FindChild (object dataObject, bool recursive) + { + throw new NotImplementedException (); + } + + public Dictionary ParentDataItem = new Dictionary (); + + public object GetParentDataItem (Type type, bool includeCurrent) + { + if (ParentDataItem.TryGetValue (type, out object parent)) { + return parent; + } + + return null; + } + + public T GetParentDataItem (bool includeCurrent) + { + throw new NotImplementedException (); + } + + public bool HasChild (string name, Type dataType) + { + throw new NotImplementedException (); + } + + public bool HasChildren () + { + throw new NotImplementedException (); + } + + public bool MoveNext () + { + throw new NotImplementedException (); + } + + public bool MoveToChild (string name, Type dataType) + { + throw new NotImplementedException (); + } + + public bool MoveToFirstChild () + { + throw new NotImplementedException (); + } + + public bool MoveToNextObject () + { + throw new NotImplementedException (); + } + + public bool MoveToObject (object dataObject) + { + throw new NotImplementedException (); + } + + public bool MoveToParent () + { + throw new NotImplementedException (); + } + + public bool MoveToParent (Type type) + { + throw new NotImplementedException (); + } + + public bool MoveToPosition (NodePosition position) + { + throw new NotImplementedException (); + } + + public bool MoveToRoot () + { + throw new NotImplementedException (); + } + + public void Remove () + { + } + + public void Remove (bool moveToParent) + { + } + + public void RestoreState (NodeState state) + { + } + + public NodeState SaveState () + { + throw new NotImplementedException (); + } + + public void ScrollToNode () + { + } + + public void Update () + { + } + + public void UpdateAll () + { + } + + public void UpdateChildren () + { + } + } +} diff --git a/main/tests/Ide.Tests/MonoDevelop.Ide.Tests.csproj b/main/tests/Ide.Tests/MonoDevelop.Ide.Tests.csproj index 438d915dea9..661d0def714 100644 --- a/main/tests/Ide.Tests/MonoDevelop.Ide.Tests.csproj +++ b/main/tests/Ide.Tests/MonoDevelop.Ide.Tests.csproj @@ -132,6 +132,8 @@ + + diff --git a/main/tests/test-projects/ConditionalFiles/ConditionalFiles.csproj b/main/tests/test-projects/ConditionalFiles/ConditionalFiles.csproj new file mode 100644 index 00000000000..5e5187e7c6a --- /dev/null +++ b/main/tests/test-projects/ConditionalFiles/ConditionalFiles.csproj @@ -0,0 +1,40 @@ + + + + Debug + AnyCPU + {200DD006-C60F-4A2F-BB12-E2496F88CA65} + Library + ConditionalFiles + ConditionalFiles + v4.7.2 + true + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + true + + + true + bin\Release + prompt + 4 + false + + + + + + + + + + + \ No newline at end of file diff --git a/main/tests/test-projects/ConditionalFiles/ConditionalFiles.sln b/main/tests/test-projects/ConditionalFiles/ConditionalFiles.sln new file mode 100644 index 00000000000..62b8278b8e3 --- /dev/null +++ b/main/tests/test-projects/ConditionalFiles/ConditionalFiles.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConditionalFiles", "ConditionalFiles.csproj", "{200DD006-C60F-4A2F-BB12-E2496F88CA65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {200DD006-C60F-4A2F-BB12-E2496F88CA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {200DD006-C60F-4A2F-BB12-E2496F88CA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {200DD006-C60F-4A2F-BB12-E2496F88CA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {200DD006-C60F-4A2F-BB12-E2496F88CA65}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/main/tests/test-projects/ConditionalFiles/MyClass-Debug.cs b/main/tests/test-projects/ConditionalFiles/MyClass-Debug.cs new file mode 100644 index 00000000000..533a56e62c8 --- /dev/null +++ b/main/tests/test-projects/ConditionalFiles/MyClass-Debug.cs @@ -0,0 +1,11 @@ + +using System; +namespace ConditionalFiles +{ + public class MyClassDebug + { + public MyClassDebug () + { + } + } +} diff --git a/main/tests/test-projects/ConditionalFiles/MyClass-Release.cs b/main/tests/test-projects/ConditionalFiles/MyClass-Release.cs new file mode 100644 index 00000000000..6d308259a48 --- /dev/null +++ b/main/tests/test-projects/ConditionalFiles/MyClass-Release.cs @@ -0,0 +1,11 @@ + +using System; +namespace ConditionalFiles +{ + public class MyClassRelease + { + public MyClassRelease () + { + } + } +} diff --git a/main/tests/test-projects/ConditionalFiles/MyClass.cs b/main/tests/test-projects/ConditionalFiles/MyClass.cs new file mode 100644 index 00000000000..110bfa9fd75 --- /dev/null +++ b/main/tests/test-projects/ConditionalFiles/MyClass.cs @@ -0,0 +1,11 @@ + +using System; +namespace ConditionalFiles +{ + public class MyClass + { + public MyClass () + { + } + } +} From 021be875ee4e37bb2ee546ce2c2bec1721dcc519 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Mon, 2 Dec 2019 13:42:31 +0000 Subject: [PATCH 2/3] [Core] Make MSBuildPropertyGroupEvaluated thread safe Adding a new .json file to a ASP.NET Core 3.1 project resulted in the files not being displayed in the Properties folder due to the project configuration's Properties being updated on a background thread on saving and the UI thread reading these properties to determine if a file is visible in the Solution pad. Switch to using a concurrent dictionary in MSBuildPropertyGroupEvaluated. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at System.Collections.Generic.Dictionary`2+ValueCollection+Enumerator[TKey,TValue].MoveNext () [0x00085] in mono-x64/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs:1628 at MonoDevelop.Projects.MSBuild.MSBuildEvaluatedPropertyCollection+d__11.MoveNext () [0x0008e] in monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs:207 at MonoDevelop.Projects.Project+d__143.MoveNext () [0x0017d] in monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs:1231 at MonoDevelop.Ide.Gui.Pads.ProjectPad.FolderNodeBuilder.GetFolderContent (MonoDevelop.Projects.Project project, System.String folder, System.Collections.Generic.List`1[MonoDevelop.Projects.ProjectFile]& files, System.Collections.Generic.List`1[System.String]& folders) [0x00149] in monodevelop/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs:87 at MonoDevelop.Ide.Gui.Pads.ProjectPad.FolderNodeBuilder.BuildChildNodes (MonoDevelop.Ide.Gui.Components.ITreeBuilder builder, System.Object dataObject) [0x00048] in monodevelop/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs:74 at MonoDevelop.Ide.Gui.Components.ExtensibleTreeView+TransactedTreeBuilder.FillNode () [0x00082] in monodevelop/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Components/TransactedTreeBuilder.cs:522 --- .../MSBuildPropertyGroupEvaluated.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs index 18d155a6d44..31025499fb2 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Xml.Linq; using MonoDevelop.Core; @@ -32,7 +33,7 @@ namespace MonoDevelop.Projects.MSBuild { class MSBuildPropertyGroupEvaluated: MSBuildNode, IMSBuildPropertyGroupEvaluated, IMSBuildProjectObject { - protected Dictionary properties = new Dictionary (StringComparer.OrdinalIgnoreCase); + protected ConcurrentDictionary properties = new ConcurrentDictionary (StringComparer.OrdinalIgnoreCase); MSBuildEngine engine; internal MSBuildPropertyGroupEvaluated (MSBuildProject parent) @@ -70,7 +71,7 @@ internal void SetProperty (string key, IMSBuildPropertyEvaluated value) internal void SetProperties (Dictionary properties) { - this.properties = properties; + this.properties = new ConcurrentDictionary (properties, StringComparer.OrdinalIgnoreCase); } public IEnumerable GetProperties () @@ -80,7 +81,7 @@ public IEnumerable GetProperties () internal bool RemoveProperty (string name) { - return properties.Remove (name); + return properties.TryRemove (name, out _); } public string GetValue (string name, string defaultValue = null) @@ -280,7 +281,7 @@ void IPropertyGroupListener.PropertyRemoved (MSBuildProperty prop) // that property group property. if (ep.IsNew || !prop.IsNew) { ep.IsNew = false; - properties.Remove (ep.Name); + properties.TryRemove (ep.Name, out _); } } } From f076c06cbea600d0a2669ee8d93c80b437f8e5e5 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Mon, 2 Dec 2019 15:18:07 +0000 Subject: [PATCH 3/3] [Core] Use lock in MSBuildPropertyGroupEvaluated Using a ConcurrentDictionary broke the tests that rely on the items being ordered in the Dictionary. The item definition test ItemDefinitionGroup_AddFilesWithoutMetadata_MetadataUsesEmptyElements failed due to the item definition properties being re-ordered. For now using a lock around the Dictionary instead of a ConcurrentDictionary. --- .../MSBuildPropertyGroupEvaluated.cs | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs index 31025499fb2..3a588c08269 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs @@ -24,16 +24,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Xml.Linq; +using System.Linq; using MonoDevelop.Core; namespace MonoDevelop.Projects.MSBuild { class MSBuildPropertyGroupEvaluated: MSBuildNode, IMSBuildPropertyGroupEvaluated, IMSBuildProjectObject { - protected ConcurrentDictionary properties = new ConcurrentDictionary (StringComparer.OrdinalIgnoreCase); + protected Dictionary properties = new Dictionary (StringComparer.OrdinalIgnoreCase); MSBuildEngine engine; internal MSBuildPropertyGroupEvaluated (MSBuildProject parent) @@ -43,45 +42,60 @@ internal MSBuildPropertyGroupEvaluated (MSBuildProject parent) internal void Sync (MSBuildEngine engine, object item, bool clearProperties = true) { - if (clearProperties) - properties.Clear (); + if (clearProperties) { + lock (properties) { + properties.Clear (); + } + } this.engine = engine; foreach (var propName in engine.GetItemMetadataNames (item)) { var prop = new MSBuildPropertyEvaluated (ParentProject, propName, engine.GetItemMetadata (item, propName), engine.GetEvaluatedItemMetadata (item, propName)); - properties [propName] = prop; + lock (properties) { + properties [propName] = prop; + } } } public bool HasProperty (string name) { - return properties.ContainsKey (name); + lock (properties) { + return properties.ContainsKey (name); + } } public IMSBuildPropertyEvaluated GetProperty (string name) { IMSBuildPropertyEvaluated prop; - properties.TryGetValue (name, out prop); + lock (properties) { + properties.TryGetValue (name, out prop); + } return prop; } internal void SetProperty (string key, IMSBuildPropertyEvaluated value) { - properties [key] = value; + lock (properties) { + properties [key] = value; + } } internal void SetProperties (Dictionary properties) { - this.properties = new ConcurrentDictionary (properties, StringComparer.OrdinalIgnoreCase); + this.properties = properties; } public IEnumerable GetProperties () { - return properties.Values; + lock (properties) { + return properties.Values.ToArray (); + } } internal bool RemoveProperty (string name) { - return properties.TryRemove (name, out _); + lock (properties) { + return properties.Remove (name); + } } public string GetValue (string name, string defaultValue = null) @@ -153,11 +167,15 @@ public MSBuildEvaluatedPropertyCollection (MSBuildProject parent): base (parent) internal void SyncCollection (MSBuildEngine e, object project) { - properties.Clear (); + lock (properties) { + properties.Clear (); + } foreach (var p in e.GetEvaluatedProperties (project)) { string name, value, finalValue; bool definedMultipleTimes; e.GetPropertyInfo (p, out name, out value, out finalValue, out definedMultipleTimes); - properties [name] = new MSBuildPropertyEvaluated (ParentProject, name, value, finalValue, definedMultipleTimes); + lock (properties) { + properties [name] = new MSBuildPropertyEvaluated (ParentProject, name, value, finalValue, definedMultipleTimes); + } } } @@ -244,7 +262,9 @@ MSBuildPropertyEvaluated AddProperty (string name) { var p = new MSBuildPropertyEvaluated (ParentProject, name, null, null); p.IsNew = true; - properties [name] = p; + lock (properties) { + properties [name] = p; + } return p; } @@ -281,13 +301,19 @@ void IPropertyGroupListener.PropertyRemoved (MSBuildProperty prop) // that property group property. if (ep.IsNew || !prop.IsNew) { ep.IsNew = false; - properties.TryRemove (ep.Name, out _); + lock (properties) { + properties.Remove (ep.Name); + } } } } public IEnumerable Properties { - get { return properties.Values; } + get { + lock (properties) { + return properties.Values.ToArray (); + } + } } }