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