Fix bug #532264.
[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.Text;
36 using System.Text.RegularExpressions;
37 using System.IO;
38 using Microsoft.Build.BuildEngine;
39
40 namespace Mono.XBuild.CommandLine {
41         class ProjectInfo {
42                 public string Name;
43                 public string FileName;
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         }
54
55         struct TargetInfo {
56                 public string Configuration;
57                 public string Platform;
58                 public bool Build;
59
60                 public TargetInfo (string configuration, string platform)
61                         : this (configuration, platform, false)
62                 {
63                 }
64
65                 public TargetInfo (string configuration, string platform, bool build)
66                 {
67                         Configuration = configuration;
68                         Platform = platform;
69                         Build = build;
70                 }
71         }
72
73
74         internal delegate void RaiseWarningHandler (int errorNumber, string message);
75
76         class SolutionParser {
77                 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
78
79                 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}}";
80
81                 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)EndProject?", RegexOptions.Singleline);
82                 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
83                 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
84
85                 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
86                 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
87
88                 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
89                 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
90                 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
91
92                 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
93                 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
94
95                 RaiseWarningHandler RaiseWarning;
96
97                 public void ParseSolution (string file, Project p, RaiseWarningHandler RaiseWarning)
98                 {
99                         this.RaiseWarning = RaiseWarning;
100                         AddGeneralSettings (file, p);
101
102                         StreamReader reader = new StreamReader (file);
103                         string line = reader.ReadToEnd ();
104                         line = line.Replace ("\r\n", "\n");
105                         string solutionDir = Path.GetDirectoryName (file);
106
107                         List<TargetInfo> solutionTargets = new List<TargetInfo> ();
108                         Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
109
110                         Match m = projectRegex.Match (line);
111                         while (m.Success) {
112                                 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
113                                 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
114                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
115                                         // Ignore solution folders
116                                         m = m.NextMatch ();
117                                         continue;
118                                 }
119                                 if (String.Compare (m.Groups [1].Value, vcprojGuid,
120                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
121                                         // Ignore vcproj 
122                                         RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
123                                         m = m.NextMatch ();
124                                         continue;
125                                 }
126
127                                 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
128
129                                 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
130                                 while (projectSectionMatch.Success) {
131                                         Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
132                                         while (projectDependencyMatch.Success) {
133                                                 // we might not have projectInfo available right now, so
134                                                 // set it to null, and fill it in later
135                                                 projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
136                                                 projectDependencyMatch = projectDependencyMatch.NextMatch ();
137                                         }
138                                         projectSectionMatch = projectSectionMatch.NextMatch ();
139                                 }
140                                 m = m.NextMatch ();
141                         }
142
143                         foreach (ProjectInfo projectInfo in projectInfos.Values) {
144                                 Project currentProject = p.ParentEngine.CreateNewProject ();
145                                 currentProject.Load (Path.Combine (solutionDir,
146                                                         projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar)));
147
148                                 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
149                                         string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
150                                         Guid guid = new Guid (projectReferenceGuid);
151                                         ProjectInfo info;
152                                         if (projectInfos.TryGetValue (guid, out info))
153                                                 // ignore if not found
154                                                 projectInfo.Dependencies [guid] = info;
155                                 }
156                         }
157
158                         // fill in the project info for deps found in the .sln file
159                         foreach (ProjectInfo projectInfo in projectInfos.Values) {
160                                 List<Guid> missingInfos = new List<Guid> ();
161                                 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
162                                         if (dependency.Value == null)
163                                                 missingInfos.Add (dependency.Key);
164                                 }
165
166                                 foreach (Guid guid in missingInfos) {
167                                         ProjectInfo info;
168                                         if (projectInfos.TryGetValue (guid, out info))
169                                                 projectInfo.Dependencies [guid] = info;
170                                         else
171                                                 projectInfo.Dependencies.Remove (guid);
172                                 }
173                         }
174
175                         Match globalMatch = globalRegex.Match (line);
176                         Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
177                         while (globalSectionMatch.Success) {
178                                 string sectionType = globalSectionMatch.Groups[1].Value;
179                                 switch (sectionType) {
180                                         case "SolutionConfigurationPlatforms":
181                                                 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
182                                                 break;
183                                         case "ProjectConfigurationPlatforms":
184                                                 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
185                                                 break;
186                                         case "SolutionProperties":
187                                                 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
188                                                 break;
189                                         case "NestedProjects":
190                                                 break;
191                                         default:
192                                                 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
193                                                 break;
194                                 }
195                                 globalSectionMatch = globalSectionMatch.NextMatch ();
196                         }
197
198                         int num_levels = AddBuildLevels (p, solutionTargets, projectInfos);
199
200                         AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
201                         AddValidateSolutionConfiguration (p);
202                         AddProjectTargets (p, solutionTargets, projectInfos);
203                         AddSolutionTargets (p, num_levels);
204                 }
205
206                 void AddGeneralSettings (string solutionFile, Project p)
207                 {
208                         p.DefaultTargets = "Build";
209                         p.InitialTargets = "ValidateSolutionConfiguration";
210                         p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
211                         p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
212
213                         BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
214                         aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
215                         aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
216
217                         string solutionFilePath = Path.GetFullPath (solutionFile);
218                         BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
219                         solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
220                         solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
221                         solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
222                         solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
223                         solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
224                 }
225
226                 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
227                 {
228                         Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
229                         while (solutionConfigurationPlatform.Success) {
230                                 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
231                                 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
232                                 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
233                                 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
234                         }
235                 }
236
237                 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
238                 {
239                         List<Guid> missingGuids = new List<Guid> ();
240                         Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
241                         while (projectConfigurationPlatform.Success) {
242                                 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
243                                 ProjectInfo projectInfo;
244                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
245                                         if (!missingGuids.Contains (guid)) {
246                                                 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
247                                                 missingGuids.Add (guid);
248                                         }
249                                         projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
250                                         continue;
251                                 }
252                                 string solConf = projectConfigurationPlatform.Groups[2].Value;
253                                 string solPlat = projectConfigurationPlatform.Groups[3].Value;
254                                 string projConf = projectConfigurationPlatform.Groups[4].Value;
255                                 string projPlat = projectConfigurationPlatform.Groups[5].Value;
256                                 // hack, what are they doing here?
257                                 if (projPlat == "Any CPU")
258                                         projPlat = "AnyCPU";
259                                 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
260                                 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
261                         }
262                         Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
263                         while (projectConfigurationPlatformBuild.Success) {
264                                 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
265                                 ProjectInfo projectInfo;
266                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
267                                         if (!missingGuids.Contains (guid)) {
268                                                 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
269                                                 missingGuids.Add (guid);
270                                         }
271                                         projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
272                                         continue;
273                                 }
274                                 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
275                                 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
276                                 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
277                                 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
278                                 // hack, what are they doing here?
279                                 if (projPlat == "Any CPU")
280                                         projPlat = "AnyCPU";
281                                 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
282                                 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
283                         }
284                 }
285
286                 void ParseSolutionProperties (string section)
287                 {
288                 }
289
290                 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
291                 {
292                         AddDefaultSolutionConfiguration (p,
293                                         solutionTargets.Count > 0 ?
294                                                 solutionTargets [0] :
295                                                 new TargetInfo ("Debug", "Any CPU"));
296
297                         foreach (TargetInfo solutionTarget in solutionTargets) {
298                                 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
299                                 platformPropertyGroup.Condition = string.Format (
300                                         " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
301                                         solutionTarget.Configuration,
302                                         solutionTarget.Platform
303                                         );
304
305                                 string solutionConfigurationContents = "<SolutionConfiguration xmlns=\"\">";
306                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
307                                         foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.Value.TargetMap) {
308                                                 if (solutionTarget.Configuration == targetInfo.Key.Configuration && solutionTarget.Platform == targetInfo.Key.Platform) {
309                                                         solutionConfigurationContents += string.Format ("<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
310                                                                 projectInfo.Key.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
311                                                 }
312                                         }
313                                 }
314                                 solutionConfigurationContents += "</SolutionConfiguration>";
315
316                                 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
317                         }
318                 }
319
320                 void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
321                 {
322                         BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
323                         configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
324                         configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
325
326                         BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
327                         platformPropertyGroup.Condition = " '$(Platform)' == '' ";
328                         platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
329                 }
330
331                 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
332                 {
333                         BuildTask task = target.AddNewTask ("Warning");
334                         task.SetParameterValue ("Text",
335                                         String.Format ("The project configuration for project '{0}' corresponding " +
336                                                 "to the solution configuration '{1}|{2}' was not found in the solution file.",
337                                                 projectName, slnConfig, slnPlatform));
338                         task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
339                                                 slnConfig, slnPlatform);
340
341                 }
342
343                 void AddValidateSolutionConfiguration (Project p)
344                 {
345                         Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
346                         BuildTask task = t.AddNewTask ("Error");
347                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
348                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
349                         task = t.AddNewTask ("Warning");
350                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
351                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
352                         task = t.AddNewTask ("Message");
353                         task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
354                         task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
355                 }
356
357                 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
358                 {
359                         foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
360                                 ProjectInfo project = projectInfo.Value;
361                                 foreach (string buildTarget in buildTargets) {
362                                         string target_name = project.Name +
363                                                 (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
364
365                                         if (IsBuildTargetName (project.Name))
366                                                 target_name = "Solution:" + target_name;
367
368                                         Target target = p.Targets.AddNewTarget (target_name);
369                                         target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; 
370
371                                         if (project.Dependencies.Count > 0) {
372                                                 StringBuilder dependencies = new StringBuilder ();
373                                                 foreach (ProjectInfo dependentInfo in project.Dependencies.Values) {
374                                                         if (dependencies.Length > 0)
375                                                                 dependencies.Append (";");
376                                                         if (IsBuildTargetName (dependentInfo.Name))
377                                                                 dependencies.Append ("Solution:");
378                                                         dependencies.Append (dependentInfo.Name);
379                                                         if (buildTarget != "Build")
380                                                                 dependencies.Append (":" + buildTarget);
381                                                 }
382                                                 target.DependsOnTargets = dependencies.ToString ();
383                                         }
384
385                                         foreach (TargetInfo targetInfo in solutionTargets) {
386                                                 BuildTask task = null;
387                                                 TargetInfo projectTargetInfo;
388                                                 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
389                                                         AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
390                                                                         targetInfo.Platform, project.Name);
391                                                         continue;
392                                                 }
393                                                 if (projectTargetInfo.Build) {
394                                                         task = target.AddNewTask ("MSBuild");
395                                                         task.SetParameterValue ("Projects", project.FileName);
396
397                                                         if (buildTarget != "Build")
398                                                                 task.SetParameterValue ("Targets", buildTarget);
399                                                         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));
400                                                 } else {
401                                                         task = target.AddNewTask ("Message");
402                                                         task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
403                                                 }
404                                                 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
405                                         }
406                                 }
407                         }
408                 }
409
410                 bool IsBuildTargetName (string name)
411                 {
412                         foreach (string tgt in buildTargets)
413                                 if (name == tgt)
414                                         return true;
415                         return false;
416                 }
417
418                 // returns number of levels
419                 int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
420                 {
421                         List<ProjectInfo>[] infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
422
423                         foreach (TargetInfo targetInfo in solutionTargets) {
424                                 BuildItemGroup big = p.AddNewItemGroup ();
425                                 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
426                                                 targetInfo.Configuration, targetInfo.Platform);
427
428                                 //FIXME: every level has projects that can be built in parallel.
429                                 //       levels are ordered on the basis of the dependency graph
430
431                                 for (int i = 0; i < infosByLevel.Length; i ++) {
432                                         string build_level = String.Format ("BuildLevel{0}", i);
433                                         string skip_level = String.Format ("SkipLevel{0}", i);
434                                         string missing_level = String.Format ("MissingConfigLevel{0}", i);
435
436                                         foreach (ProjectInfo projectInfo in infosByLevel [i]) {
437                                                 TargetInfo projectTargetInfo;
438                                                 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
439                                                         // missing project config
440                                                         big.AddNewItem (missing_level, projectInfo.Name);
441                                                         continue;
442                                                 }
443
444                                                 if (projectTargetInfo.Build) {
445                                                         BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
446                                                         item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
447                                                         item.SetMetadata ("Platform", projectTargetInfo.Platform);
448                                                 } else {
449                                                         // build disabled
450                                                         big.AddNewItem (skip_level, projectInfo.Name);
451                                                 }
452                                         }
453                                 }
454                         }
455
456                         return infosByLevel.Length;
457                 }
458
459                 void AddSolutionTargets (Project p, int num_levels)
460                 {
461                         foreach (string buildTarget in buildTargets) {
462                                 Target t = p.Targets.AddNewTarget (buildTarget);
463                                 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
464
465                                 for (int i = 0; i < num_levels; i ++) {
466                                         string level_str = String.Format ("BuildLevel{0}", i);
467                                         BuildTask task = t.AddNewTask ("MSBuild");
468                                         task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
469                                         task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
470                                         task.SetParameterValue ("Properties",
471                                                 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
472                                         if (buildTarget != "Build")
473                                                 task.SetParameterValue ("Targets", buildTarget);
474                                         //FIXME: change this to BuildInParallel=true, when parallel
475                                         //       build support gets added
476                                         task.SetParameterValue ("RunEachTargetSeparately", "true");
477
478                                         level_str = String.Format ("SkipLevel{0}", i);
479                                         task = t.AddNewTask ("Message");
480                                         task.Condition = String.Format ("'@({0})' != ''", level_str);
481                                         task.SetParameterValue ("Text",
482                                                 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
483                                                         "configuration '$(Configuration)|$(Platform)'.", level_str));
484
485                                         level_str = String.Format ("MissingConfigLevel{0}", i);
486                                         task = t.AddNewTask ("Warning");
487                                         task.Condition = String.Format ("'@({0})' != ''", level_str);
488                                         task.SetParameterValue ("Text",
489                                                 String.Format ("The project configuration for project '%({0}.Identity)' " +
490                                                         "corresponding to the solution configuration " +
491                                                         "'$(Configuration)|$(Platform)' was not found.", level_str));
492                                 }
493                         }
494                 }
495
496                 // Sorts the ProjectInfo dependency graph, to obtain
497                 // a series of build levels with projects. Projects
498                 // in each level can be run parallel (no inter-dependency).
499                 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
500                 {
501                         IList<T> allItems;
502                         allItems = items as IList<T>;
503                         if (allItems == null)
504                                 allItems = new List<T> (items);
505
506                         bool[] inserted = new bool[allItems.Count];
507                         bool[] triedToInsert = new bool[allItems.Count];
508                         int[] levels = new int [allItems.Count];
509
510                         int maxdepth = 0;
511                         for (int i = 0; i < allItems.Count; ++i) {
512                                 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
513                                 if (d > maxdepth)
514                                         maxdepth = d;
515                         }
516
517                         // Separate out the project infos by build level
518                         List<T>[] infosByLevel = new List<T>[maxdepth];
519                         for (int i = 0; i < levels.Length; i ++) {
520                                 int level = levels [i] - 1;
521                                 if (infosByLevel [level] == null)
522                                         infosByLevel [level] = new List<T> ();
523
524                                 infosByLevel [level].Add (allItems [i]);
525                         }
526
527                         return infosByLevel;
528                 }
529
530                 // returns level# for the project
531                 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
532                         where T: ProjectInfo
533                 {
534                         if (inserted [index])
535                                 return levels [index];
536
537                         if (triedToInsert[index])
538                                 throw new InvalidOperationException ("Cyclic dependency found in the project dependency graph");
539
540                         triedToInsert[index] = true;
541                         ProjectInfo insertItem = allItems[index];
542
543                         int maxdepth = 0;
544                         foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
545                                 for (int j = 0; j < allItems.Count; ++j) {
546                                         ProjectInfo checkItem = allItems [j];
547                                         if (dependency.FileName == checkItem.FileName) {
548                                                 int d = Insert (j, allItems, levels, inserted, triedToInsert);
549                                                 maxdepth = d > maxdepth ? d : maxdepth;
550                                                 break;
551                                         }
552                                 }
553                         }
554                         levels [index] = maxdepth + 1;
555                         inserted [index] = true;
556
557                         return levels [index];
558                 }
559         }
560 }
561
562 #endif