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.
34 using System.Collections.Generic;
36 using System.Text.RegularExpressions;
38 using Microsoft.Build.BuildEngine;
40 namespace Mono.XBuild.CommandLine {
43 public string FileName;
46 public ProjectInfo (string name, string fileName)
52 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
53 public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
54 public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
55 public List<string> AspNetConfigurations = new List<string> ();
58 class ProjectSection {
60 public Dictionary<string, string> Properties = new Dictionary<string, string> ();
62 public ProjectSection (string name)
69 public string Configuration;
70 public string Platform;
73 public TargetInfo (string configuration, string platform)
74 : this (configuration, platform, false)
78 public TargetInfo (string configuration, string platform, bool build)
80 Configuration = configuration;
87 internal delegate void RaiseWarningHandler (int errorNumber, string message);
89 class SolutionParser {
90 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
92 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}}";
94 static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
95 static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)EndProject?", RegexOptions.Singleline);
96 static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
97 static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
98 static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
100 static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
101 static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
103 static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
104 static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
105 static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
107 static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
108 static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
109 static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
111 RaiseWarningHandler RaiseWarning;
113 public void ParseSolution (string file, Project p, RaiseWarningHandler RaiseWarning)
115 this.RaiseWarning = RaiseWarning;
116 AddGeneralSettings (file, p);
118 StreamReader reader = new StreamReader (file);
119 string slnVersion = GetSlnFileVersion (reader);
120 if (slnVersion == "11.00")
121 p.DefaultToolsVersion = "4.0";
122 else if (slnVersion == "10.00")
123 p.DefaultToolsVersion = "3.5";
125 p.DefaultToolsVersion = "2.0";
127 string line = reader.ReadToEnd ();
128 line = line.Replace ("\r\n", "\n");
129 string solutionDir = Path.GetDirectoryName (file);
131 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
132 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
133 Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
134 List<ProjectInfo>[] infosByLevel = null;
136 Match m = projectRegex.Match (line);
138 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
139 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
140 StringComparison.InvariantCultureIgnoreCase) == 0) {
141 // Ignore solution folders
146 if (String.Compare (m.Groups [1].Value, vcprojGuid,
147 StringComparison.InvariantCultureIgnoreCase) == 0) {
149 RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
154 if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
155 StringComparison.InvariantCultureIgnoreCase) == 0)
156 websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
158 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
160 projectInfo.Guid = new Guid (m.Groups [4].Value);
162 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
163 while (projectSectionMatch.Success) {
164 string section_name = projectSectionMatch.Groups [1].Value;
165 if (String.Compare (section_name, "ProjectDependencies") == 0) {
166 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
167 while (projectDependencyMatch.Success) {
168 // we might not have projectInfo available right now, so
169 // set it to null, and fill it in later
170 projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
171 projectDependencyMatch = projectDependencyMatch.NextMatch ();
174 ProjectSection section = new ProjectSection (section_name);
175 Match propertiesMatch = projectSectionPropertiesRegex.Match (
176 projectSectionMatch.Groups [2].Value);
177 while (propertiesMatch.Success) {
178 section.Properties [propertiesMatch.Groups ["name"].Value] =
179 propertiesMatch.Groups ["value"].Value;
181 propertiesMatch = propertiesMatch.NextMatch ();
184 projectInfo.ProjectSections [section_name] = section;
186 projectSectionMatch = projectSectionMatch.NextMatch ();
191 foreach (ProjectInfo projectInfo in projectInfos.Values) {
192 string filename = Path.Combine (solutionDir,
193 projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar));
195 if (!File.Exists (filename)) {
196 RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
197 "not found. Ignoring.", filename));
201 Project currentProject = p.ParentEngine.CreateNewProject ();
203 currentProject.Load (filename);
204 } catch (InvalidProjectFileException e) {
205 RaiseWarning (0, e.Message);
209 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
210 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
211 Guid guid = new Guid (projectReferenceGuid);
213 if (projectInfos.TryGetValue (guid, out info))
214 // ignore if not found
215 projectInfo.Dependencies [guid] = info;
219 // fill in the project info for deps found in the .sln file
220 foreach (ProjectInfo projectInfo in projectInfos.Values) {
221 List<Guid> missingInfos = new List<Guid> ();
222 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
223 if (dependency.Value == null)
224 missingInfos.Add (dependency.Key);
227 foreach (Guid guid in missingInfos) {
229 if (projectInfos.TryGetValue (guid, out info))
230 projectInfo.Dependencies [guid] = info;
232 projectInfo.Dependencies.Remove (guid);
236 Match globalMatch = globalRegex.Match (line);
237 Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
238 while (globalSectionMatch.Success) {
239 string sectionType = globalSectionMatch.Groups[1].Value;
240 switch (sectionType) {
241 case "SolutionConfigurationPlatforms":
242 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
244 case "ProjectConfigurationPlatforms":
245 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
246 projectInfos, websiteProjectInfos);
248 case "SolutionProperties":
249 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
251 case "NestedProjects":
254 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
257 globalSectionMatch = globalSectionMatch.NextMatch ();
260 int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
262 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
263 AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
264 AddValidateSolutionConfiguration (p);
266 AddGetFrameworkPathTarget (p);
267 AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
268 AddProjectTargets (p, solutionTargets, projectInfos);
269 AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
272 string GetSlnFileVersion (StreamReader reader)
274 string strVersion = null;
275 string strInput = null;
278 strInput = reader.ReadLine();
279 if (strInput == null)
282 match = slnVersionRegex.Match(strInput);
283 if (!match.Success) {
284 strInput = reader.ReadLine();
285 if (strInput == null)
287 match = slnVersionRegex.Match (strInput);
291 return match.Groups[1].Value;
296 void AddGeneralSettings (string solutionFile, Project p)
298 p.DefaultTargets = "Build";
299 p.InitialTargets = "ValidateSolutionConfiguration";
300 p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
301 p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
303 string solutionFilePath = Path.GetFullPath (solutionFile);
304 BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
305 solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
306 solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
307 solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
308 solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
309 solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
312 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
314 Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
315 while (solutionConfigurationPlatform.Success) {
316 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
317 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
318 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
319 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
323 // ignores the website projects, in the websiteProjectInfos
324 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
325 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
327 List<Guid> missingGuids = new List<Guid> ();
328 Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
329 while (projectConfigurationPlatform.Success) {
330 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
331 ProjectInfo projectInfo;
332 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
333 if (!missingGuids.Contains (guid)) {
334 if (!websiteProjectInfos.ContainsKey (guid))
335 // ignore website projects
336 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
337 missingGuids.Add (guid);
339 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
342 string solConf = projectConfigurationPlatform.Groups[2].Value;
343 string solPlat = projectConfigurationPlatform.Groups[3].Value;
344 string projConf = projectConfigurationPlatform.Groups[4].Value;
345 string projPlat = projectConfigurationPlatform.Groups[5].Value;
346 // hack, what are they doing here?
347 if (projPlat == "Any CPU")
349 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
350 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
352 Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
353 while (projectConfigurationPlatformBuild.Success) {
354 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
355 ProjectInfo projectInfo;
356 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
357 if (!missingGuids.Contains (guid)) {
358 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
359 missingGuids.Add (guid);
361 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
364 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
365 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
366 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
367 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
368 // hack, what are they doing here?
369 if (projPlat == "Any CPU")
371 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
372 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
376 void ParseSolutionProperties (string section)
380 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets,
381 Dictionary<Guid, ProjectInfo> projectInfos,
382 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
384 TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
385 if (solutionTargets.Count > 0) {
387 foreach (TargetInfo tinfo in solutionTargets) {
388 if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
389 default_target_info = tinfo;
396 default_target_info = solutionTargets [0];
399 AddDefaultSolutionConfiguration (p, default_target_info);
401 foreach (TargetInfo solutionTarget in solutionTargets) {
402 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
403 platformPropertyGroup.Condition = string.Format (
404 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
405 solutionTarget.Configuration,
406 solutionTarget.Platform
409 StringBuilder solutionConfigurationContents = new StringBuilder ();
410 solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
411 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
412 AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
414 solutionConfigurationContents.Append ("</SolutionConfiguration>");
416 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents",
417 solutionConfigurationContents.ToString ());
421 void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
422 StringBuilder solutionConfigurationContents)
424 foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
425 if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
426 solutionTarget.Platform == targetInfo.Key.Platform) {
427 solutionConfigurationContents.AppendFormat (
428 "<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
429 guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
434 void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
436 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
437 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
438 configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
440 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
441 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
442 platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
444 // emit default for AspNetConfiguration also
445 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
446 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
447 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
450 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
452 BuildTask task = target.AddNewTask ("Warning");
453 task.SetParameterValue ("Text",
454 String.Format ("The project configuration for project '{0}' corresponding " +
455 "to the solution configuration '{1}|{2}' was not found in the solution file.",
456 projectName, slnConfig, slnPlatform));
457 task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
458 slnConfig, slnPlatform);
462 // Website project methods
464 void AddWebsiteProperties (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
465 Dictionary<Guid, ProjectInfo> projectInfos)
467 var propertyGroupByConfig = new Dictionary<string, BuildPropertyGroup> ();
468 foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
469 ProjectInfo info = infoPair.Value;
470 string projectGuid = infoPair.Key.ToString ();
472 ProjectSection section;
473 if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
474 RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
478 //parse project references
479 string [] ref_guids = null;
481 if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
482 ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
483 for (int i = 0; i < ref_guids.Length; i ++) {
485 ref_guids [i] = ref_guids [i].Split ('|') [0];
487 Guid r_guid = new Guid (ref_guids [i]);
488 ProjectInfo ref_info;
489 if (projectInfos.TryGetValue (r_guid, out ref_info))
490 // ignore if not found
491 info.Dependencies [r_guid] = ref_info;
495 foreach (KeyValuePair<string, string> pair in section.Properties) {
496 //looking for -- ConfigName.AspNetCompiler.PropName
497 string [] parts = pair.Key.Split ('.');
498 if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
501 string config = parts [0];
502 string propertyName = parts [2];
504 BuildPropertyGroup bpg;
505 if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
506 bpg = p.AddNewPropertyGroup (true);
507 bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
508 propertyGroupByConfig [config] = bpg;
511 bpg.AddNewProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
514 if (!info.AspNetConfigurations.Contains (config))
515 info.AspNetConfigurations.Add (config);
520 // For WebSite projects
521 // The main "Build" target:
522 // 1. builds all non-website projects
523 // 2. calls target for website project
524 // - gets target path for the referenced projects
525 // - Resolves dependencies, satellites etc for the
526 // referenced project assemblies, and copies them
528 void AddWebsiteTargets (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
529 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
530 List<TargetInfo> solutionTargets)
532 foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
533 // gets a linear list of dependencies
534 List<ProjectInfo> depInfos = new List<ProjectInfo> ();
535 foreach (List<ProjectInfo> pinfos in infosByLevel) {
536 foreach (ProjectInfo pinfo in pinfos)
537 if (w_info.Dependencies.ContainsKey (pinfo.Guid))
538 depInfos.Add (pinfo);
541 foreach (string buildTarget in new string [] {"Build", "Rebuild"})
542 AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
544 // clean/publish are not supported for website projects
545 foreach (string buildTarget in new string [] {"Clean", "Publish"})
546 AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
550 void AddWebsiteTarget (Project p, ProjectInfo webProjectInfo,
551 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
552 List<TargetInfo> solutionTargets, string buildTarget)
554 string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
556 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
557 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
558 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
560 // this item collects all the references
561 string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
562 buildTarget != "Build" ? "_" + buildTarget : String.Empty);
564 foreach (TargetInfo targetInfo in solutionTargets) {
566 foreach (ProjectInfo depInfo in depInfos) {
567 TargetInfo projectTargetInfo;
568 if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
569 // Ignore, no config, so no target path
572 // GetTargetPath from the referenced project
573 AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
574 final_ref_item, ref_num);
579 // resolve the references
580 AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
583 // emits the MSBuild task to GetTargetPath for the referenced project
584 void AddWebsiteMSBuildTaskForReference (Target target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
585 TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
587 BuildTask task = target.AddNewTask ("MSBuild");
588 task.SetParameterValue ("Projects", depInfo.FileName);
589 task.SetParameterValue ("Targets", "GetTargetPath");
591 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));
592 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
594 string ref_item = String.Format ("{0}_{1}",
595 final_ref_item, ref_num);
597 task.AddOutputItem ("TargetOutputs", ref_item);
599 task = target.AddNewTask ("CreateItem");
600 task.SetParameterValue ("Include", String.Format ("@({0})", ref_item));
601 task.SetParameterValue ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
602 depInfo.Guid.ToString ().ToUpper ()));
603 task.AddOutputItem ("Include", final_ref_item);
606 void AddWebsiteResolveAndCopyReferencesTasks (Target target, ProjectInfo webProjectInfo,
607 string final_ref_item, string w_guid)
609 BuildTask task = target.AddNewTask ("ResolveAssemblyReference");
610 task.SetParameterValue ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
611 task.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
612 task.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
613 task.SetParameterValue ("FindDependencies", "true");
614 task.SetParameterValue ("FindSatellites", "true");
615 task.SetParameterValue ("FindRelatedFiles", "true");
616 task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
618 string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
619 task.AddOutputItem ("CopyLocalFiles", copylocal_item);
621 // Copy the references
622 task = target.AddNewTask ("Copy");
623 task.SetParameterValue ("SourceFiles", String.Format ("@({0})", copylocal_item));
624 task.SetParameterValue ("DestinationFiles", String.Format (
625 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
626 copylocal_item, w_guid));
628 // AspNetConfiguration, is config for the website project, useful
629 // for overriding from command line
630 StringBuilder cond = new StringBuilder ();
631 foreach (string config in webProjectInfo.AspNetConfigurations) {
633 cond.Append (" or ");
634 cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
636 task.Condition = cond.ToString ();
638 task = target.AddNewTask ("Message");
639 cond = new StringBuilder ();
640 foreach (string config in webProjectInfo.AspNetConfigurations) {
642 cond.Append (" and ");
643 cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
645 task.Condition = cond.ToString ();
646 task.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
647 "not supported by this website project.");
650 void AddWebsiteUnsupportedTarget (Project p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
653 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
654 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
656 BuildTask task = target.AddNewTask ("Message");
657 task.SetParameterValue ("Text", String.Format (
658 "Target '{0}' not support for website projects", buildTarget));
661 string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
663 StringBuilder deps = new StringBuilder ();
664 foreach (ProjectInfo pinfo in depInfos) {
667 deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
669 deps.Append (";GetFrameworkPath");
670 return deps.ToString ();
673 void AddGetFrameworkPathTarget (Project p)
675 Target t = p.Targets.AddNewTarget ("GetFrameworkPath");
676 BuildTask task = t.AddNewTask ("GetFrameworkPath");
677 task.AddOutputProperty ("Path", "TargetFrameworkPath");
680 void AddValidateSolutionConfiguration (Project p)
682 Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
683 BuildTask task = t.AddNewTask ("Warning");
684 task.SetParameterValue ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
685 " for xbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
686 " You could override it by either setting the environment variable to nothing, as\n" +
688 "Or explicity specify its value on the command line, as\n" +
689 " xbuild Foo.sln /p:Platform=Release");
690 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
691 " and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
693 task = t.AddNewTask ("Error");
694 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
695 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
696 task = t.AddNewTask ("Warning");
697 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
698 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
699 task = t.AddNewTask ("Message");
700 task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
701 task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
704 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
706 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
707 ProjectInfo project = projectInfo.Value;
708 foreach (string buildTarget in buildTargets) {
709 string target_name = GetTargetNameForProject (project.Name, buildTarget);
710 Target target = p.Targets.AddNewTarget (target_name);
711 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
713 if (project.Dependencies.Count > 0) {
714 StringBuilder dependencies = new StringBuilder ();
715 foreach (ProjectInfo dependentInfo in project.Dependencies.Values) {
716 if (dependencies.Length > 0)
717 dependencies.Append (";");
718 if (IsBuildTargetName (dependentInfo.Name))
719 dependencies.Append ("Solution:");
720 dependencies.Append (dependentInfo.Name);
721 if (buildTarget != "Build")
722 dependencies.Append (":" + buildTarget);
724 target.DependsOnTargets = dependencies.ToString ();
727 foreach (TargetInfo targetInfo in solutionTargets) {
728 BuildTask task = null;
729 TargetInfo projectTargetInfo;
730 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
731 AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
732 targetInfo.Platform, project.Name);
735 if (projectTargetInfo.Build) {
736 task = target.AddNewTask ("MSBuild");
737 task.SetParameterValue ("Projects", project.FileName);
738 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
740 if (buildTarget != "Build")
741 task.SetParameterValue ("Targets", buildTarget);
742 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));
744 task = target.AddNewTask ("Message");
745 task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
747 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
754 string GetTargetNameForProject (string projectName, string buildTarget)
757 projectName = projectName.Replace ("\\", "/").Replace (".", "_");
758 string target_name = projectName +
759 (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
761 if (IsBuildTargetName (projectName))
762 target_name = "Solution:" + target_name;
767 bool IsBuildTargetName (string name)
769 foreach (string tgt in buildTargets)
775 // returns number of levels
776 int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
777 ref List<ProjectInfo>[] infosByLevel)
779 infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
781 foreach (TargetInfo targetInfo in solutionTargets) {
782 BuildItemGroup big = p.AddNewItemGroup ();
783 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
784 targetInfo.Configuration, targetInfo.Platform);
786 //FIXME: every level has projects that can be built in parallel.
787 // levels are ordered on the basis of the dependency graph
789 for (int i = 0; i < infosByLevel.Length; i ++) {
790 string build_level = String.Format ("BuildLevel{0}", i);
791 string skip_level = String.Format ("SkipLevel{0}", i);
792 string missing_level = String.Format ("MissingConfigLevel{0}", i);
794 foreach (ProjectInfo projectInfo in infosByLevel [i]) {
795 TargetInfo projectTargetInfo;
796 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
797 // missing project config
798 big.AddNewItem (missing_level, projectInfo.Name);
802 if (projectTargetInfo.Build) {
803 BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
804 item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
805 item.SetMetadata ("Platform", projectTargetInfo.Platform);
808 big.AddNewItem (skip_level, projectInfo.Name);
814 return infosByLevel.Length;
817 void AddSolutionTargets (Project p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
819 foreach (string buildTarget in buildTargets) {
820 Target t = p.Targets.AddNewTarget (buildTarget);
821 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
823 BuildTask task = null;
824 for (int i = 0; i < num_levels; i ++) {
825 string level_str = String.Format ("BuildLevel{0}", i);
826 task = t.AddNewTask ("MSBuild");
827 task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
828 task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
829 task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
830 task.SetParameterValue ("Properties",
831 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
832 if (buildTarget != "Build")
833 task.SetParameterValue ("Targets", buildTarget);
834 //FIXME: change this to BuildInParallel=true, when parallel
835 // build support gets added
836 task.SetParameterValue ("RunEachTargetSeparately", "true");
838 level_str = String.Format ("SkipLevel{0}", i);
839 task = t.AddNewTask ("Message");
840 task.Condition = String.Format ("'@({0})' != ''", level_str);
841 task.SetParameterValue ("Text",
842 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
843 "configuration '$(Configuration)|$(Platform)'.", level_str));
845 level_str = String.Format ("MissingConfigLevel{0}", i);
846 task = t.AddNewTask ("Warning");
847 task.Condition = String.Format ("'@({0})' != ''", level_str);
848 task.SetParameterValue ("Text",
849 String.Format ("The project configuration for project '%({0}.Identity)' " +
850 "corresponding to the solution configuration " +
851 "'$(Configuration)|$(Platform)' was not found.", level_str));
854 // "build" website projects also
855 StringBuilder w_targets = new StringBuilder ();
856 foreach (ProjectInfo info in websiteProjectInfos) {
857 if (w_targets.Length > 0)
858 w_targets.Append (";");
859 w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
862 task = t.AddNewTask ("CallTarget");
863 task.SetParameterValue ("Targets", w_targets.ToString ());
864 task.SetParameterValue ("RunEachTargetSeparately", "true");
868 // Sorts the ProjectInfo dependency graph, to obtain
869 // a series of build levels with projects. Projects
870 // in each level can be run parallel (no inter-dependency).
871 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
874 allItems = items as IList<T>;
875 if (allItems == null)
876 allItems = new List<T> (items);
878 bool[] inserted = new bool[allItems.Count];
879 bool[] triedToInsert = new bool[allItems.Count];
880 int[] levels = new int [allItems.Count];
883 for (int i = 0; i < allItems.Count; ++i) {
884 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
889 // Separate out the project infos by build level
890 List<T>[] infosByLevel = new List<T>[maxdepth];
891 for (int i = 0; i < levels.Length; i ++) {
892 int level = levels [i] - 1;
893 if (infosByLevel [level] == null)
894 infosByLevel [level] = new List<T> ();
896 infosByLevel [level].Add (allItems [i]);
902 // returns level# for the project
903 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
906 if (inserted [index])
907 return levels [index];
909 if (triedToInsert[index])
910 throw new InvalidOperationException (String.Format (
911 "Cyclic dependency involving project {0} found in the project dependency graph",
912 allItems [index].Name));
914 triedToInsert[index] = true;
915 ProjectInfo insertItem = allItems[index];
918 foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
919 for (int j = 0; j < allItems.Count; ++j) {
920 ProjectInfo checkItem = allItems [j];
921 if (dependency.FileName == checkItem.FileName) {
922 int d = Insert (j, allItems, levels, inserted, triedToInsert);
923 maxdepth = d > maxdepth ? d : maxdepth;
928 levels [index] = maxdepth + 1;
929 inserted [index] = true;
931 return levels [index];
934 public static IEnumerable<string> GetAllProjectFileNames (string solutionFile)
936 StreamReader reader = new StreamReader (solutionFile);
937 string line = reader.ReadToEnd ();
938 line = line.Replace ("\r\n", "\n");
939 string soln_dir = Path.GetDirectoryName (solutionFile);
941 Match m = projectRegex.Match (line);
943 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
944 StringComparison.InvariantCultureIgnoreCase) != 0)
945 yield return Path.Combine (soln_dir, m.Groups [3].Value).Replace ("\\", "/");