* SolutionParser.cs (ParseSolution): Ignore solution folders.
[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 //
7 // (C) 2009 Jonathan Chambers
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 #if NET_2_0
29
30 using System;
31 using System.Collections.Generic;
32 using System.Text;
33 using System.Text.RegularExpressions;
34 using System.IO;
35 using Microsoft.Build.BuildEngine;
36
37 namespace Mono.XBuild.CommandLine {
38         class ProjectInfo {
39                 public string Name;
40                 public string FileName;
41
42                 public ProjectInfo (string name, string fileName)
43                 {
44                         Name = name;
45                         FileName = fileName;
46                 }
47
48                 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
49                 public List<Guid> Dependencies = new List<Guid> ();
50         }
51
52         struct TargetInfo {
53                 public string Configuration;
54                 public string Platform;
55                 public bool Build;
56
57                 public TargetInfo (string configuration, string platform)
58                         : this (configuration, platform, false)
59                 {
60                 }
61
62                 public TargetInfo (string configuration, string platform, bool build)
63                 {
64                         Configuration = configuration;
65                         Platform = platform;
66                         Build = build;
67                 }
68         }
69
70
71         class SolutionParser {
72                 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
73
74                 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}}";
75
76                 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)EndProject?", RegexOptions.Singleline);
77                 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
78                 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
79
80                 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
81                 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
82
83                 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
84                 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
85                 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
86
87                 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
88
89                 public void ParseSolution (string file, Project p)
90                 {
91                         AddGeneralSettings (file, p);
92
93                         StreamReader reader = new StreamReader (file);
94                         string line = reader.ReadToEnd ();
95                         line = line.Replace ("\r\n", "\n");
96
97                         List<TargetInfo> solutionTargets = new List<TargetInfo> ();
98                         Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
99
100                         Match m = projectRegex.Match (line);
101                         while (m.Success) {
102                                 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
103                                 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
104                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
105                                         // Ignore solution folders
106                                         m = m.NextMatch ();
107                                         continue;
108                                 }
109
110                                 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
111
112                                 Project currentProject = p.ParentEngine.CreateNewProject ();
113                                 currentProject.Load (projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar));
114
115                                 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
116                                         string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
117                                         projectInfo.Dependencies.Add (new Guid (projectReferenceGuid));
118                                 }
119
120                                 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
121                                 while (projectSectionMatch.Success) {
122                                         Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
123                                         while (projectDependencyMatch.Success) {
124                                                 projectInfo.Dependencies.Add (new Guid (projectDependencyMatch.Groups[1].Value));
125                                                 projectDependencyMatch = projectDependencyMatch.NextMatch ();
126                                         }
127                                         projectSectionMatch = projectSectionMatch.NextMatch ();
128                                 }
129                                 m = m.NextMatch ();
130                         }
131
132                         Match globalMatch = globalRegex.Match (line);
133                         Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
134                         while (globalSectionMatch.Success) {
135                                 string sectionType = globalSectionMatch.Groups[1].Value;
136                                 switch (sectionType) {
137                                         case "SolutionConfigurationPlatforms":
138                                                 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
139                                                 break;
140                                         case "ProjectConfigurationPlatforms":
141                                                 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
142                                                 break;
143                                         case "SolutionProperties":
144                                                 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
145                                                 break;
146                                         case "NestedProjects":
147                                                 break;
148                                         default:
149                                                 ErrorUtilities.ReportError (0, string.Format("Don't know how to handle GlobalSection {0}", sectionType));
150                                                 break;
151                                 }
152                                 globalSectionMatch = globalSectionMatch.NextMatch ();
153                         }
154
155                         AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
156                         AddValidateSolutionConfiguration (p);
157                         AddProjectTargets (p, solutionTargets, projectInfos);
158                         AddSolutionTargets (p, projectInfos);
159
160                 }
161
162                 void AddGeneralSettings (string solutionFile, Project p)
163                 {
164                         p.DefaultTargets = "Build";
165                         p.InitialTargets = "ValidateSolutionConfiguration";
166                         p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
167                         p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
168
169                         BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
170                         configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
171                         configurationPropertyGroup.AddNewProperty ("Configuration", "Debug");
172
173                         BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
174                         platformPropertyGroup.Condition = " '$(Platform)' == '' ";
175                         platformPropertyGroup.AddNewProperty ("Platform", "Any CPU");
176
177                         BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
178                         aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
179                         aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
180
181                         string solutionFilePath = Path.GetFullPath (solutionFile);
182                         BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
183                         solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
184                         solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
185                         solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
186                         solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
187                         solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
188                 }
189
190                 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
191                 {
192                         Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
193                         while (solutionConfigurationPlatform.Success) {
194                                 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
195                                 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
196                                 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
197                                 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
198                         }
199                 }
200
201                 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
202                 {
203                         List<Guid> missingGuids = new List<Guid> ();
204                         Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
205                         while (projectConfigurationPlatform.Success) {
206                                 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
207                                 ProjectInfo projectInfo;
208                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
209                                         if (!missingGuids.Contains (guid)) {
210                                                 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
211                                                 missingGuids.Add (guid);
212                                         }
213                                         projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
214                                         continue;
215                                 }
216                                 string solConf = projectConfigurationPlatform.Groups[2].Value;
217                                 string solPlat = projectConfigurationPlatform.Groups[3].Value;
218                                 string projConf = projectConfigurationPlatform.Groups[4].Value;
219                                 string projPlat = projectConfigurationPlatform.Groups[5].Value;
220                                 // hack, what are they doing here?
221                                 if (projPlat == "Any CPU")
222                                         projPlat = "AnyCPU";
223                                 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
224                                 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
225                         }
226                         Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
227                         while (projectConfigurationPlatformBuild.Success) {
228                                 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
229                                 ProjectInfo projectInfo;
230                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
231                                         if (!missingGuids.Contains (guid)) {
232                                                 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
233                                                 missingGuids.Add (guid);
234                                         }
235                                         projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
236                                         continue;
237                                 }
238                                 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
239                                 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
240                                 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
241                                 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
242                                 // hack, what are they doing here?
243                                 if (projPlat == "Any CPU")
244                                         projPlat = "AnyCPU";
245                                 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
246                                 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
247                         }
248                 }
249
250                 void ParseSolutionProperties (string section)
251                 {
252                 }
253
254                 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
255                 {
256                         foreach (TargetInfo solutionTarget in solutionTargets) {
257                                 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
258                                 platformPropertyGroup.Condition = string.Format (
259                                         " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
260                                         solutionTarget.Configuration,
261                                         solutionTarget.Platform
262                                         );
263
264                                 string solutionConfigurationContents = "<SolutionConfiguration xmlns=\"\">";
265                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
266                                         foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.Value.TargetMap) {
267                                                 if (solutionTarget.Configuration == targetInfo.Key.Configuration && solutionTarget.Platform == targetInfo.Key.Platform) {
268                                                         solutionConfigurationContents += string.Format ("<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
269                                                                 projectInfo.Key.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
270                                                 }
271                                         }
272                                 }
273                                 solutionConfigurationContents += "</SolutionConfiguration>";
274
275                                 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
276                         }
277                 }
278
279                 void AddValidateSolutionConfiguration (Project p)
280                 {
281                         Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
282                         BuildTask task = t.AddNewTask ("Error");
283                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
284                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
285                         task = t.AddNewTask ("Warning");
286                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
287                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
288                         task = t.AddNewTask ("Message");
289                         task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
290                         task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
291                 }
292
293                 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
294                 {
295                         foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
296                                 ProjectInfo project = projectInfo.Value;
297                                 foreach (string buildTarget in buildTargets) {
298                                         Target target = p.Targets.AddNewTarget (project.Name + (buildTarget == "Build" ? string.Empty : ":" + buildTarget));
299                                         target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; 
300                                         string dependencies = string.Empty;
301                                         foreach (Guid dependency in project.Dependencies) {
302                                                 ProjectInfo dependentInfo;
303                                                 if (projectInfos.TryGetValue (dependency, out dependentInfo)) {
304                                                         if (dependencies.Length > 0)
305                                                                 dependencies += ";";
306                                                         dependencies += dependentInfo.Name;
307                                                         if (buildTarget != "Build")
308                                                                 dependencies += ":" + buildTarget;
309                                                 }
310                                         }
311                                         if (dependencies != string.Empty)
312                                                 target.DependsOnTargets = dependencies;
313
314                                         foreach (TargetInfo targetInfo in solutionTargets) {
315                                                 BuildTask task = null;
316                                                 TargetInfo projectTargetInfo;
317                                                 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
318                                                         ErrorUtilities.ReportError (0, string.Format ("Failed to find targets {0}|{1} for project {2}", targetInfo.Configuration, targetInfo.Platform, project.Name));
319                                                 }
320                                                 if (projectTargetInfo.Build) {
321                                                         task = target.AddNewTask ("MSBuild");
322                                                         task.SetParameterValue ("Projects", project.FileName);
323                                                         
324                                                         if (buildTarget != "Build")
325                                                                 task.SetParameterValue ("Targets", buildTarget);
326                                                         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));
327                                                 } else {
328                                                         task = target.AddNewTask ("Message");
329                                                         task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
330                                                 }
331                                                 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
332                                         }
333                                 }
334                         }
335                 }
336
337                 void AddSolutionTargets (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
338                 {
339                         foreach (string buildTarget in buildTargets) {
340                                 Target t = p.Targets.AddNewTarget (buildTarget);
341                                 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
342                                 BuildTask task = t.AddNewTask ("CallTarget");
343                                 string targets = string.Empty;
344                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
345                                         if (targets.Length > 0)
346                                                 targets += ";";
347                                         targets += projectInfo.Value.Name;
348                                         if (buildTarget != "Build")
349                                                 targets += ":" + buildTarget;
350                                 }
351                                 task.SetParameterValue ("Targets", targets);
352                                 task.SetParameterValue ("RunEachTargetSeparately", "true");
353                         }
354                 }
355         }
356 }
357
358 #endif