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