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