Merge pull request #900 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / tools / msbuild / SolutionParser.cs
1 //
2 // SolutionParser.cs: Generates a project file from a solution file.
3 //
4 // Author:
5 //   Jonathan Chambers (joncham@gmail.com)
6 //   Ankit Jain <jankit@novell.com>
7 //   Lluis Sanchez Gual <lluis@novell.com>
8 //
9 // (C) 2009 Jonathan Chambers
10 // Copyright 2008, 2009 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
31 using System;
32 using System.Collections.Generic;
33 using System.Linq;
34 using System.Text;
35 using System.Text.RegularExpressions;
36 using System.IO;
37 using Microsoft.Build.Evaluation;
38 using Microsoft.Build.Execution;
39 using Microsoft.Build.Construction;
40 using Microsoft.Build.Exceptions;
41 using System.Xml;
42
43 namespace Mono.XBuild.CommandLine {
44         class ProjectInfo {
45                 public string Name;
46                 public string FileName;
47                 public Guid Guid;
48
49                 public ProjectInfo (string name, string fileName)
50                 {
51                         Name = name;
52                         FileName = fileName;
53                 }
54
55                 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
56                 public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
57                 public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
58                 public List<string> AspNetConfigurations = new List<string> ();
59         }
60
61         class ProjectSection {
62                 public string Name;
63                 public Dictionary<string, string> Properties = new Dictionary<string, string> ();
64
65                 public ProjectSection (string name)
66                 {
67                         Name = name;
68                 }
69         }
70
71         struct TargetInfo {
72                 public string Configuration;
73                 public string Platform;
74                 public bool Build;
75
76                 public TargetInfo (string configuration, string platform)
77                         : this (configuration, platform, false)
78                 {
79                 }
80
81                 public TargetInfo (string configuration, string platform, bool build)
82                 {
83                         Configuration = configuration;
84                         Platform = platform;
85                         Build = build;
86                 }
87         }
88
89
90         internal delegate void RaiseWarningHandler (int errorNumber, string message);
91
92         class SolutionParser {
93                 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
94
95                 static string guidExpression = "{[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}}";
96
97                 static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
98                 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)(EndProject)?", RegexOptions.Singleline);
99                 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
100                 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
101                 static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
102
103                 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
104                 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
105
106                 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
107                 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
108                 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
109
110                 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
111                 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
112                 static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
113
114                 RaiseWarningHandler RaiseWarning;
115
116                 public void ParseSolution (string file, ProjectCollection projects, ProjectRootElement p, RaiseWarningHandler RaiseWarning)
117                 {
118                         this.RaiseWarning = RaiseWarning;
119                         EmitBeforeImports (p, file);
120
121                         AddGeneralSettings (file, p);
122
123                         StreamReader reader = new StreamReader (file);
124                         string slnVersion = GetSlnFileVersion (reader);
125                         if (slnVersion == "12.00")
126                                 projects.DefaultToolsVersion = "12.0";
127                         else if (slnVersion == "11.00")
128                                 projects.DefaultToolsVersion = "4.0";
129                         else if (slnVersion == "10.00")
130                                 projects.DefaultToolsVersion = "3.5";
131                         else
132                                 projects.DefaultToolsVersion = "2.0";
133
134                         string line = reader.ReadToEnd ();
135                         line = line.Replace ("\r\n", "\n");
136                         string solutionDir = Path.GetDirectoryName (file);
137
138                         List<TargetInfo> solutionTargets = new List<TargetInfo> ();
139                         Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
140                         Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
141                         List<ProjectInfo>[] infosByLevel = null;
142                         Dictionary<Guid, ProjectInfo> unsupportedProjectInfos = new Dictionary<Guid, ProjectInfo> ();
143
144                         Match m = projectRegex.Match (line);
145                         while (m.Success) {
146                                 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value,
147                                                                 Path.GetFullPath (Path.Combine (solutionDir,
148                                                                         m.Groups [3].Value.Replace ('\\', Path.DirectorySeparatorChar))));
149                                 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
150                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
151                                         // Ignore solution folders
152                                         m = m.NextMatch ();
153                                         continue;
154                                 }
155
156                                 projectInfo.Guid = new Guid (m.Groups [4].Value);
157
158                                 if (String.Compare (m.Groups [1].Value, vcprojGuid,
159                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
160                                         // Ignore vcproj 
161                                         RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
162
163                                         unsupportedProjectInfos [projectInfo.Guid] = projectInfo;
164                                         m = m.NextMatch ();
165                                         continue;
166                                 }
167
168                                 if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
169                                                 StringComparison.InvariantCultureIgnoreCase) == 0)
170                                         websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
171                                 else
172                                         projectInfos.Add (projectInfo.Guid, projectInfo);
173
174                                 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
175                                 while (projectSectionMatch.Success) {
176                                         string section_name = projectSectionMatch.Groups [1].Value;
177                                         if (String.Compare (section_name, "ProjectDependencies") == 0) {
178                                                 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
179                                                 while (projectDependencyMatch.Success) {
180                                                         // we might not have projectInfo available right now, so
181                                                         // set it to null, and fill it in later
182                                                         projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
183                                                         projectDependencyMatch = projectDependencyMatch.NextMatch ();
184                                                 }
185                                         } else {
186                                                 ProjectSection section = new ProjectSection (section_name);
187                                                 Match propertiesMatch = projectSectionPropertiesRegex.Match (
188                                                                         projectSectionMatch.Groups [2].Value);
189                                                 while (propertiesMatch.Success) {
190                                                         section.Properties [propertiesMatch.Groups ["name"].Value] =
191                                                                 propertiesMatch.Groups ["value"].Value;
192
193                                                         propertiesMatch = propertiesMatch.NextMatch ();
194                                                 }
195
196                                                 projectInfo.ProjectSections [section_name] = section;
197                                         }
198                                         projectSectionMatch = projectSectionMatch.NextMatch ();
199                                 }
200                                 m = m.NextMatch ();
201                         }
202
203                         foreach (ProjectInfo projectInfo in projectInfos.Values) {
204                                 string filename = projectInfo.FileName;
205                                 string projectDir = Path.GetDirectoryName (filename);
206
207                                 if (!File.Exists (filename)) {
208                                         RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
209                                                                 "not found. Ignoring.", filename));
210                                         continue;
211                                 }
212
213                                 Project currentProject;
214                                 try {
215                                         using (var xmlReader = XmlReader.Create (filename)) {
216                                                 var root = ProjectRootElement.Create (xmlReader, projects);
217                                                 root.FullPath = filename;
218                                                 currentProject = new Project (root, null, null, projects, ProjectLoadSettings.IgnoreMissingImports);
219                                         }
220                                 } catch (InvalidProjectFileException e) {
221                                         RaiseWarning (0, e.Message);
222                                         continue;
223                                 }
224
225                                 foreach (var bi in currentProject.GetItems ("ProjectReference")) {
226                                         ProjectInfo info = null;
227                                         string projectReferenceGuid = bi.GetMetadataValue ("Project");
228                                         bool hasGuid = !String.IsNullOrEmpty (projectReferenceGuid);
229
230                                         // try to resolve the ProjectReference by GUID
231                                         // and fallback to project filename
232
233                                         if (hasGuid) {
234                                                 Guid guid = new Guid (projectReferenceGuid);
235                                                 projectInfos.TryGetValue (guid, out info);
236                                                 if (info == null && unsupportedProjectInfos.TryGetValue (guid, out info)) {
237                                                         RaiseWarning (0, String.Format (
238                                                                         "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
239                                                                 filename, bi.EvaluatedInclude));
240                                                         continue;
241                                                 }
242                                         }
243
244                                         if (info == null || !hasGuid) {
245                                                 // Project not found by guid or guid not available
246                                                 // Try to find by project file
247
248                                                 string fullpath = Path.GetFullPath (Path.Combine (projectDir, bi.EvaluatedInclude.Replace ('\\', Path.DirectorySeparatorChar)));
249                                                 info = projectInfos.Values.FirstOrDefault (pi => pi.FileName == fullpath);
250
251                                                 if (info == null) {
252                                                         if (unsupportedProjectInfos.Values.Any (pi => pi.FileName == fullpath))
253                                                                 RaiseWarning (0, String.Format (
254                                                                                 "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
255                                                                         filename, bi.EvaluatedInclude));
256                                                         else
257                                                                 RaiseWarning (0, String.Format (
258                                                                                 "{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
259                                                                         filename, bi.EvaluatedInclude, projectReferenceGuid.Replace ("{", "").Replace ("}", ""), fullpath));
260                                                 }
261
262                                         }
263
264                                         if (info != null)
265                                                 projectInfo.Dependencies [info.Guid] = info;
266                                 }
267                         }
268
269                         // fill in the project info for deps found in the .sln file
270                         foreach (ProjectInfo projectInfo in projectInfos.Values) {
271                                 List<Guid> missingInfos = new List<Guid> ();
272                                 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
273                                         if (dependency.Value == null)
274                                                 missingInfos.Add (dependency.Key);
275                                 }
276
277                                 foreach (Guid guid in missingInfos) {
278                                         ProjectInfo info;
279                                         if (projectInfos.TryGetValue (guid, out info))
280                                                 projectInfo.Dependencies [guid] = info;
281                                         else
282                                                 projectInfo.Dependencies.Remove (guid);
283                                 }
284                         }
285
286                         Match globalMatch = globalRegex.Match (line);
287                         Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
288                         while (globalSectionMatch.Success) {
289                                 string sectionType = globalSectionMatch.Groups[1].Value;
290                                 switch (sectionType) {
291                                         case "SolutionConfigurationPlatforms":
292                                                 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
293                                                 break;
294                                         case "ProjectConfigurationPlatforms":
295                                                 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
296                                                                 projectInfos, websiteProjectInfos);
297                                                 break;
298                                         case "SolutionProperties":
299                                                 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
300                                                 break;
301                                         case "NestedProjects":
302                                                 break;
303                                         case "MonoDevelopProperties":
304                                                 break;
305                                         default:
306                                                 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
307                                                 break;
308                                 }
309                                 globalSectionMatch = globalSectionMatch.NextMatch ();
310                         }
311
312                         int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
313
314                         AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
315                         AddProjectReferences (p, projectInfos);
316                         AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
317                         AddValidateSolutionConfiguration (p);
318
319                         EmitAfterImports (p, file);
320
321                         AddGetFrameworkPathTarget (p);
322                         AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
323                         AddProjectTargets (p, solutionTargets, projectInfos);
324                         AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
325                 }
326
327                 string GetSlnFileVersion (StreamReader reader)
328                 {
329                         string strInput = null;
330                         Match match;
331
332                         strInput = reader.ReadLine();
333                         if (strInput == null)
334                                 return null;
335
336                         match = slnVersionRegex.Match(strInput);
337                         if (!match.Success) {
338                                 strInput = reader.ReadLine();
339                                 if (strInput == null)
340                                         return null;
341                                 match = slnVersionRegex.Match (strInput);
342                         }
343
344                         if (match.Success)
345                                 return match.Groups[1].Value;
346
347                         return null;
348                 }
349
350                 void EmitBeforeImports (ProjectRootElement p, string file)
351                 {
352 #if NET_4_0
353                         p.AddImportGroup ().AddImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore\\*").Condition =
354                                         "'$(ImportByWildcardBeforeSolution)' != 'false' and " +
355                                         "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore')";
356 #endif
357
358                         string before_filename = Path.Combine (Path.GetDirectoryName (file), "before." + Path.GetFileName (file) + ".targets");
359                         p.AddImportGroup ().AddImport (before_filename).Condition = String.Format ("Exists ('{0}')", before_filename);
360                 }
361
362                 void EmitAfterImports (ProjectRootElement p, string file)
363                 {
364 #if NET_4_0
365                         p.AddImportGroup ().AddImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter\\*").Condition =
366                                         "'$(ImportByWildcardAfterSolution)' != 'false' and " +
367                                         "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter')";
368 #endif
369
370                         string after_filename = Path.Combine (Path.GetDirectoryName (file), "after." + Path.GetFileName (file) + ".targets");
371                         p.AddImportGroup ().AddImport (after_filename).Condition = String.Format ("Exists ('{0}')", after_filename);
372                 }
373
374                 void AddGeneralSettings (string solutionFile, ProjectRootElement p)
375                 {
376                         p.DefaultTargets = "Build";
377                         p.InitialTargets = "ValidateSolutionConfiguration";
378                         //p.AddUsingTask ("CreateTemporaryVCProject", null, "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
379                         //p.AddUsingTask ("ResolveVCProjectOutput", null, "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
380
381                         string solutionFilePath = Path.GetFullPath (solutionFile);
382                         var solutionPropertyGroup = p.CreatePropertyGroupElement ();
383                         //solutionPropertyGroup.ChildrenReversed = true;
384                         solutionPropertyGroup.AddProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
385                         solutionPropertyGroup.AddProperty ("SolutionExt", Path.GetExtension (solutionFile));
386                         solutionPropertyGroup.AddProperty ("SolutionFileName", Path.GetFileName (solutionFile));
387                         solutionPropertyGroup.AddProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
388                         solutionPropertyGroup.AddProperty ("SolutionPath", solutionFilePath);
389                 }
390
391                 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
392                 {
393                         Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
394                         while (solutionConfigurationPlatform.Success) {
395                                 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
396                                 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
397                                 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
398                                 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
399                         }
400                 }
401
402                 // ignores the website projects, in the websiteProjectInfos
403                 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
404                                 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
405                 {
406                         List<Guid> missingGuids = new List<Guid> ();
407                         Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
408                         while (projectConfigurationPlatform.Success) {
409                                 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
410                                 ProjectInfo projectInfo;
411                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
412                                         if (!missingGuids.Contains (guid)) {
413                                                 if (!websiteProjectInfos.ContainsKey (guid))
414                                                         // ignore website projects
415                                                         RaiseWarning (0, string.Format("Failed to find project {0}", guid));
416                                                 missingGuids.Add (guid);
417                                         }
418                                         projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
419                                         continue;
420                                 }
421                                 string solConf = projectConfigurationPlatform.Groups[2].Value;
422                                 string solPlat = projectConfigurationPlatform.Groups[3].Value;
423                                 string projConf = projectConfigurationPlatform.Groups[4].Value;
424                                 string projPlat = projectConfigurationPlatform.Groups[5].Value;
425                                 // hack, what are they doing here?
426                                 if (projPlat == "Any CPU")
427                                         projPlat = "AnyCPU";
428                                 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
429                                 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
430                         }
431                         Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
432                         while (projectConfigurationPlatformBuild.Success) {
433                                 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
434                                 ProjectInfo projectInfo;
435                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
436                                         if (!missingGuids.Contains (guid)) {
437                                                 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
438                                                 missingGuids.Add (guid);
439                                         }
440                                         projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
441                                         continue;
442                                 }
443                                 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
444                                 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
445                                 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
446                                 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
447                                 // hack, what are they doing here?
448                                 if (projPlat == "Any CPU")
449                                         projPlat = "AnyCPU";
450                                 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
451                                 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
452                         }
453                 }
454
455                 void ParseSolutionProperties (string section)
456                 {
457                 }
458
459                 void AddCurrentSolutionConfigurationContents (ProjectRootElement p, List<TargetInfo> solutionTargets,
460                                 Dictionary<Guid, ProjectInfo> projectInfos,
461                                 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
462                 {
463                         TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
464                         if (solutionTargets.Count > 0) {
465                                 bool found = false;
466                                 foreach (TargetInfo tinfo in solutionTargets) {
467                                         if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
468                                                 default_target_info = tinfo;
469                                                 found = true;
470                                                 break;
471                                         }
472                                 }
473
474                                 if (!found)
475                                         default_target_info = solutionTargets [0];
476                         }
477
478                         AddDefaultSolutionConfiguration (p, default_target_info);
479
480                         foreach (TargetInfo solutionTarget in solutionTargets) {
481                                 var platformPropertyGroup = p.AddPropertyGroup ();
482                                 platformPropertyGroup.Condition = string.Format (
483                                         " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
484                                         solutionTarget.Configuration,
485                                         solutionTarget.Platform
486                                         );
487
488                                 StringBuilder solutionConfigurationContents = new StringBuilder ();
489                                 solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
490                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
491                                         AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
492                                 }
493                                 solutionConfigurationContents.Append ("</SolutionConfiguration>");
494
495                                 platformPropertyGroup.AddProperty ("CurrentSolutionConfigurationContents",
496                                                 solutionConfigurationContents.ToString ());
497                         }
498                 }
499
500                 void AddProjectReferences (ProjectRootElement p, Dictionary<Guid, ProjectInfo> projectInfos)
501                 {
502                         var big = p.AddItemGroup ();
503                         foreach (KeyValuePair<Guid, ProjectInfo> pair in projectInfos)
504                                 big.AddItem ("ProjectReference", pair.Value.FileName);
505                 }
506
507                 void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
508                                 StringBuilder solutionConfigurationContents)
509                 {
510                         foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
511                                 if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
512                                                 solutionTarget.Platform == targetInfo.Key.Platform) {
513                                         solutionConfigurationContents.AppendFormat (
514                                                         "<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
515                                         guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
516                                 }
517                         }
518                 }
519
520                 void AddDefaultSolutionConfiguration (ProjectRootElement p, TargetInfo target)
521                 {
522                         var configurationPropertyGroup = p.AddPropertyGroup ();
523                         //configurationPropertyGroup.PropertiesReversed = true;
524                         configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
525                         configurationPropertyGroup.AddProperty ("Configuration", target.Configuration);
526
527                         var platformPropertyGroup = p.AddPropertyGroup ();
528                         //platformPropertyGroup.PropertiesReversed = true;
529                         platformPropertyGroup.Condition = " '$(Platform)' == '' ";
530                         platformPropertyGroup.AddProperty ("Platform", target.Platform);
531                         
532                         // emit default for AspNetConfiguration also
533                         var aspNetConfigurationPropertyGroup = p.AddPropertyGroup ();
534                         //aspNetConfigurationPropertyGroup.PropertiesReversed = true;
535                         aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
536                         aspNetConfigurationPropertyGroup.AddProperty ("AspNetConfiguration", "$(Configuration)");
537                 }
538
539                 void AddWarningForMissingProjectConfiguration (ProjectTargetElement target, string slnConfig, string slnPlatform, string projectName)
540                 {
541                         var task = target.AddTask ("Warning");
542                         task.SetParameter ("Text",
543                                         String.Format ("The project configuration for project '{0}' corresponding " +
544                                                 "to the solution configuration '{1}|{2}' was not found in the solution file.",
545                                                 projectName, slnConfig, slnPlatform));
546                         task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
547                                                 slnConfig, slnPlatform);
548
549                 }
550
551                 // Website project methods
552
553                 void AddWebsiteProperties (ProjectRootElement p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
554                                 Dictionary<Guid, ProjectInfo> projectInfos)
555                 {
556                         var propertyGroupByConfig = new Dictionary<string, ProjectPropertyGroupElement> ();
557                         foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
558                                 ProjectInfo info = infoPair.Value;
559                                 string projectGuid = infoPair.Key.ToString ();
560
561                                 ProjectSection section;
562                                 if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
563                                         RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
564                                         return;
565                                 }
566
567                                 //parse project references
568                                 string [] ref_guids = null;
569                                 string references;
570                                 if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
571                                         ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
572                                         for (int i = 0; i < ref_guids.Length; i ++) {
573                                                 // "{guid}|foo.dll"
574                                                 ref_guids [i] = ref_guids [i].Split ('|') [0];
575
576                                                 Guid r_guid = new Guid (ref_guids [i]);
577                                                 ProjectInfo ref_info;
578                                                 if (projectInfos.TryGetValue (r_guid, out ref_info))
579                                                         // ignore if not found
580                                                         info.Dependencies [r_guid] = ref_info;
581                                         }
582                                 }
583
584                                 foreach (KeyValuePair<string, string> pair in section.Properties) {
585                                         //looking for -- ConfigName.AspNetCompiler.PropName
586                                         string [] parts = pair.Key.Split ('.');
587                                         if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
588                                                 continue;
589
590                                         string config = parts [0];
591                                         string propertyName = parts [2];
592
593                                         ProjectPropertyGroupElement bpg;
594                                         if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
595                                                 bpg = p.AddPropertyGroup ();
596                                                 //bpg.PropertiesReversed = true;
597                                                 bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
598                                                 propertyGroupByConfig [config] = bpg;
599                                         }
600
601                                         bpg.AddProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
602                                                                 pair.Value);
603
604                                         if (!info.AspNetConfigurations.Contains (config))
605                                                 info.AspNetConfigurations.Add (config);
606                                 }
607                         }
608                 }
609
610                 // For WebSite projects
611                 // The main "Build" target:
612                 //      1. builds all non-website projects
613                 //      2. calls target for website project
614                 //              - gets target path for the referenced projects
615                 //              - Resolves dependencies, satellites etc for the
616                 //                referenced project assemblies, and copies them
617                 //                to bin/ folder
618                 void AddWebsiteTargets (ProjectRootElement p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
619                                 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
620                                 List<TargetInfo> solutionTargets)
621                 {
622                         foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
623                                 // gets a linear list of dependencies
624                                 List<ProjectInfo> depInfos = new List<ProjectInfo> ();
625                                 foreach (List<ProjectInfo> pinfos in infosByLevel) {
626                                         foreach (ProjectInfo pinfo in pinfos)
627                                                 if (w_info.Dependencies.ContainsKey (pinfo.Guid))
628                                                         depInfos.Add (pinfo);
629                                 }
630
631                                 foreach (string buildTarget in new string [] {"Build", "Rebuild"})
632                                         AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
633
634                                 // clean/publish are not supported for website projects
635                                 foreach (string buildTarget in new string [] {"Clean", "Publish"})
636                                         AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
637                         }
638                 }
639
640                 void AddWebsiteTarget (ProjectRootElement p, ProjectInfo webProjectInfo,
641                                 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
642                                 List<TargetInfo> solutionTargets, string buildTarget)
643                 {
644                         string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
645
646                         var target = p.AddTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
647                         target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; 
648                         target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
649
650                         // this item collects all the references
651                         string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
652                                                         buildTarget != "Build" ? "_" + buildTarget : String.Empty);
653
654                         foreach (TargetInfo targetInfo in solutionTargets) {
655                                 int ref_num = 0;
656                                 foreach (ProjectInfo depInfo in depInfos) {
657                                         TargetInfo projectTargetInfo;
658                                         if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
659                                                 // Ignore, no config, so no target path
660                                                 continue;
661
662                                         // GetTargetPath from the referenced project
663                                         AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
664                                                         final_ref_item, ref_num);
665                                         ref_num ++;
666                                 }
667                         }
668
669                         // resolve the references
670                         AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
671                 }
672
673                 // emits the MSBuild task to GetTargetPath for the referenced project
674                 void AddWebsiteMSBuildTaskForReference (ProjectTargetElement target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
675                                 TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
676                 {
677                         var task = target.AddTask ("MSBuild");
678                         task.SetParameter ("Projects", depInfo.FileName);
679                         task.SetParameter ("Targets", "GetTargetPath");
680
681                         task.SetParameter ("Properties", string.Format ("Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", projectTargetInfo.Configuration, projectTargetInfo.Platform));
682                         task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
683
684                         string ref_item = String.Format ("{0}_{1}",
685                                                 final_ref_item, ref_num); 
686
687                         task.AddOutputItem ("TargetOutputs", ref_item);
688
689                         task = target.AddTask ("CreateItem");
690                         task.SetParameter ("Include", String.Format ("@({0})", ref_item));
691                         task.SetParameter ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
692                                                 depInfo.Guid.ToString ().ToUpper ()));
693                         task.AddOutputItem ("Include", final_ref_item);
694                 }
695
696                 void AddWebsiteResolveAndCopyReferencesTasks (ProjectTargetElement target, ProjectInfo webProjectInfo,
697                                 string final_ref_item, string w_guid)
698                 {
699                         var task = target.AddTask ("ResolveAssemblyReference");
700                         task.SetParameter ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
701                         task.SetParameter ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
702                         task.SetParameter ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
703                         task.SetParameter ("FindDependencies", "true");
704                         task.SetParameter ("FindSatellites", "true");
705                         task.SetParameter ("FindRelatedFiles", "true");
706                         task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
707
708                         string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
709                         task.AddOutputItem ("CopyLocalFiles", copylocal_item);
710
711                         // Copy the references
712                         task = target.AddTask ("Copy");
713                         task.SetParameter ("SourceFiles", String.Format ("@({0})", copylocal_item));
714                         task.SetParameter ("DestinationFiles", String.Format (
715                                                 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
716                                                 copylocal_item, w_guid));
717
718                         // AspNetConfiguration, is config for the website project, useful
719                         // for overriding from command line
720                         StringBuilder cond = new StringBuilder ();
721                         foreach (string config in webProjectInfo.AspNetConfigurations) {
722                                 if (cond.Length > 0)
723                                         cond.Append (" or ");
724                                 cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
725                         }
726                         task.Condition = cond.ToString ();
727
728                         task = target.AddTask ("Message");
729                         cond = new StringBuilder ();
730                         foreach (string config in webProjectInfo.AspNetConfigurations) {
731                                 if (cond.Length > 0)
732                                         cond.Append (" and ");
733                                 cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
734                         }
735                         task.Condition = cond.ToString ();
736                         task.SetParameter ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
737                                                 "not supported by this website project.");
738                 }
739
740                 void AddWebsiteUnsupportedTarget (ProjectRootElement p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
741                                 string buildTarget)
742                 {
743                         var target = p.AddTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
744                         target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
745
746                         var task = target.AddTask ("Message");
747                         task.SetParameter ("Text", String.Format (
748                                                 "Target '{0}' not support for website projects", buildTarget));
749                 }
750
751                 string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
752                 {
753                         StringBuilder deps = new StringBuilder ();
754                         foreach (ProjectInfo pinfo in depInfos) {
755                                 if (deps.Length > 0)
756                                         deps.Append (";");
757                                 deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
758                         }
759                         deps.Append (";GetFrameworkPath");
760                         return deps.ToString ();
761                 }
762
763                 void AddGetFrameworkPathTarget (ProjectRootElement p)
764                 {
765                         var t = p.AddTarget ("GetFrameworkPath");
766                         var task = t.AddTask ("GetFrameworkPath");
767                         task.AddOutputProperty ("Path", "TargetFrameworkPath");
768                 }
769
770                 void AddValidateSolutionConfiguration (ProjectRootElement p)
771                 {
772                         var t = p.AddTarget ("ValidateSolutionConfiguration");
773                         var task = t.AddTask ("Warning");
774                         task.SetParameter ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
775                                                 " for Mono msbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
776                                                 " You could override it by either setting the environment variable to nothing, as\n" +
777                                                 "   set Platform=\n" +
778                                                 "Or explicity specify its value on the command line, as\n" +
779                                                 "   msbuild Foo.sln /p:Platform=Release");
780                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
781                                         " and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
782
783                         task = t.AddTask ("Error");
784                         task.SetParameter ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
785                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
786                         task = t.AddTask ("Warning");
787                         task.SetParameter ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
788                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
789                         task = t.AddTask ("Message");
790                         task.SetParameter ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
791                         task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
792                 }
793
794                 void AddProjectTargets (ProjectRootElement p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
795                 {
796                         foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
797                                 ProjectInfo project = projectInfo.Value;
798                                 foreach (string buildTarget in buildTargets) {
799                                         string target_name = GetTargetNameForProject (project.Name, buildTarget);
800                                         bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
801                                         var target = p.AddTarget (target_name);
802                                         target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; 
803
804                                         if (is_build_or_rebuild)
805                                                 target.Outputs = "@(CollectedBuildOutput)";
806                                         if (project.Dependencies.Count > 0)
807                                                 target.DependsOnTargets = String.Join (";",
808                                                                 project.Dependencies.Values.Select (
809                                                                         di => GetTargetNameForProject (di.Name, buildTarget)).ToArray ());
810
811                                         foreach (TargetInfo targetInfo in solutionTargets) {
812                                                 ProjectTaskElement task = null;
813                                                 TargetInfo projectTargetInfo;
814                                                 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
815                                                         AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
816                                                                         targetInfo.Platform, project.Name);
817                                                         continue;
818                                                 }
819                                                 if (projectTargetInfo.Build) {
820                                                         task = target.AddTask ("MSBuild");
821                                                         task.SetParameter ("Projects", project.FileName);
822                                                         task.SetParameter ("ToolsVersion", "$(ProjectToolsVersion)");
823                                                         if (is_build_or_rebuild)
824                                                                 task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
825
826                                                         if (buildTarget != "Build")
827                                                                 task.SetParameter ("Targets", buildTarget);
828                                                         task.SetParameter ("Properties", string.Format ("Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", projectTargetInfo.Configuration, projectTargetInfo.Platform));
829                                                 } else {
830                                                         task = target.AddTask ("Message");
831                                                         task.SetParameter ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
832                                                 }
833                                                 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
834                                         }
835                                 }
836                         }
837                 }
838
839
840                 string GetTargetNameForProject (string projectName, string buildTarget)
841                 {
842                         //FIXME: hack
843                         projectName = projectName.Replace ("\\", "/").Replace (".", "_");
844                         string target_name = projectName +
845                                         (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
846
847                         if (IsBuildTargetName (projectName))
848                                 target_name = "Solution:" + target_name;
849
850                         return target_name;
851                 }
852
853                 bool IsBuildTargetName (string name)
854                 {
855                         foreach (string tgt in buildTargets)
856                                 if (name == tgt)
857                                         return true;
858                         return false;
859                 }
860
861                 // returns number of levels
862                 int AddBuildLevels (ProjectRootElement p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
863                                 ref List<ProjectInfo>[] infosByLevel)
864                 {
865                         infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
866
867                         foreach (TargetInfo targetInfo in solutionTargets) {
868                                 var big = p.AddItemGroup ();
869                                 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
870                                                 targetInfo.Configuration, targetInfo.Platform);
871
872                                 //FIXME: every level has projects that can be built in parallel.
873                                 //       levels are ordered on the basis of the dependency graph
874
875                                 for (int i = 0; i < infosByLevel.Length; i ++) {
876                                         string build_level = String.Format ("BuildLevel{0}", i);
877                                         string skip_level = String.Format ("SkipLevel{0}", i);
878                                         string missing_level = String.Format ("MissingConfigLevel{0}", i);
879
880                                         foreach (ProjectInfo projectInfo in infosByLevel [i]) {
881                                                 TargetInfo projectTargetInfo;
882                                                 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
883                                                         // missing project config
884                                                         big.AddItem (missing_level, projectInfo.Name);
885                                                         continue;
886                                                 }
887
888                                                 if (projectTargetInfo.Build) {
889                                                         var item = big.AddItem (build_level, projectInfo.FileName);
890                                                         item.AddMetadata ("Configuration", projectTargetInfo.Configuration);
891                                                         item.AddMetadata ("Platform", projectTargetInfo.Platform);
892                                                 } else {
893                                                         // build disabled
894                                                         big.AddItem (skip_level, projectInfo.Name);
895                                                 }
896                                         }
897                                 }
898                         }
899
900                         return infosByLevel.Length;
901                 }
902
903                 void AddSolutionTargets (ProjectRootElement p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
904                 {
905                         foreach (string buildTarget in buildTargets) {
906                                 var t = p.AddTarget (buildTarget);
907                                 bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
908
909                                 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
910                                 if (is_build_or_rebuild)
911                                         t.Outputs = "@(CollectedBuildOutput)";
912
913                                 ProjectTaskElement task = null;
914                                 for (int i = 0; i < num_levels; i ++) {
915                                         string level_str = String.Format ("BuildLevel{0}", i);
916                                         task = t.AddTask ("MSBuild");
917                                         task.SetParameter ("Condition", String.Format ("'@({0})' != ''", level_str));
918                                         task.SetParameter ("Projects", String.Format ("@({0})", level_str));
919                                         task.SetParameter ("ToolsVersion", "$(ProjectToolsVersion)");
920                                         task.SetParameter ("Properties",
921                                                 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
922                                         if (buildTarget != "Build")
923                                                 task.SetParameter ("Targets", buildTarget);
924                                         //FIXME: change this to BuildInParallel=true, when parallel
925                                         //       build support gets added
926                                         task.SetParameter ("RunEachTargetSeparately", "true");
927                                         if (is_build_or_rebuild)
928                                                 task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
929
930                                         level_str = String.Format ("SkipLevel{0}", i);
931                                         task = t.AddTask ("Message");
932                                         task.Condition = String.Format ("'@({0})' != ''", level_str);
933                                         task.SetParameter ("Text",
934                                                 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
935                                                         "configuration '$(Configuration)|$(Platform)'.", level_str));
936
937                                         level_str = String.Format ("MissingConfigLevel{0}", i);
938                                         task = t.AddTask ("Warning");
939                                         task.Condition = String.Format ("'@({0})' != ''", level_str);
940                                         task.SetParameter ("Text",
941                                                 String.Format ("The project configuration for project '%({0}.Identity)' " +
942                                                         "corresponding to the solution configuration " +
943                                                         "'$(Configuration)|$(Platform)' was not found.", level_str));
944                                 }
945
946                                 // "build" website projects also
947                                 StringBuilder w_targets = new StringBuilder ();
948                                 foreach (ProjectInfo info in websiteProjectInfos) {
949                                         if (w_targets.Length > 0)
950                                                 w_targets.Append (";");
951                                         w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
952                                 }
953
954                                 task = t.AddTask ("CallTarget");
955                                 task.SetParameter ("Targets", w_targets.ToString ());
956                                 task.SetParameter ("RunEachTargetSeparately", "true");
957                         }
958                 }
959
960                 // Sorts the ProjectInfo dependency graph, to obtain
961                 // a series of build levels with projects. Projects
962                 // in each level can be run parallel (no inter-dependency).
963                 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
964                 {
965                         IList<T> allItems;
966                         allItems = items as IList<T>;
967                         if (allItems == null)
968                                 allItems = new List<T> (items);
969
970                         bool[] inserted = new bool[allItems.Count];
971                         bool[] triedToInsert = new bool[allItems.Count];
972                         int[] levels = new int [allItems.Count];
973
974                         int maxdepth = 0;
975                         for (int i = 0; i < allItems.Count; ++i) {
976                                 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
977                                 if (d > maxdepth)
978                                         maxdepth = d;
979                         }
980
981                         // Separate out the project infos by build level
982                         List<T>[] infosByLevel = new List<T>[maxdepth];
983                         for (int i = 0; i < levels.Length; i ++) {
984                                 int level = levels [i] - 1;
985                                 if (infosByLevel [level] == null)
986                                         infosByLevel [level] = new List<T> ();
987
988                                 infosByLevel [level].Add (allItems [i]);
989                         }
990
991                         return infosByLevel;
992                 }
993
994                 // returns level# for the project
995                 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
996                         where T: ProjectInfo
997                 {
998                         if (inserted [index])
999                                 return levels [index];
1000
1001                         if (triedToInsert[index])
1002                                 throw new InvalidOperationException (String.Format (
1003                                                 "Cyclic dependency involving project {0} found in the project dependency graph",
1004                                                 allItems [index].Name));
1005
1006                         triedToInsert[index] = true;
1007                         ProjectInfo insertItem = allItems[index];
1008
1009                         int maxdepth = 0;
1010                         foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
1011                                 for (int j = 0; j < allItems.Count; ++j) {
1012                                         ProjectInfo checkItem = allItems [j];
1013                                         if (dependency.FileName == checkItem.FileName) {
1014                                                 int d = Insert (j, allItems, levels, inserted, triedToInsert);
1015                                                 maxdepth = d > maxdepth ? d : maxdepth;
1016                                                 break;
1017                                         }
1018                                 }
1019                         }
1020                         levels [index] = maxdepth + 1;
1021                         inserted [index] = true;
1022
1023                         return levels [index];
1024                 }
1025
1026                 public static IEnumerable<string> GetAllProjectFileNames (string solutionFile)
1027                 {
1028                         StreamReader reader = new StreamReader (solutionFile);
1029                         string line = reader.ReadToEnd ();
1030                         line = line.Replace ("\r\n", "\n");
1031                         string soln_dir = Path.GetDirectoryName (solutionFile);
1032
1033                         Match m = projectRegex.Match (line);
1034                         while (m.Success) {
1035                                 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
1036                                                 StringComparison.InvariantCultureIgnoreCase) != 0)
1037                                         yield return Path.Combine (soln_dir, m.Groups [3].Value).Replace ("\\", "/");
1038
1039                                 m = m.NextMatch ();
1040                         }
1041                 }
1042         }
1043 }