2010-06-21 Atsushi Enomoto <atsushi@ximian.com>
[mcs.git] / tools / xbuild / SolutionParser.cs
blob552b2f306306d9956730d9dc2539f0607e4d3102
1 //
2 // SolutionParser.cs: Generates a project file from a solution file.
3 //
4 // Author:
5 // Jonathan Chambers (joncham@gmail.com)
6 // Ankit Jain <jankit@novell.com>
7 // Lluis Sanchez Gual <lluis@novell.com>
8 //
9 // (C) 2009 Jonathan Chambers
10 // Copyright 2008, 2009 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 #if NET_2_0
33 using System;
34 using System.Collections.Generic;
35 using System.Linq;
36 using System.Text;
37 using System.Text.RegularExpressions;
38 using System.IO;
39 using Microsoft.Build.BuildEngine;
41 namespace Mono.XBuild.CommandLine {
42 class ProjectInfo {
43 public string Name;
44 public string FileName;
45 public Guid Guid;
47 public ProjectInfo (string name, string fileName)
49 Name = name;
50 FileName = fileName;
53 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
54 public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
55 public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
56 public List<string> AspNetConfigurations = new List<string> ();
59 class ProjectSection {
60 public string Name;
61 public Dictionary<string, string> Properties = new Dictionary<string, string> ();
63 public ProjectSection (string name)
65 Name = name;
69 struct TargetInfo {
70 public string Configuration;
71 public string Platform;
72 public bool Build;
74 public TargetInfo (string configuration, string platform)
75 : this (configuration, platform, false)
79 public TargetInfo (string configuration, string platform, bool build)
81 Configuration = configuration;
82 Platform = platform;
83 Build = build;
88 internal delegate void RaiseWarningHandler (int errorNumber, string message);
90 class SolutionParser {
91 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
93 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}}";
95 static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
96 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)EndProject?", RegexOptions.Singleline);
97 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
98 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
99 static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
101 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
102 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
104 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
105 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
106 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
108 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
109 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
110 static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
112 RaiseWarningHandler RaiseWarning;
114 public void ParseSolution (string file, Project p, RaiseWarningHandler RaiseWarning)
116 this.RaiseWarning = RaiseWarning;
117 AddGeneralSettings (file, p);
119 StreamReader reader = new StreamReader (file);
120 string slnVersion = GetSlnFileVersion (reader);
121 if (slnVersion == "11.00")
122 p.DefaultToolsVersion = "4.0";
123 else if (slnVersion == "10.00")
124 p.DefaultToolsVersion = "3.5";
125 else
126 p.DefaultToolsVersion = "2.0";
128 string line = reader.ReadToEnd ();
129 line = line.Replace ("\r\n", "\n");
130 string solutionDir = Path.GetDirectoryName (file);
132 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
133 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
134 Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
135 List<ProjectInfo>[] infosByLevel = null;
137 Match m = projectRegex.Match (line);
138 while (m.Success) {
139 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value,
140 Path.GetFullPath (Path.Combine (solutionDir,
141 m.Groups [3].Value.Replace ('\\', Path.DirectorySeparatorChar))));
142 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
143 StringComparison.InvariantCultureIgnoreCase) == 0) {
144 // Ignore solution folders
145 m = m.NextMatch ();
146 continue;
149 if (String.Compare (m.Groups [1].Value, vcprojGuid,
150 StringComparison.InvariantCultureIgnoreCase) == 0) {
151 // Ignore vcproj
152 RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
153 m = m.NextMatch ();
154 continue;
157 if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
158 StringComparison.InvariantCultureIgnoreCase) == 0)
159 websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
160 else
161 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
163 projectInfo.Guid = new Guid (m.Groups [4].Value);
165 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
166 while (projectSectionMatch.Success) {
167 string section_name = projectSectionMatch.Groups [1].Value;
168 if (String.Compare (section_name, "ProjectDependencies") == 0) {
169 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
170 while (projectDependencyMatch.Success) {
171 // we might not have projectInfo available right now, so
172 // set it to null, and fill it in later
173 projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
174 projectDependencyMatch = projectDependencyMatch.NextMatch ();
176 } else {
177 ProjectSection section = new ProjectSection (section_name);
178 Match propertiesMatch = projectSectionPropertiesRegex.Match (
179 projectSectionMatch.Groups [2].Value);
180 while (propertiesMatch.Success) {
181 section.Properties [propertiesMatch.Groups ["name"].Value] =
182 propertiesMatch.Groups ["value"].Value;
184 propertiesMatch = propertiesMatch.NextMatch ();
187 projectInfo.ProjectSections [section_name] = section;
189 projectSectionMatch = projectSectionMatch.NextMatch ();
191 m = m.NextMatch ();
194 foreach (ProjectInfo projectInfo in projectInfos.Values) {
195 string filename = projectInfo.FileName;
196 string projectDir = Path.GetDirectoryName (filename);
198 if (!File.Exists (filename)) {
199 RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
200 "not found. Ignoring.", filename));
201 continue;
204 Project currentProject = p.ParentEngine.CreateNewProject ();
205 try {
206 currentProject.Load (filename);
207 } catch (InvalidProjectFileException e) {
208 RaiseWarning (0, e.Message);
209 continue;
212 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
213 ProjectInfo info = null;
214 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
215 bool hasGuid = !String.IsNullOrEmpty (projectReferenceGuid);
217 // try to resolve the ProjectReference by GUID
218 // and fallback to project filename
220 if (hasGuid) {
221 Guid guid = new Guid (projectReferenceGuid);
222 projectInfos.TryGetValue (guid, out info);
225 if (info == null || !hasGuid) {
226 // Project not found by guid or guid not available
227 // Try to find by project file
229 string fullpath = Path.GetFullPath (Path.Combine (projectDir, bi.Include.Replace ('\\', Path.DirectorySeparatorChar)));
230 info = projectInfos.Values.FirstOrDefault (pi => pi.FileName == fullpath);
232 if (info == null)
233 RaiseWarning (0, String.Format (
234 "{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
235 filename, bi.Include, projectReferenceGuid, fullpath));
239 if (info != null)
240 projectInfo.Dependencies [info.Guid] = info;
244 // fill in the project info for deps found in the .sln file
245 foreach (ProjectInfo projectInfo in projectInfos.Values) {
246 List<Guid> missingInfos = new List<Guid> ();
247 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
248 if (dependency.Value == null)
249 missingInfos.Add (dependency.Key);
252 foreach (Guid guid in missingInfos) {
253 ProjectInfo info;
254 if (projectInfos.TryGetValue (guid, out info))
255 projectInfo.Dependencies [guid] = info;
256 else
257 projectInfo.Dependencies.Remove (guid);
261 Match globalMatch = globalRegex.Match (line);
262 Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
263 while (globalSectionMatch.Success) {
264 string sectionType = globalSectionMatch.Groups[1].Value;
265 switch (sectionType) {
266 case "SolutionConfigurationPlatforms":
267 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
268 break;
269 case "ProjectConfigurationPlatforms":
270 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
271 projectInfos, websiteProjectInfos);
272 break;
273 case "SolutionProperties":
274 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
275 break;
276 case "NestedProjects":
277 break;
278 case "MonoDevelopProperties":
279 break;
280 default:
281 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
282 break;
284 globalSectionMatch = globalSectionMatch.NextMatch ();
287 int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
289 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
290 AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
291 AddValidateSolutionConfiguration (p);
293 AddGetFrameworkPathTarget (p);
294 AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
295 AddProjectTargets (p, solutionTargets, projectInfos);
296 AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
299 string GetSlnFileVersion (StreamReader reader)
301 string strVersion = null;
302 string strInput = null;
303 Match match;
305 strInput = reader.ReadLine();
306 if (strInput == null)
307 return null;
309 match = slnVersionRegex.Match(strInput);
310 if (!match.Success) {
311 strInput = reader.ReadLine();
312 if (strInput == null)
313 return null;
314 match = slnVersionRegex.Match (strInput);
317 if (match.Success)
318 return match.Groups[1].Value;
320 return null;
323 void AddGeneralSettings (string solutionFile, Project p)
325 p.DefaultTargets = "Build";
326 p.InitialTargets = "ValidateSolutionConfiguration";
327 p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
328 p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
330 string solutionFilePath = Path.GetFullPath (solutionFile);
331 BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
332 solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
333 solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
334 solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
335 solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
336 solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
339 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
341 Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
342 while (solutionConfigurationPlatform.Success) {
343 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
344 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
345 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
346 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
350 // ignores the website projects, in the websiteProjectInfos
351 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
352 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
354 List<Guid> missingGuids = new List<Guid> ();
355 Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
356 while (projectConfigurationPlatform.Success) {
357 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
358 ProjectInfo projectInfo;
359 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
360 if (!missingGuids.Contains (guid)) {
361 if (!websiteProjectInfos.ContainsKey (guid))
362 // ignore website projects
363 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
364 missingGuids.Add (guid);
366 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
367 continue;
369 string solConf = projectConfigurationPlatform.Groups[2].Value;
370 string solPlat = projectConfigurationPlatform.Groups[3].Value;
371 string projConf = projectConfigurationPlatform.Groups[4].Value;
372 string projPlat = projectConfigurationPlatform.Groups[5].Value;
373 // hack, what are they doing here?
374 if (projPlat == "Any CPU")
375 projPlat = "AnyCPU";
376 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
377 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
379 Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
380 while (projectConfigurationPlatformBuild.Success) {
381 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
382 ProjectInfo projectInfo;
383 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
384 if (!missingGuids.Contains (guid)) {
385 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
386 missingGuids.Add (guid);
388 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
389 continue;
391 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
392 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
393 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
394 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
395 // hack, what are they doing here?
396 if (projPlat == "Any CPU")
397 projPlat = "AnyCPU";
398 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
399 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
403 void ParseSolutionProperties (string section)
407 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets,
408 Dictionary<Guid, ProjectInfo> projectInfos,
409 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
411 TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
412 if (solutionTargets.Count > 0) {
413 bool found = false;
414 foreach (TargetInfo tinfo in solutionTargets) {
415 if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
416 default_target_info = tinfo;
417 found = true;
418 break;
422 if (!found)
423 default_target_info = solutionTargets [0];
426 AddDefaultSolutionConfiguration (p, default_target_info);
428 foreach (TargetInfo solutionTarget in solutionTargets) {
429 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
430 platformPropertyGroup.Condition = string.Format (
431 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
432 solutionTarget.Configuration,
433 solutionTarget.Platform
436 StringBuilder solutionConfigurationContents = new StringBuilder ();
437 solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
438 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
439 AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
441 solutionConfigurationContents.Append ("</SolutionConfiguration>");
443 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents",
444 solutionConfigurationContents.ToString ());
448 void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
449 StringBuilder solutionConfigurationContents)
451 foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
452 if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
453 solutionTarget.Platform == targetInfo.Key.Platform) {
454 solutionConfigurationContents.AppendFormat (
455 "<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
456 guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
461 void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
463 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
464 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
465 configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
467 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
468 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
469 platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
471 // emit default for AspNetConfiguration also
472 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
473 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
474 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
477 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
479 BuildTask task = target.AddNewTask ("Warning");
480 task.SetParameterValue ("Text",
481 String.Format ("The project configuration for project '{0}' corresponding " +
482 "to the solution configuration '{1}|{2}' was not found in the solution file.",
483 projectName, slnConfig, slnPlatform));
484 task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
485 slnConfig, slnPlatform);
489 // Website project methods
491 void AddWebsiteProperties (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
492 Dictionary<Guid, ProjectInfo> projectInfos)
494 var propertyGroupByConfig = new Dictionary<string, BuildPropertyGroup> ();
495 foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
496 ProjectInfo info = infoPair.Value;
497 string projectGuid = infoPair.Key.ToString ();
499 ProjectSection section;
500 if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
501 RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
502 return;
505 //parse project references
506 string [] ref_guids = null;
507 string references;
508 if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
509 ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
510 for (int i = 0; i < ref_guids.Length; i ++) {
511 // "{guid}|foo.dll"
512 ref_guids [i] = ref_guids [i].Split ('|') [0];
514 Guid r_guid = new Guid (ref_guids [i]);
515 ProjectInfo ref_info;
516 if (projectInfos.TryGetValue (r_guid, out ref_info))
517 // ignore if not found
518 info.Dependencies [r_guid] = ref_info;
522 foreach (KeyValuePair<string, string> pair in section.Properties) {
523 //looking for -- ConfigName.AspNetCompiler.PropName
524 string [] parts = pair.Key.Split ('.');
525 if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
526 continue;
528 string config = parts [0];
529 string propertyName = parts [2];
531 BuildPropertyGroup bpg;
532 if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
533 bpg = p.AddNewPropertyGroup (true);
534 bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
535 propertyGroupByConfig [config] = bpg;
538 bpg.AddNewProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
539 pair.Value);
541 if (!info.AspNetConfigurations.Contains (config))
542 info.AspNetConfigurations.Add (config);
547 // For WebSite projects
548 // The main "Build" target:
549 // 1. builds all non-website projects
550 // 2. calls target for website project
551 // - gets target path for the referenced projects
552 // - Resolves dependencies, satellites etc for the
553 // referenced project assemblies, and copies them
554 // to bin/ folder
555 void AddWebsiteTargets (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
556 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
557 List<TargetInfo> solutionTargets)
559 foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
560 // gets a linear list of dependencies
561 List<ProjectInfo> depInfos = new List<ProjectInfo> ();
562 foreach (List<ProjectInfo> pinfos in infosByLevel) {
563 foreach (ProjectInfo pinfo in pinfos)
564 if (w_info.Dependencies.ContainsKey (pinfo.Guid))
565 depInfos.Add (pinfo);
568 foreach (string buildTarget in new string [] {"Build", "Rebuild"})
569 AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
571 // clean/publish are not supported for website projects
572 foreach (string buildTarget in new string [] {"Clean", "Publish"})
573 AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
577 void AddWebsiteTarget (Project p, ProjectInfo webProjectInfo,
578 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
579 List<TargetInfo> solutionTargets, string buildTarget)
581 string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
583 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
584 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
585 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
587 // this item collects all the references
588 string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
589 buildTarget != "Build" ? "_" + buildTarget : String.Empty);
591 foreach (TargetInfo targetInfo in solutionTargets) {
592 int ref_num = 0;
593 foreach (ProjectInfo depInfo in depInfos) {
594 TargetInfo projectTargetInfo;
595 if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
596 // Ignore, no config, so no target path
597 continue;
599 // GetTargetPath from the referenced project
600 AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
601 final_ref_item, ref_num);
602 ref_num ++;
606 // resolve the references
607 AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
610 // emits the MSBuild task to GetTargetPath for the referenced project
611 void AddWebsiteMSBuildTaskForReference (Target target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
612 TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
614 BuildTask task = target.AddNewTask ("MSBuild");
615 task.SetParameterValue ("Projects", depInfo.FileName);
616 task.SetParameterValue ("Targets", "GetTargetPath");
618 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));
619 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
621 string ref_item = String.Format ("{0}_{1}",
622 final_ref_item, ref_num);
624 task.AddOutputItem ("TargetOutputs", ref_item);
626 task = target.AddNewTask ("CreateItem");
627 task.SetParameterValue ("Include", String.Format ("@({0})", ref_item));
628 task.SetParameterValue ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
629 depInfo.Guid.ToString ().ToUpper ()));
630 task.AddOutputItem ("Include", final_ref_item);
633 void AddWebsiteResolveAndCopyReferencesTasks (Target target, ProjectInfo webProjectInfo,
634 string final_ref_item, string w_guid)
636 BuildTask task = target.AddNewTask ("ResolveAssemblyReference");
637 task.SetParameterValue ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
638 task.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
639 task.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
640 task.SetParameterValue ("FindDependencies", "true");
641 task.SetParameterValue ("FindSatellites", "true");
642 task.SetParameterValue ("FindRelatedFiles", "true");
643 task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
645 string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
646 task.AddOutputItem ("CopyLocalFiles", copylocal_item);
648 // Copy the references
649 task = target.AddNewTask ("Copy");
650 task.SetParameterValue ("SourceFiles", String.Format ("@({0})", copylocal_item));
651 task.SetParameterValue ("DestinationFiles", String.Format (
652 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
653 copylocal_item, w_guid));
655 // AspNetConfiguration, is config for the website project, useful
656 // for overriding from command line
657 StringBuilder cond = new StringBuilder ();
658 foreach (string config in webProjectInfo.AspNetConfigurations) {
659 if (cond.Length > 0)
660 cond.Append (" or ");
661 cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
663 task.Condition = cond.ToString ();
665 task = target.AddNewTask ("Message");
666 cond = new StringBuilder ();
667 foreach (string config in webProjectInfo.AspNetConfigurations) {
668 if (cond.Length > 0)
669 cond.Append (" and ");
670 cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
672 task.Condition = cond.ToString ();
673 task.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
674 "not supported by this website project.");
677 void AddWebsiteUnsupportedTarget (Project p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
678 string buildTarget)
680 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
681 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
683 BuildTask task = target.AddNewTask ("Message");
684 task.SetParameterValue ("Text", String.Format (
685 "Target '{0}' not support for website projects", buildTarget));
688 string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
690 StringBuilder deps = new StringBuilder ();
691 foreach (ProjectInfo pinfo in depInfos) {
692 if (deps.Length > 0)
693 deps.Append (";");
694 deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
696 deps.Append (";GetFrameworkPath");
697 return deps.ToString ();
700 void AddGetFrameworkPathTarget (Project p)
702 Target t = p.Targets.AddNewTarget ("GetFrameworkPath");
703 BuildTask task = t.AddNewTask ("GetFrameworkPath");
704 task.AddOutputProperty ("Path", "TargetFrameworkPath");
707 void AddValidateSolutionConfiguration (Project p)
709 Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
710 BuildTask task = t.AddNewTask ("Warning");
711 task.SetParameterValue ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
712 " for xbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
713 " You could override it by either setting the environment variable to nothing, as\n" +
714 " set Platform=\n" +
715 "Or explicity specify its value on the command line, as\n" +
716 " xbuild Foo.sln /p:Platform=Release");
717 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
718 " and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
720 task = t.AddNewTask ("Error");
721 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
722 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
723 task = t.AddNewTask ("Warning");
724 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
725 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
726 task = t.AddNewTask ("Message");
727 task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
728 task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
731 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
733 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
734 ProjectInfo project = projectInfo.Value;
735 foreach (string buildTarget in buildTargets) {
736 string target_name = GetTargetNameForProject (project.Name, buildTarget);
737 Target target = p.Targets.AddNewTarget (target_name);
738 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
740 if (project.Dependencies.Count > 0) {
741 StringBuilder dependencies = new StringBuilder ();
742 foreach (ProjectInfo dependentInfo in project.Dependencies.Values) {
743 if (dependencies.Length > 0)
744 dependencies.Append (";");
745 if (IsBuildTargetName (dependentInfo.Name))
746 dependencies.Append ("Solution:");
747 dependencies.Append (dependentInfo.Name);
748 if (buildTarget != "Build")
749 dependencies.Append (":" + buildTarget);
751 target.DependsOnTargets = dependencies.ToString ();
754 foreach (TargetInfo targetInfo in solutionTargets) {
755 BuildTask task = null;
756 TargetInfo projectTargetInfo;
757 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
758 AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
759 targetInfo.Platform, project.Name);
760 continue;
762 if (projectTargetInfo.Build) {
763 task = target.AddNewTask ("MSBuild");
764 task.SetParameterValue ("Projects", project.FileName);
765 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
767 if (buildTarget != "Build")
768 task.SetParameterValue ("Targets", buildTarget);
769 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));
770 } else {
771 task = target.AddNewTask ("Message");
772 task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
774 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
781 string GetTargetNameForProject (string projectName, string buildTarget)
783 //FIXME: hack
784 projectName = projectName.Replace ("\\", "/").Replace (".", "_");
785 string target_name = projectName +
786 (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
788 if (IsBuildTargetName (projectName))
789 target_name = "Solution:" + target_name;
791 return target_name;
794 bool IsBuildTargetName (string name)
796 foreach (string tgt in buildTargets)
797 if (name == tgt)
798 return true;
799 return false;
802 // returns number of levels
803 int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
804 ref List<ProjectInfo>[] infosByLevel)
806 infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
808 foreach (TargetInfo targetInfo in solutionTargets) {
809 BuildItemGroup big = p.AddNewItemGroup ();
810 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
811 targetInfo.Configuration, targetInfo.Platform);
813 //FIXME: every level has projects that can be built in parallel.
814 // levels are ordered on the basis of the dependency graph
816 for (int i = 0; i < infosByLevel.Length; i ++) {
817 string build_level = String.Format ("BuildLevel{0}", i);
818 string skip_level = String.Format ("SkipLevel{0}", i);
819 string missing_level = String.Format ("MissingConfigLevel{0}", i);
821 foreach (ProjectInfo projectInfo in infosByLevel [i]) {
822 TargetInfo projectTargetInfo;
823 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
824 // missing project config
825 big.AddNewItem (missing_level, projectInfo.Name);
826 continue;
829 if (projectTargetInfo.Build) {
830 BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
831 item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
832 item.SetMetadata ("Platform", projectTargetInfo.Platform);
833 } else {
834 // build disabled
835 big.AddNewItem (skip_level, projectInfo.Name);
841 return infosByLevel.Length;
844 void AddSolutionTargets (Project p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
846 foreach (string buildTarget in buildTargets) {
847 Target t = p.Targets.AddNewTarget (buildTarget);
848 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
850 BuildTask task = null;
851 for (int i = 0; i < num_levels; i ++) {
852 string level_str = String.Format ("BuildLevel{0}", i);
853 task = t.AddNewTask ("MSBuild");
854 task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
855 task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
856 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
857 task.SetParameterValue ("Properties",
858 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
859 if (buildTarget != "Build")
860 task.SetParameterValue ("Targets", buildTarget);
861 //FIXME: change this to BuildInParallel=true, when parallel
862 // build support gets added
863 task.SetParameterValue ("RunEachTargetSeparately", "true");
865 level_str = String.Format ("SkipLevel{0}", i);
866 task = t.AddNewTask ("Message");
867 task.Condition = String.Format ("'@({0})' != ''", level_str);
868 task.SetParameterValue ("Text",
869 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
870 "configuration '$(Configuration)|$(Platform)'.", level_str));
872 level_str = String.Format ("MissingConfigLevel{0}", i);
873 task = t.AddNewTask ("Warning");
874 task.Condition = String.Format ("'@({0})' != ''", level_str);
875 task.SetParameterValue ("Text",
876 String.Format ("The project configuration for project '%({0}.Identity)' " +
877 "corresponding to the solution configuration " +
878 "'$(Configuration)|$(Platform)' was not found.", level_str));
881 // "build" website projects also
882 StringBuilder w_targets = new StringBuilder ();
883 foreach (ProjectInfo info in websiteProjectInfos) {
884 if (w_targets.Length > 0)
885 w_targets.Append (";");
886 w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
889 task = t.AddNewTask ("CallTarget");
890 task.SetParameterValue ("Targets", w_targets.ToString ());
891 task.SetParameterValue ("RunEachTargetSeparately", "true");
895 // Sorts the ProjectInfo dependency graph, to obtain
896 // a series of build levels with projects. Projects
897 // in each level can be run parallel (no inter-dependency).
898 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
900 IList<T> allItems;
901 allItems = items as IList<T>;
902 if (allItems == null)
903 allItems = new List<T> (items);
905 bool[] inserted = new bool[allItems.Count];
906 bool[] triedToInsert = new bool[allItems.Count];
907 int[] levels = new int [allItems.Count];
909 int maxdepth = 0;
910 for (int i = 0; i < allItems.Count; ++i) {
911 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
912 if (d > maxdepth)
913 maxdepth = d;
916 // Separate out the project infos by build level
917 List<T>[] infosByLevel = new List<T>[maxdepth];
918 for (int i = 0; i < levels.Length; i ++) {
919 int level = levels [i] - 1;
920 if (infosByLevel [level] == null)
921 infosByLevel [level] = new List<T> ();
923 infosByLevel [level].Add (allItems [i]);
926 return infosByLevel;
929 // returns level# for the project
930 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
931 where T: ProjectInfo
933 if (inserted [index])
934 return levels [index];
936 if (triedToInsert[index])
937 throw new InvalidOperationException (String.Format (
938 "Cyclic dependency involving project {0} found in the project dependency graph",
939 allItems [index].Name));
941 triedToInsert[index] = true;
942 ProjectInfo insertItem = allItems[index];
944 int maxdepth = 0;
945 foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
946 for (int j = 0; j < allItems.Count; ++j) {
947 ProjectInfo checkItem = allItems [j];
948 if (dependency.FileName == checkItem.FileName) {
949 int d = Insert (j, allItems, levels, inserted, triedToInsert);
950 maxdepth = d > maxdepth ? d : maxdepth;
951 break;
955 levels [index] = maxdepth + 1;
956 inserted [index] = true;
958 return levels [index];
961 public static IEnumerable<string> GetAllProjectFileNames (string solutionFile)
963 StreamReader reader = new StreamReader (solutionFile);
964 string line = reader.ReadToEnd ();
965 line = line.Replace ("\r\n", "\n");
966 string soln_dir = Path.GetDirectoryName (solutionFile);
968 Match m = projectRegex.Match (line);
969 while (m.Success) {
970 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
971 StringComparison.InvariantCultureIgnoreCase) != 0)
972 yield return Path.Combine (soln_dir, m.Groups [3].Value).Replace ("\\", "/");
974 m = m.NextMatch ();
980 #endif