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