2 // SolutionParser.cs: Generates a project file from a solution file.
5 // Jonathan Chambers (joncham@gmail.com)
7 // (C) 2009 Jonathan Chambers
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections.Generic;
33 using System.Text.RegularExpressions;
35 using Microsoft.Build.BuildEngine;
37 namespace Mono.XBuild.CommandLine {
40 public string FileName;
42 public ProjectInfo (string name, string fileName)
48 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
49 public List<Guid> Dependencies = new List<Guid> ();
53 public string Configuration;
54 public string Platform;
57 public TargetInfo (string configuration, string platform)
58 : this (configuration, platform, false)
62 public TargetInfo (string configuration, string platform, bool build)
64 Configuration = configuration;
71 class SolutionParser {
72 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
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}}";
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 + ")");
80 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
81 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
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 = (.*?)\\|(.+)");
87 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
88 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
90 public void ParseSolution (string file, Project p)
92 AddGeneralSettings (file, p);
94 StreamReader reader = new StreamReader (file);
95 string line = reader.ReadToEnd ();
96 line = line.Replace ("\r\n", "\n");
97 string solutionDir = Path.GetDirectoryName (file);
99 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
100 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
102 Match m = projectRegex.Match (line);
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
111 if (String.Compare (m.Groups [1].Value, vcprojGuid,
112 StringComparison.InvariantCultureIgnoreCase) == 0) {
114 ErrorUtilities.ReportWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
119 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
121 Project currentProject = p.ParentEngine.CreateNewProject ();
122 currentProject.Load (Path.Combine (solutionDir,
123 projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar)));
125 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
126 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
127 projectInfo.Dependencies.Add (new Guid (projectReferenceGuid));
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 ();
137 projectSectionMatch = projectSectionMatch.NextMatch ();
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);
150 case "ProjectConfigurationPlatforms":
151 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
153 case "SolutionProperties":
154 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
156 case "NestedProjects":
159 ErrorUtilities.ReportWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
162 globalSectionMatch = globalSectionMatch.NextMatch ();
165 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
166 AddValidateSolutionConfiguration (p);
167 AddProjectTargets (p, solutionTargets, projectInfos);
168 AddSolutionTargets (p, projectInfos);
172 void AddGeneralSettings (string solutionFile, Project p)
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");
179 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
180 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
181 configurationPropertyGroup.AddNewProperty ("Configuration", "Debug");
183 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
184 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
185 platformPropertyGroup.AddNewProperty ("Platform", "Any CPU");
187 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
188 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
189 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
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);
200 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
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 ();
211 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
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);
223 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
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")
233 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
234 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
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);
245 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
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")
255 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
256 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
260 void ParseSolutionProperties (string section)
264 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
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
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);
283 solutionConfigurationContents += "</SolutionConfiguration>";
285 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
289 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
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);
301 void AddValidateSolutionConfiguration (Project p)
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)' != ''";
315 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
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)
328 dependencies += dependentInfo.Name;
329 if (buildTarget != "Build")
330 dependencies += ":" + buildTarget;
333 if (dependencies != string.Empty)
334 target.DependsOnTargets = dependencies;
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);
344 if (projectTargetInfo.Build) {
345 task = target.AddNewTask ("MSBuild");
346 task.SetParameterValue ("Projects", project.FileName);
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));
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));
355 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
361 void AddSolutionTargets (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
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)
371 targets += projectInfo.Value.Name;
372 if (buildTarget != "Build")
373 targets += ":" + buildTarget;
375 task.SetParameterValue ("Targets", targets);
376 task.SetParameterValue ("RunEachTargetSeparately", "true");