From 6e08cfe87e8d650ef702c35f96da0c615d9db20c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 13 Mar 2013 00:23:16 +0100 Subject: [PATCH] Reimplemented project configuration management. --- SharpDevelop.Tests.sln | 2 +- .../AspNet.Mvc/Test/Src/WebProjectTests.cs | 3 +- .../CreateNewWixProjectObjectTestFixture.cs | 20 +- .../Project/ICSharpCode.SharpDevelop.addin | 2 + .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Project/Build/ProjectBuildOptions.cs | 2 +- .../Configuration/ConfigurationAndPlatform.cs | 2 +- .../Project/Src/Project/CompilableProject.cs | 2 +- .../Src/Project/MSBuildBasedProject.cs | 309 +++--------------- ...ldConfigurationOrPlatformNameCollection.cs | 250 ++++++++++++++ .../Project/Src/Project/MSBuildInternals.cs | 5 +- .../EditAvailableConfigurationsDialog.cs | 2 +- 12 files changed, 327 insertions(+), 273 deletions(-) create mode 100644 src/Main/Base/Project/Src/Project/MSBuildConfigurationOrPlatformNameCollection.cs diff --git a/SharpDevelop.Tests.sln b/SharpDevelop.Tests.sln index e63f4fc36c..c16bcc493c 100644 --- a/SharpDevelop.Tests.sln +++ b/SharpDevelop.Tests.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -# SharpDevelop 5.0 +# SharpDevelop 4.3 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Main", "Main", "{256F5C28-532C-44C0-8AB8-D8EC5E492E01}" ProjectSection(SolutionItems) = postProject EndProjectSection diff --git a/src/AddIns/BackendBindings/AspNet.Mvc/Test/Src/WebProjectTests.cs b/src/AddIns/BackendBindings/AspNet.Mvc/Test/Src/WebProjectTests.cs index 7206d00021..13cc2f4abd 100644 --- a/src/AddIns/BackendBindings/AspNet.Mvc/Test/Src/WebProjectTests.cs +++ b/src/AddIns/BackendBindings/AspNet.Mvc/Test/Src/WebProjectTests.cs @@ -55,7 +55,8 @@ namespace AspNet.Mvc.Tests { var fileContentsBuilder = new StringBuilder(); var stringWriter = new StringWriter(fileContentsBuilder); - msbuildProject.MSBuildProjectFile.Save(stringWriter); + lock (msbuildProject.SyncRoot) + msbuildProject.MSBuildProjectFile.Save(stringWriter); return GetProjectExtensions(fileContentsBuilder.ToString()); } diff --git a/src/AddIns/BackendBindings/WixBinding/Test/Project/CreateNewWixProjectObjectTestFixture.cs b/src/AddIns/BackendBindings/WixBinding/Test/Project/CreateNewWixProjectObjectTestFixture.cs index 967dce17ab..3779eb487a 100644 --- a/src/AddIns/BackendBindings/WixBinding/Test/Project/CreateNewWixProjectObjectTestFixture.cs +++ b/src/AddIns/BackendBindings/WixBinding/Test/Project/CreateNewWixProjectObjectTestFixture.cs @@ -129,10 +129,12 @@ namespace WixBinding.Tests.Project /// ProjectPropertyElement GetMSBuildProperty(string name) { - foreach (ProjectPropertyGroupElement propertyGroup in project.MSBuildProjectFile.PropertyGroups) { - foreach (ProjectPropertyElement element in propertyGroup.Properties) { - if (element.Name == name) { - return element; + lock (project.SyncRoot) { + foreach (ProjectPropertyGroupElement propertyGroup in project.MSBuildProjectFile.PropertyGroups) { + foreach (ProjectPropertyElement element in propertyGroup.Properties) { + if (element.Name == name) { + return element; + } } } } @@ -145,10 +147,12 @@ namespace WixBinding.Tests.Project ProjectPropertyElement GetLastMSBuildProperty(string name) { ProjectPropertyElement matchedElement = null; - foreach (ProjectPropertyGroupElement propertyGroup in project.MSBuildProjectFile.PropertyGroups) { - foreach (ProjectPropertyElement element in propertyGroup.Properties) { - if (element.Name == name) { - matchedElement = element; + lock (project.SyncRoot) { + foreach (ProjectPropertyGroupElement propertyGroup in project.MSBuildProjectFile.PropertyGroups) { + foreach (ProjectPropertyElement element in propertyGroup.Properties) { + if (element.Name == name) { + matchedElement = element; + } } } } diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin index bf9782f5f8..771e944bbf 100755 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin @@ -80,6 +80,8 @@ class="ICSharpCode.SharpDevelop.Project.MSBuildEngine"/> + diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 559bfb27c3..17b9919bdc 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -233,6 +233,7 @@ OutputWindowOptionsPanel.xaml Code + diff --git a/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs b/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs index bfb47392eb..e19d0fbfd1 100644 --- a/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs +++ b/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs @@ -12,7 +12,7 @@ namespace ICSharpCode.SharpDevelop.Project public class ProjectBuildOptions { BuildTarget target; - IDictionary properties = new SortedList(); + IDictionary properties = new SortedList(MSBuildInternals.PropertyNameComparer); public BuildTarget Target { get { return target; } diff --git a/src/Main/Base/Project/Project/Configuration/ConfigurationAndPlatform.cs b/src/Main/Base/Project/Project/Configuration/ConfigurationAndPlatform.cs index 7be777bb14..02dfe6db18 100644 --- a/src/Main/Base/Project/Project/Configuration/ConfigurationAndPlatform.cs +++ b/src/Main/Base/Project/Project/Configuration/ConfigurationAndPlatform.cs @@ -12,7 +12,7 @@ namespace ICSharpCode.SharpDevelop.Project /// public struct ConfigurationAndPlatform : IEquatable { - public static readonly StringComparer ConfigurationNameComparer = StringComparer.Ordinal; + public static readonly StringComparer ConfigurationNameComparer = StringComparer.OrdinalIgnoreCase; public static bool IsValidName(string name) { diff --git a/src/Main/Base/Project/Src/Project/CompilableProject.cs b/src/Main/Base/Project/Src/Project/CompilableProject.cs index 1ff019c8ca..3c2579e16f 100644 --- a/src/Main/Base/Project/Src/Project/CompilableProject.cs +++ b/src/Main/Base/Project/Src/Project/CompilableProject.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.SharpDevelop.Project PropertyStorageLocations.ConfigurationSpecific, true); SetProperty("Release", null, "OutputPath", @"bin\Release\", PropertyStorageLocations.ConfigurationSpecific, true); - InvalidateConfigurationPlatformNames(); + LoadConfigurationPlatformNamesFromMSBuild(); SetProperty("Debug", null, "DebugSymbols", "True", PropertyStorageLocations.ConfigurationSpecific, true); diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index 47fe0b0b25..b61907369e 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -13,16 +12,14 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; + using ICSharpCode.Core; using ICSharpCode.NRefactory; using ICSharpCode.SharpDevelop.Dom; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Internal.Templates; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using MSBuild = Microsoft.Build.Evaluation; -using StringPair = System.Tuple; namespace ICSharpCode.SharpDevelop.Project { @@ -57,7 +54,7 @@ namespace ICSharpCode.SharpDevelop.Project /// Use this for properties that could reference other properties, e.g. /// PostBuildEvent references OutputPath. /// - protected readonly ISet saveAfterImportsProperties = new SortedSet { + protected readonly ISet saveAfterImportsProperties = new SortedSet(MSBuildInternals.PropertyNameComparer) { "PostBuildEvent", "PreBuildEvent" }; @@ -85,6 +82,7 @@ namespace ICSharpCode.SharpDevelop.Project get { if (projectFile == null) throw new ObjectDisposedException("MSBuildBasedProject"); + Debug.Assert(Monitor.IsEntered(SyncRoot)); return projectFile; } } @@ -98,6 +96,7 @@ namespace ICSharpCode.SharpDevelop.Project get { if (projectFile == null) throw new ObjectDisposedException("MSBuildBasedProject"); + Debug.Assert(Monitor.IsEntered(SyncRoot)); return userProjectFile; } } @@ -161,6 +160,8 @@ namespace ICSharpCode.SharpDevelop.Project : base(information) { this.itemsCollection = new ProjectItemCollection(this); + this.configurationNames = new MSBuildConfigurationOrPlatformNameCollection(this, false); + this.platformNames = new MSBuildConfigurationOrPlatformNameCollection(this, true); this.projectFile = ProjectRootElement.Create(MSBuildProjectCollection); this.userProjectFile = ProjectRootElement.Create(MSBuildProjectCollection); @@ -174,10 +175,11 @@ namespace ICSharpCode.SharpDevelop.Project AddGuardedProperty("Platform", information.ActiveProjectConfiguration.Platform); string platform = information.ActiveProjectConfiguration.Platform; - if (platform == "x86") + if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(platform, "x86")) SetProperty(null, platform, "PlatformTarget", "x86", PropertyStorageLocations.PlatformSpecific, false); else SetProperty(null, platform, "PlatformTarget", "AnyCPU", PropertyStorageLocations.PlatformSpecific, false); + LoadConfigurationPlatformNamesFromMSBuild(); } /// @@ -391,7 +393,7 @@ namespace ICSharpCode.SharpDevelop.Project if (platform == null) platform = this.ActiveConfiguration.Platform; - bool openCurrentConfiguration = configuration == this.ActiveConfiguration.Configuration && platform == this.ActiveConfiguration.Platform; + bool openCurrentConfiguration = new ConfigurationAndPlatform(configuration, platform) == this.ActiveConfiguration; if (currentlyOpenProject != null && openCurrentConfiguration) { // use currently open project @@ -399,7 +401,7 @@ namespace ICSharpCode.SharpDevelop.Project return new ConfiguredProject(this, currentlyOpenProject, false); } - Dictionary globalProps = new Dictionary(); + Dictionary globalProps = new Dictionary(MSBuildInternals.PropertyNameComparer); var msbuildEngine = SD.Services.GetService(); if (msbuildEngine != null) globalProps.AddRange(msbuildEngine.GlobalBuildProperties); @@ -534,8 +536,9 @@ namespace ICSharpCode.SharpDevelop.Project var configFromCondition = ConfigurationAndPlatform.FromCondition(g.Condition); string gConfiguration = configFromCondition.Configuration; string gPlatform = configFromCondition.Platform; - if ((configuration == null || configuration == gConfiguration || gConfiguration == null) - && (platform == null || platform == gPlatform || gPlatform == null)) + StringComparer comparer = ConfigurationAndPlatform.ConfigurationNameComparer; + if ((configuration == null || comparer.Equals(configuration, gConfiguration) || gConfiguration == null) + && (platform == null || comparer.Equals(platform, gPlatform) || gPlatform == null)) { if (gConfiguration == null && gPlatform == null) { location = PropertyStorageLocations.Base; @@ -597,12 +600,12 @@ namespace ICSharpCode.SharpDevelop.Project PropertyStorageLocations FindExistingPropertyInAllConfigurations(string propertyName) { foreach (var g in projectFile.PropertyGroups) { - if (g.Properties.Any(p => MSBuildInternals.PropertyNameComparer.Equals(p.Name == propertyName))) { + if (g.Properties.Any(p => MSBuildInternals.PropertyNameComparer.Equals(p.Name, propertyName))) { return MSBuildInternals.GetLocationFromCondition(g.Condition); } } foreach (var g in userProjectFile.PropertyGroups) { - if (g.Properties.Any(p => MSBuildInternals.PropertyNameComparer.Equals(p.Name == propertyName))) { + if (g.Properties.Any(p => MSBuildInternals.PropertyNameComparer.Equals(p.Name, propertyName))) { return MSBuildInternals.GetLocationFromCondition(g.Condition) | PropertyStorageLocations.UserFile; } @@ -713,7 +716,7 @@ namespace ICSharpCode.SharpDevelop.Project break; case PropertyStorageLocations.ConfigurationSpecific: // Get any value usable as existing property value (once per configuration) - Dictionary oldValuesConf = new Dictionary(); + Dictionary oldValuesConf = new Dictionary(ConfigurationAndPlatform.ConfigurationNameComparer); foreach (string conf in this.ConfigurationNames) { oldValuesConf[conf] = GetAnyUnevaluatedPropertyValue(conf, null, propertyName); } @@ -733,7 +736,7 @@ namespace ICSharpCode.SharpDevelop.Project break; case PropertyStorageLocations.PlatformSpecific: // Get any value usable as existing property value (once per platform) - Dictionary oldValuesPlat = new Dictionary(); + Dictionary oldValuesPlat = new Dictionary(ConfigurationAndPlatform.ConfigurationNameComparer); foreach (string plat in this.PlatformNames) { oldValuesPlat[plat] = GetAnyUnevaluatedPropertyValue(null, plat, propertyName); } @@ -753,10 +756,10 @@ namespace ICSharpCode.SharpDevelop.Project break; case PropertyStorageLocations.ConfigurationAndPlatformSpecific: // Get any value usable as existing property value (once per configuration+platform) - Dictionary oldValues = new Dictionary(); + Dictionary oldValues = new Dictionary(); foreach (string conf in this.ConfigurationNames) { foreach (string plat in this.PlatformNames) { - oldValues[new StringPair(conf, plat)] = GetAnyUnevaluatedPropertyValue(conf, plat, propertyName); + oldValues[new ConfigurationAndPlatform(conf, plat)] = GetAnyUnevaluatedPropertyValue(conf, plat, propertyName); } } @@ -764,10 +767,10 @@ namespace ICSharpCode.SharpDevelop.Project RemovePropertyCompletely(propertyName); // Recreate the property using the saved value - foreach (KeyValuePair pair in oldValues) { + foreach (KeyValuePair pair in oldValues) { if (pair.Value != null) { MSBuildSetProperty(targetProject, propertyName, pair.Value, - ConfigurationAndPlatform.CreateCondition(pair.Key.Item1, pair.Key.Item2, location), + ConfigurationAndPlatform.CreateCondition(pair.Key.Configuration, pair.Key.Platform, location), propertyInsertionPosition, false); } @@ -1087,7 +1090,7 @@ namespace ICSharpCode.SharpDevelop.Project itemsReadOnly = null; // remove readonly variant of item list - will regenerate on next Items call string newInclude = item.TreatIncludeAsLiteral ? MSBuildInternals.Escape(item.Include) : item.Include; - var newMetadata = new Dictionary(); + var newMetadata = new Dictionary(MSBuildInternals.PropertyNameComparer); foreach (string name in item.MetadataNames) { newMetadata[name] = item.GetMetadata(name); } @@ -1190,6 +1193,9 @@ namespace ICSharpCode.SharpDevelop.Project : base(loadInformation) { this.itemsCollection = new ProjectItemCollection(this); + this.configurationNames = new MSBuildConfigurationOrPlatformNameCollection(this, false); + this.platformNames = new MSBuildConfigurationOrPlatformNameCollection(this, true); + isLoading = true; bool success = false; try { @@ -1311,61 +1317,44 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region GetConfigurationNames / GetPlatformNames - IReadOnlyCollection configurationNames, platformNames; + readonly MSBuildConfigurationOrPlatformNameCollection configurationNames, platformNames; - #warning reimplement configuration management - /*public override IReadOnlyCollection ConfigurationNames { - get { - lock (SyncRoot) { - if (configurationNames == null) { - LoadConfigurationPlatformNamesFromMSBuild(); - } - return configurationNames; - } - } + public override IConfigurationOrPlatformNameCollection ConfigurationNames { + get { return configurationNames; } } - public override IReadOnlyCollection PlatformNames { - get { - lock (SyncRoot) { - if (platformNames == null) { - LoadConfigurationPlatformNamesFromMSBuild(); - } - return platformNames; - } - } - } - */ - protected void InvalidateConfigurationPlatformNames() - { - lock (SyncRoot) { - configurationNames = null; - platformNames = null; - } + public override IConfigurationOrPlatformNameCollection PlatformNames { + get { return platformNames; } } /// /// Load available configurations and platforms from the project file /// by looking at which conditions are used. /// - void LoadConfigurationPlatformNamesFromMSBuild() + protected internal void LoadConfigurationPlatformNamesFromMSBuild() { - ISet configurationNames = new SortedSet(); - ISet platformNames = new SortedSet(); - - LoadConfigurationPlatformNamesFromMSBuildInternal(projectFile, configurationNames, platformNames); - LoadConfigurationPlatformNamesFromMSBuildInternal(userProjectFile, configurationNames, platformNames); - - if (configurationNames.Count == 0) { - configurationNames.Add("Debug"); - configurationNames.Add("Release"); - } - if (platformNames.Count == 0) { - platformNames.Add("AnyCPU"); + lock (SyncRoot) { + ISet configurationNames = new SortedSet(ConfigurationAndPlatform.ConfigurationNameComparer); + ISet platformNames = new SortedSet(ConfigurationAndPlatform.ConfigurationNameComparer); + + LoadConfigurationPlatformNamesFromMSBuildInternal(projectFile, configurationNames, platformNames); + LoadConfigurationPlatformNamesFromMSBuildInternal(userProjectFile, configurationNames, platformNames); + + if (configurationNames.Count == 0) { + configurationNames.Add("Debug"); + configurationNames.Add("Release"); + } + if (platformNames.Count == 0) { + platformNames.Add("AnyCPU"); + } + + var oldConfigurationNames = this.configurationNames.CreateSnapshot(); + var oldPlatformNames = this.platformNames.CreateSnapshot(); + this.configurationNames.SetContents(configurationNames); + this.platformNames.SetContents(platformNames); + this.configurationNames.OnCollectionChanged(oldConfigurationNames, configurationNames.ToArray()); + this.platformNames.OnCollectionChanged(oldPlatformNames, platformNames.ToArray()); } - - this.configurationNames = configurationNames.ToArray(); - this.platformNames = platformNames.ToArray(); } static void LoadConfigurationPlatformNamesFromMSBuildInternal( @@ -1395,200 +1384,6 @@ namespace ICSharpCode.SharpDevelop.Project } #endregion - #region IProjectAllowChangeConfigurations interface implementation - /* - bool IProjectAllowChangeConfigurations.RenameProjectConfiguration(string oldName, string newName) - { - lock (SyncRoot) { - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.Concat(userProjectFile.PropertyGroups)) { - // Rename the default configuration setting - var prop = g.Properties.FirstOrDefault(p => p.Name == "Configuration"); - if (prop != null && prop.Value == oldName) { - prop.Value = newName; - } - - // Rename the configuration in conditions - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gConfiguration == oldName) { - g.Condition = CreateCondition(newName, gPlatform); - } - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - - bool IProjectAllowChangeConfigurations.RenameProjectPlatform(string oldName, string newName) - { - lock (SyncRoot) { - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.Concat(userProjectFile.PropertyGroups)) { - // Rename the default platform setting - var prop = g.Properties.FirstOrDefault(p => p.Name == "Platform"); - if (prop != null && prop.Value == oldName) { - prop.Value = newName; - } - - // Rename the platform in conditions - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gPlatform == oldName) { - g.Condition = CreateCondition(gConfiguration, newName); - } - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - - bool IProjectAllowChangeConfigurations.AddProjectConfiguration(string newName, string copyFrom) - { - lock (SyncRoot) { - bool copiedGroupInMainFile = false; - if (copyFrom != null) { - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.ToList()) { - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gConfiguration == copyFrom) { - CopyProperties(projectFile, g, newName, gPlatform); - copiedGroupInMainFile = true; - } - } - foreach (ProjectPropertyGroupElement g in userProjectFile.PropertyGroups.ToList()) { - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gConfiguration == copyFrom) { - CopyProperties(userProjectFile, g, newName, gPlatform); - } - } - } - if (!copiedGroupInMainFile) { - projectFile.AddPropertyGroup().Condition = CreateCondition(newName, null); - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - - bool IProjectAllowChangeConfigurations.AddProjectPlatform(string newName, string copyFrom) - { - lock (SyncRoot) { - bool copiedGroupInMainFile = false; - if (copyFrom != null) { - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.ToList()) { - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gPlatform == copyFrom) { - CopyProperties(projectFile, g, gConfiguration, newName); - copiedGroupInMainFile = true; - } - } - foreach (ProjectPropertyGroupElement g in userProjectFile.PropertyGroups.ToList()) { - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gPlatform == copyFrom) { - CopyProperties(userProjectFile, g, gConfiguration, newName); - } - } - } - if (!copiedGroupInMainFile) { - projectFile.AddPropertyGroup().Condition = CreateCondition(null, newName); - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - - /// - /// copy properties from g into a new property group for newConfiguration and newPlatform - /// - void CopyProperties(ProjectRootElement project, ProjectPropertyGroupElement g, string newConfiguration, string newPlatform) - { - ProjectPropertyGroupElement ng = project.AddPropertyGroup(); - ng.Condition = CreateCondition(newConfiguration, newPlatform); - foreach (var p in g.Properties) { - ng.AddProperty(p.Name, p.Value).Condition = p.Condition; - } - } - - bool IProjectAllowChangeConfigurations.RemoveProjectConfiguration(string name) - { - lock (SyncRoot) { - string otherConfigurationName = null; - foreach (string configName in this.ConfigurationNames) { - if (configName != name) { - otherConfigurationName = name; - break; - } - } - if (otherConfigurationName == null) { - throw new InvalidOperationException("cannot remove the last configuration"); - } - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.Concat(userProjectFile.PropertyGroups).ToList()) { - ProjectPropertyElement prop = g.Properties.FirstOrDefault(p => p.Name == "Configuration"); - if (prop != null && prop.Value == name) { - prop.Value = otherConfigurationName; - } - - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gConfiguration == name) { - g.Parent.RemoveChild(g); - } - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - - bool IProjectAllowChangeConfigurations.RemoveProjectPlatform(string name) - { - lock (SyncRoot) { - string otherPlatformName = null; - foreach (string platformName in this.PlatformNames) { - if (platformName != name) { - otherPlatformName = name; - break; - } - } - if (otherPlatformName == null) { - throw new InvalidOperationException("cannot remove the last platform"); - } - foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.Concat(userProjectFile.PropertyGroups).ToList()) { - ProjectPropertyElement prop = g.Properties.FirstOrDefault(p => p.Name == "Platform"); - if (prop != null && prop.Value == name) { - prop.Value = otherPlatformName; - } - - string gConfiguration, gPlatform; - MSBuildInternals.GetConfigurationAndPlatformFromCondition(g.Condition, - out gConfiguration, - out gPlatform); - if (gPlatform == name) { - g.Parent.RemoveChild(g); - } - } - LoadConfigurationPlatformNamesFromMSBuild(); - return true; - } - } - */ - #endregion - #region ProjectExtensions public override bool ContainsProjectExtension(string name) { diff --git a/src/Main/Base/Project/Src/Project/MSBuildConfigurationOrPlatformNameCollection.cs b/src/Main/Base/Project/Src/Project/MSBuildConfigurationOrPlatformNameCollection.cs new file mode 100644 index 0000000000..1e2ab9a30b --- /dev/null +++ b/src/Main/Base/Project/Src/Project/MSBuildConfigurationOrPlatformNameCollection.cs @@ -0,0 +1,250 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; + +using ICSharpCode.NRefactory; +using ICSharpCode.SharpDevelop.Dom; +using Microsoft.Build.Construction; + +namespace ICSharpCode.SharpDevelop.Project +{ + /// + /// The collection for and . + /// + class MSBuildConfigurationOrPlatformNameCollection : IConfigurationOrPlatformNameCollection + { + public event ModelCollectionChangedEventHandler CollectionChanged; + + volatile IReadOnlyList listSnapshot = EmptyList.Instance; + readonly MSBuildBasedProject project; + readonly bool isPlatform; + + public MSBuildConfigurationOrPlatformNameCollection(MSBuildBasedProject project, bool isPlatform) + { + this.project = project; + this.isPlatform = isPlatform; + } + + internal void SetContents(IEnumerable updatedItems) + { + this.listSnapshot = updatedItems.ToArray(); + } + + internal void OnCollectionChanged(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) + { + if (oldItems.SequenceEqual(newItems)) + return; + var eh = CollectionChanged; + if (eh != null) + eh(oldItems, newItems); + } + + #region IReadOnlyCollection implementation + + public IReadOnlyCollection CreateSnapshot() + { + return listSnapshot; + } + + public int Count { + get { + return listSnapshot.Count; + } + } + + public IEnumerator GetEnumerator() + { + return listSnapshot.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return listSnapshot.GetEnumerator(); + } + + #endregion + + public string ValidateName(string name) + { + if (name == null) + return null; + name = name.Trim(); + if (!ConfigurationAndPlatform.IsValidName(name)) + return null; + if (isPlatform) + return MSBuildInternals.FixPlatformNameForProject(name); + else + return name; + } + + string GetName(ConfigurationAndPlatform config) + { + return isPlatform ? config.Platform : config.Configuration; + } + + bool HasName(ConfigurationAndPlatform config, string name) + { + return ConfigurationAndPlatform.ConfigurationNameComparer.Equals(GetName(config), name); + } + + ConfigurationAndPlatform SetName(ConfigurationAndPlatform config, string newName) + { + if (isPlatform) + return new ConfigurationAndPlatform(config.Configuration, newName); + else + return new ConfigurationAndPlatform(newName, config.Platform); + } + + void IConfigurationOrPlatformNameCollection.Add(string newName, string copyFrom) + { + SD.MainThread.VerifyAccess(); + newName = ValidateName(newName); + if (newName == null) + throw new ArgumentException(); + lock (project.SyncRoot) { + var projectFile = project.MSBuildProjectFile; + var userProjectFile = project.MSBuildUserProjectFile; + bool copiedGroupInMainFile = false; + if (copyFrom != null) { + foreach (ProjectPropertyGroupElement g in projectFile.PropertyGroups.ToList()) { + var gConfig = ConfigurationAndPlatform.FromCondition(g.Condition); + if (HasName(gConfig, copyFrom)) { + CopyProperties(projectFile, g, SetName(gConfig, newName)); + copiedGroupInMainFile = true; + } + } + foreach (ProjectPropertyGroupElement g in userProjectFile.PropertyGroups.ToList()) { + var gConfig = ConfigurationAndPlatform.FromCondition(g.Condition); + if (HasName(gConfig, copyFrom)) { + CopyProperties(userProjectFile, g, SetName(gConfig, newName)); + } + } + } + if (!copiedGroupInMainFile) { + projectFile.AddPropertyGroup().Condition = (isPlatform ? new ConfigurationAndPlatform(null, newName) : new ConfigurationAndPlatform(newName, null)).ToCondition(); + } + project.LoadConfigurationPlatformNamesFromMSBuild(); + + // Adjust mapping: + // If the new config/platform already exists in the solution and is mapped to some old project config/platform, + // re-map it to the new config/platform. + var mapping = project.ConfigurationMapping; + if (isPlatform) { + string newNameForSolution = MSBuildInternals.FixPlatformNameForSolution(newName); + if (project.ParentSolution.PlatformNames.Contains(newNameForSolution, ConfigurationAndPlatform.ConfigurationNameComparer)) { + foreach (string solutionConfiguration in project.ParentSolution.ConfigurationNames) { + var solutionConfig = new ConfigurationAndPlatform(solutionConfiguration, newNameForSolution); + var projectConfig = mapping.GetProjectConfiguration(solutionConfig); + mapping.SetProjectConfiguration(solutionConfig, SetName(projectConfig, newName)); + } + } + } else { + if (project.ParentSolution.ConfigurationNames.Contains(newName, ConfigurationAndPlatform.ConfigurationNameComparer)) { + foreach (string solutionPlatform in project.ParentSolution.PlatformNames) { + var solutionConfig = new ConfigurationAndPlatform(newName, solutionPlatform); + var projectConfig = mapping.GetProjectConfiguration(solutionConfig); + mapping.SetProjectConfiguration(solutionConfig, SetName(projectConfig, newName)); + } + } + } + project.ActiveConfiguration = mapping.GetProjectConfiguration(project.ParentSolution.ActiveConfiguration); + } + } + + /// + /// copy properties from g into a new property group for newConfiguration and newPlatform + /// + void CopyProperties(ProjectRootElement project, ProjectPropertyGroupElement g, ConfigurationAndPlatform newConfig) + { + ProjectPropertyGroupElement ng = project.AddPropertyGroup(); + ng.Condition = newConfig.ToCondition(); + foreach (var p in g.Properties) { + ng.AddProperty(p.Name, p.Value).Condition = p.Condition; + } + } + + /// + /// Finds the <Configuration> or <Platform> element in this property group. + /// + ProjectPropertyElement FindConfigElement(ProjectPropertyGroupElement g) + { + return g.Properties.FirstOrDefault(p => MSBuildInternals.PropertyNameComparer.Equals(p.Name, isPlatform ? "Platform" : "Configuration")); + } + + void IConfigurationOrPlatformNameCollection.Remove(string name) + { + SD.MainThread.VerifyAccess(); + lock (project.SyncRoot) { + string otherName = null; + foreach (string configName in this) { + if (!ConfigurationAndPlatform.ConfigurationNameComparer.Equals(configName, name)) { + otherName = name; + break; + } + } + if (otherName == null) { + throw new InvalidOperationException("cannot remove the last configuration/platform"); + } + foreach (ProjectPropertyGroupElement g in project.MSBuildProjectFile.PropertyGroups.Concat(project.MSBuildUserProjectFile.PropertyGroups).ToList()) { + ProjectPropertyElement prop = FindConfigElement(g); + if (prop != null && ConfigurationAndPlatform.ConfigurationNameComparer.Equals(prop.Value, name)) { + prop.Value = otherName; + } + + var gConfig = ConfigurationAndPlatform.FromCondition(g.Condition); + if (HasName(gConfig, name)) { + g.Parent.RemoveChild(g); + } + } + project.LoadConfigurationPlatformNamesFromMSBuild(); + + AdjustMapping(name, otherName); + } + } + + void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName) + { + newName = ValidateName(newName); + if (newName == null) + throw new ArgumentException(); + + lock (project.SyncRoot) { + foreach (ProjectPropertyGroupElement g in project.MSBuildProjectFile.PropertyGroups.Concat(project.MSBuildUserProjectFile.PropertyGroups)) { + // Rename the default configuration setting + ProjectPropertyElement prop = FindConfigElement(g); + if (prop != null && ConfigurationAndPlatform.ConfigurationNameComparer.Equals(prop.Value, oldName)) { + prop.Value = newName; + } + + // Rename the configuration in conditions + var gConfig = ConfigurationAndPlatform.FromCondition(g.Condition); + if (HasName(gConfig, oldName)) { + g.Condition = SetName(gConfig, newName).ToCondition(); + } + } + project.LoadConfigurationPlatformNamesFromMSBuild(); + + AdjustMapping(oldName, newName); + } + } + + void AdjustMapping(string oldName, string newName) + { + var mapping = project.ConfigurationMapping; + foreach (string solutionConfiguration in project.ParentSolution.ConfigurationNames) { + foreach (string solutionPlatform in project.ParentSolution.PlatformNames) { + var solutionConfig = new ConfigurationAndPlatform(solutionConfiguration, solutionPlatform); + var projectConfig = mapping.GetProjectConfiguration(solutionConfig); + if (HasName(projectConfig, oldName)) + mapping.SetProjectConfiguration(solutionConfig, SetName(projectConfig, newName)); + } + } + // Adjust active configuration: + if (HasName(project.ActiveConfiguration, oldName)) + project.ActiveConfiguration = SetName(project.ActiveConfiguration, newName); + } + } +} diff --git a/src/Main/Base/Project/Src/Project/MSBuildInternals.cs b/src/Main/Base/Project/Src/Project/MSBuildInternals.cs index 7475b74477..385a4d79ca 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildInternals.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildInternals.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.SharpDevelop.Project // TODO: I think MSBuild actually uses OrdinalIgnoreCase. SharpDevelop 3.x just used string.operator ==, so I'm keeping // that setting until all code is ported to use PropertyNameComparer and we've verified what MSBuild is actually using. public readonly static StringComparer PropertyNameComparer = StringComparer.Ordinal; + public readonly static StringComparer ConfigurationNameComparer = ConfigurationAndPlatform.ConfigurationNameComparer; internal static void UnloadProject(MSBuild.Evaluation.ProjectCollection projectCollection, MSBuild.Evaluation.Project project) { @@ -123,9 +124,9 @@ namespace ICSharpCode.SharpDevelop.Project return PropertyStorageLocations.Base; } PropertyStorageLocations location = 0; // 0 is unknown - if (condition.Contains("$(Configuration)")) + if (condition.IndexOf("$(Configuration)", StringComparison.OrdinalIgnoreCase) >= 0) location |= PropertyStorageLocations.ConfigurationSpecific; - if (condition.Contains("$(Platform)")) + if (condition.IndexOf("$(Platform)", StringComparison.OrdinalIgnoreCase) >= 0) location |= PropertyStorageLocations.PlatformSpecific; return location; } diff --git a/src/Main/SharpDevelop/Project/Configuration/EditAvailableConfigurationsDialog.cs b/src/Main/SharpDevelop/Project/Configuration/EditAvailableConfigurationsDialog.cs index 71c95476f6..423075541e 100644 --- a/src/Main/SharpDevelop/Project/Configuration/EditAvailableConfigurationsDialog.cs +++ b/src/Main/SharpDevelop/Project/Configuration/EditAvailableConfigurationsDialog.cs @@ -106,7 +106,7 @@ namespace ICSharpCode.SharpDevelop.Project return false; } foreach (string item in listBox.Items) { - if (string.Equals(item, newName, StringComparison.OrdinalIgnoreCase)) { + if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(item, newName)) { MessageService.ShowMessage("${res:Dialog.EditAvailableConfigurationsDialog.DuplicateName}"); return false; }