2009-05-20 Jonathan Chambers <joncham@gmail.com>
[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                 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
89
90                 public void ParseSolution (string file, Project p)
91                 {
92                         AddGeneralSettings (file, p);
93
94                         StreamReader reader = new StreamReader (file);
95                         string line = reader.ReadToEnd ();
96                         line = line.Replace ("\r\n", "\n");
97                         string solutionDir = Path.GetDirectoryName (file);
98
99                         List<TargetInfo> solutionTargets = new List<TargetInfo> ();
100                         Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
101
102                         Match m = projectRegex.Match (line);
103                         while (m.Success) {
104                                 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
105                                 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
106                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
107                                         // Ignore solution folders
108                                         m = m.NextMatch ();
109                                         continue;
110                                 }
111                                 if (String.Compare (m.Groups [1].Value, vcprojGuid,
112                                                 StringComparison.InvariantCultureIgnoreCase) == 0) {
113                                         // Ignore vcproj 
114                                         ErrorUtilities.ReportWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
115                                         m = m.NextMatch ();
116                                         continue;
117                                 }
118
119                                 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
120
121                                 Project currentProject = p.ParentEngine.CreateNewProject ();
122                                 currentProject.Load (Path.Combine (solutionDir,
123                                                         projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar)));
124
125                                 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
126                                         string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
127                                         projectInfo.Dependencies.Add (new Guid (projectReferenceGuid));
128                                 }
129
130                                 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
131                                 while (projectSectionMatch.Success) {
132                                         Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
133                                         while (projectDependencyMatch.Success) {
134                                                 projectInfo.Dependencies.Add (new Guid (projectDependencyMatch.Groups[1].Value));
135                                                 projectDependencyMatch = projectDependencyMatch.NextMatch ();
136                                         }
137                                         projectSectionMatch = projectSectionMatch.NextMatch ();
138                                 }
139                                 m = m.NextMatch ();
140                         }
141
142                         Match globalMatch = globalRegex.Match (line);
143                         Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
144                         while (globalSectionMatch.Success) {
145                                 string sectionType = globalSectionMatch.Groups[1].Value;
146                                 switch (sectionType) {
147                                         case "SolutionConfigurationPlatforms":
148                                                 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
149                                                 break;
150                                         case "ProjectConfigurationPlatforms":
151                                                 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
152                                                 break;
153                                         case "SolutionProperties":
154                                                 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
155                                                 break;
156                                         case "NestedProjects":
157                                                 break;
158                                         default:
159                                                 ErrorUtilities.ReportWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
160                                                 break;
161                                 }
162                                 globalSectionMatch = globalSectionMatch.NextMatch ();
163                         }
164
165                         AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
166                         AddValidateSolutionConfiguration (p);
167                         AddProjectTargets (p, solutionTargets, projectInfos);
168                         AddSolutionTargets (p, projectInfos);
169
170                 }
171
172                 void AddGeneralSettings (string solutionFile, Project p)
173                 {
174                         p.DefaultTargets = "Build";
175                         p.InitialTargets = "ValidateSolutionConfiguration";
176                         p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
177                         p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
178
179                         BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
180                         configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
181                         configurationPropertyGroup.AddNewProperty ("Configuration", "Debug");
182
183                         BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
184                         platformPropertyGroup.Condition = " '$(Platform)' == '' ";
185                         platformPropertyGroup.AddNewProperty ("Platform", "Any CPU");
186
187                         BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
188                         aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
189                         aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
190
191                         string solutionFilePath = Path.GetFullPath (solutionFile);
192                         BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
193                         solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
194                         solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
195                         solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
196                         solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
197                         solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
198                 }
199
200                 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
201                 {
202                         Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
203                         while (solutionConfigurationPlatform.Success) {
204                                 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
205                                 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
206                                 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
207                                 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
208                         }
209                 }
210
211                 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
212                 {
213                         List<Guid> missingGuids = new List<Guid> ();
214                         Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
215                         while (projectConfigurationPlatform.Success) {
216                                 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
217                                 ProjectInfo projectInfo;
218                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
219                                         if (!missingGuids.Contains (guid)) {
220                                                 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
221                                                 missingGuids.Add (guid);
222                                         }
223                                         projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
224                                         continue;
225                                 }
226                                 string solConf = projectConfigurationPlatform.Groups[2].Value;
227                                 string solPlat = projectConfigurationPlatform.Groups[3].Value;
228                                 string projConf = projectConfigurationPlatform.Groups[4].Value;
229                                 string projPlat = projectConfigurationPlatform.Groups[5].Value;
230                                 // hack, what are they doing here?
231                                 if (projPlat == "Any CPU")
232                                         projPlat = "AnyCPU";
233                                 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
234                                 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
235                         }
236                         Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
237                         while (projectConfigurationPlatformBuild.Success) {
238                                 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
239                                 ProjectInfo projectInfo;
240                                 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
241                                         if (!missingGuids.Contains (guid)) {
242                                                 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
243                                                 missingGuids.Add (guid);
244                                         }
245                                         projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
246                                         continue;
247                                 }
248                                 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
249                                 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
250                                 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
251                                 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
252                                 // hack, what are they doing here?
253                                 if (projPlat == "Any CPU")
254                                         projPlat = "AnyCPU";
255                                 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
256                                 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
257                         }
258                 }
259
260                 void ParseSolutionProperties (string section)
261                 {
262                 }
263
264                 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
265                 {
266                         foreach (TargetInfo solutionTarget in solutionTargets) {
267                                 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
268                                 platformPropertyGroup.Condition = string.Format (
269                                         " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
270                                         solutionTarget.Configuration,
271                                         solutionTarget.Platform
272                                         );
273
274                                 string solutionConfigurationContents = "<SolutionConfiguration xmlns=\"\">";
275                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
276                                         foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.Value.TargetMap) {
277                                                 if (solutionTarget.Configuration == targetInfo.Key.Configuration && solutionTarget.Platform == targetInfo.Key.Platform) {
278                                                         solutionConfigurationContents += string.Format ("<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
279                                                                 projectInfo.Key.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
280                                                 }
281                                         }
282                                 }
283                                 solutionConfigurationContents += "</SolutionConfiguration>";
284
285                                 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
286                         }
287                 }
288
289                 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
290                 {
291                         BuildTask task = target.AddNewTask ("Warning");
292                         task.SetParameterValue ("Text",
293                                         String.Format ("The project configuration for project '{0}' corresponding " +
294                                                 "to the solution configuration '{1}|{2}' was not found in the solution file.",
295                                                 projectName, slnConfig, slnPlatform));
296                         task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
297                                                 slnConfig, slnPlatform);
298
299                 }
300
301                 void AddValidateSolutionConfiguration (Project p)
302                 {
303                         Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
304                         BuildTask task = t.AddNewTask ("Error");
305                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
306                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
307                         task = t.AddNewTask ("Warning");
308                         task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
309                         task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
310                         task = t.AddNewTask ("Message");
311                         task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
312                         task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
313                 }
314
315                 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
316                 {
317                         foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
318                                 ProjectInfo project = projectInfo.Value;
319                                 foreach (string buildTarget in buildTargets) {
320                                         Target target = p.Targets.AddNewTarget (project.Name + (buildTarget == "Build" ? string.Empty : ":" + buildTarget));
321                                         target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; 
322                                         string dependencies = string.Empty;
323                                         foreach (Guid dependency in project.Dependencies) {
324                                                 ProjectInfo dependentInfo;
325                                                 if (projectInfos.TryGetValue (dependency, out dependentInfo)) {
326                                                         if (dependencies.Length > 0)
327                                                                 dependencies += ";";
328                                                         dependencies += dependentInfo.Name;
329                                                         if (buildTarget != "Build")
330                                                                 dependencies += ":" + buildTarget;
331                                                 }
332                                         }
333                                         if (dependencies != string.Empty)
334                                                 target.DependsOnTargets = dependencies;
335
336                                         foreach (TargetInfo targetInfo in solutionTargets) {
337                                                 BuildTask task = null;
338                                                 TargetInfo projectTargetInfo;
339                                                 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
340                                                         AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
341                                                                         targetInfo.Platform, project.Name);
342                                                         continue;
343                                                 }
344                                                 if (projectTargetInfo.Build) {
345                                                         task = target.AddNewTask ("MSBuild");
346                                                         task.SetParameterValue ("Projects", project.FileName);
347                                                         
348                                                         if (buildTarget != "Build")
349                                                                 task.SetParameterValue ("Targets", buildTarget);
350                                                         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));
351                                                 } else {
352                                                         task = target.AddNewTask ("Message");
353                                                         task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
354                                                 }
355                                                 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
356                                         }
357                                 }
358                         }
359                 }
360
361                 void AddSolutionTargets (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
362                 {
363                         foreach (string buildTarget in buildTargets) {
364                                 Target t = p.Targets.AddNewTarget (buildTarget);
365                                 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
366                                 BuildTask task = t.AddNewTask ("CallTarget");
367                                 string targets = string.Empty;
368                                 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
369                                         if (targets.Length > 0)
370                                                 targets += ";";
371                                         targets += projectInfo.Value.Name;
372                                         if (buildTarget != "Build")
373                                                 targets += ":" + buildTarget;
374                                 }
375                                 task.SetParameterValue ("Targets", targets);
376                                 task.SetParameterValue ("RunEachTargetSeparately", "true");
377                         }
378                 }
379         }
380 }
381
382 #endif