2 // SolutionParser.cs: Generates a project file from a solution file.
5 // Jonathan Chambers (joncham@gmail.com)
6 // Ankit Jain <jankit@novell.com>
7 // Lluis Sanchez Gual <lluis@novell.com>
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.
32 using System.Collections.Generic;
35 using System.Text.RegularExpressions;
37 using Microsoft.Build.BuildEngine;
39 namespace Mono.XBuild.CommandLine {
42 public string FileName;
45 public ProjectInfo (string name, string fileName)
51 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
52 public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
53 public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
54 public List<string> AspNetConfigurations = new List<string> ();
57 class ProjectSection {
59 public Dictionary<string, string> Properties = new Dictionary<string, string> ();
61 public ProjectSection (string name)
68 public string Configuration;
69 public string Platform;
72 public TargetInfo (string configuration, string platform)
73 : this (configuration, platform, false)
77 public TargetInfo (string configuration, string platform, bool build)
79 Configuration = configuration;
86 internal delegate void RaiseWarningHandler (int errorNumber, string message);
88 class SolutionParser {
89 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
91 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}}";
93 static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
94 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)(EndProject)?", RegexOptions.Singleline);
95 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
96 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
97 static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
99 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
100 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
102 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
103 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
104 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
106 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
107 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
108 static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
110 RaiseWarningHandler RaiseWarning;
112 public void ParseSolution (string file, Project p, RaiseWarningHandler RaiseWarning)
114 this.RaiseWarning = RaiseWarning;
115 EmitBeforeImports (p, file);
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";
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;
136 Dictionary<Guid, ProjectInfo> unsupportedProjectInfos = new Dictionary<Guid, ProjectInfo> ();
138 Match m = projectRegex.Match (line);
140 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value,
141 Path.GetFullPath (Path.Combine (solutionDir,
142 m.Groups [3].Value.Replace ('\\', Path.DirectorySeparatorChar))));
143 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
144 StringComparison.InvariantCultureIgnoreCase) == 0) {
145 // Ignore solution folders
150 projectInfo.Guid = new Guid (m.Groups [4].Value);
152 if (String.Compare (m.Groups [1].Value, vcprojGuid,
153 StringComparison.InvariantCultureIgnoreCase) == 0) {
155 RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
157 unsupportedProjectInfos [projectInfo.Guid] = projectInfo;
162 if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
163 StringComparison.InvariantCultureIgnoreCase) == 0)
164 websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
166 projectInfos.Add (projectInfo.Guid, projectInfo);
168 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
169 while (projectSectionMatch.Success) {
170 string section_name = projectSectionMatch.Groups [1].Value;
171 if (String.Compare (section_name, "ProjectDependencies") == 0) {
172 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
173 while (projectDependencyMatch.Success) {
174 // we might not have projectInfo available right now, so
175 // set it to null, and fill it in later
176 projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
177 projectDependencyMatch = projectDependencyMatch.NextMatch ();
180 ProjectSection section = new ProjectSection (section_name);
181 Match propertiesMatch = projectSectionPropertiesRegex.Match (
182 projectSectionMatch.Groups [2].Value);
183 while (propertiesMatch.Success) {
184 section.Properties [propertiesMatch.Groups ["name"].Value] =
185 propertiesMatch.Groups ["value"].Value;
187 propertiesMatch = propertiesMatch.NextMatch ();
190 projectInfo.ProjectSections [section_name] = section;
192 projectSectionMatch = projectSectionMatch.NextMatch ();
197 foreach (ProjectInfo projectInfo in projectInfos.Values) {
198 string filename = projectInfo.FileName;
199 string projectDir = Path.GetDirectoryName (filename);
201 if (!File.Exists (filename)) {
202 RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
203 "not found. Ignoring.", filename));
207 Project currentProject = p.ParentEngine.CreateNewProject ();
209 currentProject.Load (filename, ProjectLoadSettings.IgnoreMissingImports);
210 } catch (InvalidProjectFileException e) {
211 RaiseWarning (0, e.Message);
215 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
216 ProjectInfo info = null;
217 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
218 bool hasGuid = !String.IsNullOrEmpty (projectReferenceGuid);
220 // try to resolve the ProjectReference by GUID
221 // and fallback to project filename
224 Guid guid = new Guid (projectReferenceGuid);
225 projectInfos.TryGetValue (guid, out info);
226 if (info == null && unsupportedProjectInfos.TryGetValue (guid, out info)) {
227 RaiseWarning (0, String.Format (
228 "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
229 filename, bi.Include));
234 if (info == null || !hasGuid) {
235 // Project not found by guid or guid not available
236 // Try to find by project file
238 string fullpath = Path.GetFullPath (Path.Combine (projectDir, bi.Include.Replace ('\\', Path.DirectorySeparatorChar)));
239 info = projectInfos.Values.FirstOrDefault (pi => pi.FileName == fullpath);
242 if (unsupportedProjectInfos.Values.Any (pi => pi.FileName == fullpath))
243 RaiseWarning (0, String.Format (
244 "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
245 filename, bi.Include));
247 RaiseWarning (0, String.Format (
248 "{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
249 filename, bi.Include, projectReferenceGuid.Replace ("{", "").Replace ("}", ""), fullpath));
255 projectInfo.Dependencies [info.Guid] = info;
259 // fill in the project info for deps found in the .sln file
260 foreach (ProjectInfo projectInfo in projectInfos.Values) {
261 List<Guid> missingInfos = new List<Guid> ();
262 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
263 if (dependency.Value == null)
264 missingInfos.Add (dependency.Key);
267 foreach (Guid guid in missingInfos) {
269 if (projectInfos.TryGetValue (guid, out info))
270 projectInfo.Dependencies [guid] = info;
272 projectInfo.Dependencies.Remove (guid);
276 Match globalMatch = globalRegex.Match (line);
277 Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
278 while (globalSectionMatch.Success) {
279 string sectionType = globalSectionMatch.Groups[1].Value;
280 switch (sectionType) {
281 case "SolutionConfigurationPlatforms":
282 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
284 case "ProjectConfigurationPlatforms":
285 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
286 projectInfos, websiteProjectInfos);
288 case "SolutionProperties":
289 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
291 case "NestedProjects":
293 case "MonoDevelopProperties":
296 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
299 globalSectionMatch = globalSectionMatch.NextMatch ();
302 int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
304 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
305 AddProjectReferences (p, projectInfos);
306 AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
307 AddValidateSolutionConfiguration (p);
309 EmitAfterImports (p, file);
311 AddGetFrameworkPathTarget (p);
312 AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
313 AddProjectTargets (p, solutionTargets, projectInfos);
314 AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
317 string GetSlnFileVersion (StreamReader reader)
319 string strInput = null;
322 strInput = reader.ReadLine();
323 if (strInput == null)
326 match = slnVersionRegex.Match(strInput);
327 if (!match.Success) {
328 strInput = reader.ReadLine();
329 if (strInput == null)
331 match = slnVersionRegex.Match (strInput);
335 return match.Groups[1].Value;
340 void EmitBeforeImports (Project p, string file)
343 p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore\\*",
344 "'$(ImportByWildcardBeforeSolution)' != 'false' and " +
345 "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore')");
348 string before_filename = Path.Combine (Path.GetDirectoryName (file), "before." + Path.GetFileName (file) + ".targets");
349 p.AddNewImport (before_filename, String.Format ("Exists ('{0}')", before_filename));
352 void EmitAfterImports (Project p, string file)
355 p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter\\*",
356 "'$(ImportByWildcardAfterSolution)' != 'false' and " +
357 "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter')");
360 string after_filename = Path.Combine (Path.GetDirectoryName (file), "after." + Path.GetFileName (file) + ".targets");
361 p.AddNewImport (after_filename, String.Format ("Exists ('{0}')", after_filename));
364 void AddGeneralSettings (string solutionFile, Project p)
366 p.DefaultTargets = "Build";
367 p.InitialTargets = "ValidateSolutionConfiguration";
368 p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
369 p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
371 string solutionFilePath = Path.GetFullPath (solutionFile);
372 BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
373 solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
374 solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
375 solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
376 solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
377 solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
380 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
382 Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
383 while (solutionConfigurationPlatform.Success) {
384 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
385 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
386 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
387 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
391 // ignores the website projects, in the websiteProjectInfos
392 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
393 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
395 List<Guid> missingGuids = new List<Guid> ();
396 Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
397 while (projectConfigurationPlatform.Success) {
398 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
399 ProjectInfo projectInfo;
400 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
401 if (!missingGuids.Contains (guid)) {
402 if (!websiteProjectInfos.ContainsKey (guid))
403 // ignore website projects
404 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
405 missingGuids.Add (guid);
407 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
410 string solConf = projectConfigurationPlatform.Groups[2].Value;
411 string solPlat = projectConfigurationPlatform.Groups[3].Value;
412 string projConf = projectConfigurationPlatform.Groups[4].Value;
413 string projPlat = projectConfigurationPlatform.Groups[5].Value;
414 // hack, what are they doing here?
415 if (projPlat == "Any CPU")
417 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
418 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
420 Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
421 while (projectConfigurationPlatformBuild.Success) {
422 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
423 ProjectInfo projectInfo;
424 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
425 if (!missingGuids.Contains (guid)) {
426 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
427 missingGuids.Add (guid);
429 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
432 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
433 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
434 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
435 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
436 // hack, what are they doing here?
437 if (projPlat == "Any CPU")
439 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
440 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
444 void ParseSolutionProperties (string section)
448 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets,
449 Dictionary<Guid, ProjectInfo> projectInfos,
450 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
452 TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
453 if (solutionTargets.Count > 0) {
455 foreach (TargetInfo tinfo in solutionTargets) {
456 if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
457 default_target_info = tinfo;
464 default_target_info = solutionTargets [0];
467 AddDefaultSolutionConfiguration (p, default_target_info);
469 foreach (TargetInfo solutionTarget in solutionTargets) {
470 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
471 platformPropertyGroup.Condition = string.Format (
472 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
473 solutionTarget.Configuration,
474 solutionTarget.Platform
477 StringBuilder solutionConfigurationContents = new StringBuilder ();
478 solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
479 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
480 AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
482 solutionConfigurationContents.Append ("</SolutionConfiguration>");
484 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents",
485 solutionConfigurationContents.ToString ());
489 void AddProjectReferences (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
491 BuildItemGroup big = p.AddNewItemGroup ();
492 foreach (KeyValuePair<Guid, ProjectInfo> pair in projectInfos)
493 big.AddNewItem ("ProjectReference", pair.Value.FileName);
496 void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
497 StringBuilder solutionConfigurationContents)
499 foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
500 if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
501 solutionTarget.Platform == targetInfo.Key.Platform) {
502 solutionConfigurationContents.AppendFormat (
503 "<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
504 guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
509 void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
511 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
512 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
513 configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
515 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
516 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
517 platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
519 // emit default for AspNetConfiguration also
520 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
521 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
522 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
525 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
527 BuildTask task = target.AddNewTask ("Warning");
528 task.SetParameterValue ("Text",
529 String.Format ("The project configuration for project '{0}' corresponding " +
530 "to the solution configuration '{1}|{2}' was not found in the solution file.",
531 projectName, slnConfig, slnPlatform));
532 task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
533 slnConfig, slnPlatform);
537 // Website project methods
539 void AddWebsiteProperties (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
540 Dictionary<Guid, ProjectInfo> projectInfos)
542 var propertyGroupByConfig = new Dictionary<string, BuildPropertyGroup> ();
543 foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
544 ProjectInfo info = infoPair.Value;
545 string projectGuid = infoPair.Key.ToString ();
547 ProjectSection section;
548 if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
549 RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
553 //parse project references
554 string [] ref_guids = null;
556 if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
557 ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
558 for (int i = 0; i < ref_guids.Length; i ++) {
560 ref_guids [i] = ref_guids [i].Split ('|') [0];
562 Guid r_guid = new Guid (ref_guids [i]);
563 ProjectInfo ref_info;
564 if (projectInfos.TryGetValue (r_guid, out ref_info))
565 // ignore if not found
566 info.Dependencies [r_guid] = ref_info;
570 foreach (KeyValuePair<string, string> pair in section.Properties) {
571 //looking for -- ConfigName.AspNetCompiler.PropName
572 string [] parts = pair.Key.Split ('.');
573 if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
576 string config = parts [0];
577 string propertyName = parts [2];
579 BuildPropertyGroup bpg;
580 if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
581 bpg = p.AddNewPropertyGroup (true);
582 bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
583 propertyGroupByConfig [config] = bpg;
586 bpg.AddNewProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
589 if (!info.AspNetConfigurations.Contains (config))
590 info.AspNetConfigurations.Add (config);
595 // For WebSite projects
596 // The main "Build" target:
597 // 1. builds all non-website projects
598 // 2. calls target for website project
599 // - gets target path for the referenced projects
600 // - Resolves dependencies, satellites etc for the
601 // referenced project assemblies, and copies them
603 void AddWebsiteTargets (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
604 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
605 List<TargetInfo> solutionTargets)
607 foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
608 // gets a linear list of dependencies
609 List<ProjectInfo> depInfos = new List<ProjectInfo> ();
610 foreach (List<ProjectInfo> pinfos in infosByLevel) {
611 foreach (ProjectInfo pinfo in pinfos)
612 if (w_info.Dependencies.ContainsKey (pinfo.Guid))
613 depInfos.Add (pinfo);
616 foreach (string buildTarget in new string [] {"Build", "Rebuild"})
617 AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
619 // clean/publish are not supported for website projects
620 foreach (string buildTarget in new string [] {"Clean", "Publish"})
621 AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
625 void AddWebsiteTarget (Project p, ProjectInfo webProjectInfo,
626 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
627 List<TargetInfo> solutionTargets, string buildTarget)
629 string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
631 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
632 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
633 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
635 // this item collects all the references
636 string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
637 buildTarget != "Build" ? "_" + buildTarget : String.Empty);
639 foreach (TargetInfo targetInfo in solutionTargets) {
641 foreach (ProjectInfo depInfo in depInfos) {
642 TargetInfo projectTargetInfo;
643 if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
644 // Ignore, no config, so no target path
647 // GetTargetPath from the referenced project
648 AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
649 final_ref_item, ref_num);
654 // resolve the references
655 AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
658 // emits the MSBuild task to GetTargetPath for the referenced project
659 void AddWebsiteMSBuildTaskForReference (Target target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
660 TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
662 BuildTask task = target.AddNewTask ("MSBuild");
663 task.SetParameterValue ("Projects", depInfo.FileName);
664 task.SetParameterValue ("Targets", "GetTargetPath");
666 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));
667 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
669 string ref_item = String.Format ("{0}_{1}",
670 final_ref_item, ref_num);
672 task.AddOutputItem ("TargetOutputs", ref_item);
674 task = target.AddNewTask ("CreateItem");
675 task.SetParameterValue ("Include", String.Format ("@({0})", ref_item));
676 task.SetParameterValue ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
677 depInfo.Guid.ToString ().ToUpper ()));
678 task.AddOutputItem ("Include", final_ref_item);
681 void AddWebsiteResolveAndCopyReferencesTasks (Target target, ProjectInfo webProjectInfo,
682 string final_ref_item, string w_guid)
684 BuildTask task = target.AddNewTask ("ResolveAssemblyReference");
685 task.SetParameterValue ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
686 task.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
687 task.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
688 task.SetParameterValue ("FindDependencies", "true");
689 task.SetParameterValue ("FindSatellites", "true");
690 task.SetParameterValue ("FindRelatedFiles", "true");
691 task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
693 string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
694 task.AddOutputItem ("CopyLocalFiles", copylocal_item);
696 // Copy the references
697 task = target.AddNewTask ("Copy");
698 task.SetParameterValue ("SourceFiles", String.Format ("@({0})", copylocal_item));
699 task.SetParameterValue ("DestinationFiles", String.Format (
700 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
701 copylocal_item, w_guid));
703 // AspNetConfiguration, is config for the website project, useful
704 // for overriding from command line
705 StringBuilder cond = new StringBuilder ();
706 foreach (string config in webProjectInfo.AspNetConfigurations) {
708 cond.Append (" or ");
709 cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
711 task.Condition = cond.ToString ();
713 task = target.AddNewTask ("Message");
714 cond = new StringBuilder ();
715 foreach (string config in webProjectInfo.AspNetConfigurations) {
717 cond.Append (" and ");
718 cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
720 task.Condition = cond.ToString ();
721 task.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
722 "not supported by this website project.");
725 void AddWebsiteUnsupportedTarget (Project p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
728 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
729 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
731 BuildTask task = target.AddNewTask ("Message");
732 task.SetParameterValue ("Text", String.Format (
733 "Target '{0}' not support for website projects", buildTarget));
736 string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
738 StringBuilder deps = new StringBuilder ();
739 foreach (ProjectInfo pinfo in depInfos) {
742 deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
744 deps.Append (";GetFrameworkPath");
745 return deps.ToString ();
748 void AddGetFrameworkPathTarget (Project p)
750 Target t = p.Targets.AddNewTarget ("GetFrameworkPath");
751 BuildTask task = t.AddNewTask ("GetFrameworkPath");
752 task.AddOutputProperty ("Path", "TargetFrameworkPath");
755 void AddValidateSolutionConfiguration (Project p)
757 Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
758 BuildTask task = t.AddNewTask ("Warning");
759 task.SetParameterValue ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
760 " for xbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
761 " You could override it by either setting the environment variable to nothing, as\n" +
763 "Or explicity specify its value on the command line, as\n" +
764 " xbuild Foo.sln /p:Platform=Release");
765 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
766 " and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
768 task = t.AddNewTask ("Error");
769 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
770 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
771 task = t.AddNewTask ("Warning");
772 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
773 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
774 task = t.AddNewTask ("Message");
775 task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
776 task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
779 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
781 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
782 ProjectInfo project = projectInfo.Value;
783 foreach (string buildTarget in buildTargets) {
784 string target_name = GetTargetNameForProject (project.Name, buildTarget);
785 bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
786 Target target = p.Targets.AddNewTarget (target_name);
787 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
789 if (is_build_or_rebuild)
790 target.Outputs = "@(CollectedBuildOutput)";
791 if (project.Dependencies.Count > 0)
792 target.DependsOnTargets = String.Join (";",
793 project.Dependencies.Values.Select (
794 di => GetTargetNameForProject (di.Name, buildTarget)).ToArray ());
796 foreach (TargetInfo targetInfo in solutionTargets) {
797 BuildTask task = null;
798 TargetInfo projectTargetInfo;
799 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
800 AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
801 targetInfo.Platform, project.Name);
804 if (projectTargetInfo.Build) {
805 task = target.AddNewTask ("MSBuild");
806 task.SetParameterValue ("Projects", project.FileName);
807 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
808 if (is_build_or_rebuild)
809 task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
811 if (buildTarget != "Build")
812 task.SetParameterValue ("Targets", buildTarget);
813 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));
815 task = target.AddNewTask ("Message");
816 task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
818 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
825 string GetTargetNameForProject (string projectName, string buildTarget)
828 projectName = projectName.Replace ("\\", "/").Replace (".", "_");
829 string target_name = projectName +
830 (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
832 if (IsBuildTargetName (projectName))
833 target_name = "Solution:" + target_name;
838 bool IsBuildTargetName (string name)
840 foreach (string tgt in buildTargets)
846 // returns number of levels
847 int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
848 ref List<ProjectInfo>[] infosByLevel)
850 infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
852 foreach (TargetInfo targetInfo in solutionTargets) {
853 BuildItemGroup big = p.AddNewItemGroup ();
854 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
855 targetInfo.Configuration, targetInfo.Platform);
857 //FIXME: every level has projects that can be built in parallel.
858 // levels are ordered on the basis of the dependency graph
860 for (int i = 0; i < infosByLevel.Length; i ++) {
861 string build_level = String.Format ("BuildLevel{0}", i);
862 string skip_level = String.Format ("SkipLevel{0}", i);
863 string missing_level = String.Format ("MissingConfigLevel{0}", i);
865 foreach (ProjectInfo projectInfo in infosByLevel [i]) {
866 TargetInfo projectTargetInfo;
867 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
868 // missing project config
869 big.AddNewItem (missing_level, projectInfo.Name);
873 if (projectTargetInfo.Build) {
874 BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
875 item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
876 item.SetMetadata ("Platform", projectTargetInfo.Platform);
879 big.AddNewItem (skip_level, projectInfo.Name);
885 return infosByLevel.Length;
888 void AddSolutionTargets (Project p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
890 foreach (string buildTarget in buildTargets) {
891 Target t = p.Targets.AddNewTarget (buildTarget);
892 bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
894 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
895 if (is_build_or_rebuild)
896 t.Outputs = "@(CollectedBuildOutput)";
898 BuildTask task = null;
899 for (int i = 0; i < num_levels; i ++) {
900 string level_str = String.Format ("BuildLevel{0}", i);
901 task = t.AddNewTask ("MSBuild");
902 task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
903 task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
904 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
905 task.SetParameterValue ("Properties",
906 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
907 if (buildTarget != "Build")
908 task.SetParameterValue ("Targets", buildTarget);
909 //FIXME: change this to BuildInParallel=true, when parallel
910 // build support gets added
911 task.SetParameterValue ("RunEachTargetSeparately", "true");
912 if (is_build_or_rebuild)
913 task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
915 level_str = String.Format ("SkipLevel{0}", i);
916 task = t.AddNewTask ("Message");
917 task.Condition = String.Format ("'@({0})' != ''", level_str);
918 task.SetParameterValue ("Text",
919 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
920 "configuration '$(Configuration)|$(Platform)'.", level_str));
922 level_str = String.Format ("MissingConfigLevel{0}", i);
923 task = t.AddNewTask ("Warning");
924 task.Condition = String.Format ("'@({0})' != ''", level_str);
925 task.SetParameterValue ("Text",
926 String.Format ("The project configuration for project '%({0}.Identity)' " +
927 "corresponding to the solution configuration " +
928 "'$(Configuration)|$(Platform)' was not found.", level_str));
931 // "build" website projects also
932 StringBuilder w_targets = new StringBuilder ();
933 foreach (ProjectInfo info in websiteProjectInfos) {
934 if (w_targets.Length > 0)
935 w_targets.Append (";");
936 w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
939 task = t.AddNewTask ("CallTarget");
940 task.SetParameterValue ("Targets", w_targets.ToString ());
941 task.SetParameterValue ("RunEachTargetSeparately", "true");
945 // Sorts the ProjectInfo dependency graph, to obtain
946 // a series of build levels with projects. Projects
947 // in each level can be run parallel (no inter-dependency).
948 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
951 allItems = items as IList<T>;
952 if (allItems == null)
953 allItems = new List<T> (items);
955 bool[] inserted = new bool[allItems.Count];
956 bool[] triedToInsert = new bool[allItems.Count];
957 int[] levels = new int [allItems.Count];
960 for (int i = 0; i < allItems.Count; ++i) {
961 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
966 // Separate out the project infos by build level
967 List<T>[] infosByLevel = new List<T>[maxdepth];
968 for (int i = 0; i < levels.Length; i ++) {
969 int level = levels [i] - 1;
970 if (infosByLevel [level] == null)
971 infosByLevel [level] = new List<T> ();
973 infosByLevel [level].Add (allItems [i]);
979 // returns level# for the project
980 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
983 if (inserted [index])
984 return levels [index];
986 if (triedToInsert[index])
987 throw new InvalidOperationException (String.Format (
988 "Cyclic dependency involving project {0} found in the project dependency graph",
989 allItems [index].Name));
991 triedToInsert[index] = true;
992 ProjectInfo insertItem = allItems[index];
995 foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
996 for (int j = 0; j < allItems.Count; ++j) {
997 ProjectInfo checkItem = allItems [j];
998 if (dependency.FileName == checkItem.FileName) {
999 int d = Insert (j, allItems, levels, inserted, triedToInsert);
1000 maxdepth = d > maxdepth ? d : maxdepth;
1005 levels [index] = maxdepth + 1;
1006 inserted [index] = true;
1008 return levels [index];
1011 public static IEnumerable<string> GetAllProjectFileNames (string solutionFile)
1013 StreamReader reader = new StreamReader (solutionFile);
1014 string line = reader.ReadToEnd ();
1015 line = line.Replace ("\r\n", "\n");
1016 string soln_dir = Path.GetDirectoryName (solutionFile);
1018 Match m = projectRegex.Match (line);
1020 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
1021 StringComparison.InvariantCultureIgnoreCase) != 0)
1022 yield return Path.Combine (soln_dir, m.Groups [3].Value).Replace ("\\", "/");