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..3a588c08269 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildPropertyGroupEvaluated.cs @@ -25,14 +25,14 @@ // THE SOFTWARE. using System; using System.Collections.Generic; -using System.Xml.Linq; +using System.Linq; using MonoDevelop.Core; namespace MonoDevelop.Projects.MSBuild { class MSBuildPropertyGroupEvaluated: MSBuildNode, IMSBuildPropertyGroupEvaluated, IMSBuildProjectObject { - protected Dictionary properties = new Dictionary (StringComparer.OrdinalIgnoreCase); + protected Dictionary properties = new Dictionary (StringComparer.OrdinalIgnoreCase); MSBuildEngine engine; internal MSBuildPropertyGroupEvaluated (MSBuildProject parent) @@ -42,30 +42,41 @@ 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) @@ -75,12 +86,16 @@ internal void SetProperties (Dictionary proper public IEnumerable GetProperties () { - return properties.Values; + lock (properties) { + return properties.Values.ToArray (); + } } internal bool RemoveProperty (string name) { - return properties.Remove (name); + lock (properties) { + return properties.Remove (name); + } } public string GetValue (string name, string defaultValue = null) @@ -152,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); + } } } @@ -243,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; } @@ -280,13 +301,19 @@ void IPropertyGroupListener.PropertyRemoved (MSBuildProperty prop) // that property group property. if (ep.IsNew || !prop.IsNew) { ep.IsNew = false; - properties.Remove (ep.Name); + lock (properties) { + properties.Remove (ep.Name); + } } } } public IEnumerable Properties { - get { return properties.Values; } + get { + lock (properties) { + return properties.Values.ToArray (); + } + } } } 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 () + { + } + } +}