2
// ConfigurationMerger.cs
5
// Lluis Sanchez Gual <lluis@xamarin.com>
7
// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
using System.Collections.Generic;
28
using MonoDevelop.Projects;
29
using MonoDevelop.Core.Execution;
32
namespace MonoDevelop.Components.MainToolbar
35
/// This class is used to generate a list of configurations to show in the configuration
36
/// selector of the MonoDevelop toolbar. The class tries to reduce the number of configurations
37
/// by merging those which have the same prefix and build the same project configurations
38
/// for the current startup project. It also can be used to get a list of execution targets.
40
class ConfigurationMerger
42
List<TargetPartition> currentTargetPartitions = new List<TargetPartition> ();
43
List<string> currentSolutionConfigurations = new List<string> ();
44
HashSet<string> reducedConfigurations = new HashSet<string> ();
45
DummyExecutionTarget dummyExecutionTarget = new DummyExecutionTarget ();
48
/// Resulting list of configurations. Some of them may be merged.
50
public List<string> SolutionConfigurations {
51
get { return currentSolutionConfigurations; }
55
/// Load configuration information for a solution
57
public void Load (Solution sol)
59
currentSolutionConfigurations.Clear ();
60
currentTargetPartitions.Clear ();
61
reducedConfigurations.Clear ();
66
var project = sol.StartupItem;
68
// Create a set of configuration partitions. Each partition will contain configurations
69
// which are implicitly selected when selecting an execution target. For example, in
70
// an iOS project we would have two partitions:
71
// 1) Debug|IPhoneSimulator, Release|IPhoneSimulator
72
// targets: iPhone, iPad
73
// 2) Debug|IPhone, Release|IPhone
76
List<TargetPartition> partitions = new List<TargetPartition> ();
77
if (project != null) {
78
foreach (var conf in project.Configurations) {
79
var targets = project.GetExecutionTargets (conf.Selector);
80
if (!targets.Any ()) {
81
targets = new ExecutionTarget[] { dummyExecutionTarget };
83
var parts = partitions.Where (p => targets.Any (t => p.Targets.Contains (t))).ToArray();
84
if (parts.Length == 0) {
85
// Create a new partition for this configuration
86
var p = new TargetPartition ();
87
p.Configurations.Add (conf.Id);
88
p.Targets.UnionWith (targets);
91
else if (parts.Length == 1) {
92
// Register the configuration into an existing partition
93
parts[0].Configurations.Add (conf.Id);
94
parts[0].Targets.UnionWith (targets);
97
// The partitions have to be merged into a single one
98
for (int n=1; n<parts.Length; n++) {
99
parts[0].Configurations.UnionWith (parts[n].Configurations);
100
parts[0].Targets.UnionWith (parts[n].Targets);
101
partitions.Remove (parts[n]);
106
// The startup project configuration partitions are used to create solution configuration partitions
108
foreach (var solConf in sol.Configurations) {
109
var pconf = solConf.GetEntryForItem (project);
110
if (pconf != null && pconf.Build) {
111
var part = partitions.FirstOrDefault (p => p.Configurations.Contains (pconf.ItemConfiguration));
113
part.SolutionConfigurations.Add (solConf.Id);
117
// The solution configuration is not bound to the startup project
118
// Add it to all partitions so that it can still take part of
119
// the solution configuration simplification process
120
foreach (var p in partitions)
121
p.SolutionConfigurations.Add (solConf.Id);
125
if (partitions.Count == 0) {
126
// There is no startup project, just use all solution configurations in this case
127
var p = new TargetPartition ();
128
p.SolutionConfigurations.AddRange (sol.GetConfigurations ());
131
// There can be several configurations with the same prefix and different platform but which build the same projects.
132
// If all configurations with the same prefix are identical, all of them can be reduced into a single configuration
133
// with no platform name. This loop detects such configurations
135
var notReducibleConfigurations = new HashSet<string> ();
137
foreach (var p in partitions) {
138
var groupedConfigs = p.SolutionConfigurations.GroupBy (sc => {
140
ItemConfiguration.ParseConfigurationId (sc, out name, out plat);
143
foreach (var confGroup in groupedConfigs) {
144
var configs = confGroup.ToArray ();
145
var baseConf = sol.Configurations[configs[0]];
146
if (configs.Skip (1).All (c => ConfigurationEquals (sol, baseConf, sol.Configurations[c])))
147
p.ReducedConfigurations.Add (confGroup.Key);
149
notReducibleConfigurations.Add (confGroup.Key);
153
// To really be able to use reduced configuration names, all partitions must have that reduced configuration
154
// Find the configurations that have been reduced in all partitions
156
reducedConfigurations = new HashSet<string> (partitions.SelectMany (p => p.ReducedConfigurations));
157
reducedConfigurations.ExceptWith (notReducibleConfigurations);
159
// Final merge of configurations
161
var result = new HashSet<string> ();
162
foreach (var p in partitions)
163
result.UnionWith (p.SolutionConfigurations);
165
// Replace reduced configurations
167
foreach (var reducedConf in reducedConfigurations) {
168
result.RemoveWhere (c => {
170
ItemConfiguration.ParseConfigurationId (c, out name, out plat);
171
return name == reducedConf;
173
result.Add (reducedConf);
175
currentTargetPartitions = partitions;
176
currentSolutionConfigurations.AddRange (result);
177
currentSolutionConfigurations.Sort ();
181
/// Gets the full configuration name given a possibly merged configuration name and execution target
183
/// <param name='currentConfig'>
184
/// A configuration name (can be a merged configuration name)
186
/// <param name='currentTarget'>
187
/// Selected execution target
189
/// <param name='resolvedConfig'>
190
/// Resolved configuration
192
/// <param name='resolvedTarget'>
193
/// If the provided target is not valid for the provided configuration, this returns a valid target
195
public void ResolveConfiguration (string currentConfig, ExecutionTarget currentTarget, out string resolvedConfig, out ExecutionTarget resolvedTarget)
197
resolvedConfig = null;
198
resolvedTarget = currentTarget;
200
if (!reducedConfigurations.Contains (currentConfig)) {
201
// The selected configuration is not reduced, just use it as full config name
202
resolvedConfig = currentConfig;
203
var part = currentTargetPartitions.FirstOrDefault (p => p.SolutionConfigurations.Contains (currentConfig));
205
resolvedTarget = null;
206
else if (!part.Targets.Contains (resolvedTarget))
207
resolvedTarget = part.Targets.FirstOrDefault (t => !(t is DummyExecutionTarget));
209
// Reduced configuration. Find the partition and guess the implicit project configuration
211
var part = currentTargetPartitions.FirstOrDefault (p => p.Targets.Contains (currentTarget ?? dummyExecutionTarget));
213
resolvedConfig = part.SolutionConfigurations.FirstOrDefault (c => {
215
ItemConfiguration.ParseConfigurationId (c, out name, out plat);
216
return name == currentConfig;
219
if (resolvedConfig == null) {
220
part = currentTargetPartitions.FirstOrDefault (p => p.ReducedConfigurations.Contains (currentConfig));
222
part = currentTargetPartitions.FirstOrDefault (p => p.SolutionConfigurations.Contains (currentConfig));
224
resolvedTarget = part.Targets.FirstOrDefault (t => !(t is DummyExecutionTarget));
225
resolvedConfig = part.SolutionConfigurations.FirstOrDefault (c => {
227
ItemConfiguration.ParseConfigurationId (c, out name, out plat);
228
return name == currentConfig;
230
if (resolvedConfig == null)
231
resolvedConfig = currentConfig;
233
resolvedTarget = null;
234
resolvedConfig = currentConfig;
238
if (resolvedTarget == dummyExecutionTarget)
239
resolvedTarget = null;
243
/// Gets the targets which are valid for a configuration
245
public IEnumerable<ExecutionTarget> GetTargetsForConfiguration (string fullConfigurationId, bool ignorePlatform)
248
ItemConfiguration.ParseConfigurationId (fullConfigurationId, out conf, out plat);
250
if (ignorePlatform && reducedConfigurations.Contains (conf)) {
251
// Reduced configuration. Show all targets since they will be used to guess the implicit platform
252
return currentTargetPartitions.Where (p => p.ReducedConfigurations.Contains (conf)).SelectMany (p => p.Targets);
254
// Show targets for the configuration
255
var part = currentTargetPartitions.FirstOrDefault (p => p.SolutionConfigurations.Contains (fullConfigurationId));
259
return new ExecutionTarget[0];
264
/// Given a full configuration id, returns the merged configuration
266
public string GetUnresolvedConfiguration (string fullConfigurationId)
269
ItemConfiguration.ParseConfigurationId (fullConfigurationId, out conf, out plat);
271
if (reducedConfigurations.Contains (conf))
274
return fullConfigurationId;
277
bool ConfigurationEquals (Solution sol, SolutionConfiguration s1, SolutionConfiguration s2)
279
foreach (var p in sol.GetAllSolutionItems<SolutionEntityItem> ()) {
280
var c1 = s1.GetEntryForItem (p);
281
var c2 = s2.GetEntryForItem (p);
282
if (c1 == null && c2 == null)
284
if (c1 == null || c2 == null)
286
if (c1.Build != c2.Build || c1.ItemConfiguration != c2.ItemConfiguration)
292
class DummyExecutionTarget: ExecutionTarget
294
public override string Name {
295
get { return "Default"; }
298
public override string Id {
300
return "MonoDevelop.Default";
305
class TargetPartition
308
/// Targets included in this partition
310
public HashSet<ExecutionTarget> Targets = new HashSet<ExecutionTarget> ();
313
/// Project configurations included in this partition
315
public HashSet<string> Configurations = new HashSet<string> ();
318
/// Solution configurations included in this partition (configurations which are bound to the project configurations)
320
public List<string> SolutionConfigurations = new List<string> ();
323
/// Configurations (without platform) that have been reduced
325
public HashSet<string> ReducedConfigurations = new HashSet<string> ();