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 + ")\"(.*?)((.*?)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 + ")");
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 = (.*?)\\|(.+)");
88 public void ParseSolution (string file, Project p)
90 AddGeneralSettings (file, p);
92 StreamReader reader = new StreamReader (file);
93 string line = reader.ReadToEnd ();
94 line = line.Replace ("\r\n", "\n");
96 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
97 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
99 Match m = projectRegex.Match (line);
101 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
102 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
104 Project currentProject = p.ParentEngine.CreateNewProject ();
105 currentProject.Load (projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar));
107 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
108 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
109 projectInfo.Dependencies.Add (new Guid (projectReferenceGuid));
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 ();
119 projectSectionMatch = projectSectionMatch.NextMatch ();
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);
132 case "ProjectConfigurationPlatforms":
133 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos);
135 case "SolutionProperties":
136 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
139 ErrorUtilities.ReportError (0, string.Format("Don't know how to handle GlobalSection {0}", sectionType));
142 globalSectionMatch = globalSectionMatch.NextMatch ();
145 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos);
146 AddValidateSolutionConfiguration (p);
147 AddProjectTargets (p, solutionTargets, projectInfos);
148 AddSolutionTargets (p, projectInfos);
152 void AddGeneralSettings (string solutionFile, Project p)
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");
159 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
160 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
161 configurationPropertyGroup.AddNewProperty ("Configuration", "Debug");
163 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
164 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
165 platformPropertyGroup.AddNewProperty ("Platform", "Any CPU");
167 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
168 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
169 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
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);
180 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
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 ();
191 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos)
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 ();
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")
209 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
210 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
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")
223 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
224 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
228 void ParseSolutionProperties (string section)
232 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
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
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);
251 solutionConfigurationContents += "</SolutionConfiguration>";
253 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents);
257 void AddValidateSolutionConfiguration (Project p)
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)' != ''";
271 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
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)
284 dependencies += dependentInfo.Name;
285 if (buildTarget != "Build")
286 dependencies += ":" + buildTarget;
289 if (dependencies != string.Empty)
290 target.DependsOnTargets = dependencies;
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));
298 if (projectTargetInfo.Build) {
299 task = target.AddNewTask ("MSBuild");
300 task.SetParameterValue ("Projects", project.FileName);
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));
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));
309 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
315 void AddSolutionTargets (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
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)
325 targets += projectInfo.Value.Name;
326 if (buildTarget != "Build")
327 targets += ":" + buildTarget;
329 task.SetParameterValue ("Targets", targets);
330 task.SetParameterValue ("RunEachTargetSeparately", "true");