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