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}";
89 public void ParseSolution (string file, Project p)
91 AddGeneralSettings (file, p);
93 StreamReader reader = new StreamReader (file);
94 string line = reader.ReadToEnd ();
95 line = line.Replace ("\r\n", "\n");
97 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
98 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
100 Match m = projectRegex.Match (line);
102 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
103 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
104 StringComparison.InvariantCultureIgnoreCase) == 0) {
105 // Ignore solution folders
110 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
112 Project currentProject = p.ParentEngine.CreateNewProject ();
113 currentProject.Load (projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar));
115 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
116 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
117 projectInfo.Dependencies.Add (new Guid (projectReferenceGuid));
120 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
121 while (projectSectionMatch.Success) {
122 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
123 while (projectDependencyMatch.Success) {
124 projectInfo.Dependencies.Add (new Guid (projectDependencyMatch.Groups[1].Value));
125 projectDependencyMatch = projectDependencyMatch.NextMatch ();
127 projectSectionMatch = projectSectionMatch.NextMatch ();
132 Match globalMatch = globalRegex.Match (line);
133 Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
134 while (globalSectionMatch.Success) {
135 string sectionType = globalSectionMatch.Groups[1].Value;
136 switch (sectionType) {
137 case "SolutionConfigurationPlatforms":
138 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
140 case "ProjectConfigurationPlatforms":
141 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
143 case "SolutionProperties":
144 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
146 case "NestedProjects":
149 ErrorUtilities.ReportError (0, string.Format("Don't know how to handle GlobalSection {0}", sectionType));
152 globalSectionMatch = globalSectionMatch.NextMatch ();
155 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
156 AddValidateSolutionConfiguration (p);
157 AddProjectTargets (p, solutionTargets, projectInfos);
158 AddSolutionTargets (p, projectInfos);
162 void AddGeneralSettings (string solutionFile, Project p)
164 p.DefaultTargets = "Build";
165 p.InitialTargets = "ValidateSolutionConfiguration";
166 p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
167 p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
169 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
170 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
171 configurationPropertyGroup.AddNewProperty ("Configuration", "Debug");
173 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
174 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
175 platformPropertyGroup.AddNewProperty ("Platform", "Any CPU");
177 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
178 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
179 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
181 string solutionFilePath = Path.GetFullPath (solutionFile);
182 BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
183 solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
184 solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
185 solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
186 solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
187 solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
190 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
192 Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
193 while (solutionConfigurationPlatform.Success) {
194 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
195 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
196 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
197 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
201 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
203 List<Guid> missingGuids = new List<Guid> ();
204 Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
205 while (projectConfigurationPlatform.Success) {
206 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
207 ProjectInfo projectInfo;
208 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
209 if (!missingGuids.Contains (guid)) {
210 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
211 missingGuids.Add (guid);
213 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
216 string solConf = projectConfigurationPlatform.Groups[2].Value;
217 string solPlat = projectConfigurationPlatform.Groups[3].Value;
218 string projConf = projectConfigurationPlatform.Groups[4].Value;
219 string projPlat = projectConfigurationPlatform.Groups[5].Value;
220 // hack, what are they doing here?
221 if (projPlat == "Any CPU")
223 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
224 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
226 Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
227 while (projectConfigurationPlatformBuild.Success) {
228 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
229 ProjectInfo projectInfo;
230 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
231 if (!missingGuids.Contains (guid)) {
232 ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid));
233 missingGuids.Add (guid);
235 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
238 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
239 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
240 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
241 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
242 // hack, what are they doing here?
243 if (projPlat == "Any CPU")
245 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
246 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
250 void ParseSolutionProperties (string section)
254 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
256 foreach (TargetInfo solutionTarget in solutionTargets) {
257 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
258 platformPropertyGroup.Condition = string.Format (
259 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
260 solutionTarget.Configuration,
261 solutionTarget.Platform
264 string solutionConfigurationContents = "<SolutionConfiguration xmlns=\"\">";
265 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
266 foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.Value.TargetMap) {
267 if (solutionTarget.Configuration == targetInfo.Key.Configuration && solutionTarget.Platform == targetInfo.Key.Platform) {
268 solutionConfigurationContents += string.Format ("<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
269 projectInfo.Key.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
273 solutionConfigurationContents += "</SolutionConfiguration>";
275 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
279 void AddValidateSolutionConfiguration (Project p)
281 Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
282 BuildTask task = t.AddNewTask ("Error");
283 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
284 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
285 task = t.AddNewTask ("Warning");
286 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
287 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
288 task = t.AddNewTask ("Message");
289 task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
290 task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
293 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
295 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
296 ProjectInfo project = projectInfo.Value;
297 foreach (string buildTarget in buildTargets) {
298 Target target = p.Targets.AddNewTarget (project.Name + (buildTarget == "Build" ? string.Empty : ":" + buildTarget));
299 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
300 string dependencies = string.Empty;
301 foreach (Guid dependency in project.Dependencies) {
302 ProjectInfo dependentInfo;
303 if (projectInfos.TryGetValue (dependency, out dependentInfo)) {
304 if (dependencies.Length > 0)
306 dependencies += dependentInfo.Name;
307 if (buildTarget != "Build")
308 dependencies += ":" + buildTarget;
311 if (dependencies != string.Empty)
312 target.DependsOnTargets = dependencies;
314 foreach (TargetInfo targetInfo in solutionTargets) {
315 BuildTask task = null;
316 TargetInfo projectTargetInfo;
317 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
318 ErrorUtilities.ReportError (0, string.Format ("Failed to find targets {0}|{1} for project {2}", targetInfo.Configuration, targetInfo.Platform, project.Name));
320 if (projectTargetInfo.Build) {
321 task = target.AddNewTask ("MSBuild");
322 task.SetParameterValue ("Projects", project.FileName);
324 if (buildTarget != "Build")
325 task.SetParameterValue ("Targets", buildTarget);
326 task.SetParameterValue ("Properties", string.Format ("Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", projectTargetInfo.Configuration, projectTargetInfo.Platform));
328 task = target.AddNewTask ("Message");
329 task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
331 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
337 void AddSolutionTargets (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
339 foreach (string buildTarget in buildTargets) {
340 Target t = p.Targets.AddNewTarget (buildTarget);
341 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
342 BuildTask task = t.AddNewTask ("CallTarget");
343 string targets = string.Empty;
344 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
345 if (targets.Length > 0)
347 targets += projectInfo.Value.Name;
348 if (buildTarget != "Build")
349 targets += ":" + buildTarget;
351 task.SetParameterValue ("Targets", targets);
352 task.SetParameterValue ("RunEachTargetSeparately", "true");